photolib/photoapp/thumb.py

84 lines
3.5 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
2018-09-08 15:49:16 -07:00
2019-07-04 18:41:57 -07:00
class ThumbGenerator(object):
def __init__(self, library, cache_path):
self.library = library
2018-09-11 22:25:09 -07:00
self.cache_path = cache_path
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)}
2018-09-09 12:05:13 -07:00
dest = os.path.join(self.cache_path, "thumbs", style, "{}.jpg".format(photo.uuid))
if os.path.exists(dest):
return os.path.abspath(dest)
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
2018-09-09 12:05:13 -07:00
if photo.uuid not in self._failed_thumbs_cache[style]:
2018-09-09 16:47:05 -07:00
thumb_width, thumb_height, flip_ok = styles[style]
i_width = photo.width
i_height = photo.height
2018-09-09 17:21:23 -07:00
im_is_rotated = photo.orientation % 2 != 0 or i_height > i_width
2018-09-09 16:47:05 -07:00
if im_is_rotated and flip_ok:
thumb_width, thumb_height = thumb_height, thumb_width
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
2019-07-04 18:41:57 -07:00
# TODO have the subprocess download the file
with tempfile.TemporaryDirectory() as tmpdir:
fpath = os.path.join(tmpdir, "image")
with self.library.storage.open(photo.path, 'rb') as fsrc, open(fpath, 'wb') as fdest:
copyfileobj(fsrc, fdest)
p = Process(target=self.gen_thumb, args=(fpath, dest, thumb_width, thumb_height, photo.orientation))
p.start()
p.join()
if p.exitcode != 0:
self._failed_thumbs_cache[style][photo.uuid] = True # dont retry failed generations
return None
return os.path.abspath(dest)
2018-09-09 12:05:13 -07:00
return None
@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')
print("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)