1 """Interface to the thumbnail spec. This provides a functions to look up
2 thumbnails for files and a class which you can extend to generate a thumbnail
3 image for a type of file.
5 The thumbnail standard is at http://jens.triq.net/thumbnail-spec/index.html
15 return hashlib
.md5(s
).hexdigest()
20 return md5
.new(s
).hexdigest()
22 import rox
, rox
.basedir
, rox
.mime
25 path
=os
.path
.abspath(fname
)
26 uri
='file://'+rox
.escape(path
)
27 return md5hash(uri
)+'.png'
30 """Given a file name return the full path of an existing thumbnail
31 image. If no thumbnail image exists, return None"""
34 for sdir
in ('normal', 'large'):
35 path
=os
.path
.join(os
.environ
['HOME'], '.thumbnails',
37 if os
.access(path
, os
.R_OK
):
40 def get_path_save(fname
, ttype
='normal'):
41 """Given a file name return the full path of the location to store the
44 ttype should be 'normal' or 'large' to specify the size, or 'fail' when
45 thumbnail creation has failed"""
47 return os
.path
.join(os
.environ
['HOME'], '.thumbnails', ttype
, leaf
)
50 """Given a file name return a GdkPixbuf of the thumbnail for that file.
51 If no thumbnail image exists return None."""
57 pbuf
=rox
.g
.gdk
.pixbuf_new_from_file(path
)
62 tsize
=int(pbuf
.get_option('tEXt::Thumb::Size'))
63 tmtime
=float(pbuf
.get_option('tEXt::Thumb::MTime'))
65 if tsize
!=int(s
.st_size
) or int(tmtime
)!=int(s
.st_mtime
):
71 def get_method(path
=None, mtype
=None):
72 """Look up the program for generating a thumbnail. Specify either
73 a path to a file or a MIME type.
75 This returns False if there is no defined method to generate the thumbnail,
76 True if the thumbnail would be generated internally using GdkPixbuf, or
77 a string giving the full path to a program called to generate the
81 mtype
=rox
.mime
.get_type(path
)
83 if isinstance(mtype
, basestring
):
84 mtype
=rox
.mime
.lookup(mtype
)
89 mthd
=rox
.basedir
.load_first_config('rox.sourceforge.net',
91 '%s_%s' %(mtype
.media
, mtype
.subtype
))
94 if rox
.isappdir(mthd
):
95 return os
.path
.join(mthd
, 'AppRun')
98 for fmt
in rox
.g
.gdk
.pixbuf_get_formats():
99 for t
in fmt
['mime_types']:
106 """Generate the thumbnail for a file. If a generator for the type of
107 path is not available then None is returned, otherwise an integer
108 which is the exit code of the generation process (0 for success)."""
110 method
=get_method(path
)
115 th
=GdkPixbufThumbnailer()
121 outname
=get_path_save(path
)
124 return os
.spawnl(os
.P_WAIT
, method
, method
, path
, outname
, str(size
))
126 # Class for thumbnail programs
128 """Base class for programs which generate thumbnails.
130 The method run() creates the thumbnail for a source file. This
131 calls the methods get_image(), process_image() and store_image().
132 process_image() takes the image returned by get_image() and scales it to
133 the correct dimensions then passes it through post_process_image() (which
136 You should override the method get_image() to create the image. You can
137 also override post_process_image() if you wish to work on the scaled
140 def __init__(self
, name
, fname
, use_wdir
=False, debug
=False):
141 """Initialise the thumbnailer.
142 name - name of the program
143 fname - a string to use in generated temp file names
144 use_wdir - if true then use a temp directory to store files
145 debug - if false then suppress most error messages
149 self
.use_wdir
=use_wdir
154 def run(self
, inname
, outname
=None, rsize
=96):
155 """Generate the thumbnail from the file
157 outname - path to store thumbnail image, or None for default location
158 rsize - maximum size of thumbnail (in either axis)
161 outname
=get_path_save(inname
)
163 elif not os
.path
.isabs(outname
):
164 outname
=os
.path
.abspath(outname
)
167 self
.make_working_dir()
170 img
=self
.get_image(inname
, rsize
)
174 img
=self
.process_image(img
, rsize
)
175 self
.store_image(img
, inname
, outname
, ow
, oh
)
178 # Thumbnail creation has failed.
179 self
.creation_failed(inname
, outname
, rsize
)
182 self
.report_exception()
185 self
.remove_working_dir()
187 def get_image(self
, inname
, rsize
):
188 """Method you must define for your thumbnailer to do anything"""
189 raise _("Thumbnail not implemented")
191 def process_image(self
, img
, rsize
):
192 """Take the raw image and scale it to the correct size.
193 Returns the result of scaling img and passing it to
194 post_process_image()"""
198 s
=float(rsize
)/float(ow
)
200 s
=float(rsize
)/float(oh
)
205 img
=img
.scale_simple(w
, h
, rox
.g
.gdk
.INTERP_BILINEAR
)
207 return self
.post_process_image(img
, w
, h
)
209 def post_process_image(self
, img
, w
, h
):
210 """Perform some post-processing on the image.
211 img - gdk-pixbuf of the image
214 Return: modified image
215 The default implementation just returns the image unchanged."""
218 def store_image(self
, img
, inname
, outname
, ow
, oh
):
219 """Store the thumbnail image it the correct location, adding
220 the extra data required by the thumbnail spec."""
223 img
.save(outname
+self
.fname
, 'png',
224 {'tEXt::Thumb::Image::Width': str(ow
),
225 'tEXt::Thumb::Image::Height': str(oh
),
226 "tEXt::Thumb::Size": str(s
.st_size
),
227 "tEXt::Thumb::MTime": str(s
.st_mtime
),
228 'tEXt::Thumb::URI': rox
.escape('file://'+inname
),
229 'tEXt::Software': self
.name
})
230 os
.rename(outname
+self
.fname
, outname
)
233 def make_working_dir(self
):
234 """Create the temporary directory and change into it."""
236 self
.work_dir
=tempfile
.mkdtemp()
238 self
.report_exception()
242 self
.old_dir
=os
.getcwd()
243 os
.chdir(self
.work_dir
)
245 def remove_working_dir(self
):
246 """Remove our temporary directory, after changing back to the
248 if not self
.work_dir
:
251 os
.chdir(self
.old_dir
)
254 shutil
.rmtree(self
.work_dir
)
256 self
.report_exception()
259 def creation_failed(self
, inname
, outname
, rsize
):
260 """Creation of a thumbnail failed. Stores a dummy file to mark it
261 as per the Thumbnail spec."""
265 dummy
=rox
.g
.gdk
.Pixbuf(rox
.g
.gdk
.COLORSPACE_RGB
, False,
267 outname
=get_path_save(inname
, ttype
=os
.path
.join('fail',
269 d
=os
.path
.dirname(outname
)
273 if exc
.errno
!=errno
.EEXIST
:
276 dummy
.save(outname
+self
.fname
, 'png',
277 {"tEXt::Thumb::Size": str(s
.st_size
),
278 "tEXt::Thumb::MTime": str(s
.st_mtime
),
279 'tEXt::Thumb::URI': rox
.escape('file://'+inname
),
280 'tEXt::Software': self
.name
})
281 os
.rename(outname
+self
.fname
, outname
)
285 def report_exception(self
):
286 """Report an exception if debug enabled, otherwise ignore it"""
289 rox
.report_exception()
291 class GdkPixbufThumbnailer(Thumbnailer
):
292 """An example implementation of a Thumbnailer class. It uses GdkPixbuf
293 to generate thumbnails of image files."""
296 Thumbnailer
.__init
__(self
, 'GdkPixbufThumbnailer', 'pixbuf',
299 def get_image(self
, inname
, rsize
):
300 if hasattr(rox
.g
.gdk
, 'pixbuf_new_from_file_at_size'):
301 img
=rox
.g
.gdk
.pixbuf_new_from_file_at_size(inname
, rsize
, rsize
)
303 img
=rox
.g
.gdk
.pixbuf_new_from_file(inname
)