edit_video and export_video now run as media jobs: both registrations join the shared operation list the API and the worker build their runtime from, name every artifact they read (source, appended clips, subtitle files) so starting a job refuses artifacts the user cannot read, and their tool schemas use the runtime's continue_after helper. Container deployments get ffmpeg, ffprobe and the subtitle font in the API/worker image. Tests cover the handlers against a fake job context: cuts, blurred 9:16 reframes with subtitles, appended silent clips, audio extraction, single frames, batch exports with posters and progress, rotated phone video, clear input errors and cancellation. They skip where ffmpeg is not installed; the planner and parser tests always run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
57 lines
2.1 KiB
TypeScript
57 lines
2.1 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { EditVideoInputSchema, ExportVideoInputSchema } from "./media-video.js";
|
|
|
|
const edit = (steps: unknown[]) =>
|
|
EditVideoInputSchema.safeParse({ artifact_id: "a", title: "Reel", steps });
|
|
|
|
describe("edit_video input", () => {
|
|
it("accepts ordered steps and fills defaults", () => {
|
|
const parsed = EditVideoInputSchema.parse({
|
|
artifact_id: "a",
|
|
title: " Reel ",
|
|
steps: [
|
|
{ op: "trim", start: 1, end: 5 },
|
|
{ op: "reframe", preset: "vertical" },
|
|
{ op: "extract_audio" },
|
|
],
|
|
});
|
|
expect(parsed.title).toBe("Reel");
|
|
expect(parsed.steps[1]).toEqual({ op: "reframe", preset: "vertical", fit: "crop" });
|
|
// The job stores the parsed input and parses it again when it runs.
|
|
expect(EditVideoInputSchema.parse(parsed)).toEqual(parsed);
|
|
});
|
|
|
|
it("rejects malformed step lists", () => {
|
|
expect(edit([]).success).toBe(false);
|
|
expect(edit([{ op: "trim", start: 5, end: 5 }]).success).toBe(false);
|
|
expect(edit([{ op: "keep", segments: [{ start: 3, end: 1 }] }]).success).toBe(false);
|
|
expect(edit([{ op: "speed", factor: 8 }]).success).toBe(false);
|
|
expect(edit([{ op: "extract_audio" }, { op: "mute" }]).success).toBe(false);
|
|
expect(edit([{ op: "poster", at: 1 }, { op: "extract_audio" }]).success).toBe(false);
|
|
expect(
|
|
edit([
|
|
{ op: "poster", at: 1 },
|
|
{ op: "poster", at: 2 },
|
|
]).success,
|
|
).toBe(false);
|
|
expect(edit([{ op: "blur" }]).success).toBe(false);
|
|
expect(
|
|
EditVideoInputSchema.safeParse({ artifact_id: "a", steps: [{ op: "mute" }] }).success,
|
|
).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("export_video input", () => {
|
|
it("needs at least one preset and defaults to cropping", () => {
|
|
expect(ExportVideoInputSchema.parse({ artifact_id: "a", presets: ["square"] })).toEqual({
|
|
artifact_id: "a",
|
|
presets: ["square"],
|
|
fit: "crop",
|
|
});
|
|
expect(ExportVideoInputSchema.safeParse({ artifact_id: "a", presets: [] }).success).toBe(false);
|
|
expect(ExportVideoInputSchema.safeParse({ artifact_id: "a", presets: ["imax"] }).success).toBe(
|
|
false,
|
|
);
|
|
});
|
|
});
|