photolib/photoapp/thumbtool.py

66 lines
2.6 KiB
Python

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)