contact thumbserver for video thumbs

dave 2 years ago
parent 44e7bc81d6
commit 613124dbf9

@ -601,6 +601,7 @@ def main():
default=int(os.environ.get("PHOTOLIB_PORT", 8080)), type=int)
parser.add_argument('-l', '--library', default=os.environ.get("STORAGE_URL"), help="library path")
parser.add_argument('-c', '--cache', default=os.environ.get("CACHE_URL"), help="cache url")
parser.add_argument('-t', '--thumb-service', default=os.environ.get("THUMB_SERVICE_URL"), help="thumbnail service url")
parser.add_argument('-s', '--database', help="sqlalchemy database connection uri",
@ -612,6 +613,9 @@ def main():
args = parser.parse_args()
logging.basicConfig(level=logging.INFO if args.debug else logging.WARNING,
format="%(asctime)-15s %(levelname)-8s %(filename)s:%(lineno)d %(message)s")
if not args.database:
parser.error("--database or DATABASE_URL is required")
@ -621,8 +625,8 @@ def main():
if not args.cache:
parser.error("--cache or CACHE_URL is required")
logging.basicConfig(level=logging.INFO if args.debug else logging.WARNING,
format="%(asctime)-15s %(levelname)-8s %(filename)s:%(lineno)d %(message)s")
if not args.thumb_service:
logging.warning("THUMB_SERVICE_URL not set. Video thumbnails will be unavailable")
# Get database connection
engine = get_db_engine(args.database)
@ -634,7 +638,7 @@ def main():
# Create various internal tools
library_storage = uri_to_storage(args.library)
library_manager = LibraryManager(library_storage)
thumbnail_tool = ThumbGenerator(library_manager, uri_to_storage(args.cache))
thumbnail_tool = ThumbGenerator(library_manager, uri_to_storage(args.cache), args.thumb_service)
# Setup and mount web ui
tpl_dir = os.path.join(APPROOT, "templates") if not args.debug else "templates"

@ -4,9 +4,11 @@ import cherrypy
import shutil
import tempfile
import traceback
import requests
from threading import Thread
from contextlib import closing
from queue import Queue, Empty
from shutil import copyfileobj
from subprocess import check_call
from photoapp.dbutils import SAEnginePlugin, SATool, db, get_db_engine, create_db_sessionmaker
from import uri_to_storage
@ -46,7 +48,7 @@ def setup_thumb_user(engine):
class ThumbWorker(Thread):
def __init__(self, engine, library, cache, thumbtool):
def __init__(self, engine, library, cache):
self.daemon = True
@ -120,6 +122,20 @@ class ThumbServiceWeb(object):
yield "ok"
class ThumbClient(object):
Client for interacting with the thumbserver api
def __init__(self, server_url):
self.server_url = server_url
self.session = requests.Session()
a = requests.adapters.HTTPAdapter(max_retries=0)
self.session.mount('http://', a)
def request_thumb(self, photo_uuid, style_name):
self.session.get(self.server_url, params=dict(uuid=photo_uuid, style=style_name))
# TODO dedupe me
def validate_password(realm, username, password):
if db.query(User).filter( == username, User.password == pwhash(password)).first():
@ -170,8 +186,7 @@ def main():
library_storage = uri_to_storage(args.library)
library_manager = LibraryManager(library_storage)
cache_storage = uri_to_storage(args.cache)
thumbnail_tool = ThumbGenerator(library_manager, cache_storage)
thumbnail_worker = ThumbWorker(engine, library_storage, cache_storage, thumbnail_tool)
thumbnail_worker = ThumbWorker(engine, library_storage, cache_storage)
# Setup and mount web ui

@ -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 = 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, "rb")
if photo.uuid in self._failed_thumbs_cache[style_name]:
return None
# 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
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
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, 'rb') as fsrc:
with open(fpath, 'wb') as ftmpdest:
@ -45,4 +56,10 @@ class ThumbGenerator(object):
copyfileobj(fthumblocal, fdest)
# 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)