From 0cbb48d01979aa71206c0fc4e8cbd1bec5a1192f Mon Sep 17 00:00:00 2001 From: lifegpc Date: Sun, 27 Aug 2023 09:37:37 +0800 Subject: [PATCH] Update /api/thumbnail --- config.ts | 2 +- routes/api/status.ts | 15 ++++++++++ routes/api/thumbnail/[id].ts | 39 ++++++++++++++++++++++-- routes/thumbnail/[id].ts | 46 ++++++++++------------------- server/status.ts | 1 + thumbnail/base.ts | 57 +++++++++++++++++++++++++++++++++++- thumbnail/ffmpeg_api.ts | 24 ++++++++++++++- 7 files changed, 149 insertions(+), 35 deletions(-) diff --git a/config.ts b/config.ts index 4536d49..26715ef 100644 --- a/config.ts +++ b/config.ts @@ -123,7 +123,7 @@ export class Config { return this._return_string("ffmpeg_path") || "ffmpeg"; } get thumbnail_method() { - const n = this._return_number("thumbnail") || 0; + const n = this._return_number("thumbnail_method") || 0; if (n < 0 || n > 1) return ThumbnailMethod.FFMPEG_BINARY; return n as ThumbnailMethod; } diff --git a/routes/api/status.ts b/routes/api/status.ts index 91eed8f..f1cb3cc 100644 --- a/routes/api/status.ts +++ b/routes/api/status.ts @@ -3,6 +3,19 @@ import { get_task_manager } from "../../server.ts"; import type { StatusData } from "../../server/status.ts"; import { return_data } from "../../server/utils.ts"; import { check_ffmpeg_binary } from "../../thumbnail/ffmpeg_binary.ts"; +import type * as FFMPEG_API from "../../thumbnail/ffmpeg_api.ts"; + +let ffmpeg_api: typeof FFMPEG_API | undefined; + +async function check_ffmpeg_api() { + if (ffmpeg_api) return true; + try { + ffmpeg_api = await import("../../thumbnail/ffmpeg_api.ts"); + return true; + } catch (_) { + return false; + } +} export const handler: Handlers = { async GET(_req, ctx) { @@ -12,6 +25,7 @@ export const handler: Handlers = { const ffmpeg_binary_enabled = await check_ffmpeg_binary( m.cfg.ffmpeg_path, ); + const ffmpeg_api_enabled = await check_ffmpeg_api(); const meilisearch_enabled = m.meilisearch !== undefined; const meilisearch = is_authed && meilisearch_enabled && m.cfg.meili_host && @@ -24,6 +38,7 @@ export const handler: Handlers = { : undefined; const no_user = m.db.get_user_count() === 0; return return_data({ + ffmpeg_api_enabled, ffmpeg_binary_enabled, meilisearch_enabled, meilisearch, diff --git a/routes/api/thumbnail/[id].ts b/routes/api/thumbnail/[id].ts index 8779bcd..4c9e6a9 100644 --- a/routes/api/thumbnail/[id].ts +++ b/routes/api/thumbnail/[id].ts @@ -3,7 +3,10 @@ import { exists } from "std/fs/exists.ts"; import { get_task_manager } from "../../../server.ts"; import { parse_bool, parse_int } from "../../../server/parse_form.ts"; import { + gen_thumbnail_config_params, generate_filename, + parse_thumbnail_align, + parse_thumbnail_method, ThumbnailConfig, ThumbnailGenMethod, } from "../../../thumbnail/base.ts"; @@ -18,6 +21,9 @@ import { get_host } from "../../../server/utils.ts"; import pbkdf2Hmac from "pbkdf2-hmac"; import { encode } from "std/encoding/base64.ts"; import { SortableURLSearchParams } from "../../../server/SortableURLSearchParams.ts"; +import type * as FFMPEG_API from "../../../thumbnail/ffmpeg_api.ts"; + +let ffmpeg_api: typeof FFMPEG_API | undefined; export const handler: Handlers = { async GET(req, ctx) { @@ -39,11 +45,14 @@ export const handler: Handlers = { const height = await parse_int(u.searchParams.get("height"), null); const quality = await parse_int(u.searchParams.get("quality"), 1); const force = await parse_bool(u.searchParams.get("force"), false); + const tmethod = parse_thumbnail_method(u.searchParams.get("method")); + const align = parse_thumbnail_align(u.searchParams.get("align")); const cfg: ThumbnailConfig = { width: 0, height: 0, quality, - method: ThumbnailGenMethod.Unknown, + method: tmethod, + align: align, }; if (width !== null && height !== null) { cfg.width = width; @@ -51,16 +60,20 @@ export const handler: Handlers = { } else if (width !== null) { cfg.width = width; cfg.height = Math.floor(f.height / f.width * width); + cfg.method = ThumbnailGenMethod.Unknown; } else if (height !== null) { cfg.height = height; cfg.width = Math.floor(f.width / f.height * height); + cfg.method = ThumbnailGenMethod.Unknown; } else { if (f.width > f.height) { cfg.width = max; cfg.height = Math.floor(f.height / f.width * max); + cfg.method = ThumbnailGenMethod.Unknown; } else { cfg.height = max; cfg.width = Math.floor(f.width / f.height * max); + cfg.method = ThumbnailGenMethod.Unknown; } } if (!force) { @@ -68,6 +81,9 @@ export const handler: Handlers = { return Response.redirect(`${get_host(req)}/api/file/${f.id}`); } } + if (method === ThumbnailMethod.FFMPEG_BINARY) { + cfg.method = ThumbnailGenMethod.Unknown; + } const output = generate_filename(b, f, cfg); if (!(await exists(output))) { if (method === ThumbnailMethod.FFMPEG_BINARY) { @@ -82,13 +98,32 @@ export const handler: Handlers = { status: 500, }); } + } else if (method === ThumbnailMethod.FFMPEG_API) { + if (!ffmpeg_api) { + ffmpeg_api = await import( + "../../../thumbnail/ffmpeg_api.ts" + ); + } + const re = await ffmpeg_api.fa_generate_thumbnail( + f.path, + output, + cfg, + ); + if (!re) { + return new Response("Failed to generate thumbnail.", { + status: 500, + }); + } } } const opts: GetFileResponseOptions = {}; if (m.cfg.img_verify_secret) { const verify = u.searchParams.get("verify"); if (verify === null) { - const bs = new SortableURLSearchParams(u.search, ["verify"]); + const bs = new SortableURLSearchParams( + gen_thumbnail_config_params(cfg), + ["verify"], + ); const tverify = encode( new Uint8Array( await pbkdf2Hmac( diff --git a/routes/thumbnail/[id].ts b/routes/thumbnail/[id].ts index 6d91a08..642d5cd 100644 --- a/routes/thumbnail/[id].ts +++ b/routes/thumbnail/[id].ts @@ -2,11 +2,7 @@ import { Handlers } from "$fresh/server.ts"; import { exists } from "std/fs/exists.ts"; import { get_task_manager } from "../../server.ts"; import { parse_int } from "../../server/parse_form.ts"; -import { - generate_filename, - ThumbnailConfig, - ThumbnailGenMethod, -} from "../../thumbnail/base.ts"; +import { generate_filename, ThumbnailConfig } from "../../thumbnail/base.ts"; import { sure_dir } from "../../utils.ts"; import { get_file_response, @@ -50,34 +46,24 @@ export const handler: Handlers = { if (verify !== tverify) { return new Response("verify is invalid.", { status: 400 }); } - const max = await parse_int(u.searchParams.get("max"), 1200); const width = await parse_int(u.searchParams.get("width"), null); const height = await parse_int(u.searchParams.get("height"), null); - const quality = await parse_int(u.searchParams.get("quality"), 1); - const cfg: ThumbnailConfig = { - width: 0, - height: 0, - quality, - method: ThumbnailGenMethod.Unknown, - }; - if (width !== null && height !== null) { - cfg.width = width; - cfg.height = height; - } else if (width !== null) { - cfg.width = width; - cfg.height = Math.floor(f.height / f.width * width); - } else if (height !== null) { - cfg.height = height; - cfg.width = Math.floor(f.width / f.height * height); - } else { - if (f.width > f.height) { - cfg.width = max; - cfg.height = Math.floor(f.height / f.width * max); - } else { - cfg.height = max; - cfg.width = Math.floor(f.width / f.height * max); - } + const quality = await parse_int(u.searchParams.get("quality"), null); + const method = await parse_int(u.searchParams.get("method"), null); + const align = await parse_int(u.searchParams.get("align"), null); + if ( + width === null || height === null || quality === null || + method === null || align === null + ) { + return new Response("params is missing", { status: 400 }); } + const cfg: ThumbnailConfig = { + width, + height, + quality, + method, + align, + }; const output = generate_filename(b, f, cfg); if (!(await exists(output))) { return new Response("file not exists.", { status: 500 }); diff --git a/server/status.ts b/server/status.ts index 83c3fd7..5e7224e 100644 --- a/server/status.ts +++ b/server/status.ts @@ -1,4 +1,5 @@ export type StatusData = { + ffmpeg_api_enabled: boolean; ffmpeg_binary_enabled: boolean; meilisearch_enabled: boolean; meilisearch?: { diff --git a/thumbnail/base.ts b/thumbnail/base.ts index d70a634..c24ff0e 100644 --- a/thumbnail/base.ts +++ b/thumbnail/base.ts @@ -22,20 +22,75 @@ export type ThumbnailConfig = { height: number; quality: number; method: ThumbnailGenMethod; + align: ThumbnailAlign; }; +export function gen_thumbnail_config_params(cfg: ThumbnailConfig) { + return { + width: cfg.width.toString(), + height: cfg.height.toString(), + quality: cfg.quality.toString(), + method: cfg.method.toString(), + align: cfg.align.toString(), + }; +} + +export function parse_thumbnail_method(s: string | null) { + if (s === null) return ThumbnailGenMethod.Unknown; + const t = s.toLowerCase(); + switch (t) { + case "cover": + return ThumbnailGenMethod.Cover; + case "contain": + return ThumbnailGenMethod.Contain; + case "fill": + return ThumbnailGenMethod.Fill; + default: + return ThumbnailGenMethod.Unknown; + } +} + +export function parse_thumbnail_align(s: string | null) { + if (s === null) return ThumbnailAlign.Left; + const t = s.toLowerCase(); + switch (t) { + case "center": + return ThumbnailAlign.Center; + case "right": + case "bottom": + return ThumbnailAlign.Right; + default: + return ThumbnailAlign.Left; + } +} + export function generate_filename( base: string, f: EhFile, cfg: ThumbnailConfig, ) { let method = ""; + let balign = ""; + let align = ""; + switch (cfg.align) { + case ThumbnailAlign.Left: + balign = "-left"; + break; + case ThumbnailAlign.Center: + balign = "-center"; + break; + case ThumbnailAlign.Right: + balign = "-right"; + break; + } switch (cfg.method) { case ThumbnailGenMethod.Cover: method = "-cover"; + align = balign; break; case ThumbnailGenMethod.Contain: method = "-contain"; + align = balign; break; case ThumbnailGenMethod.Fill: method = "-fill"; @@ -44,7 +99,7 @@ export function generate_filename( return join( base, filterFilename( - `${f.id}-${f.token}-${cfg.width}x${cfg.height}-q${cfg.quality}${method}.jpg`, + `${f.id}-${f.token}-${cfg.width}x${cfg.height}-q${cfg.quality}${method}${align}.jpg`, ), ); } diff --git a/thumbnail/ffmpeg_api.ts b/thumbnail/ffmpeg_api.ts index 40680b6..6bce07b 100644 --- a/thumbnail/ffmpeg_api.ts +++ b/thumbnail/ffmpeg_api.ts @@ -1,6 +1,6 @@ /// import { Struct } from "pwn/mod.ts"; -import { ThumbnailAlign, ThumbnailGenMethod } from "./base.ts"; +import { ThumbnailAlign, ThumbnailConfig, ThumbnailGenMethod } from "./base.ts"; let libSuffix = ""; let libPrefix = "lib"; @@ -78,3 +78,25 @@ export async function gen_thumbnail( if (re.e) return get_error(ore); return; } + +export async function fa_generate_thumbnail( + i: string, + o: string, + cfg: ThumbnailConfig, +) { + let method = cfg.method; + if (method === ThumbnailGenMethod.Unknown) method = ThumbnailGenMethod.Fill; + const re = await gen_thumbnail( + i, + o, + cfg.width, + cfg.height, + method, + cfg.align, + cfg.quality, + ); + if (re) { + console.error(re); + } + return re === undefined; +}