Add /api/thumbnail/[id]

This commit is contained in:
2023-06-23 11:09:23 +08:00
parent 4e9fa3632b
commit 19afc034dd
6 changed files with 189 additions and 4 deletions

View File

@@ -12,8 +12,10 @@ import * as $6 from "./routes/api/file/random.ts";
import * as $7 from "./routes/api/filemeta.ts";
import * as $8 from "./routes/api/filemeta/[token].ts";
import * as $9 from "./routes/api/gallery/[gid].ts";
import * as $10 from "./routes/api/task.ts";
import * as $11 from "./routes/index.tsx";
import * as $10 from "./routes/api/status.ts";
import * as $11 from "./routes/api/task.ts";
import * as $12 from "./routes/api/thumbnail/[id].ts";
import * as $13 from "./routes/index.tsx";
import * as $$0 from "./islands/Container.tsx";
import * as $$1 from "./islands/Settings.tsx";
import * as $$2 from "./islands/TaskManager.tsx";
@@ -30,8 +32,10 @@ const manifest = {
"./routes/api/filemeta.ts": $7,
"./routes/api/filemeta/[token].ts": $8,
"./routes/api/gallery/[gid].ts": $9,
"./routes/api/task.ts": $10,
"./routes/index.tsx": $11,
"./routes/api/status.ts": $10,
"./routes/api/task.ts": $11,
"./routes/api/thumbnail/[id].ts": $12,
"./routes/index.tsx": $13,
},
islands: {
"./islands/Container.tsx": $$0,

28
routes/api/status.ts Normal file
View File

@@ -0,0 +1,28 @@
import { Handlers } from "$fresh/server.ts";
import { get_task_manager } from "../../server.ts";
import { StatusData } from "../../server/status.ts";
import { return_data } from "../../server/utils.ts";
import { check_ffmpeg_binary } from "../../thumbnail/ffmpeg_binary.ts";
export const handler: Handlers = {
async GET(_req, _ctx) {
const m = get_task_manager();
const ffmpeg_binary_enabled = await check_ffmpeg_binary(
m.cfg.ffmpeg_path,
);
const meilisearch_enabled = m.meilisearch !== undefined;
const meilisearch = meilisearch_enabled && m.cfg.meili_host &&
m.cfg.meili_update_api_key
? {
host: m.cfg.meili_host,
key: m.cfg.meili_search_api_key ||
m.cfg.meili_update_api_key,
}
: undefined;
return return_data<StatusData>({
ffmpeg_binary_enabled,
meilisearch_enabled,
meilisearch,
});
},
};

View File

@@ -0,0 +1,80 @@
import { Handlers } from "$fresh/server.ts";
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 { generate_filename, ThumbnailConfig } from "../../../thumbnail/base.ts";
import { sure_dir } from "../../../utils.ts";
import { ThumbnailMethod } from "../../../config.ts";
import { fb_generate_thumbnail } from "../../../thumbnail/ffmpeg_binary.ts";
import {
get_file_response,
GetFileResponseOptions,
} from "../../../server/get_file_response.ts";
export const handler: Handlers = {
async GET(req, ctx) {
const id = parseInt(ctx.params.id);
if (isNaN(id)) {
return new Response("Bad Request", { status: 400 });
}
const m = get_task_manager();
const b = m.cfg.thumbnail_dir;
const method = m.cfg.thumbnail_method;
await sure_dir(b);
const f = m.db.get_file(id);
if (!f) {
return new Response("File not found.", { status: 404 });
}
const u = new URL(req.url);
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 force = await parse_bool(u.searchParams.get("force"), false);
const cfg: ThumbnailConfig = { width: 0, height: 0, quality };
if (width !== null && height !== null) {
cfg.width = width;
cfg.height = height;
} else if (width !== null) {
cfg.width = width;
cfg.height = Math.round(f.height / f.width * width);
} else if (height !== null) {
cfg.height = height;
cfg.width = Math.round(f.width / f.height * height);
} else {
if (f.width > f.height) {
cfg.width = max;
cfg.height = Math.round(f.height / f.width * max);
} else {
cfg.height = max;
cfg.width = Math.round(f.width / f.height * max);
}
}
if (!force) {
if (cfg.width > f.width || cfg.height > f.height) {
return Response.redirect(`${u.origin}/api/file/${f.id}`);
}
}
const output = generate_filename(b, f, cfg);
if (!(await exists(output))) {
if (method === ThumbnailMethod.FFMPEG_BINARY) {
const re = await fb_generate_thumbnail(
m.cfg.ffmpeg_path,
f.path,
output,
cfg,
);
if (!re) {
return new Response("Failed to generate thumbnail.", {
status: 500,
});
}
}
}
const opts: GetFileResponseOptions = {};
opts.range = req.headers.get("range");
opts.if_modified_since = req.headers.get("If-Modified-Since");
opts.if_unmodified_since = req.headers.get("If-Unmodified-Since");
return await get_file_response(output, opts);
},
};

8
server/status.ts Normal file
View File

@@ -0,0 +1,8 @@
export type StatusData = {
ffmpeg_binary_enabled: boolean;
meilisearch_enabled: boolean;
meilisearch?: {
host: string;
key: string;
};
};

22
thumbnail/base.ts Normal file
View File

@@ -0,0 +1,22 @@
import { join } from "std/path/mod.ts";
import { filterFilename } from "../utils.ts";
import { EhFile } from "../db.ts";
export type ThumbnailConfig = {
width: number;
height: number;
quality: number;
};
export function generate_filename(
base: string,
f: EhFile,
cfg: ThumbnailConfig,
) {
return join(
base,
filterFilename(
`${f.id}-${f.token}-${cfg.width}x${cfg.height}-q${cfg.quality}.jpg`,
),
);
}

View File

@@ -0,0 +1,43 @@
import { ThumbnailConfig } from "./base.ts";
export async function check_ffmpeg_binary(p: string) {
const cmd = new Deno.Command(p, {
stdout: "null",
stderr: "null",
args: ["-h"],
});
const c = cmd.spawn();
const o = await c.output();
return o.code === 0;
}
export async function fb_generate_thumbnail(
p: string,
i: string,
o: string,
cfg: ThumbnailConfig,
) {
const args = [
"-i",
i,
"-vf",
`scale=${cfg.width}:${cfg.height}`,
"-qmin",
`${cfg.quality}`,
"-qmax",
`${cfg.quality}`,
o,
];
const cmd = new Deno.Command(p, { args, stdout: "null", stderr: "piped" });
const c = cmd.spawn();
const s = await c.output();
if (s.code !== 0) {
try {
const d = (new TextDecoder()).decode(s.stderr);
console.log(d);
} catch (_) {
console.log(s.stderr);
}
}
return s.code === 0;
}