import staticPlugin from "@elysiajs/static"; import { Elysia, ElysiaFile, t } from "elysia"; import path from "node:path"; import { parseArgs } from "node:util"; import { musicRoot, fileTypeCache, ServerError, allowedTypes, probeCache, mediaTypes } from "./shared"; import { getPathInfo, probeFile, listFiles, findCover, matchesType, needsTranscoding, convertWithFFmpeg, } from "./utils"; //TODO: transcoding cache? //TODO: add reasonable timeouts for caches //TODO: better ffmpeg errors const { values } = parseArgs({ args: Bun.argv, options: { serve: { type: "string", }, }, strict: true, allowPositionals: true, }); if (!values.serve) { console.error("Please define the serve option"); process.exit(1); } const app = new Elysia() .use(staticPlugin({ assets: values.serve, prefix: "/" })) .post("/reset-cache", () => { fileTypeCache.clear(); probeCache.clear(); }) .get("/download/*", async ({ params, set }) => { const filePath = path.join(musicRoot, decodeURI(params["*"])); const fileScan = await getPathInfo(filePath); if (!fileScan || fileScan instanceof ServerError) { set.status = fileScan?.error ? "Internal Server Error" : "Bad Request"; return fileScan?.error || "Not a file"; } if (!matchesType(fileScan.mimeType, allowedTypes)) { set.status = "Forbidden"; return "Forbidden file type"; } set.status = "OK"; set.headers["content-type"] = fileScan.mimeType; return new ElysiaFile(filePath); }) .get("/transcode/*", async ({ request, query, set, params }) => { const filePath = path.join(musicRoot, decodeURI(params["*"])); const fileScan = await getPathInfo(filePath); if (!fileScan || fileScan instanceof ServerError) { set.status = fileScan?.error ? "Internal Server Error" : "Bad Request"; return fileScan?.error || "Not a file"; } if (!matchesType(fileScan.mimeType, allowedTypes)) { set.status = "Forbidden"; return "Forbidden file type"; } const bitrate = Number.parseInt(query.bitrate) || 96000; if ( !matchesType(fileScan.mimeType, mediaTypes) || !needsTranscoding(await probeFile(filePath), bitrate) ) { set.status = "Temporary Redirect"; set.headers.Location = `/download/${params["*"]}`; return "No transcoding necessary or probe failed, redirecting to normal endpoint"; } const cmd = convertWithFFmpeg(filePath, bitrate); request.signal.addEventListener("abort", () => cmd.kill("SIGKILL")); set.headers["content-type"] = "audio/opus"; return new Response(cmd.stdout); }) .get( "/list/*", async ({ params, set, query }) => { const dirPath = path.join(musicRoot, decodeURI(params["*"])); const fileList = await listFiles(dirPath, query.recursive || false); if (fileList instanceof ServerError) { set.status = fileList.status; return fileList.error; } const json = JSON.stringify(fileList); set.headers["Content-Type"] = "application/json"; set.status = "OK"; return json; }, { query: t.Optional(t.Object({ recursive: t.Boolean() })) }, ) .get("/cover/*", async ({ params, set }) => { set.status = "Not Found"; const dirPath = path.join(musicRoot, decodeURI(params["*"])); const fileScanResult = await findCover(dirPath); if ("path" in fileScanResult) { set.headers["Content-Type"] = fileScanResult.info.mimeType; set.status = "OK"; return new ElysiaFile(fileScanResult.path); } set.status = fileScanResult.info.status; return fileScanResult.info.error; }) .listen(3000); console.log(`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`);