authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2025-07-03 10:34:19-07:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2025-10-14 02:40:45-07:00
log5e2a792bb470e4c9f1d4a97391400b4c0b711b14
tree1f6c9adf44a9a769bc728d8ed19d5c2ffd752944
parentcd82bee436de52183b6310615e651ff0561ebe36
signature Commit is signed but in an unrecognized format.

actually run ffmpeg fr fr


3 files changed, 59 insertions(+), 22 deletions(-)

framework/lib/async.ts+1-1
...@@ -74,7 +74,7 @@ export class Queue<T, R> {...@@ -74,7 +74,7 @@ export class Queue<T, R> {
74 addReturn(args: T) { 74 addReturn(args: T) {
75 this.#total += 1; 75 this.#total += 1;
76 this.updateTotal(); 76 this.updateTotal();
77 if (this.#active.length > this.#maxJobs) { 77 if (this.#active.length >= this.#maxJobs) {
78 const { promise, resolve, reject } = Promise.withResolvers<R>(); 78 const { promise, resolve, reject } = Promise.withResolvers<R>();
79 this.#queue.push([args, resolve, reject]); 79 this.#queue.push([args, resolve, reject]);
80 return promise; 80 return promise;
src/file-viewer/bin/scan3.ts+53-19
...@@ -136,7 +136,7 @@ export async function main() {...@@ -136,7 +136,7 @@ export async function main() {
136 }, 136 },
137 getItemText: ({ mediaFile, processor }) => 137 getItemText: ({ mediaFile, processor }) =>
138 `${mediaFile.path.slice(1)} - ${processor.name}`, 138 `${mediaFile.path.slice(1)} - ${processor.name}`,
139 maxJobs: 2, 139 maxJobs: 1,
140 }); 140 });
141 141
142 function decodeProcessors(input: string) { 142 function decodeProcessors(input: string) {
...@@ -430,46 +430,79 @@ const procImageSubsets: Process = {...@@ -430,46 +430,79 @@ const procImageSubsets: Process = {
430 } 430 }
431 }, 431 },
432}; 432};
433 433const qualityMap: Record<string, string> = {
434const procVideos = transcodeRules.videoFormats.map((preset) => ({ 434 u: "ultra-high",
435 name: "encode image subsets", 435 h: "high",
436 include: rules.extsImage, 436 m: "medium",
437 l: "low",
438 d: "data-saving",
439};
440const procVideos = transcodeRules.videoFormats.map<Process>((preset) => ({
441 name: `encode ${preset.codec} ${UNWRAP(qualityMap[preset.id[1]])}`,
442 include: rules.extsVideo,
437 enable: ffmpegBin != null, 443 enable: ffmpegBin != null,
438 async run({ absPath, mediaFile, stat, spin }) { 444 async run({ absPath, mediaFile, spin }) {
439 await produceAsset(`${mediaFile}/${preset.id}`, async (base) => { 445 await produceAsset(`${mediaFile.hash}/${preset.id}`, async (base) => {
440 base = path.dirname(base); 446 base = path.dirname(base);
447 await fs.mkdir(base);
448
449 let inputArgs = ["-i", absPath];
450 try {
451 const config = await fs.readJson<any>(
452 path.join(
453 path.dirname(absPath),
454 path.basename(absPath, path.extname(absPath)) + ".json",
455 ),
456 );
457 if (config.encoder && typeof config.encoder.videoSrc === "string") {
458 const { videoSrc, audioSrc, rate } = config.encoder;
459 inputArgs = [
460 ...rate ? ["-r", String(rate)] : [],
461 "-i",
462 videoSrc,
463 ...audioSrc ? ["-i", audioSrc] : [],
464 ];
465 }
466 } catch (err: any) {
467 if (err?.code !== "ENOENT") throw err;
468 }
469
441 const args = transcodeRules.getVideoArgs( 470 const args = transcodeRules.getVideoArgs(
442 preset, 471 preset,
443 base, 472 base,
444 ["-i", absPath], 473 inputArgs,
445 ); 474 );
446 try { 475 try {
447 const quality = { 476 const fakeProgress = new Progress({ text: spin.text, spinner: null });
448 u: "ultra-high", 477 fakeProgress.stop();
449 h: "high", 478 spin.format = (now: number) => fakeProgress.format(now);
450 m: "medium", 479 // @ts-expect-error
451 l: "low", 480 fakeProgress.redraw = () => spin.redraw();
452 d: "data-saving", 481
453 }[preset.id[1]] ?? preset.id;
454 await ffmpeg.spawn({ 482 await ffmpeg.spawn({
455 ffmpeg: ffmpegBin!, 483 ffmpeg: ffmpegBin!,
456 title: `${mediaFile.path.slice(1)} (${preset.codec} ${quality})`, 484 title: fakeProgress.text,
485 progress: fakeProgress,
457 args, 486 args,
458 }); 487 });
459 return await collectFiles(); 488 return await collectFiles();
460 } catch (err) { 489 } catch (err) {
461 for (const file of await collectFiles()) { 490 for (const file of await collectFiles()) {
462 // TODO: delete assets off disk 491 try {
492 fs.rm(file);
493 } catch {}
463 } 494 }
464 throw err; 495 throw err;
465 } 496 }
466 497
467 async function collectFiles(): Promise<string[]> { 498 async function collectFiles(): Promise<string[]> {
468 throw new Error("!"); 499 return (await fs.readdir(base))
500 .filter((basename) => basename.startsWith(preset.id))
501 .map((basename) => path.join(base, basename));
469 } 502 }
470 }); 503 });
471 }, 504 },
472} satisfies Process)); 505}));
473 506
474const processors = [ 507const processors = [
475 procDimensions, 508 procDimensions,
...@@ -596,6 +629,7 @@ export function testProgram(name: string, helpArgument: string) {...@@ -596,6 +629,7 @@ export function testProgram(name: string, helpArgument: string) {
596 629
597const monthMilliseconds = 30 * 24 * 60 * 60 * 1000; 630const monthMilliseconds = 30 * 24 * 60 * 60 * 1000;
598 631
632import { Progress } from "@paperclover/console/Progress";
599import { Spinner } from "@paperclover/console/Spinner"; 633import { Spinner } from "@paperclover/console/Spinner";
600import * as async from "#sitegen/async"; 634import * as async from "#sitegen/async";
601import * as fs from "#sitegen/fs"; 635import * as fs from "#sitegen/fs";
src/file-viewer/ffmpeg.ts+5-2
...@@ -23,13 +23,15 @@ export interface SpawnOptions {...@@ -23,13 +23,15 @@ export interface SpawnOptions {
23 title: string;23 title: string;
24 ffmpeg?: string;24 ffmpeg?: string;
25 progress?: Progress;25 progress?: Progress;
26 cwd: string;
26}27}
2728
28export async function spawn(options: SpawnOptions) {29export async function spawn(options: SpawnOptions) {
29 const { ffmpeg = "ffmpeg", args, title } = options;30 const { ffmpeg = "ffmpeg", args, title, cwd } = options;
30 const proc = child_process.spawn(ffmpeg, args, {31 const proc = child_process.spawn(ffmpeg, args, {
31 stdio: ["ignore", "inherit", "pipe"],32 stdio: ["ignore", "inherit", "pipe"],
32 env: { ...process.env, SVT_LOG: "2" },33 env: { ...process.env, SVT_LOG: "2" },
34 cwd,
33 });35 });
34 const parser = new Parse();36 const parser = new Parse();
35 const bar = options.progress ?? new Progress({ text: title });37 const bar = options.progress ?? new Progress({ text: title });
...@@ -63,8 +65,10 @@ export async function spawn(options: SpawnOptions) {...@@ -63,8 +65,10 @@ export async function spawn(options: SpawnOptions) {
63 e.args = [ffmpeg, ...args].join(" ");65 e.args = [ffmpeg, ...args].join(" ");
64 e.code = code;66 e.code = code;
65 e.signal = signal;67 e.signal = signal;
68 bar.error(e.message);
66 return e;69 return e;
67 }70 }
71 bar.success(title);
68}72}
6973
70export class Parse {74export class Parse {
...@@ -158,4 +162,3 @@ import * as process from "node:process";...@@ -158,4 +162,3 @@ import * as process from "node:process";
158import events from "node:events";162import events from "node:events";
159import * as path from "node:path";163import * as path from "node:path";
160import { Progress } from "@paperclover/console/Progress";164import { Progress } from "@paperclover/console/Progress";
161