Initial commit from Create Next App

This commit is contained in:
Atakan Doğan Özban
2026-07-12 15:40:35 +02:00
commit 4f29120768
52 changed files with 10164 additions and 0 deletions
+100
View File
@@ -0,0 +1,100 @@
import { spawn } from "child_process";
import fs from "fs/promises";
import path from "path";
import { getResolution } from "../constants";
import { getWatermarkPath } from "../storage";
export async function encodeVideo(options: {
imagePath: string;
audioPath: string;
outputPath: string;
resolution: string;
includeWatermark: boolean;
}): Promise<void> {
const res = getResolution(options.resolution);
if (!res) throw new Error(`Invalid resolution: ${options.resolution}`);
await fs.mkdir(path.dirname(options.outputPath), { recursive: true });
const scaleFilter = `scale=${res.width}:${res.height}:force_original_aspect_ratio=decrease,pad=${res.width}:${res.height}:(ow-iw)/2:(oh-ih)/2:black`;
const watermarkPath = getWatermarkPath();
let watermarkExists = false;
try {
await fs.access(watermarkPath);
watermarkExists = true;
} catch {
watermarkExists = false;
}
const useWatermark = options.includeWatermark && watermarkExists;
const args = ["-y", "-loop", "1", "-i", options.imagePath, "-i", options.audioPath];
if (useWatermark) {
args.push("-i", watermarkPath);
args.push(
"-filter_complex",
`[0:v]${scaleFilter}[scaled];[2:v]scale=iw*0.15:-1[wm];[scaled][wm]overlay=W-w-20:H-h-20[vout]`,
"-map",
"[vout]",
"-map",
"1:a",
);
} else if (options.includeWatermark && !watermarkExists) {
args.push(
"-filter_complex",
`[0:v]${scaleFilter},drawtext=text='s2yt':fontsize=24:fontcolor=white@0.7:x=w-tw-20:y=h-th-20[vout]`,
"-map",
"[vout]",
"-map",
"1:a",
);
} else {
args.push("-vf", scaleFilter);
}
args.push(
"-c:v",
"libx264",
"-tune",
"stillimage",
"-c:a",
"aac",
"-b:a",
"192k",
"-pix_fmt",
"yuv420p",
"-shortest",
options.outputPath,
);
await runFfmpeg(args);
}
function runFfmpeg(args: string[]): Promise<void> {
return new Promise((resolve, reject) => {
const proc = spawn("ffmpeg", args, { stdio: ["ignore", "pipe", "pipe"] });
let stderr = "";
proc.stderr.on("data", (chunk) => {
stderr += chunk.toString();
});
proc.on("close", (code) => {
if (code === 0) resolve();
else reject(new Error(`FFmpeg failed (code ${code}): ${stderr.slice(-500)}`));
});
proc.on("error", (err) => {
reject(new Error(`FFmpeg not found or failed to start: ${err.message}`));
});
});
}
export async function cleanupFiles(paths: string[]) {
for (const p of paths) {
try {
await fs.unlink(p);
} catch {
// ignore missing files
}
}
}