import os from collections import defaultdict import tempfile from shutil import copyfileobj from contextlib import closing from photoapp.types import video_mimes from photoapp.thumb import thumb_path, image_file_style_process from photoapp.thumbserver import ThumbClient class ThumbGenerator(object): def __init__(self, library, storage, thumb_service_url=None): self.library = library self.storage = storage self.thumb_service = ThumbClient(thumb_service_url) if thumb_service_url else None self._failed_thumbs_cache = defaultdict(dict) def make_thumb(self, photo, style_name): """ Create a thumbnail of the given photo, scaled/cropped to the given named style :return: file-like object of the thumbnail image's data or None if no thumbnail is available """ dest = thumb_path(style_name, photo.uuid) if self.storage.exists(dest): return self.storage.open(dest, "rb") if photo.uuid in self._failed_thumbs_cache[style_name]: return # video thumbnails are handled by an external service if photo.format in video_mimes: if self.thumb_service is None: # videos thumbs capability is disabled, show placeholder return self.make_video_thumb(photo.uuid, style_name) # thumbserver will eventually generate a thumb (and the exists call above will return it) # for now, return None to show a placeholder return fthumblocal = tempfile.NamedTemporaryFile() # TODO have the subprocess download the file # TODO thundering herd with tempfile.TemporaryDirectory() as tmpdir: fpath = os.path.join(tmpdir, "image") with self.library.storage.open(photo.path, 'rb') as fsrc: with open(fpath, 'wb') as ftmpdest: copyfileobj(fsrc, ftmpdest) if not image_file_style_process(fpath, fthumblocal.name, style_name, photo.orientation): self._failed_thumbs_cache[style_name][photo.uuid] = True # dont retry failed generations return with closing(self.storage.open(dest, 'wb')) as fdest: copyfileobj(fthumblocal, fdest) fthumblocal.seek(0) # TODO fthumblocal is leaked if we don't hit this return return fthumblocal def make_video_thumb(self, photo_uuid, style_name): #TODO make something like ThumbServiceError so we can differentiate requests stuff from other errors without #having to deal with requests outside of thumb_service's module self.thumb_service.request_thumb(photo_uuid, style_name)