contact thumbserver for video thumbs

This commit is contained in:
dave 2021-08-20 18:00:13 -07:00
parent 44e7bc81d6
commit 613124dbf9
3 changed files with 48 additions and 12 deletions

View File

@ -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")
# https://docs.sqlalchemy.org/en/13/core/engines.html
parser.add_argument('-s', '--database', help="sqlalchemy database connection uri",
default=os.environ.get("DATABASE_URL")),
@ -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"

View File

@ -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 photoapp.storage 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):
super().__init__()
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(User.name == 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)
thumbnail_worker.start()
# Setup and mount web ui

View File

@ -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)