Source code for tagit.model.thumbnail

"""Thumbnail creation, loading and storing.

Thumbnails can be stored in many ways and formats. Maybe we even want several
copies. For example embedded and external ones. For this purpose, the thumbnail
operations are outsourced from *DataModel*, but still basically a functionality
provider.

.. TODO::
    Works, BUT in combination with the AsyncImage, the loading progress
    is not shown when creating the thumbnail. Obviously, because we first
    create the thumbnail, then use the AsyncImage.
    However, it would be perfect to have a custom loader which covers
    the thumbnailing process as well. Or load the image and create the
    thumbnail in the background. Or anything to get some user feedback
    while the image is being processed.

"""
# STANDARD IMPORTS
from PIL import Image
from PIL import VERSION as PILVERSION
import os
import os.path
from StringIO import StringIO

# INNER-MODULE IMPORTS
from resize import resize
from ..basics import fst, string_types, check_version

check_version(map(int, PILVERSION.split('.')), (1, 1, 7))

# CONSTANTS
THUMBNAIL_QUALITY = 75
THUMBNAIL_RESOLUTION = (400, 400)

# EXPORTS
__all__ = ('Thumbnailer_Exif', 'Thumbnailer_FS', 'Thumbnailer_FS_Flat', 'Thumbnailer_Chain')

## CODE ##

[docs]class Thumbnailer(object): """Interface for thumbnail operations. *Thumbnailer* defines an interface for any thumbnail handling method. It implements creation but remains abstract for *get* and *set*. """
[docs] def create(self, image, resolution=THUMBNAIL_RESOLUTION): """Create a thumbnail of *image* and return the Image object. """ # create thumbnail of *image* if not os.path.exists(image): raise Exception('Image does not exist') return resize(Image.open(image), resolution)
[docs] def has(self, image): """Search for a thumbnail of *image*. Return success status as boolean. """ return self.get(image) is not None
[docs] def get(self, image, resolution=THUMBNAIL_RESOLUTION): """Search for a thumbnail of *image*. Return the path (file-based) or object (embedded) image. Return None if no thumbnail was found. """ abstract()
[docs] def set(self, image, thumb): """Write the thumbnail *thumb* to *image*. """ abstract()
[docs]class Thumbnailer_Exif(Thumbnailer): """Read and write the exif thumbnail """ def __init__(self, meta_adapter): super(Thumbnailer_Exif, self).__init__() self.meta_adapter = meta_adapter def get(self, image, resolution=THUMBNAIL_RESOLUTION): thumb = self.meta_adapter.get_thumbnail(image) if thumb is not None: return Image.open(thumb) return None def set(self, image, thumb): data = StringIO() thumb.save(data, format="jpeg", quality=THUMBNAIL_QUALITY) current_size = self.meta_adapter.get_thumbnail(image) # Overwrite only if inexistent or larger if current_size is None or len(current_size.getvalue()) < len(data.getvalue()): self.meta_adapter.set_thumbnail(image, data.getvalue()) return thumb
[docs]class Thumbnailer_FS(Thumbnailer): """Read and write thumbnails from/to the file system. Clones the original file structure within a *root* directory. """ def __init__(self, root): super(Thumbnailer_FS, self).__init__() self.root = root def _add_prefix(self, image): """Add the thumbnail root to the image path.""" return os.path.normpath(self.root + os.path.sep + image) # Can't use os.path.join if *image* is an absolute path. def _add_resolution(self, path, resolution): """Add the resolution information to the image path.""" root, ext = os.path.splitext(path) return os.path.join(path, str(fst(resolution)) + ext) def _resolutions(self, root): """List resolutions of thumbnail copies.""" if not os.path.exists(root): return [] return map(lambda s: int(fst(os.path.splitext(s))), os.listdir(root)) def has(self, image): imgdir = self._add_prefix(image) return os.path.exists(imgdir) and len(os.listdir(imgdir)) > 0 def get(self, image, resolution=THUMBNAIL_RESOLUTION): imgdir = self._add_prefix(image) exact = self._add_resolution(imgdir, resolution) if os.path.exists(exact): return exact copies = sorted(self._resolutions(imgdir)) for res in copies: if res >= resolution[0]: return self._add_resolution(imgdir, (res, 0)) if len(copies) > 0: return self._add_resolution(imgdir, (copies[-1], 0)) return None def set(self, image, thumb): imgdir = self._add_prefix(image) try: os.makedirs(imgdir) except OSError: pass trg = self._add_resolution(imgdir, thumb.size) thumb.save(trg, "JPEG", quality=THUMBNAIL_QUALITY) return trg # DEBUG return thumb
[docs]class Thumbnailer_FS_Flat(Thumbnailer): """Read and write thumbnails from/to the file system. All thumbnails go to the same directory, with a generic file name. The database links the image and its thumb file. """ def __init__(self, conn, root): super(Thumbnailer_FS_Flat, self).__init__() self.conn = conn self.root = root def get(self, image, resolution=THUMBNAIL_RESOLUTION): # query database # check path raise NotImplementedError() def set(self, image, thumb): # generate path # write to path # update database raise NotImplementedError() return thumb
[docs]class Thumbnailer_Database(Thumbnailer): """Read and write thumbnails from/to the database. """ def __init__(self, conn): super(Thumbnailer_Database, self).__init__() self.conn = conn def get(self, image, resolution=THUMBNAIL_RESOLUTION): # query database raise NotImplementedError() def set(self, image, thumb): # write *thumb* to database raise NotImplementedError() return thumb
[docs]class Thumbnailer_Chain(Thumbnailer): """Link several *Thumbnailers* together. get: The first match is returned. set: Applied on all childs. """ def __init__(self, *args): super(Thumbnailer_Chain, self).__init__() self.children = args def has(self, image): for child in self.children: if child.has(image): return True return False def get(self, image, resolution=THUMBNAIL_RESOLUTION): # query childs until the first hit obj = None for child in self.children: thumb = child.get(image, resolution) if isinstance(thumb, string_types): # String has highest priority. Return immediately return thumb elif thumb is not None: obj = thumb # Object or no thumbnail found return obj def set(self, image, thumb): # write to all childs obj, file_ = None, None for child in self.children: # Set image on child. # Note that this is ignorant of existing data and may overwrite it img = child.set(image, thumb) if isinstance(img, string_types): file_ = img elif img is not None: obj = img # Return file-based if file_ is not None: return file_ # Return object based or None if thumb not set. return obj ## EOF ##