actually run ffmpeg fr fr

This commit is contained in:
chloe caruso 2025-07-03 10:34:19 -07:00
parent 2320091125
commit 502786b689
3 changed files with 59 additions and 22 deletions

View file

@ -74,7 +74,7 @@ export class Queue<T, R> {
addReturn(args: T) { addReturn(args: T) {
this.#total += 1; this.#total += 1;
this.updateTotal(); this.updateTotal();
if (this.#active.length > this.#maxJobs) { if (this.#active.length >= this.#maxJobs) {
const { promise, resolve, reject } = Promise.withResolvers<R>(); const { promise, resolve, reject } = Promise.withResolvers<R>();
this.#queue.push([args, resolve, reject]); this.#queue.push([args, resolve, reject]);
return promise; return promise;

View file

@ -136,7 +136,7 @@ export async function main() {
}, },
getItemText: ({ mediaFile, processor }) => getItemText: ({ mediaFile, processor }) =>
`${mediaFile.path.slice(1)} - ${processor.name}`, `${mediaFile.path.slice(1)} - ${processor.name}`,
maxJobs: 2, maxJobs: 1,
}); });
function decodeProcessors(input: string) { function decodeProcessors(input: string) {
@ -430,46 +430,79 @@ const procImageSubsets: Process = {
} }
}, },
}; };
const qualityMap: Record<string, string> = {
const procVideos = transcodeRules.videoFormats.map((preset) => ({
name: "encode image subsets",
include: rules.extsImage,
enable: ffmpegBin != null,
async run({ absPath, mediaFile, stat, spin }) {
await produceAsset(`${mediaFile}/${preset.id}`, async (base) => {
base = path.dirname(base);
const args = transcodeRules.getVideoArgs(
preset,
base,
["-i", absPath],
);
try {
const quality = {
u: "ultra-high", u: "ultra-high",
h: "high", h: "high",
m: "medium", m: "medium",
l: "low", l: "low",
d: "data-saving", d: "data-saving",
}[preset.id[1]] ?? preset.id; };
const procVideos = transcodeRules.videoFormats.map<Process>((preset) => ({
name: `encode ${preset.codec} ${UNWRAP(qualityMap[preset.id[1]])}`,
include: rules.extsVideo,
enable: ffmpegBin != null,
async run({ absPath, mediaFile, spin }) {
await produceAsset(`${mediaFile.hash}/${preset.id}`, async (base) => {
base = path.dirname(base);
await fs.mkdir(base);
let inputArgs = ["-i", absPath];
try {
const config = await fs.readJson<any>(
path.join(
path.dirname(absPath),
path.basename(absPath, path.extname(absPath)) + ".json",
),
);
if (config.encoder && typeof config.encoder.videoSrc === "string") {
const { videoSrc, audioSrc, rate } = config.encoder;
inputArgs = [
...rate ? ["-r", String(rate)] : [],
"-i",
videoSrc,
...audioSrc ? ["-i", audioSrc] : [],
];
}
} catch (err: any) {
if (err?.code !== "ENOENT") throw err;
}
const args = transcodeRules.getVideoArgs(
preset,
base,
inputArgs,
);
try {
const fakeProgress = new Progress({ text: spin.text, spinner: null });
fakeProgress.stop();
spin.format = (now: number) => fakeProgress.format(now);
// @ts-expect-error
fakeProgress.redraw = () => spin.redraw();
await ffmpeg.spawn({ await ffmpeg.spawn({
ffmpeg: ffmpegBin!, ffmpeg: ffmpegBin!,
title: `${mediaFile.path.slice(1)} (${preset.codec} ${quality})`, title: fakeProgress.text,
progress: fakeProgress,
args, args,
}); });
return await collectFiles(); return await collectFiles();
} catch (err) { } catch (err) {
for (const file of await collectFiles()) { for (const file of await collectFiles()) {
// TODO: delete assets off disk try {
fs.rm(file);
} catch {}
} }
throw err; throw err;
} }
async function collectFiles(): Promise<string[]> { async function collectFiles(): Promise<string[]> {
throw new Error("!"); return (await fs.readdir(base))
.filter((basename) => basename.startsWith(preset.id))
.map((basename) => path.join(base, basename));
} }
}); });
}, },
} satisfies Process)); }));
const processors = [ const processors = [
procDimensions, procDimensions,
@ -596,6 +629,7 @@ export function testProgram(name: string, helpArgument: string) {
const monthMilliseconds = 30 * 24 * 60 * 60 * 1000; const monthMilliseconds = 30 * 24 * 60 * 60 * 1000;
import { Progress } from "@paperclover/console/Progress";
import { Spinner } from "@paperclover/console/Spinner"; import { Spinner } from "@paperclover/console/Spinner";
import * as async from "#sitegen/async"; import * as async from "#sitegen/async";
import * as fs from "#sitegen/fs"; import * as fs from "#sitegen/fs";

View file

@ -23,13 +23,15 @@ export interface SpawnOptions {
title: string; title: string;
ffmpeg?: string; ffmpeg?: string;
progress?: Progress; progress?: Progress;
cwd: string;
} }
export async function spawn(options: SpawnOptions) { export async function spawn(options: SpawnOptions) {
const { ffmpeg = "ffmpeg", args, title } = options; const { ffmpeg = "ffmpeg", args, title, cwd } = options;
const proc = child_process.spawn(ffmpeg, args, { const proc = child_process.spawn(ffmpeg, args, {
stdio: ["ignore", "inherit", "pipe"], stdio: ["ignore", "inherit", "pipe"],
env: { ...process.env, SVT_LOG: "2" }, env: { ...process.env, SVT_LOG: "2" },
cwd,
}); });
const parser = new Parse(); const parser = new Parse();
const bar = options.progress ?? new Progress({ text: title }); const bar = options.progress ?? new Progress({ text: title });
@ -63,8 +65,10 @@ export async function spawn(options: SpawnOptions) {
e.args = [ffmpeg, ...args].join(" "); e.args = [ffmpeg, ...args].join(" ");
e.code = code; e.code = code;
e.signal = signal; e.signal = signal;
bar.error(e.message);
return e; return e;
} }
bar.success(title);
} }
export class Parse { export class Parse {
@ -158,4 +162,3 @@ import * as process from "node:process";
import events from "node:events"; import events from "node:events";
import * as path from "node:path"; import * as path from "node:path";
import { Progress } from "@paperclover/console/Progress"; import { Progress } from "@paperclover/console/Progress";