From 696e3ab5689ef8472c2cd969435536b321bdc87a Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Sat, 18 Nov 2023 11:38:25 -0800 Subject: [PATCH] Implement media preview thumbnails for Gallery --- cista/app.py | 3 +- cista/preview.py | 40 ++++++++++++++++++++++++ frontend/src/App.vue | 2 +- frontend/src/components/HeaderMain.vue | 2 +- frontend/src/components/MediaPreview.vue | 7 +++-- frontend/src/repositories/Document.ts | 3 ++ frontend/src/stores/main.ts | 2 +- frontend/src/views/ExplorerView.vue | 6 ++-- frontend/vite.config.ts | 1 + 9 files changed, 57 insertions(+), 9 deletions(-) create mode 100644 cista/preview.py diff --git a/cista/app.py b/cista/app.py index c36a858..9a98c61 100644 --- a/cista/app.py +++ b/cista/app.py @@ -16,7 +16,7 @@ from sanic.exceptions import Forbidden, NotFound from sanic.log import logger from stream_zip import ZIP_AUTO, stream_zip -from cista import auth, config, session, watching +from cista import auth, config, preview, session, watching from cista.api import bp from cista.util.apphelpers import handle_sanic_exception @@ -25,6 +25,7 @@ sanic.helpers._ENTITY_HEADERS = frozenset() app = Sanic("cista", strict_slashes=True) app.blueprint(auth.bp) +app.blueprint(preview.bp) app.blueprint(bp) app.exception(Exception)(handle_sanic_exception) diff --git a/cista/preview.py b/cista/preview.py new file mode 100644 index 0000000..129a0ca --- /dev/null +++ b/cista/preview.py @@ -0,0 +1,40 @@ +import asyncio +import io +from pathlib import PurePosixPath +from urllib.parse import unquote + +from PIL import Image +from sanic import Blueprint, raw +from sanic.exceptions import Forbidden, NotFound + +from cista import config +from cista.util.filename import sanitize + +bp = Blueprint("preview", url_prefix="/preview") + + +@bp.get("/") +async def preview(req, path): + """Preview a file""" + width = int(req.query_string) if req.query_string else 768 + rel = PurePosixPath(sanitize(unquote(path))) + path = config.config.path / rel + if not path.is_file(): + raise NotFound("File not found") + size = path.lstat().st_size + if size > 20 * 10**6: + raise Forbidden("File too large") + img = await asyncio.get_event_loop().run_in_executor( + req.app.ctx.threadexec, process_image, path, width + ) + return raw(img, content_type="image/webp") + + +def process_image(path, maxsize): + img = Image.open(path) + w, h = img.size + img.thumbnail((min(w, maxsize), min(h, maxsize))) + # Save as webp + imgdata = io.BytesIO() + img.save(imgdata, format="webp", quality=70, method=6) + return imgdata.getvalue() diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 54ab67f..f737c68 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -93,7 +93,7 @@ const globalShortcutHandler = (event: KeyboardEvent) => { } // G toggles Gallery else if (!input && keyup && event.key === 'g') { - store.gallery = !store.gallery + store.prefs.gallery = !store.prefs.gallery } // Keys Backquote-1-2-3 to sort columns else if ( diff --git a/frontend/src/components/HeaderMain.vue b/frontend/src/components/HeaderMain.vue index f0e3a0e..ba38221 100644 --- a/frontend/src/components/HeaderMain.vue +++ b/frontend/src/components/HeaderMain.vue @@ -23,7 +23,7 @@ /> - + diff --git a/frontend/src/components/MediaPreview.vue b/frontend/src/components/MediaPreview.vue index e467060..32ae7e3 100644 --- a/frontend/src/components/MediaPreview.vue +++ b/frontend/src/components/MediaPreview.vue @@ -1,5 +1,5 @@