photolib/photoapp/thumb.py

96 lines
3.8 KiB
Python
Raw Normal View History

2018-09-08 15:49:16 -07:00
import os
2018-09-09 12:05:13 -07:00
import sys
import traceback
from time import time
from collections import defaultdict
from multiprocessing import Process
from PIL import Image, ImageOps
2019-07-04 18:41:57 -07:00
import tempfile
from shutil import copyfileobj
2019-07-11 19:26:22 -07:00
from contextlib import closing
import logging
2018-09-08 15:49:16 -07:00
2019-07-04 18:41:57 -07:00
class ThumbGenerator(object):
2019-07-11 19:26:22 -07:00
def __init__(self, library, storage):
2019-07-04 18:41:57 -07:00
self.library = library
2019-07-11 19:26:22 -07:00
self.storage = storage
2018-09-09 12:05:13 -07:00
self._failed_thumbs_cache = defaultdict(dict)
2018-09-08 15:49:16 -07:00
def get_datedir_path(self, date):
"""
Return a path like 2018/3/31 given a datetime object representing the same date
"""
return os.path.join(str(date.year), str(date.month), str(date.day))
2018-09-09 12:05:13 -07:00
def make_thumb(self, photo, style):
"""
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
"""
2018-09-09 16:47:05 -07:00
# style tuples: max x, max y, rotate ok)
# rotate ok means x and y maxes can be swapped if it fits the image's aspect ratio better
styles = {"tiny": (80, 80, False),
"small": (100, 100, False),
"feed": (250, 250, False),
"preview": (1024, 768, True),
"big": (2048, 1536, True)}
2019-07-11 19:26:22 -07:00
dest = os.path.join("thumbs", style, photo.uuid[0:2], "{}.jpg".format(photo.uuid))
if self.storage.exists(dest):
return self.storage.open(dest, "rb")
2018-09-09 13:45:26 -07:00
if photo.width is None: # todo better detection of images that PIL can't open
return None
2019-07-11 09:16:14 -07:00
if photo.uuid in self._failed_thumbs_cache[style]:
return None
thumb_width, thumb_height, flip_ok = styles[style]
i_width = photo.width
i_height = photo.height
im_is_rotated = photo.orientation % 2 != 0 or i_height > i_width
2018-09-09 16:47:05 -07:00
2019-07-11 09:16:14 -07:00
if im_is_rotated and flip_ok:
thumb_width, thumb_height = thumb_height, thumb_width
2018-09-09 16:47:05 -07:00
2019-07-11 09:16:14 -07:00
thumb_width = min(thumb_width, i_width if i_width > 0 else 999999999) # TODO do we even have photo.width if PIL can't read the image?
thumb_height = min(thumb_height, i_height if i_height > 0 else 999999999) # TODO this seems bad
2018-09-09 16:47:05 -07:00
2019-07-11 09:16:14 -07:00
# TODO have the subprocess download the file
2019-07-11 19:26:22 -07:00
# TODO thundering herd
2019-07-11 09:16:14 -07:00
with tempfile.TemporaryDirectory() as tmpdir:
2019-07-11 19:26:22 -07:00
fthumblocal = tempfile.NamedTemporaryFile(delete=True)
2019-07-11 09:16:14 -07:00
fpath = os.path.join(tmpdir, "image")
with self.library.storage.open(photo.path, 'rb') as fsrc:
2019-07-11 19:26:22 -07:00
with open(fpath, 'wb') as ftmpdest:
copyfileobj(fsrc, ftmpdest)
2019-07-04 18:41:57 -07:00
2019-07-11 19:26:22 -07:00
p = Process(target=self.gen_thumb, args=(fpath, fthumblocal.name, thumb_width, thumb_height, photo.orientation))
2019-07-11 09:16:14 -07:00
p.start()
p.join()
if p.exitcode != 0:
self._failed_thumbs_cache[style][photo.uuid] = True # dont retry failed generations
return None
2019-07-11 19:26:22 -07:00
with closing(self.storage.open(dest, 'wb')) as fdest:
copyfileobj(fthumblocal, fdest)
fthumblocal.seek(0)
return fthumblocal
2018-09-09 12:05:13 -07:00
@staticmethod
2018-09-09 13:45:26 -07:00
def gen_thumb(src_img, dest_img, width, height, rotation):
2018-09-09 12:05:13 -07:00
try:
start = time()
# TODO lock around the dir creation
os.makedirs(os.path.split(dest_img)[0], exist_ok=True)
image = Image.open(src_img)
2018-09-09 13:45:26 -07:00
image = image.rotate(90 * rotation, expand=True)
2018-09-09 12:05:13 -07:00
thumb = ImageOps.fit(image, (width, height), Image.ANTIALIAS)
thumb.save(dest_img, 'JPEG')
2019-07-11 19:26:22 -07:00
logging.info("Generated {} in {}s".format(dest_img, round(time() - start, 4)))
2019-07-04 18:41:57 -07:00
except Exception:
2018-09-09 12:05:13 -07:00
traceback.print_exc()
if os.path.exists(dest_img):
os.unlink(dest_img)
sys.exit(1)