added ffmpeg

This commit is contained in:
C4illin 2024-05-21 15:34:38 +02:00
parent e55b8abaa0
commit 4aeeaa5060
8 changed files with 911 additions and 113 deletions

View file

@ -15,29 +15,26 @@ RUN mkdir -p /temp/prod
COPY package.json bun.lockb /temp/prod/ COPY package.json bun.lockb /temp/prod/
RUN cd /temp/prod && bun install --frozen-lockfile --production RUN cd /temp/prod && bun install --frozen-lockfile --production
# install pandoc
RUN apt-get update && apt-get install -y pandoc
# copy node_modules from temp directory # copy node_modules from temp directory
# then copy all (non-ignored) project files into the image # then copy all (non-ignored) project files into the image
FROM base AS prerelease # FROM base AS prerelease
COPY --from=install /temp/dev/node_modules node_modules # COPY --from=install /temp/dev/node_modules node_modules
COPY . . # COPY . .
# [optional] tests & build # # [optional] tests & build
ENV NODE_ENV=production # ENV NODE_ENV=production
# RUN bun test # RUN bun test
# RUN bun run build # RUN bun run build
# copy production dependencies and source code into final image # copy production dependencies and source code into final image
FROM base AS release FROM base AS release
COPY --from=install /temp/prod/node_modules node_modules COPY --from=install /temp/prod/node_modules node_modules
COPY --from=prerelease /app/src/index.tsx /app/src/ # COPY --from=prerelease /app/src/index.tsx /app/src/
COPY --from=prerelease /app/package.json . # COPY --from=prerelease /app/package.json .
COPY . . COPY . .
# copy pandoc # install additional dependencies
COPY --from=install /usr/bin/pandoc /usr/bin/pandoc RUN apt-get update && apt-get install -y pandoc texlive-latex-recommended ffmpeg
# run the app # run the app
USER bun USER bun

673
src/converters/ffmpeg.ts Normal file
View file

