|
|
@ -7,12 +7,14 @@ from multiprocessing import Process |
|
|
|
from PIL import Image, ImageOps |
|
|
|
import tempfile |
|
|
|
from shutil import copyfileobj |
|
|
|
from contextlib import closing |
|
|
|
import logging |
|
|
|
|
|
|
|
|
|
|
|
class ThumbGenerator(object): |
|
|
|
def __init__(self, library, cache_path): |
|
|
|
def __init__(self, library, storage): |
|
|
|
self.library = library |
|
|
|
self.cache_path = cache_path |
|
|
|
self.storage = storage |
|
|
|
self._failed_thumbs_cache = defaultdict(dict) |
|
|
|
|
|
|
|
def get_datedir_path(self, date): |
|
|
@ -33,9 +35,10 @@ class ThumbGenerator(object): |
|
|
|
"feed": (250, 250, False), |
|
|
|
"preview": (1024, 768, True), |
|
|
|
"big": (2048, 1536, True)} |
|
|
|
dest = os.path.join(self.cache_path, "thumbs", style, "{}.jpg".format(photo.uuid)) |
|
|
|
if os.path.exists(dest): |
|
|
|
return os.path.abspath(dest) |
|
|
|
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") |
|
|
|
if photo.width is None: # todo better detection of images that PIL can't open |
|
|
|
return None |
|
|
|
if photo.uuid in self._failed_thumbs_cache[style]: |
|
|
@ -53,19 +56,26 @@ class ThumbGenerator(object): |
|
|
|
thumb_height = min(thumb_height, i_height if i_height > 0 else 999999999) # TODO this seems bad |
|
|
|
|
|
|
|
# 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 fdest: |
|
|
|
copyfileobj(fsrc, fdest) |
|
|
|
with open(fpath, 'wb') as ftmpdest: |
|
|
|
copyfileobj(fsrc, ftmpdest) |
|
|
|
|
|
|
|
p = Process(target=self.gen_thumb, args=(fpath, dest, thumb_width, thumb_height, photo.orientation)) |
|
|
|
p = Process(target=self.gen_thumb, args=(fpath, fthumblocal.name, 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) |
|
|
|
|
|
|
|
with closing(self.storage.open(dest, 'wb')) as fdest: |
|
|
|
copyfileobj(fthumblocal, fdest) |
|
|
|
|
|
|
|
fthumblocal.seek(0) |
|
|
|
return fthumblocal |
|
|
|
|
|
|
|
@staticmethod |
|
|
|
def gen_thumb(src_img, dest_img, width, height, rotation): |
|
|
@ -77,7 +87,7 @@ class ThumbGenerator(object): |
|
|
|
image = image.rotate(90 * rotation, expand=True) |
|
|
|
thumb = ImageOps.fit(image, (width, height), Image.ANTIALIAS) |
|
|
|
thumb.save(dest_img, 'JPEG') |
|
|
|
print("Generated {} in {}s".format(dest_img, round(time() - start, 4))) |
|
|
|
logging.info("Generated {} in {}s".format(dest_img, round(time() - start, 4))) |
|
|
|
except Exception: |
|
|
|
traceback.print_exc() |
|
|
|
if os.path.exists(dest_img): |
|
|
|