"""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 ##