@ -0,0 +1,673 @@
import { exec } from "node:child_process";
// This could be done dynamically by running `ffmpeg -formats` and parsing the output
export const properties = {
from: {
video: [
"012v",
"4xm",
"8bps",
"aasc",
"agm",
"aic",
"alias_pix",
"amv",
"anm",
"ansi",
"apng",
"arbc",
"argo",
"asv1",
"asv2",
"aura",
"aura2",
"av1",
"avrn",
"avrp",
"avs",
"avui",
"ayuv",
"bethsoftvid",
"bfi",
"binkvideo",
"bintext",
"bitpacked",
"bmp",
"bmv_video",
"brender_pix",
"c93",
"cavs",
"cdgraphics",
"cdtoons",
"cdxl",
"cfhd",
"cinepak",
"clearvideo",
"cljr",
"cllc",
"cmv",
"cpia",
"cri",
"cscd",
"cyuv",
"dds",
"dfa",
"dirac",
"dnxhd",
"dpx",
"dsicinvideo",
"dvvideo",
"dxa",
"dxtory",
"dxv",
"escape124",
"escape130",
"exr",
"ffv1",
"ffvhuff",
"fic",
"fits",
"flashsv",
"flashsv2",
"flic",
"flv1",
"fmvc",
"fraps",
"frwu",
"g2m",
"gdv",
"gif",
"h261",
"h263",
"h263i",
"h263p",
"h264",
"hap",
"hevc",
"hnm4video",
"hq_hqa",
"hqx",
"huffyuv",
"hymt",
"idcin",
"idf",
"iff_ilbm",
"imm4",
"imm5",
"indeo2",
"indeo3",
"indeo4",
"indeo5",
"interplayvideo",
"ipu",
"jpeg2000",
"jpegls",
"jv",
"kgv1",
"kmvc",
"lagarith",
"loco",
"lscr",
"m101",
"mad",
"magicyuv",
"mdec",
"mimic",
"mjpeg",
"mjpegb",
"mmvideo",
"mobiclip",
"motionpixels",
"mpeg1video",
"mpeg2video",
"mpeg4",
"msa1",
"mscc",
"msmpeg4v1",
"msmpeg4v2",
"msmpeg4v3",
"msp2",
"msrle",
"mss1",
"mss2",
"msvideo1",
"mszh",
"mts2",
"mv30",
"mvc1",
"mvc2",
"mvdv",
"mvha",
"mwsc",
"mxpeg",
"notchlc",
"nuv",
"paf_video",
"pam",
"pbm",
"pcx",
"pfm",
"pgm",
"pgmyuv",
"pgx",
"photocd",
"pictor",
"pixlet",
"png",
"ppm",
"prores",
"prosumer",
"psd",
"ptx",
"qdraw",
"qpeg",
"qtrle",
"r10k",
"r210",
"rasc",
"rawvideo",
"rl2",
"roq",
"rpza",
"rscc",
"rv10",
"rv20",
"rv30",
"rv40",
"sanm",
"scpr",
"screenpresso",
"sga",
"sgi",
"sgirle",
"sheervideo",
"simbiosis_imx",
"smackvideo",
"smc",
"smvjpeg",
"snow",
"sp5x",
"speedhq",
"srgc",
"sunrast",
"svg",
"svq1",
"svq3",
"targa",
"targa_y216",
"tdsc",
"tgq",
"tgv",
"theora",
"thp",
"tiertexseqvideo",
"tiff",
"tmv",
"tqi",
"truemotion1",
"truemotion2",
"truemotion2rt",
"tscc",
"tscc2",
"txd",
"ulti",
"utvideo",
"v210",
"v210x",
"v308",
"v408",
"v410",
"vb",
"vble",
"vc1",
"vc1image",
"vcr1",
"vixl",
"vmdvideo",
"vmnc",
"vp3",
"vp4",
"vp5",
"vp6",
"vp6a",
"vp6f",
"vp7",
"vp8",
"vp9",
"wcmv",
"webp",
"wmv1",
"wmv2",
"wmv3",
"wmv3image",
"wnv1",
"wrapped_avframe",
"ws_vqa",
"xan_wc3",
"xan_wc4",
"xbin",
"xbm",
"xface",
"xpm",
"xwd",
"y41p",
"ylc",
"yop",
"yuv4",
"zerocodec",
"zlib",
"zmbv",
],
audio: [
"8svx_exp",
"8svx_fib",
"aac",
"aac_latm",
"ac3",
"acelp.kelvin",
"adpcm_4xm",
"adpcm_adx",
"adpcm_afc",
"adpcm_agm",
"adpcm_aica",
"adpcm_argo",
"adpcm_ct",
"adpcm_dtk",
"adpcm_ea",
"adpcm_ea_maxis_xa",
"adpcm_ea_r1",
"adpcm_ea_r2",
"adpcm_ea_r3",
"adpcm_ea_xas",
"adpcm_g722",
"adpcm_g726",
"adpcm_g726le",
"adpcm_ima_alp",
"adpcm_ima_amv",
"adpcm_ima_apc",
"adpcm_ima_apm",
"adpcm_ima_cunning",
"adpcm_ima_dat4",
"adpcm_ima_dk3",
"adpcm_ima_dk4",
"adpcm_ima_ea_eacs",
"adpcm_ima_ea_sead",
"adpcm_ima_iss",
"adpcm_ima_moflex",
"adpcm_ima_mtf",
"adpcm_ima_oki",
"adpcm_ima_qt",
"adpcm_ima_rad",
"adpcm_ima_smjpeg",
"adpcm_ima_ssi",
"adpcm_ima_wav",
"adpcm_ima_ws",
"adpcm_ms",
"adpcm_mtaf",
"adpcm_psx",
"adpcm_sbpro_2",
"adpcm_sbpro_3",
"adpcm_sbpro_4",
"adpcm_swf",
"adpcm_thp",
"adpcm_thp_le",
"adpcm_vima",
"adpcm_xa",
"adpcm_yamaha",
"adpcm_zork",
"alac",
"amr_nb",
"amr_wb",
"ape",
"aptx",
"aptx_hd",
"atrac1",
"atrac3",
"atrac3al",
"atrac3p",
"atrac3pal",
"atrac9",
"avc",
"binkaudio_dct",
"binkaudio_rdft",
"bmv_audio",
"codec2",
"comfortnoise",
"cook",
"derf_dpcm",
"dolby_e",
"dsd_lsbf",
"dsd_lsbf_planar",
"dsd_msbf",
"dsd_msbf_planar",
"dsicinaudio",
"dss_sp",
"dst",
"dts",
"dvaudio",
"eac3",
"evrc",
"fastaudio",
"flac",
"g723_1",
"g729",
"gremlin_dpcm",
"gsm",
"gsm_ms",
"hca",
"hcom",
"iac",
"ilbc",
"imc",
"interplay_dpcm",
"interplayacm",
"mace3",
"mace6",
"metasound",
"mlp",
"mp1",
"mp2",
"mp3",
"mp3adu",
"mp3on4",
"mp4als",
"musepack7",
"musepack8",
"nellymoser",
"opus",
"paf_audio",
"pcm_alaw",
"pcm_bluray",
"pcm_dvd",
"pcm_f16le",
"pcm_f24le",
"pcm_f32be",
"pcm_f32le",
"pcm_f64be",
"pcm_f64le",
"pcm_lxf",
"pcm_mulaw",
"pcm_s16be",
"pcm_s16be_planar",
"pcm_s16le",
"pcm_s16le_planar",
"pcm_s24be",
"pcm_s24daud",
"pcm_s24le",
"pcm_s24le_planar",
"pcm_s32be",
"pcm_s32le",
"pcm_s32le_planar",
"pcm_s64be",
"pcm_s64le",
"pcm_s8",
"pcm_s8_planar",
"pcm_sga",
"pcm_u16be",
"pcm_u16le",
"pcm_u24be",
"pcm_u24le",
"pcm_u32be",
"pcm_u32le",
"pcm_u8",
"pcm_vidc",
"qcelp",
"qdm2",
"qdmc",
"ra_144",
"ra_288",
"ralf",
"roq_dpcm",
"s302m",
"sbc",
"sdx2_dpcm",
"shorten",
"sipr",
"siren",
"smackaudio",
"sol_dpcm",
"sonic",
"speex",
"tak",
"truehd",
"truespeech",
"tta",
"twinvq",
"vmdaudio",
"vorbis",
"wavesynth",
"wavpack",
"westwood_snd1",
"wmalossless",
"wmapro",
"wmav1",
"wmav2",
"wmavoice",
"xan_dpcm",
"xma1",
"xma2",
],
subtitles: [
"ass",
"dvb_subtitle",
"dvb_teletext",
"dvd_subtitle",
"eia_608",
"hdmv_pgs_subtitle",
"jacosub",
"microdvd",
"mov_text",
"mpl2",
"pjs",
"realtext",
"sami",
"stl",
"subrip",
"subviewer",
"subviewer1",
"text",
"vplayer",
"webvtt",
"xsub",
],
},
to: {
video: [
"a64_multi",
"a64_multi5",
"ljpeg",
"alias_pix",
"amv",
"apng",
"asv1",
"asv2",
"av1",
"avrp",
"avui",
"ayuv",
"bmp",
"cfhd",
"cinepak",
"cljr",
"dirac",
"dnxhd",
"dpx",
"dvvideo",
"exr",
"ffv1",
"ffvhuff",
"fits",
"flashsv",
"flashsv2",
"flv1",
"gif",
"h261",
"h263",
"h263p",
"h264",
"hap",
"hevc",
"huffyuv",
"jpeg2000",
"jpegls",
"magicyuv",
"mjpeg",
"mpeg1video",
"mpeg2video",
"mpeg4",
"msmpeg4v2",
"msmpeg4v3",
"msvideo1",
"pam",
"pbm",
"pcx",
"pfm",
"pgm",
"pgmyuv",
"png",
"ppm",
"prores",
"qtrle",
"r10k",
"r210",
"rawvideo",
"roq",
"rpza",
"rv10",
"rv20",
"sgi",
"snow",
"speedhq",
"sunrast",
"svq1",
"targa",
"theora",
"tiff",
"utvideo",
"v210",
"v308",
"v408",
"v410",
"vp8",
"vp9",
"webp",
"wmv1",
"wmv2",
"wrapped_avframe",
"xbm",
"xface",
"xwd",
"y41p",
"yuv4",
"zlib",
"zmbv",
],
audio: [
"aac",
"ac3",
"adpcm_adx",
"adpcm_argo",
"adpcm_g722",
"adpcm_g726",
"adpcm_g726le",
"adpcm_ima_alp",
"adpcm_ima_amv",
"adpcm_ima_apm",
"adpcm_ima_qt",
"adpcm_ima_ssi",
"adpcm_ima_wav",
"adpcm_ms",
"adpcm_swf",
"adpcm_yamaha",
"alac",
"aptx",
"aptx_hd",
"codec2",
"comfortnoise",
"dts",
"eac3",
"flac",
"g723_1",
"gsm",
"gsm_ms",
"mlp",
"mp2",
"mp3",
"nellymoser",
"opus",
"pcm_alaw",
"pcm_dvd",
"pcm_f32be",
"pcm_f32le",
"pcm_f64be",
"pcm_f64le",
"pcm_mulaw",
"pcm_s16be",
"pcm_s16be_planar",
"pcm_s16le",
"pcm_s16le_planar",
"pcm_s24be",
"pcm_s24daud",
"pcm_s24le",
"pcm_s24le_planar",
"pcm_s32be",
"pcm_s32le",
"pcm_s32le_planar",
"pcm_s64be",
"pcm_s64le",
"pcm_s8",
"pcm_s8_planar",
"pcm_u16be",
"pcm_u16le",
"pcm_u24be",
"pcm_u24le",
"pcm_u32be",
"pcm_u32le",
"pcm_u8",
"pcm_vidc",
"ra_144",
"roq_dpcm",
"s302m",
"sbc",
"sonic",
"speex",
"truehd",
"tta",
"vorbis",
"wavpack",
"wmav1",
"wmav2",
],
subtitles: [
"ass",
"dvb_subtitle",
"dvd_subtitle",
"mov_text",
"subrip",
"text",
"webvtt",
"xsub",
],
},
};
export async function convert(
filePath: string,
fileType: string,
convertTo: string,
targetPath: string,
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
options?: any,
) {
return exec(
`ffmpeg -f "${fileType}" -i "${filePath}" -f "${convertTo}" "${targetPath}"`,
(error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
return;
}
console.log(`stdout: ${stdout}`);
console.error(`stderr: ${stderr}`);
},
);
}

