mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
74 lines
2.1 KiB
TypeScript
74 lines
2.1 KiB
TypeScript
import { dirname, join } from "node:path";
|
|
import { probeMedia, resolveEncoder, resolveFontFile } from "@snapotter/media-engine";
|
|
import type { FastifyInstance } from "fastify";
|
|
import { z } from "zod";
|
|
import { runFfmpegWithProgress, stageMediaInputs } from "../../lib/media-tool.js";
|
|
import { InputValidationError } from "../../modality/contract.js";
|
|
import { createToolRoute } from "../tool-factory.js";
|
|
|
|
const settingsSchema = z.object({
|
|
fontSize: z.number().int().min(8).max(72).default(24),
|
|
});
|
|
|
|
export function registerBurnSubtitles(app: FastifyInstance) {
|
|
createToolRoute(app, {
|
|
toolId: "burn-subtitles",
|
|
maxInputs: 2,
|
|
inputKinds: ["video", "subtitle"],
|
|
settingsSchema,
|
|
process: async () => {
|
|
throw new Error("burn-subtitles is v2-only");
|
|
},
|
|
processV2: async (ctx) => {
|
|
if (ctx.inputs.length !== 2) {
|
|
throw new InputValidationError("Provide a video file and a subtitle file");
|
|
}
|
|
|
|
const settings = settingsSchema.parse(ctx.settings);
|
|
const base = ctx.inputs[0].filename.replace(/\.[^.]+$/, "");
|
|
const outName = `${base}_subtitled.mp4`;
|
|
|
|
const font = resolveFontFile();
|
|
if (!font) {
|
|
throw new Error("No usable font found for subtitles (set SNAPOTTER_FONT_FILE)");
|
|
}
|
|
|
|
const paths = await stageMediaInputs(ctx);
|
|
const videoPath = paths[0];
|
|
const subPath = paths[1];
|
|
const info = await probeMedia(videoPath);
|
|
|
|
const vf = `subtitles=${subPath}:fontsdir=${dirname(font.file)}:force_style='FontName=${font.family},FontSize=${settings.fontSize}'`;
|
|
|
|
const outPath = join(ctx.scratchDir, "media", outName);
|
|
await runFfmpegWithProgress(
|
|
ctx,
|
|
[
|
|
"-i",
|
|
videoPath,
|
|
"-vf",
|
|
vf,
|
|
"-c:v",
|
|
resolveEncoder("h264"),
|
|
"-crf",
|
|
"20",
|
|
"-preset",
|
|
"medium",
|
|
"-pix_fmt",
|
|
"yuv420p",
|
|
"-c:a",
|
|
"copy",
|
|
outPath,
|
|
],
|
|
info.durationS,
|
|
);
|
|
|
|
return {
|
|
scratchPath: outPath,
|
|
filename: outName,
|
|
contentType: "video/mp4",
|
|
};
|
|
},
|
|
});
|
|
}
|