|
|
|
@ -3,19 +3,22 @@ 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):
|
|
|
|
|
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: local path to thumbnail file or None if creation failed or was blocked
|
|
|
|
|
:return: file-like object of the thumbnail image's data or None if no thumbnail is available
|
|
|
|
|
"""
|
|
|
|
|
dest = thumb_path(style_name, photo.uuid)
|
|
|
|
|
|
|
|
|
@ -23,15 +26,23 @@ class ThumbGenerator(object):
|
|
|
|
|
return self.storage.open(dest, "rb")
|
|
|
|
|
|
|
|
|
|
if photo.uuid in self._failed_thumbs_cache[style_name]:
|
|
|
|
|
return None
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# if photo.width is None: # todo better detection of images that PIL can't open
|
|
|
|
|
# return None
|
|
|
|
|
# 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:
|
|
|
|
|
fthumblocal = tempfile.NamedTemporaryFile(delete=True)
|
|
|
|
|
fpath = os.path.join(tmpdir, "image")
|
|
|
|
|
with self.library.storage.open(photo.path, 'rb') as fsrc:
|
|
|
|
|
with open(fpath, 'wb') as ftmpdest:
|
|
|
|
@ -45,4 +56,10 @@ class ThumbGenerator(object):
|
|
|
|
|
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)
|
|
|
|
|