View file

@ -8,78 +8,164 @@ import {
convert as convertPandoc, convert as convertPandoc,
} from "./pandoc"; } from "./pandoc";
import {
properties as propertiesFfmpeg,
convert as convertFfmpeg,
} from "./ffmpeg";
const properties: {
[key: string]: {
properties: {
from: string[] | { [key: string]: string[] };
to: string[] | { [key: string]: string[] };
options?: {
[key: string]: {
[key: string]: {
description: string;
type: string;
default: number;
};
};
};
};
converter: (
filePath: string,
fileType: string,
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
convertTo: any,
targetPath: string,
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
options?: any,
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
) => any;
};
} = {
sharp: {
properties: propertiesImage,
converter: convertImage,
},
pandoc: {
properties: propertiesPandoc,
converter: convertPandoc,
},
ffmpeg: {
properties: propertiesFfmpeg,
converter: convertFfmpeg,
},
};
import { normalizeFiletype } from "../helpers/normalizeFiletype"; import { normalizeFiletype } from "../helpers/normalizeFiletype";
export async function mainConverter( export async function mainConverter(
inputFilePath: string, inputFilePath: string,
fileType: string, fileTypeOriginal: string,
convertTo: string, // biome-ignore lint/suspicious/noExplicitAny: <explanation>
convertTo: any,
targetPath: string, targetPath: string,
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
options?: any, options?: any,
converter?: string,
) { ) {
// Check if the fileType and convertTo are supported by the sharp converter const fileType = normalizeFiletype(fileTypeOriginal);
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
let converterFunc: any;
if (converter) {
converterFunc = properties[converter];
} else {
// Iterate over each converter in properties
for (const converterName in properties) {
const converterObj = properties[converterName];
if (!converterObj) {
break;
}
// if converter properties.from is an object loop thorugh the keys otherwise use the array
// for example ffmpeg is an object eg from: {video: ["mp4", "webm"], audio: ["mp3"]}
if (Array.isArray(converterObj.properties.from) && Array.isArray(converterObj.properties.to)) {
if ( if (
propertiesImage.from.includes(fileType) && converterObj.properties.from.includes(fileType) &&
propertiesImage.to.includes(convertTo) converterObj.properties.to.includes(convertTo)
) { ) {
// Use the sharp converter converterFunc = converterObj.converter;
try { break;
await convertImage(
inputFilePath,
fileType,
convertTo,
targetPath,
options,
);
console.log(
`Converted ${inputFilePath} from ${fileType} to ${convertTo} successfully.`,
);
} catch (error) {
console.error(
`Failed to convert ${inputFilePath} from ${fileType} to ${convertTo}.`,
error,
);
}
}
// Check if the fileType and convertTo are supported by the pandoc converter
else if (
propertiesPandoc.from.includes(fileType) &&
propertiesPandoc.to.includes(convertTo)
) {
// Use the pandoc converter
try {
await convertPandoc(
inputFilePath,
fileType,
convertTo,
targetPath,
options,
);
console.log(
`Converted ${inputFilePath} from ${fileType} to ${convertTo} successfully.`,
);
} catch (error) {
console.error(
`Failed to convert ${inputFilePath} from ${fileType} to ${convertTo}.`,
error,
);
} }
} else { } else {
for (const key in converterObj.properties.from) {
if (
converterObj.properties.from[key].includes(fileType) &&
converterObj.properties.to[key].includes(convertTo)
) {
converterFunc = converterObj.converter;
break;
}
}
}
}
}
if (!converterFunc) {
console.log( console.log(
`Neither the sharp nor pandoc converter support converting from ${fileType} to ${convertTo}.`, `No available converter supports converting from ${fileType} to ${convertTo}.`,
);
return;
}
try {
await converterFunc(
inputFilePath,
fileType,
convertTo,
targetPath,
options,
);
console.log(
`Converted ${inputFilePath} from ${fileType} to ${convertTo} successfully using ${converter}.`,
);
} catch (error) {
console.error(
`Failed to convert ${inputFilePath} from ${fileType} to ${convertTo} using ${converter}.`,
error,
); );
} }
} }
const possibleConversions: { [key: string]: string[] } = {}; const possibleConversions: { [key: string]: string[] } = {};
for (const from of propertiesImage.from) { for (const converterName in properties) {
possibleConversions[from] = propertiesImage.to; const converterProperties = properties[converterName]?.properties;
if (!converterProperties) {
continue;
} }
for (const from of propertiesPandoc.from) { if (Array.isArray(converterProperties.from)) {
possibleConversions[from] = propertiesPandoc.to; for (const extension of converterProperties.from) {
possibleConversions[extension] = converterProperties.to;
} }
} else {
for (const key in converterProperties.from) {
if (!converterProperties.from[key] || !converterProperties.to[key]) {
continue;
}
for (const extension of converterProperties.from[key]) {
possibleConversions[extension] = converterProperties.to[key];
}
}
}
}
// // save all possible conversions to a file
// import fs from "fs";
// import path from "path";
// import { FormatEnum } from "sharp";
// fs.writeFileSync(
// path.join(__dirname, ".", "possibleConversions.json"),
// JSON.stringify(possibleConversions),
// );
export const getPossibleConversions = (from: string): string[] => { export const getPossibleConversions = (from: string): string[] => {
const fromClean = normalizeFiletype(from); const fromClean = normalizeFiletype(from);
@ -87,6 +173,20 @@ export const getPossibleConversions = (from: string): string[] => {
return possibleConversions[fromClean] || ([] as string[]); return possibleConversions[fromClean] || ([] as string[]);
}; };
let allTargets: string[] = [];
for (const converterName in properties) {
const converterProperties = properties[converterName].properties;
if (Array.isArray(converterProperties.from)) {
allTargets = allTargets.concat(converterProperties.to);
} else {
for (const key in converterProperties.to) {
allTargets = allTargets.concat(converterProperties.to[key]);
}
}
}
export const getAllTargets = () => { export const getAllTargets = () => {
return [...propertiesImage.to, ...propertiesPandoc.to]; return allTargets;
}; };

View file

@ -115,15 +115,23 @@ export const properties = {
], ],
}; };
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
export function convert( export function convert(
filePath: string, filePath: string,
fileType: string, fileType: string,
convertTo: string, convertTo: string,
targetPath: string, targetPath: string,
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
options?: any, options?: any,
) { ) {
return exec( return exec(
`pandoc "${filePath}" -f ${fileType} -t ${convertTo} -o "${targetPath}"`, `pandoc "${filePath}" -f ${fileType} -t ${convertTo} -o "${targetPath}"`,
(error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
return;
}
console.log(`stdout: ${stdout}`);
console.error(`stderr: ${stderr}`);
},
); );
} }

