diff --git a/config.ts b/config.ts index fecf356..9979953 100644 --- a/config.ts +++ b/config.ts @@ -28,6 +28,7 @@ export type ConfigType = { flutter_frontend?: string; fetch_timeout: number; download_timeout: number; + ffprobe_path: string; }; export enum ThumbnailMethod { @@ -172,6 +173,9 @@ export class Config { get download_timeout() { return this._return_number("download_timeout") || 10000; } + get ffprobe_path() { + return this._return_string("ffprobe_path") || "ffprobe"; + } to_json(): ConfigType { return { cookies: typeof this.cookies === "string", @@ -200,6 +204,7 @@ export class Config { flutter_frontend: this.flutter_frontend, fetch_timeout: this.fetch_timeout, download_timeout: this.download_timeout, + ffprobe_path: this.ffprobe_path, }; } } diff --git a/fresh.gen.ts b/fresh.gen.ts index 1a8a945..c64d2e5 100644 --- a/fresh.gen.ts +++ b/fresh.gen.ts @@ -10,27 +10,30 @@ import * as $4 from "./routes/api/exit.ts"; import * as $5 from "./routes/api/export/gallery/zip/[gid].ts"; import * as $6 from "./routes/api/file/[id].ts"; import * as $7 from "./routes/api/file/random.ts"; -import * as $8 from "./routes/api/filemeta.ts"; -import * as $9 from "./routes/api/filemeta/[token].ts"; -import * as $10 from "./routes/api/files/[token].ts"; -import * as $11 from "./routes/api/gallery/[gid].ts"; -import * as $12 from "./routes/api/gallery/list.ts"; -import * as $13 from "./routes/api/status.ts"; -import * as $14 from "./routes/api/tag/[id].ts"; -import * as $15 from "./routes/api/tag/rows.ts"; -import * as $16 from "./routes/api/task.ts"; -import * as $17 from "./routes/api/thumbnail/[id].ts"; -import * as $18 from "./routes/api/token.ts"; -import * as $19 from "./routes/api/user.ts"; -import * as $20 from "./routes/file/[id].ts"; -import * as $21 from "./routes/file/_middleware.ts"; -import * as $22 from "./routes/index.tsx"; -import * as $23 from "./routes/manifest.json.ts"; -import * as $24 from "./routes/thumbnail/[id].ts"; -import * as $25 from "./routes/thumbnail/_middleware.ts"; +import * as $8 from "./routes/api/file/upload.ts"; +import * as $9 from "./routes/api/filemeta.ts"; +import * as $10 from "./routes/api/filemeta/[token].ts"; +import * as $11 from "./routes/api/files/[token].ts"; +import * as $12 from "./routes/api/gallery/[gid].ts"; +import * as $13 from "./routes/api/gallery/list.ts"; +import * as $14 from "./routes/api/status.ts"; +import * as $15 from "./routes/api/tag/[id].ts"; +import * as $16 from "./routes/api/tag/rows.ts"; +import * as $17 from "./routes/api/task.ts"; +import * as $18 from "./routes/api/thumbnail/[id].ts"; +import * as $19 from "./routes/api/token.ts"; +import * as $20 from "./routes/api/user.ts"; +import * as $21 from "./routes/file/[id].ts"; +import * as $22 from "./routes/file/_middleware.ts"; +import * as $23 from "./routes/index.tsx"; +import * as $24 from "./routes/manifest.json.ts"; +import * as $25 from "./routes/thumbnail/[id].ts"; +import * as $26 from "./routes/thumbnail/_middleware.ts"; +import * as $27 from "./routes/upload.tsx"; import * as $$0 from "./islands/Container.tsx"; import * as $$1 from "./islands/Settings.tsx"; import * as $$2 from "./islands/TaskManager.tsx"; +import * as $$3 from "./islands/Upload.tsx"; const manifest = { routes: { @@ -42,29 +45,32 @@ const manifest = { "./routes/api/export/gallery/zip/[gid].ts": $5, "./routes/api/file/[id].ts": $6, "./routes/api/file/random.ts": $7, - "./routes/api/filemeta.ts": $8, - "./routes/api/filemeta/[token].ts": $9, - "./routes/api/files/[token].ts": $10, - "./routes/api/gallery/[gid].ts": $11, - "./routes/api/gallery/list.ts": $12, - "./routes/api/status.ts": $13, - "./routes/api/tag/[id].ts": $14, - "./routes/api/tag/rows.ts": $15, - "./routes/api/task.ts": $16, - "./routes/api/thumbnail/[id].ts": $17, - "./routes/api/token.ts": $18, - "./routes/api/user.ts": $19, - "./routes/file/[id].ts": $20, - "./routes/file/_middleware.ts": $21, - "./routes/index.tsx": $22, - "./routes/manifest.json.ts": $23, - "./routes/thumbnail/[id].ts": $24, - "./routes/thumbnail/_middleware.ts": $25, + "./routes/api/file/upload.ts": $8, + "./routes/api/filemeta.ts": $9, + "./routes/api/filemeta/[token].ts": $10, + "./routes/api/files/[token].ts": $11, + "./routes/api/gallery/[gid].ts": $12, + "./routes/api/gallery/list.ts": $13, + "./routes/api/status.ts": $14, + "./routes/api/tag/[id].ts": $15, + "./routes/api/tag/rows.ts": $16, + "./routes/api/task.ts": $17, + "./routes/api/thumbnail/[id].ts": $18, + "./routes/api/token.ts": $19, + "./routes/api/user.ts": $20, + "./routes/file/[id].ts": $21, + "./routes/file/_middleware.ts": $22, + "./routes/index.tsx": $23, + "./routes/manifest.json.ts": $24, + "./routes/thumbnail/[id].ts": $25, + "./routes/thumbnail/_middleware.ts": $26, + "./routes/upload.tsx": $27, }, islands: { "./islands/Container.tsx": $$0, "./islands/Settings.tsx": $$1, "./islands/TaskManager.tsx": $$2, + "./islands/Upload.tsx": $$3, }, baseUrl: import.meta.url, }; diff --git a/islands/Upload.tsx b/islands/Upload.tsx new file mode 100644 index 0000000..e85a845 --- /dev/null +++ b/islands/Upload.tsx @@ -0,0 +1,44 @@ +import t, { i18n_map, I18NMap } from "../server/i18n.ts"; + +export type UploaderProps = { + i18n?: I18NMap; + lang?: string; +}; + +export default function Uploader(props: UploaderProps) { + if (props.i18n) i18n_map.value = props.i18n; + return ( +
+ ); +} diff --git a/routes/api/file/upload.ts b/routes/api/file/upload.ts new file mode 100644 index 0000000..dbcbca1 --- /dev/null +++ b/routes/api/file/upload.ts @@ -0,0 +1,75 @@ +import { Handlers } from "$fresh/server.ts"; +import type { EhFile } from "../../../db.ts"; +import { get_task_manager } from "../../../server.ts"; +import { return_data, return_error } from "../../../server/utils.ts"; +import { get_string, parse_bool } from "../../../server/parse_form.ts"; +import { fb_get_size } from "../../../thumbnail/ffmpeg_binary.ts"; +import { sure_dir } from "../../../utils.ts"; +import mime from "mime"; +import { extname, join, resolve } from "std/path/mod.ts"; + +export const handler: Handlers = { + async POST(req, _ctx) { + const m = get_task_manager(); + try { + const form = await req.formData(); + const file = form.get("file"); + if (!file) { + return return_error(1, "Missing file."); + } + const mext = typeof file === "string" + ? null + : `.${mime.getExtension(file.type)}`; + const filename = (await get_string(form.get("filename"))) || + (typeof file === "string" ? null : file.name); + if (!filename) { + return return_error(2, "Missing filename."); + } + const fext = extname(filename); + const fn = mext == fext + ? filename + : `${filename.slice(0, filename.length - fext.length)}${mext}`; + const dir = (await get_string(form.get("dir"))) || + join(m.cfg.base, "uploaded"); + const is_original = await parse_bool( + form.get("is_original"), + false, + ); + const token = await get_string(form.get("token")); + if (!token) { + return return_error(3, "Missing token."); + } + const path = join(dir, fn); + await sure_dir(dir); + try { + if (typeof file === "string") { + await Deno.writeTextFile(path, file); + } else { + await Deno.writeFile(path, file.stream()); + } + const size = await fb_get_size(path); + if (!size) { + await Deno.remove(path); + return return_error(4, "Failed to get file size."); + } + const rpath = resolve(path); + const f = { + id: 0, + path: rpath, + width: size.width, + height: size.height, + is_original, + token, + } as EhFile; + const nf = m.db.add_file(f, false); + return return_data(nf); + } catch (e) { + await Deno.remove(path); + throw e; + } + } catch (e) { + console.error(e); + return return_error(500, "Internal Server Error."); + } + }, +}; diff --git a/routes/api/status.ts b/routes/api/status.ts index 4be2e9e..fcb0f83 100644 --- a/routes/api/status.ts +++ b/routes/api/status.ts @@ -26,6 +26,9 @@ export const handler: Handlers = { m.cfg.ffmpeg_path, ); const ffmpeg_api_enabled = await check_ffmpeg_api(); + const ffprobe_binary_enabled = await check_ffmpeg_binary( + m.cfg.ffprobe_path, + ); const meilisearch_enabled = m.meilisearch !== undefined; let meilisearch; if ( @@ -50,6 +53,7 @@ export const handler: Handlers = { return return_data