migrate to root dir

This commit is contained in:
2025-08-08 19:26:21 +02:00
parent cf8219691b
commit 8720500442
41 changed files with 2478 additions and 4440 deletions

74
lib/pipeline/video.ts Normal file
View File

@@ -0,0 +1,74 @@
import { spawn } from "child_process";
import { StoryConfig } from "./config";
import * as path from "path";
import ffmpeg from "ffmpeg-static";
function escapeForFilter(filePath: string): string {
return filePath.replace(/\\/g, "\\\\").replace(/:/g, "\\:").replace(/,/g, "\\,").replace(/'/g, "\\'");
}
export async function createVideo(
storyName: string,
storyConfig: StoryConfig,
imageFiles: string[],
chunkDurations: number[],
srtPath: string
): Promise<void> {
const ffmpegPath: string = (ffmpeg as unknown as string) || "ffmpeg";
const audioPath = path.resolve("stories", storyName, "final_audio", "final.mp3");
const videoPath = path.resolve("stories", storyName, "video", "final.mp4");
const totalDuration = chunkDurations.reduce((a, b) => a + b, 0);
const resolution = storyConfig.config.export_settings?.resolution || "1024x1024";
const inputs = imageFiles.map((file) => ["-loop", "1", "-i", file]).flat();
inputs.push("-i", audioPath);
const filterGraph = imageFiles
.map((_, i) => {
const duration = chunkDurations[i];
const zoompan = `zoompan=z='min(zoom+0.0015,1.5)':d=${25 * duration}:x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':s=${resolution}`;
return `[${i}:v]${zoompan},fade=t=out:st=${duration - 1}:d=1[v${i}]`;
})
.join(";");
const streamSpecifiers = imageFiles.map((_, i) => `[v${i}]`).join("");
const escapedSrt = escapeForFilter(srtPath);
const concatGraph = `${filterGraph};${streamSpecifiers}concat=n=${imageFiles.length}:v=1:a=0,format=yuv420p[v0]`;
const finalFilterGraph = `${concatGraph};[v0]subtitles='${escapedSrt}'[v]`;
const args = [
"-y",
...inputs,
"-filter_complex",
finalFilterGraph,
"-map",
"[v]",
"-map",
`${imageFiles.length}:a`,
"-c:v",
"libx264",
"-tune",
"stillimage",
"-c:a",
"aac",
"-b:a",
"192k",
"-pix_fmt",
"yuv420p",
"-t",
totalDuration.toString(),
videoPath,
];
const ffmpegProcess = spawn(ffmpegPath, args);
return new Promise<void>((resolve, reject) => {
ffmpegProcess.on("close", (code: number | null) => {
if (code === 0) {
resolve();
} else {
reject(new Error(`ffmpeg process exited with code ${code}`));
}
});
});
}