1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249 | import os
from os.path import isfile, isdir, getmtime, dirname, splitext, getsize
from tempfile import mkstemp
from shutil import copyfile
from subprocess import Popen, PIPE
from PIL import Image, ImageFilter
from sorl.thumbnail import defaults
from sorl.thumbnail.processors import get_valid_options, dynamic_import
class ThumbnailException(Exception):
# Stop Django templates from choking if something goes wrong.
silent_variable_failure = True
class Thumbnail(object):
def __init__(self, source, requested_size, opts=None, quality=85,
dest=None, convert_path=defaults.CONVERT,
wvps_path=defaults.WVPS, processors=None):
# Paths to external commands
self.convert_path = convert_path
self.wvps_path = wvps_path
# Absolute paths to files
self.source = source
self.dest = dest
self.type = type
# Thumbnail settings
self.requested_size = requested_size
if not 0 < quality <= 100:
raise TypeError('Thumbnail received invalid value for quality '
'argument: %s' % quality)
self.quality = quality
# Processors
if processors is None:
processors = dynamic_import(defaults.PROCESSORS)
self.processors = processors
# Set Thumbnail opt(ion)s
VALID_OPTIONS = get_valid_options(processors)
opts = opts or []
# Check that all options received are valid
for opt in opts:
if not opt in VALID_OPTIONS:
raise TypeError('Thumbnail received an invalid option: %s'
% opt)
self.opts = [opt for opt in VALID_OPTIONS if opt in opts]
if self.dest is not None:
self.generate()
def generate(self):
"""
Generates the thumbnail if it doesn't exist or if the file date of the
source file is newer than that of the thumbnail.
"""
# Ensure dest(ination) attribute is set
if not self.dest:
raise ThumbnailException("No destination filename set.")
if not isinstance(self.dest, basestring):
# We'll assume dest is a file-like instance if it exists but isn't
# a string.
self._do_generate()
elif not isfile(self.dest) or (self.source_exists and
getmtime(self.source) > getmtime(self.dest)):
# Ensure the directory exists
directory = dirname(self.dest)
if not isdir(directory):
os.makedirs(directory)
self._do_generate()
def _check_source_exists(self):
"""
Ensure the source file exists. If source is not a string then it is
assumed to be a file-like instance which "exists".
"""
if not hasattr(self, '_source_exists'):
self._source_exists = (self.source and
(not isinstance(self.source, basestring) or
isfile(self.source)))
return self._source_exists
source_exists = property(_check_source_exists)
def _get_source_filetype(self):
"""
Set the source filetype. First it tries to use magic and
if import error it will just use the extension
"""
if not hasattr(self, '_source_filetype'):
if not isinstance(self.source, basestring):
# Assuming a file-like object - we won't know it's type.
return None
try:
import magic
except ImportError:
self._source_filetype = splitext(self.source)[1].lower().\
replace('.', '').replace('jpeg', 'jpg')
else:
m = magic.open(magic.MAGIC_NONE)
m.load()
ftype = m.file(self.source)
if ftype.find('Microsoft Office Document') != -1:
self._source_filetype = 'doc'
elif ftype.find('PDF document') != -1:
self._source_filetype = 'pdf'
elif ftype.find('JPEG') != -1:
self._source_filetype = 'jpg'
else:
self._source_filetype = ftype
return self._source_filetype
source_filetype = property(_get_source_filetype)
# data property is the image data of the (generated) thumbnail
def _get_data(self):
if not hasattr(self, '_data'):
try:
self._data = Image.open(self.dest)
except IOError, detail:
raise ThumbnailException(detail)
return self._data
def _set_data(self, im):
self._data = im
data = property(_get_data, _set_data)
# source_data property is the image data from the source file
def _get_source_data(self):
if not hasattr(self, '_source_data'):
if not self.source_exists:
raise ThumbnailException("Source file: '%s' does not exist." %
self.source)
if self.source_filetype == 'doc':
self._convert_wvps(self.source)
elif self.source_filetype == 'pdf':
self._convert_imagemagick(self.source)
else:
self.source_data = self.source
return self._source_data
def _set_source_data(self, image):
if isinstance(image, Image.Image):
self._source_data = image
else:
try:
self._source_data = Image.open(image)
except IOError, detail:
raise ThumbnailException("%s: %s" % (detail, image))
source_data = property(_get_source_data, _set_source_data)
def _convert_wvps(self, filename):
tmp = mkstemp('.ps')[1]
try:
p = Popen((self.wvps_path, filename, tmp), stdout=PIPE)
p.wait()
except OSError, detail:
os.remove(tmp)
raise ThumbnailException('wvPS error: %s' % detail)
self._convert_imagemagick(tmp)
os.remove(tmp)
def _convert_imagemagick(self, filename):
tmp = mkstemp('.png')[1]
if 'crop' in self.opts or 'autocrop' in self.opts:
x,y = [d*3 for d in self.requested_size]
else:
x,y = self.requested_size
try:
p = Popen((self.convert_path, '-size', '%sx%s' % (x,y),
'-antialias', '-colorspace', 'rgb', '-format', 'PNG24',
'%s[0]' % filename, tmp), stdout=PIPE)
p.wait()
except OSError, detail:
os.remove(tmp)
raise ThumbnailException('ImageMagick error: %s' % detail)
self.source_data = tmp
os.remove(tmp)
def _do_generate(self):
"""
Generates the thumbnail image.
This a semi-private method so it isn't directly available to template
authors if this object is passed to the template context.
"""
im = self.source_data
for processor in self.processors:
im = processor(im, self.requested_size, self.opts)
self.data = im
dest_extension = os.path.splitext(self.dest)[1][1:]
if (self.source_data == self.data and
self.source_filetype == dest_extension):
copyfile(self.source, self.dest)
else:
try:
im.save(self.dest, quality=self.quality, optimize=1)
except IOError:
# Try again, without optimization (PIL can't optimize an image
# larger than ImageFile.MAXBLOCK, which is 64k by default)
try:
im.save(self.dest, quality=self.quality)
except IOError, detail:
raise ThumbnailException(detail)
# Some helpful methods
def _dimension(self, axis):
if self.dest is None:
return None
return self.data.size[axis]
def width(self):
return self._dimension(0)
def height(self):
return self._dimension(1)
def _get_filesize(self):
if self.dest is None:
return None
if not hasattr(self, '_filesize'):
self._filesize = getsize(self.dest)
return self._filesize
filesize = property(_get_filesize)
def _source_dimension(self, axis):
if self.source_filetype in ['pdf', 'doc']:
return None
else:
return self.source_data.size[axis]
def source_width(self):
return self._source_dimension(0)
def source_height(self):
return self._source_dimension(1)
def _get_source_filesize(self):
if not hasattr(self, '_source_filesize'):
self._source_filesize = getsize(self.source)
return self._source_filesize
source_filesize = property(_get_source_filesize)
|