Update /api/thumbnail

This commit is contained in:
2023-08-27 09:37:37 +08:00
parent 289ea9586c
commit 0cbb48d019
7 changed files with 149 additions and 35 deletions

View File

@@ -123,7 +123,7 @@ export class Config {
return this._return_string("ffmpeg_path") || "ffmpeg"; return this._return_string("ffmpeg_path") || "ffmpeg";
} }
get thumbnail_method() { 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; if (n < 0 || n > 1) return ThumbnailMethod.FFMPEG_BINARY;
return n as ThumbnailMethod; return n as ThumbnailMethod;
} }

View File

@@ -3,6 +3,19 @@ import { get_task_manager } from "../../server.ts";
import type { StatusData } from "../../server/status.ts"; import type { StatusData } from "../../server/status.ts";
import { return_data } from "../../server/utils.ts"; import { return_data } from "../../server/utils.ts";
import { check_ffmpeg_binary } from "../../thumbnail/ffmpeg_binary.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 = { export const handler: Handlers = {
async GET(_req, ctx) { async GET(_req, ctx) {
@@ -12,6 +25,7 @@ export const handler: Handlers = {
const ffmpeg_binary_enabled = await check_ffmpeg_binary( const ffmpeg_binary_enabled = await check_ffmpeg_binary(
m.cfg.ffmpeg_path, m.cfg.ffmpeg_path,
); );
const ffmpeg_api_enabled = await check_ffmpeg_api();
const meilisearch_enabled = m.meilisearch !== undefined; const meilisearch_enabled = m.meilisearch !== undefined;
const meilisearch = const meilisearch =
is_authed && meilisearch_enabled && m.cfg.meili_host && is_authed && meilisearch_enabled && m.cfg.meili_host &&
@@ -24,6 +38,7 @@ export const handler: Handlers = {
: undefined; : undefined;
const no_user = m.db.get_user_count() === 0; const no_user = m.db.get_user_count() === 0;
return return_data<StatusData>({ return return_data<StatusData>({
ffmpeg_api_enabled,
ffmpeg_binary_enabled, ffmpeg_binary_enabled,
meilisearch_enabled, meilisearch_enabled,
meilisearch, meilisearch,

View File

@@ -3,7 +3,10 @@ import { exists } from "std/fs/exists.ts";
import { get_task_manager } from "../../../server.ts"; import { get_task_manager } from "../../../server.ts";
import { parse_bool, parse_int } from "../../../server/parse_form.ts"; import { parse_bool, parse_int } from "../../../server/parse_form.ts";
import { import {
gen_thumbnail_config_params,
generate_filename, generate_filename,
parse_thumbnail_align,
parse_thumbnail_method,
ThumbnailConfig, ThumbnailConfig,
ThumbnailGenMethod, ThumbnailGenMethod,
} from "../../../thumbnail/base.ts"; } from "../../../thumbnail/base.ts";
@@ -18,6 +21,9 @@ import { get_host } from "../../../server/utils.ts";
import pbkdf2Hmac from "pbkdf2-hmac"; import pbkdf2Hmac from "pbkdf2-hmac";
import { encode } from "std/encoding/base64.ts"; import { encode } from "std/encoding/base64.ts";
import { SortableURLSearchParams } from "../../../server/SortableURLSearchParams.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 = { export const handler: Handlers = {
async GET(req, ctx) { async GET(req, ctx) {
@@ -39,11 +45,14 @@ export const handler: Handlers = {
const height = await parse_int(u.searchParams.get("height"), null); const height = await parse_int(u.searchParams.get("height"), null);
const quality = await parse_int(u.searchParams.get("quality"), 1); const quality = await parse_int(u.searchParams.get("quality"), 1);
const force = await parse_bool(u.searchParams.get("force"), false); 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 = { const cfg: ThumbnailConfig = {
width: 0, width: 0,
height: 0, height: 0,
quality, quality,
method: ThumbnailGenMethod.Unknown, method: tmethod,
align: align,
}; };
if (width !== null && height !== null) { if (width !== null && height !== null) {
cfg.width = width; cfg.width = width;
@@ -51,16 +60,20 @@ export const handler: Handlers = {
} else if (width !== null) { } else if (width !== null) {
cfg.width = width; cfg.width = width;
cfg.height = Math.floor(f.height / f.width * width); cfg.height = Math.floor(f.height / f.width * width);
cfg.method = ThumbnailGenMethod.Unknown;
} else if (height !== null) { } else if (height !== null) {
cfg.height = height; cfg.height = height;
cfg.width = Math.floor(f.width / f.height * height); cfg.width = Math.floor(f.width / f.height * height);
cfg.method = ThumbnailGenMethod.Unknown;
} else { } else {
if (f.width > f.height) { if (f.width > f.height) {
cfg.width = max; cfg.width = max;
cfg.height = Math.floor(f.height / f.width * max); cfg.height = Math.floor(f.height / f.width * max);
cfg.method = ThumbnailGenMethod.Unknown;
} else { } else {
cfg.height = max; cfg.height = max;
cfg.width = Math.floor(f.width / f.height * max); cfg.width = Math.floor(f.width / f.height * max);
cfg.method = ThumbnailGenMethod.Unknown;
} }
} }
if (!force) { if (!force) {
@@ -68,6 +81,9 @@ export const handler: Handlers = {
return Response.redirect(`${get_host(req)}/api/file/${f.id}`); 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); const output = generate_filename(b, f, cfg);
if (!(await exists(output))) { if (!(await exists(output))) {
if (method === ThumbnailMethod.FFMPEG_BINARY) { if (method === ThumbnailMethod.FFMPEG_BINARY) {
@@ -82,13 +98,32 @@ export const handler: Handlers = {
status: 500, 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 = {}; const opts: GetFileResponseOptions = {};
if (m.cfg.img_verify_secret) { if (m.cfg.img_verify_secret) {
const verify = u.searchParams.get("verify"); const verify = u.searchParams.get("verify");
if (verify === null) { if (verify === null) {
const bs = new SortableURLSearchParams(u.search, ["verify"]); const bs = new SortableURLSearchParams(
gen_thumbnail_config_params(cfg),
["verify"],
);
const tverify = encode( const tverify = encode(
new Uint8Array( new Uint8Array(
await pbkdf2Hmac( await pbkdf2Hmac(

View File

@@ -2,11 +2,7 @@ import { Handlers } from "$fresh/server.ts";
import { exists } from "std/fs/exists.ts"; import { exists } from "std/fs/exists.ts";
import { get_task_manager } from "../../server.ts"; import { get_task_manager } from "../../server.ts";
import { parse_int } from "../../server/parse_form.ts"; import { parse_int } from "../../server/parse_form.ts";
import { import { generate_filename, ThumbnailConfig } from "../../thumbnail/base.ts";
generate_filename,
ThumbnailConfig,
ThumbnailGenMethod,
} from "../../thumbnail/base.ts";
import { sure_dir } from "../../utils.ts"; import { sure_dir } from "../../utils.ts";
import { import {
get_file_response, get_file_response,
@@ -50,34 +46,24 @@ export const handler: Handlers = {
if (verify !== tverify) { if (verify !== tverify) {
return new Response("verify is invalid.", { status: 400 }); 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 width = await parse_int(u.searchParams.get("width"), null);
const height = await parse_int(u.searchParams.get("height"), null); const height = await parse_int(u.searchParams.get("height"), null);
const quality = await parse_int(u.searchParams.get("quality"), 1); const quality = await parse_int(u.searchParams.get("quality"), null);
const cfg: ThumbnailConfig = { const method = await parse_int(u.searchParams.get("method"), null);
width: 0, const align = await parse_int(u.searchParams.get("align"), null);
height: 0, if (
quality, width === null || height === null || quality === null ||
method: ThumbnailGenMethod.Unknown, method === null || align === null
}; ) {
if (width !== null && height !== null) { return new Response("params is missing", { status: 400 });
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 cfg: ThumbnailConfig = {
width,
height,
quality,
method,
align,
};
const output = generate_filename(b, f, cfg); const output = generate_filename(b, f, cfg);
if (!(await exists(output))) { if (!(await exists(output))) {
return new Response("file not exists.", { status: 500 }); return new Response("file not exists.", { status: 500 });

View File

@@ -1,4 +1,5 @@
export type StatusData = { export type StatusData = {
ffmpeg_api_enabled: boolean;
ffmpeg_binary_enabled: boolean; ffmpeg_binary_enabled: boolean;
meilisearch_enabled: boolean; meilisearch_enabled: boolean;
meilisearch?: { meilisearch?: {

View File

@@ -22,20 +22,75 @@ export type ThumbnailConfig = {
height: number; height: number;
quality: number; quality: number;
method: ThumbnailGenMethod; 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( export function generate_filename(
base: string, base: string,
f: EhFile, f: EhFile,
cfg: ThumbnailConfig, cfg: ThumbnailConfig,
) { ) {
let method = ""; 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) { switch (cfg.method) {
case ThumbnailGenMethod.Cover: case ThumbnailGenMethod.Cover:
method = "-cover"; method = "-cover";
align = balign;
break; break;
case ThumbnailGenMethod.Contain: case ThumbnailGenMethod.Contain:
method = "-contain"; method = "-contain";
align = balign;
break; break;
case ThumbnailGenMethod.Fill: case ThumbnailGenMethod.Fill:
method = "-fill"; method = "-fill";
@@ -44,7 +99,7 @@ export function generate_filename(
return join( return join(
base, base,
filterFilename( 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`,
), ),
); );
} }

View File

@@ -1,6 +1,6 @@
/// <reference lib="deno.unstable" /> /// <reference lib="deno.unstable" />
import { Struct } from "pwn/mod.ts"; import { Struct } from "pwn/mod.ts";
import { ThumbnailAlign, ThumbnailGenMethod } from "./base.ts"; import { ThumbnailAlign, ThumbnailConfig, ThumbnailGenMethod } from "./base.ts";
let libSuffix = ""; let libSuffix = "";
let libPrefix = "lib"; let libPrefix = "lib";
@@ -78,3 +78,25 @@ export async function gen_thumbnail(
if (re.e) return get_error(ore); if (re.e) return get_error(ore);
return; 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;
}