66 lines
2.6 KiB
Python
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)
|