mirror of
https://github.com/lifegpc/eh-downloader.git
synced 2026-06-06 05:38:44 +08:00
Add /api/thumbnail/[id]
This commit is contained in:
12
fresh.gen.ts
12
fresh.gen.ts
@@ -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
28
routes/api/status.ts
Normal 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,
|
||||
});
|
||||
},
|
||||
};
|
||||
80
routes/api/thumbnail/[id].ts
Normal file
80
routes/api/thumbnail/[id].ts
Normal 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
8
server/status.ts
Normal 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
22
thumbnail/base.ts
Normal 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`,
|
||||
),
|
||||
);
|
||||
}
|
||||
43
thumbnail/ffmpeg_binary.ts
Normal file
43
thumbnail/ffmpeg_binary.ts
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user