File diff suppressed because one or more lines are too long

View file

@ -1,4 +1,5 @@
import sharp from "sharp"; import sharp from "sharp";
import type { FormatEnum } from "sharp";
// declare possible conversions // declare possible conversions
export const properties = { export const properties = {
@ -11,11 +12,18 @@ export const properties = {
type: "number", type: "number",
default: 1, default: 1,
}, },
} },
} },
} };
export async function convert(filePath: string, fileType: string, convertTo: string, targetPath: string, options?: any) { export async function convert(
filePath: string,
fileType: string,
convertTo: keyof FormatEnum,
targetPath: string,
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
options?: any,
) {
if (fileType === "svg") { if (fileType === "svg") {
const scale = options.scale || 1; const scale = options.scale || 1;
const metadata = await sharp(filePath).metadata(); const metadata = await sharp(filePath).metadata();
@ -24,8 +32,8 @@ export async function convert(filePath: string, fileType: string, convertTo: str
throw new Error("Could not get metadata from image"); throw new Error("Could not get metadata from image");
} }
const newWidth = Math.round(metadata.width * scale) const newWidth = Math.round(metadata.width * scale);
const newHeight = Math.round(metadata.height * scale) const newHeight = Math.round(metadata.height * scale);
return await sharp(filePath) return await sharp(filePath)
.resize(newWidth, newHeight) .resize(newWidth, newHeight)

View file

@ -130,7 +130,7 @@ const app = new Elysia()
}) })
.post( .post(
"/register", "/register",
async function handler({ body, set, jwt, cookie: { auth } }) { async ({ body, set, jwt, cookie: { auth } }) => {
const existingUser = await db const existingUser = await db
.query("SELECT * FROM users WHERE email = ?") .query("SELECT * FROM users WHERE email = ?")
.get(body.email); .get(body.email);
@ -177,6 +177,7 @@ const app = new Elysia()
Location: "/", Location: "/",
}; };
}, },
{ body: t.Object({ email: t.String(), password: t.String() }) },
) )
.get("/login", async ({ jwt, redirect, cookie: { auth } }) => { .get("/login", async ({ jwt, redirect, cookie: { auth } }) => {
console.log("login handler"); console.log("login handler");
@ -276,17 +277,15 @@ const app = new Elysia()
sameSite: "strict", sameSite: "strict",
}); });
// redirect to home redirect("/");
set.status = 302;
set.headers = {
Location: "/",
};
}, },
{ body: t.Object({ email: t.String(), password: t.String() }) },
) )
.get("/logout", ({ redirect, cookie: { auth } }) => { .get("/logout", ({ redirect, cookie: { auth } }) => {
if (auth?.value) { if (auth?.value) {
auth.remove(); auth.remove();
} }
return redirect("/login"); return redirect("/login");
}) })
.post("/logout", ({ redirect, cookie: { auth } }) => { .post("/logout", ({ redirect, cookie: { auth } }) => {
@ -518,8 +517,9 @@ const app = new Elysia()
return redirect(`/results/${jobId.value}`); return redirect(`/results/${jobId.value}`);
}, },
) )
.get("/history", async ({ jwt, redirect, cookie: { auth } }) => { .get("/hist", async ({ body, jwt, redirect, cookie: { auth } }) => {
console.log("results page"); console.log("results page");
if (!auth?.value) { if (!auth?.value) {
console.log("no auth value"); console.log("no auth value");
return redirect("/login"); return redirect("/login");
@ -533,14 +533,7 @@ const app = new Elysia()
const userJobs = db const userJobs = db
.query("SELECT * FROM jobs WHERE user_id = ?") .query("SELECT * FROM jobs WHERE user_id = ?")
.all(user.id) as { .all(user.id) as IJobs[];
id: number;
user_id: number;
date_created: string;
status: string;
num_files: number;
finished_files: number;
}[];
for (const job of userJobs) { for (const job of userJobs) {
const files = db const files = db
@ -631,6 +624,7 @@ const app = new Elysia()
<button <button
type="button" type="button"
style={{ width: "10rem", float: "right" }} style={{ width: "10rem", float: "right" }}
onclick="downloadAll()"
> >
Download All Download All
</button> </button>
@ -671,6 +665,7 @@ const app = new Elysia()
</table> </table>
</article> </article>
</main> </main>
<script src="/downloadAll.js" defer />
</BaseHtml> </BaseHtml>
); );
}, },
@ -703,7 +698,9 @@ const app = new Elysia()
return Bun.file(filePath); return Bun.file(filePath);
}, },
) )
.get("/zip/:userId/:jobId", async ({ params, jwt, redirect, cookie: { auth } }) => { .get(
"/zip/:userId/:jobId",
async ({ params, jwt, redirect, cookie: { auth } }) => {
// TODO: Implement zip download // TODO: Implement zip download
if (!auth?.value) { if (!auth?.value) {
return redirect("/login"); return redirect("/login");
@ -727,7 +724,8 @@ const app = new Elysia()
const outputPath = `${outputDir}${userId}/${jobId}/`; const outputPath = `${outputDir}${userId}/${jobId}/`;
// return Bun.zip(outputPath); // return Bun.zip(outputPath);
}) },
)
.onError(({ code, error, request }) => { .onError(({ code, error, request }) => {
// log.error(` ${request.method} ${request.url}`, code, error); // log.error(` ${request.method} ${request.url}`, code, error);
console.error(error); console.error(error);

13
src/public/downloadAll.js Normal file
View file

@ -0,0 +1,13 @@
window.downloadAll = function () {
// Get all download links
const downloadLinks = document.querySelectorAll("a[download]");
// Trigger download for each link
downloadLinks.forEach((link, index) => {
// We add a delay for each download to prevent them from starting at the same time
setTimeout(() => {
const event = new MouseEvent("click");
link.dispatchEvent(event);
}, index * 100);
});
};