@assemblyline-agents/ffmpeg 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 OpenEve contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # @assemblyline-agents/ffmpeg
2
+
3
+ Official Assembly Line stdio connection for workspace-scoped FFmpeg operations.
4
+
5
+ FFmpeg is a host executable, not a remote account connection. This package
6
+ bundles a small MCP server that exposes reviewed media operations and launches
7
+ `ffmpeg` or `ffprobe` without a shell. It never exposes arbitrary command-line
8
+ flags to the model.
9
+
10
+ ## Setup
11
+
12
+ Install FFmpeg on every runtime host:
13
+
14
+ ```sh
15
+ # macOS
16
+ brew install ffmpeg
17
+
18
+ # Debian/Ubuntu
19
+ apt-get update && apt-get install -y ffmpeg
20
+
21
+ assembly-line add ffmpeg agent
22
+ ```
23
+
24
+ Enable editing explicitly:
25
+
26
+ ```ts
27
+ import { defineFfmpegConnection } from "@assemblyline-agents/ffmpeg";
28
+
29
+ export default defineFfmpegConnection({
30
+ workspaceRoot: "/workspace/media",
31
+ access: {
32
+ read: true,
33
+ write: { approval: "always" }
34
+ }
35
+ });
36
+ ```
37
+
38
+ The connection exposes:
39
+
40
+ - `probe_media`
41
+ - `trim_media`
42
+ - `transcode_media`
43
+ - `concat_media`
44
+ - `replace_audio`
45
+ - `extract_frames`
46
+ - `reframe_vertical`
47
+
48
+ All input and output paths must resolve inside `workspaceRoot`, including
49
+ symlink targets. The deployment owner can select trusted `ffmpegCommand` and
50
+ `ffprobeCommand` paths; agents cannot replace those executables or add
51
+ unreviewed flags.
52
+
53
+ `concat_media` expects codec-compatible, normalized clips. Transcode source
54
+ clips to a common size, frame rate, codec, and audio layout before stitching
55
+ when they come from different devices or platforms.
56
+
57
+ `extract_frames` returns paths only by default. Set `previewFrames` explicitly
58
+ (one to twelve) only when the user requests or approves a frame-based
59
+ approximation. Assembly Line never invokes frame sampling as a fallback for native
60
+ video understanding, and agents must not claim that sampled stills are
61
+ equivalent to watching temporal video.
@@ -0,0 +1,8 @@
1
+ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
+ /** Build bounded, evenly sampled MCP image content from an FFmpeg output pattern. */
3
+ export declare function framePreviewResult(outputPattern: string, result: {
4
+ stdout: string;
5
+ stderr: string;
6
+ }, previewFrames: number, workspaceRoot: string): CallToolResult;
7
+ export declare function matchingFramePaths(outputPattern: string): string[];
8
+ //# sourceMappingURL=frame-preview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frame-preview.d.ts","sourceRoot":"","sources":["../src/frame-preview.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAKzE,qFAAqF;AACrF,wBAAgB,kBAAkB,CAChC,aAAa,EAAE,MAAM,EACrB,MAAM,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAC1C,aAAa,EAAE,MAAM,EACrB,aAAa,EAAE,MAAM,GACpB,cAAc,CA+BhB;AAED,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,EAAE,CAalE"}
@@ -0,0 +1,82 @@
1
+ import { readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
2
+ import { basename, dirname, extname, isAbsolute, join, relative } from "node:path";
3
+ const MAX_PREVIEW_FRAME_BYTES = 20 * 1024 * 1024;
4
+ const MAX_PREVIEW_TOTAL_BYTES = 50 * 1024 * 1024;
5
+ /** Build bounded, evenly sampled MCP image content from an FFmpeg output pattern. */
6
+ export function framePreviewResult(outputPattern, result, previewFrames, workspaceRoot) {
7
+ const root = realpathSync(workspaceRoot);
8
+ const framePaths = matchingFramePaths(outputPattern).filter((path) => isInside(root, realpathSync(path)));
9
+ const previewPaths = sampledPaths(framePaths, previewFrames);
10
+ const images = [];
11
+ const includedPaths = [];
12
+ let totalBytes = 0;
13
+ for (const path of previewPaths) {
14
+ const size = statSync(path).size;
15
+ if (size > MAX_PREVIEW_FRAME_BYTES || totalBytes + size > MAX_PREVIEW_TOTAL_BYTES)
16
+ continue;
17
+ const mimeType = frameMimeType(path);
18
+ if (!mimeType)
19
+ continue;
20
+ images.push({ type: "image", data: readFileSync(path).toString("base64"), mimeType });
21
+ includedPaths.push(path);
22
+ totalBytes += size;
23
+ }
24
+ return {
25
+ content: [
26
+ {
27
+ type: "text",
28
+ text: JSON.stringify({
29
+ outputPattern,
30
+ frameCount: framePaths.length,
31
+ previewPaths: includedPaths,
32
+ stdout: result.stdout,
33
+ stderr: result.stderr
34
+ }, null, 2)
35
+ },
36
+ ...images
37
+ ]
38
+ };
39
+ }
40
+ export function matchingFramePaths(outputPattern) {
41
+ const directory = dirname(outputPattern);
42
+ const filename = basename(outputPattern);
43
+ const token = /%(?:0([1-9][0-9]*))?d/u.exec(filename);
44
+ if (!token || token.index === undefined)
45
+ return [];
46
+ const before = filename.slice(0, token.index);
47
+ const after = filename.slice(token.index + token[0].length);
48
+ const digits = token[1] ? `\\d{${token[1]}}` : "\\d+";
49
+ const matcher = new RegExp(`^${escapeRegex(before)}${digits}${escapeRegex(after)}$`, "u");
50
+ return readdirSync(directory)
51
+ .filter((name) => matcher.test(name))
52
+ .sort((left, right) => left.localeCompare(right, undefined, { numeric: true }))
53
+ .map((name) => join(directory, name));
54
+ }
55
+ function sampledPaths(paths, count) {
56
+ if (count <= 0 || paths.length === 0)
57
+ return [];
58
+ if (paths.length <= count)
59
+ return paths;
60
+ if (count === 1)
61
+ return [paths[0]];
62
+ const indexes = new Set(Array.from({ length: count }, (_, index) => Math.round((index * (paths.length - 1)) / (count - 1))));
63
+ return [...indexes].map((index) => paths[index]);
64
+ }
65
+ function frameMimeType(path) {
66
+ const extension = extname(path).toLowerCase();
67
+ if (extension === ".jpg" || extension === ".jpeg")
68
+ return "image/jpeg";
69
+ if (extension === ".png")
70
+ return "image/png";
71
+ if (extension === ".webp")
72
+ return "image/webp";
73
+ return undefined;
74
+ }
75
+ function escapeRegex(value) {
76
+ return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
77
+ }
78
+ function isInside(root, candidate) {
79
+ const relation = relative(root, candidate);
80
+ return !relation.startsWith("..") && !isAbsolute(relation);
81
+ }
82
+ //# sourceMappingURL=frame-preview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frame-preview.js","sourceRoot":"","sources":["../src/frame-preview.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import { type StdioMcpPluginConnectionOptions } from "@assemblyline-agents/core";
2
+ export interface FfmpegConnectionOptions extends StdioMcpPluginConnectionOptions {
3
+ /** Directory containing every readable input and writable output. */
4
+ workspaceRoot?: string;
5
+ /** Trusted FFmpeg executable selected by the deployment owner. */
6
+ ffmpegCommand?: string;
7
+ /** Trusted FFprobe executable selected by the deployment owner. */
8
+ ffprobeCommand?: string;
9
+ }
10
+ export declare function defineFfmpegConnection(options: FfmpegConnectionOptions): import("@assemblyline-agents/core").McpStdioClientConnectionDefinition;
11
+ export declare const openevePlugin: import("@assemblyline-agents/core").PluginModule;
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,+BAA+B,EACrC,MAAM,2BAA2B,CAAC;AAKnC,MAAM,WAAW,uBAAwB,SAAQ,+BAA+B;IAC9E,qEAAqE;IACrE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kEAAkE;IAClE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,mEAAmE;IACnE,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,uBAAuB,0EAuBtE;AAED,eAAO,MAAM,aAAa,kDAA0C,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,26 @@
1
+ import { resolve } from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { connectionPluginMetadata, definePlugin, defineStdioMcpPluginConnection } from "@assemblyline-agents/core";
4
+ const PLUGIN = connectionPluginMetadata("ffmpeg");
5
+ const MCP_SERVER_PATH = fileURLToPath(new URL("./mcp-server.js", import.meta.url));
6
+ export function defineFfmpegConnection(options) {
7
+ const { workspaceRoot: configuredWorkspaceRoot, ffmpegCommand = "ffmpeg", ffprobeCommand = "ffprobe", ...connection } = options;
8
+ const workspaceRoot = resolve(configuredWorkspaceRoot ?? connection.cwd ?? process.cwd());
9
+ const serverArgs = [
10
+ MCP_SERVER_PATH,
11
+ "--workspace-root",
12
+ workspaceRoot,
13
+ "--ffmpeg-command",
14
+ ffmpegCommand,
15
+ "--ffprobe-command",
16
+ ffprobeCommand
17
+ ];
18
+ return defineStdioMcpPluginConnection(PLUGIN, {
19
+ ...connection,
20
+ command: connection.command ?? process.execPath,
21
+ args: connection.args ?? serverArgs,
22
+ cwd: connection.cwd ?? workspaceRoot
23
+ });
24
+ }
25
+ export const openevePlugin = definePlugin({ connections: [PLUGIN] });
26
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EACL,wBAAwB,EACxB,YAAY,EACZ,8BAA8B,EAE/B,MAAM,2BAA2B,CAAC;AAEnC,MAAM,MAAM,GAAG,wBAAwB,CAAC,QAAQ,CAAE,CAAC;AACnD,MAAM,eAAe,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,iBAAiB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAWnF,MAAM,UAAU,sBAAsB,CAAC,OAAgC;IACrE,MAAM,EACJ,aAAa,EAAE,uBAAuB,EACtC,aAAa,GAAG,QAAQ,EACxB,cAAc,GAAG,SAAS,EAC1B,GAAG,UAAU,EACd,GAAG,OAAO,CAAC;IACZ,MAAM,aAAa,GAAG,OAAO,CAAC,uBAAuB,IAAI,UAAU,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAC1F,MAAM,UAAU,GAAG;QACjB,eAAe;QACf,kBAAkB;QAClB,aAAa;QACb,kBAAkB;QAClB,aAAa;QACb,mBAAmB;QACnB,cAAc;KACf,CAAC;IACF,OAAO,8BAA8B,CAAC,MAAM,EAAE;QAC5C,GAAG,UAAU;QACb,OAAO,EAAE,UAAU,CAAC,OAAO,IAAI,OAAO,CAAC,QAAQ;QAC/C,IAAI,EAAE,UAAU,CAAC,IAAI,IAAI,UAAU;QACnC,GAAG,EAAE,UAAU,CAAC,GAAG,IAAI,aAAa;KACrC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,MAAM,aAAa,GAAG,YAAY,CAAC,EAAE,WAAW,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=mcp-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":""}
@@ -0,0 +1,216 @@
1
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { tmpdir } from "node:os";
4
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
7
+ import { runProcess } from "./process.js";
8
+ import { framePreviewResult } from "./frame-preview.js";
9
+ import { arrayValue, enumValue, evenDimension, integerValue, numberValue, optionalEvenDimension, recordValue, stringValue } from "./tool-input.js";
10
+ import { FFMPEG_TOOLS } from "./tools.js";
11
+ import { MediaWorkspace } from "./workspace.js";
12
+ const options = parseServerOptions(process.argv.slice(2));
13
+ const workspace = new MediaWorkspace(options.workspaceRoot);
14
+ const server = new Server({ name: "openeve-ffmpeg", version: "0.1.0" }, {
15
+ capabilities: { tools: {} },
16
+ instructions: "Media operations are typed and restricted to the configured workspace root."
17
+ });
18
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: FFMPEG_TOOLS }));
19
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
20
+ try {
21
+ return await callTool(request.params.name, recordValue(request.params.arguments));
22
+ }
23
+ catch (error) {
24
+ return toolResult({ error: error instanceof Error ? error.message : String(error) }, true);
25
+ }
26
+ });
27
+ await server.connect(new StdioServerTransport());
28
+ async function callTool(name, input) {
29
+ if (name === "probe_media") {
30
+ const inputPath = workspace.input(stringValue(input.inputPath, "inputPath"));
31
+ const result = await run(options.ffprobeCommand, [
32
+ "-v", "error",
33
+ "-show_format",
34
+ "-show_streams",
35
+ "-of", "json",
36
+ inputPath
37
+ ]);
38
+ return toolResult(JSON.parse(result.stdout || "{}"));
39
+ }
40
+ if (name === "trim_media") {
41
+ const inputPath = workspace.input(stringValue(input.inputPath, "inputPath"));
42
+ const outputPath = workspace.output(stringValue(input.outputPath, "outputPath"));
43
+ const start = numberValue(input.startSeconds, "startSeconds", 0);
44
+ const duration = numberValue(input.durationSeconds, "durationSeconds", Number.MIN_VALUE);
45
+ const args = [
46
+ "-y", "-ss", String(start), "-i", inputPath, "-t", String(duration),
47
+ "-map", "0:v:0", "-map", "0:a?",
48
+ "-c:v", "libx264",
49
+ "-preset", enumValue(input.preset, X264_PRESETS, "medium"),
50
+ "-crf", String(integerValue(input.crf, "crf", 0, 51, 18)),
51
+ "-c:a", "aac", "-b:a", "192k", "-movflags", "+faststart",
52
+ outputPath
53
+ ];
54
+ return writeResult(outputPath, await run(options.ffmpegCommand, args));
55
+ }
56
+ if (name === "transcode_media") {
57
+ const inputPath = workspace.input(stringValue(input.inputPath, "inputPath"));
58
+ const outputPath = workspace.output(stringValue(input.outputPath, "outputPath"));
59
+ const codec = enumValue(input.codec, ["h264", "h265", "vp9", "prores"], "h264");
60
+ const width = optionalEvenDimension(input.width, "width", 7680);
61
+ const height = optionalEvenDimension(input.height, "height", 7680);
62
+ const video = videoCodecArguments(codec, input);
63
+ const args = [
64
+ "-y", "-i", inputPath,
65
+ "-map", "0:v:0", "-map", "0:a?",
66
+ ...scaleArguments(width, height),
67
+ ...video,
68
+ ...(codec === "prores" ? ["-c:a", "pcm_s16le"] : ["-c:a", "aac", "-b:a", "192k"]),
69
+ outputPath
70
+ ];
71
+ return writeResult(outputPath, await run(options.ffmpegCommand, args));
72
+ }
73
+ if (name === "concat_media") {
74
+ const values = arrayValue(input.inputPaths, "inputPaths", 2, 100);
75
+ const inputPaths = values.map((value, index) => workspace.input(stringValue(value, `inputPaths[${index}]`)));
76
+ const outputPath = workspace.output(stringValue(input.outputPath, "outputPath"));
77
+ const mode = enumValue(input.mode, ["copy", "reencode"], "reencode");
78
+ const temporary = mkdtempSync(join(tmpdir(), "openeve-ffmpeg-concat-"));
79
+ const listPath = join(temporary, "inputs.txt");
80
+ try {
81
+ writeFileSync(listPath, inputPaths.map((path) => `file '${escapeConcatPath(path)}'`).join("\n"));
82
+ const args = [
83
+ "-y", "-f", "concat", "-safe", "0", "-i", listPath,
84
+ ...(mode === "copy"
85
+ ? ["-c", "copy"]
86
+ : [
87
+ "-c:v", "libx264", "-preset", "medium",
88
+ "-crf", String(integerValue(input.crf, "crf", 0, 51, 18)),
89
+ "-c:a", "aac", "-b:a", "192k", "-movflags", "+faststart"
90
+ ]),
91
+ outputPath
92
+ ];
93
+ return writeResult(outputPath, await run(options.ffmpegCommand, args));
94
+ }
95
+ finally {
96
+ rmSync(temporary, { recursive: true, force: true });
97
+ }
98
+ }
99
+ if (name === "replace_audio") {
100
+ const videoPath = workspace.input(stringValue(input.videoPath, "videoPath"));
101
+ const audioPath = workspace.input(stringValue(input.audioPath, "audioPath"));
102
+ const outputPath = workspace.output(stringValue(input.outputPath, "outputPath"));
103
+ const volume = input.volume === undefined ? 1 : numberValue(input.volume, "volume", 0, 10);
104
+ const args = [
105
+ "-y", "-i", videoPath, "-i", audioPath,
106
+ "-map", "0:v:0", "-map", "1:a:0",
107
+ "-c:v", "copy", "-filter:a", `volume=${volume}`,
108
+ "-c:a", "aac", "-b:a", "192k", "-shortest",
109
+ outputPath
110
+ ];
111
+ return writeResult(outputPath, await run(options.ffmpegCommand, args));
112
+ }
113
+ if (name === "extract_frames") {
114
+ const inputPath = workspace.input(stringValue(input.inputPath, "inputPath"));
115
+ const patternValue = stringValue(input.outputPattern, "outputPattern");
116
+ if (!/%(?:0[1-9][0-9]*)?d/u.test(patternValue)) {
117
+ throw new Error("outputPattern must include a frame counter such as %04d.");
118
+ }
119
+ const outputPattern = workspace.outputPattern(patternValue);
120
+ const args = [
121
+ "-y",
122
+ ...(input.startSeconds === undefined ? [] : ["-ss", String(numberValue(input.startSeconds, "startSeconds", 0))]),
123
+ "-i", inputPath,
124
+ ...(input.durationSeconds === undefined
125
+ ? []
126
+ : ["-t", String(numberValue(input.durationSeconds, "durationSeconds", Number.MIN_VALUE))]),
127
+ "-vf", `fps=${numberValue(input.fps, "fps", Number.MIN_VALUE, 60)}`,
128
+ "-q:v", String(integerValue(input.quality, "quality", 2, 31, 2)),
129
+ outputPattern
130
+ ];
131
+ const result = await run(options.ffmpegCommand, args);
132
+ return framePreviewResult(outputPattern, result, integerValue(input.previewFrames, "previewFrames", 0, 12, 0), workspace.root);
133
+ }
134
+ if (name === "reframe_vertical") {
135
+ const inputPath = workspace.input(stringValue(input.inputPath, "inputPath"));
136
+ const outputPath = workspace.output(stringValue(input.outputPath, "outputPath"));
137
+ const width = evenDimension(input.width, "width", 2160, 1080);
138
+ const height = evenDimension(input.height, "height", 3840, 1920);
139
+ const focus = enumValue(input.focus, ["left", "center", "right"], "center");
140
+ const horizontal = focus === "left" ? "0" : focus === "right" ? "iw-ow" : "(iw-ow)/2";
141
+ const filter = `scale=${width}:${height}:force_original_aspect_ratio=increase,crop=${width}:${height}:${horizontal}:(ih-oh)/2`;
142
+ const args = [
143
+ "-y", "-i", inputPath,
144
+ "-map", "0:v:0", "-map", "0:a?",
145
+ "-vf", filter,
146
+ "-c:v", "libx264", "-preset", "medium",
147
+ "-crf", String(integerValue(input.crf, "crf", 0, 51, 18)),
148
+ "-c:a", "aac", "-b:a", "192k", "-movflags", "+faststart",
149
+ outputPath
150
+ ];
151
+ return writeResult(outputPath, await run(options.ffmpegCommand, args));
152
+ }
153
+ throw new Error(`Unknown FFmpeg tool: ${name}`);
154
+ }
155
+ const X264_PRESETS = [
156
+ "ultrafast", "superfast", "veryfast", "faster", "fast",
157
+ "medium", "slow", "slower", "veryslow"
158
+ ];
159
+ function videoCodecArguments(codec, input) {
160
+ if (codec === "prores")
161
+ return ["-c:v", "prores_ks", "-profile:v", "3"];
162
+ if (codec === "vp9") {
163
+ return ["-c:v", "libvpx-vp9", "-crf", String(integerValue(input.crf, "crf", 0, 51, 28)), "-b:v", "0"];
164
+ }
165
+ return [
166
+ "-c:v", codec === "h265" ? "libx265" : "libx264",
167
+ "-preset", enumValue(input.preset, X264_PRESETS, "medium"),
168
+ "-crf", String(integerValue(input.crf, "crf", 0, 51, codec === "h265" ? 24 : 18))
169
+ ];
170
+ }
171
+ function scaleArguments(width, height) {
172
+ if (width && height) {
173
+ return ["-vf", `scale=${width}:${height}:force_original_aspect_ratio=decrease,pad=${width}:${height}:(ow-iw)/2:(oh-ih)/2`];
174
+ }
175
+ if (width)
176
+ return ["-vf", `scale=${width}:-2`];
177
+ if (height)
178
+ return ["-vf", `scale=-2:${height}`];
179
+ return [];
180
+ }
181
+ function run(command, args) {
182
+ return runProcess(command, args, { cwd: workspace.root });
183
+ }
184
+ function writeResult(outputPath, result) {
185
+ return toolResult({ outputPath, stdout: result.stdout, stderr: result.stderr });
186
+ }
187
+ function parseServerOptions(args) {
188
+ let workspaceRoot = process.cwd();
189
+ let ffmpegCommand = "ffmpeg";
190
+ let ffprobeCommand = "ffprobe";
191
+ for (let index = 0; index < args.length; index += 2) {
192
+ const flag = args[index];
193
+ const value = args[index + 1];
194
+ if (!flag || value === undefined)
195
+ throw new Error(`Missing value for ${flag ?? "server option"}.`);
196
+ if (flag === "--workspace-root")
197
+ workspaceRoot = value;
198
+ else if (flag === "--ffmpeg-command")
199
+ ffmpegCommand = value;
200
+ else if (flag === "--ffprobe-command")
201
+ ffprobeCommand = value;
202
+ else
203
+ throw new Error(`Unknown FFmpeg MCP server option: ${flag}`);
204
+ }
205
+ return { workspaceRoot, ffmpegCommand, ffprobeCommand };
206
+ }
207
+ function escapeConcatPath(value) {
208
+ return value.replaceAll("'", "'\\''");
209
+ }
210
+ function toolResult(value, isError = false) {
211
+ return {
212
+ content: [{ type: "text", text: JSON.stringify(value, null, 2) }],
213
+ ...(isError ? { isError: true } : {})
214
+ };
215
+ }
216
+ //# sourceMappingURL=mcp-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-server.js","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export interface ProcessResult {
2
+ stdout: string;
3
+ stderr: string;
4
+ }
5
+ export declare function runProcess(command: string, args: string[], options: {
6
+ cwd: string;
7
+ timeoutMs?: number;
8
+ }): Promise<ProcessResult>;
9
+ //# sourceMappingURL=process.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../src/process.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,UAAU,CACxB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAC3C,OAAO,CAAC,aAAa,CAAC,CAuCxB"}
@@ -0,0 +1,43 @@
1
+ import { spawn } from "node:child_process";
2
+ const MAX_CAPTURE_BYTES = 512 * 1024;
3
+ export function runProcess(command, args, options) {
4
+ return new Promise((resolve, reject) => {
5
+ const child = spawn(command, args, {
6
+ cwd: options.cwd,
7
+ env: process.env,
8
+ shell: false,
9
+ stdio: ["ignore", "pipe", "pipe"]
10
+ });
11
+ let stdout = "";
12
+ let stderr = "";
13
+ let capturedBytes = 0;
14
+ const capture = (target, chunk) => {
15
+ capturedBytes += chunk.byteLength;
16
+ if (capturedBytes > MAX_CAPTURE_BYTES) {
17
+ child.kill("SIGKILL");
18
+ reject(new Error(`Process output exceeded ${MAX_CAPTURE_BYTES} bytes.`));
19
+ return;
20
+ }
21
+ if (target === "stdout")
22
+ stdout += chunk.toString();
23
+ else
24
+ stderr += chunk.toString();
25
+ };
26
+ child.stdout.on("data", (chunk) => capture("stdout", chunk));
27
+ child.stderr.on("data", (chunk) => capture("stderr", chunk));
28
+ child.once("error", reject);
29
+ const timeout = setTimeout(() => {
30
+ child.kill("SIGKILL");
31
+ reject(new Error(`Process timed out after ${options.timeoutMs ?? 30 * 60_000}ms.`));
32
+ }, options.timeoutMs ?? 30 * 60_000);
33
+ child.once("close", (code, signal) => {
34
+ clearTimeout(timeout);
35
+ if (code === 0) {
36
+ resolve({ stdout: stdout.trim(), stderr: stderr.trim() });
37
+ return;
38
+ }
39
+ reject(new Error(`${command} exited with ${code ?? signal ?? "unknown status"}${stderr.trim() ? `: ${stderr.trim()}` : ""}`));
40
+ });
41
+ });
42
+ }
43
+ //# sourceMappingURL=process.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"process.js","sourceRoot":"","sources":["../src/process.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,MAAM,iBAAiB,GAAG,GAAG,GAAG,IAAI,CAAC;AAOrC,MAAM,UAAU,UAAU,CACxB,OAAe,EACf,IAAc,EACd,OAA4C;IAE5C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE;YACjC,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,KAAK,EAAE,KAAK;YACZ,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;SAClC,CAAC,CAAC;QACH,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,aAAa,GAAG,CAAC,CAAC;QACtB,MAAM,OAAO,GAAG,CAAC,MAA2B,EAAE,KAAa,EAAQ,EAAE;YACnE,aAAa,IAAI,KAAK,CAAC,UAAU,CAAC;YAClC,IAAI,aAAa,GAAG,iBAAiB,EAAE,CAAC;gBACtC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBACtB,MAAM,CAAC,IAAI,KAAK,CAAC,2BAA2B,iBAAiB,SAAS,CAAC,CAAC,CAAC;gBACzE,OAAO;YACT,CAAC;YACD,IAAI,MAAM,KAAK,QAAQ;gBAAE,MAAM,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;;gBAC/C,MAAM,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QAClC,CAAC,CAAC;QACF,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC;QACrE,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC;QACrE,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC5B,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;YAC9B,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACtB,MAAM,CAAC,IAAI,KAAK,CAAC,2BAA2B,OAAO,CAAC,SAAS,IAAI,EAAE,GAAG,MAAM,KAAK,CAAC,CAAC,CAAC;QACtF,CAAC,EAAE,OAAO,CAAC,SAAS,IAAI,EAAE,GAAG,MAAM,CAAC,CAAC;QACrC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE;YACnC,YAAY,CAAC,OAAO,CAAC,CAAC;YACtB,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;gBACf,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gBAC1D,OAAO;YACT,CAAC;YACD,MAAM,CAAC,IAAI,KAAK,CACd,GAAG,OAAO,gBAAgB,IAAI,IAAI,MAAM,IAAI,gBAAgB,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAC3G,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,9 @@
1
+ export declare function recordValue(value: unknown): Record<string, unknown>;
2
+ export declare function stringValue(value: unknown, name: string): string;
3
+ export declare function arrayValue(value: unknown, name: string, minimum: number, maximum: number): unknown[];
4
+ export declare function numberValue(value: unknown, name: string, minimum: number, maximum?: number): number;
5
+ export declare function integerValue(value: unknown, name: string, minimum: number, maximum: number, fallback?: number): number;
6
+ export declare function evenDimension(value: unknown, name: string, maximum: number, fallback: number): number;
7
+ export declare function optionalEvenDimension(value: unknown, name: string, maximum: number): number | undefined;
8
+ export declare function enumValue<T extends string>(value: unknown, allowed: readonly T[], fallback: T): T;
9
+ //# sourceMappingURL=tool-input.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-input.d.ts","sourceRoot":"","sources":["../src/tool-input.ts"],"names":[],"mappings":"AAAA,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAInE;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAGhE;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,EAAE,CAKpG;AAED,wBAAgB,WAAW,CACzB,KAAK,EAAE,OAAO,EACd,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,SAA0B,GAChC,MAAM,CAKR;AAED,wBAAgB,YAAY,CAC1B,KAAK,EAAE,OAAO,EACd,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,MAAM,GAChB,MAAM,CAMR;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAIrG;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEvG;AAED,wBAAgB,SAAS,CAAC,CAAC,SAAS,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,EAAE,QAAQ,EAAE,CAAC,GAAG,CAAC,CAMjG"}
@@ -0,0 +1,48 @@
1
+ export function recordValue(value) {
2
+ return value && typeof value === "object" && !Array.isArray(value)
3
+ ? value
4
+ : {};
5
+ }
6
+ export function stringValue(value, name) {
7
+ if (typeof value !== "string" || !value.trim())
8
+ throw new Error(`${name} must be a non-empty string.`);
9
+ return value;
10
+ }
11
+ export function arrayValue(value, name, minimum, maximum) {
12
+ if (!Array.isArray(value) || value.length < minimum || value.length > maximum) {
13
+ throw new Error(`${name} must contain ${minimum} to ${maximum} items.`);
14
+ }
15
+ return value;
16
+ }
17
+ export function numberValue(value, name, minimum, maximum = Number.MAX_SAFE_INTEGER) {
18
+ if (typeof value !== "number" || !Number.isFinite(value) || value < minimum || value > maximum) {
19
+ throw new Error(`${name} must be a number from ${minimum} to ${maximum}.`);
20
+ }
21
+ return value;
22
+ }
23
+ export function integerValue(value, name, minimum, maximum, fallback) {
24
+ if (value === undefined && fallback !== undefined)
25
+ return fallback;
26
+ if (typeof value !== "number" || !Number.isInteger(value) || value < minimum || value > maximum) {
27
+ throw new Error(`${name} must be an integer from ${minimum} to ${maximum}.`);
28
+ }
29
+ return value;
30
+ }
31
+ export function evenDimension(value, name, maximum, fallback) {
32
+ const dimension = integerValue(value, name, 16, maximum, fallback);
33
+ if (dimension % 2 !== 0)
34
+ throw new Error(`${name} must be even.`);
35
+ return dimension;
36
+ }
37
+ export function optionalEvenDimension(value, name, maximum) {
38
+ return value === undefined ? undefined : evenDimension(value, name, maximum, 16);
39
+ }
40
+ export function enumValue(value, allowed, fallback) {
41
+ if (value === undefined)
42
+ return fallback;
43
+ if (typeof value !== "string" || !allowed.includes(value)) {
44
+ throw new Error(`Value must be one of: ${allowed.join(", ")}.`);
45
+ }
46
+ return value;
47
+ }
48
+ //# sourceMappingURL=tool-input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-input.js","sourceRoot":"","sources":["../src/tool-input.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { Tool } from "@modelcontextprotocol/sdk/types.js";
2
+ export declare const FFMPEG_TOOLS: Tool[];
3
+ //# sourceMappingURL=tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oCAAoC,CAAC;AAE/D,eAAO,MAAM,YAAY,EAAE,IAAI,EA0H9B,CAAC"}
package/dist/tools.js ADDED
@@ -0,0 +1,124 @@
1
+ export const FFMPEG_TOOLS = [
2
+ {
3
+ name: "probe_media",
4
+ description: "Inspect media streams, codecs, dimensions, frame rates, duration, and container metadata.",
5
+ inputSchema: {
6
+ type: "object",
7
+ required: ["inputPath"],
8
+ properties: { inputPath: { type: "string", minLength: 1 } },
9
+ additionalProperties: false
10
+ }
11
+ },
12
+ {
13
+ name: "trim_media",
14
+ description: "Create a precisely trimmed H.264/AAC clip from a workspace media file.",
15
+ inputSchema: {
16
+ type: "object",
17
+ required: ["inputPath", "outputPath", "startSeconds", "durationSeconds"],
18
+ properties: {
19
+ inputPath: { type: "string" },
20
+ outputPath: { type: "string" },
21
+ startSeconds: { type: "number", minimum: 0 },
22
+ durationSeconds: { type: "number", exclusiveMinimum: 0 },
23
+ crf: { type: "integer", minimum: 0, maximum: 51 },
24
+ preset: {
25
+ type: "string",
26
+ enum: ["ultrafast", "superfast", "veryfast", "faster", "fast", "medium", "slow", "slower", "veryslow"]
27
+ }
28
+ },
29
+ additionalProperties: false
30
+ }
31
+ },
32
+ {
33
+ name: "transcode_media",
34
+ description: "Transcode video to a reviewed codec, optionally resizing and padding to exact dimensions.",
35
+ inputSchema: {
36
+ type: "object",
37
+ required: ["inputPath", "outputPath"],
38
+ properties: {
39
+ inputPath: { type: "string" },
40
+ outputPath: { type: "string" },
41
+ codec: { type: "string", enum: ["h264", "h265", "vp9", "prores"] },
42
+ width: { type: "integer", minimum: 16, maximum: 7680 },
43
+ height: { type: "integer", minimum: 16, maximum: 7680 },
44
+ crf: { type: "integer", minimum: 0, maximum: 51 },
45
+ preset: {
46
+ type: "string",
47
+ enum: ["ultrafast", "superfast", "veryfast", "faster", "fast", "medium", "slow", "slower", "veryslow"]
48
+ }
49
+ },
50
+ additionalProperties: false
51
+ }
52
+ },
53
+ {
54
+ name: "concat_media",
55
+ description: "Stitch two or more normalized media clips in order using FFmpeg's concat demuxer.",
56
+ inputSchema: {
57
+ type: "object",
58
+ required: ["inputPaths", "outputPath"],
59
+ properties: {
60
+ inputPaths: { type: "array", minItems: 2, maxItems: 100, items: { type: "string" } },
61
+ outputPath: { type: "string" },
62
+ mode: { type: "string", enum: ["copy", "reencode"] },
63
+ crf: { type: "integer", minimum: 0, maximum: 51 }
64
+ },
65
+ additionalProperties: false
66
+ }
67
+ },
68
+ {
69
+ name: "replace_audio",
70
+ description: "Replace a video's audio track with a workspace audio file and stop at the shorter stream.",
71
+ inputSchema: {
72
+ type: "object",
73
+ required: ["videoPath", "audioPath", "outputPath"],
74
+ properties: {
75
+ videoPath: { type: "string" },
76
+ audioPath: { type: "string" },
77
+ outputPath: { type: "string" },
78
+ volume: { type: "number", minimum: 0, maximum: 10 }
79
+ },
80
+ additionalProperties: false
81
+ }
82
+ },
83
+ {
84
+ name: "extract_frames",
85
+ description: "Extract a numbered image sequence from a video at a controlled frame rate.",
86
+ inputSchema: {
87
+ type: "object",
88
+ required: ["inputPath", "outputPattern", "fps"],
89
+ properties: {
90
+ inputPath: { type: "string" },
91
+ outputPattern: { type: "string", description: "Workspace path containing a printf counter such as frames/frame-%04d.jpg." },
92
+ fps: { type: "number", exclusiveMinimum: 0, maximum: 60 },
93
+ startSeconds: { type: "number", minimum: 0 },
94
+ durationSeconds: { type: "number", exclusiveMinimum: 0 },
95
+ quality: { type: "integer", minimum: 2, maximum: 31 },
96
+ previewFrames: {
97
+ type: "integer",
98
+ minimum: 0,
99
+ maximum: 12,
100
+ description: "Explicit opt-in: return this many evenly sampled frames as model-visible images. Defaults to 0. Never use this as a silent substitute for native video understanding."
101
+ }
102
+ },
103
+ additionalProperties: false
104
+ }
105
+ },
106
+ {
107
+ name: "reframe_vertical",
108
+ description: "Create a 9:16-style vertical crop with a controlled horizontal focus.",
109
+ inputSchema: {
110
+ type: "object",
111
+ required: ["inputPath", "outputPath"],
112
+ properties: {
113
+ inputPath: { type: "string" },
114
+ outputPath: { type: "string" },
115
+ width: { type: "integer", minimum: 16, maximum: 2160 },
116
+ height: { type: "integer", minimum: 16, maximum: 3840 },
117
+ focus: { type: "string", enum: ["left", "center", "right"] },
118
+ crf: { type: "integer", minimum: 0, maximum: 51 }
119
+ },
120
+ additionalProperties: false
121
+ }
122
+ }
123
+ ];
124
+ //# sourceMappingURL=tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ export declare class MediaWorkspace {
2
+ readonly root: string;
3
+ constructor(root: string);
4
+ input(value: string, name?: string): string;
5
+ output(value: string, name?: string): string;
6
+ outputPattern(value: string): string;
7
+ private lexical;
8
+ private assertInside;
9
+ private existingAncestor;
10
+ }
11
+ //# sourceMappingURL=workspace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"AAaA,qBAAa,cAAc;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,IAAI,EAAE,MAAM;IAIxB,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,SAAc,GAAG,MAAM;IAQhD,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,SAAe,GAAG,MAAM;IAWlD,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAQpC,OAAO,CAAC,OAAO;IAOf,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,gBAAgB;CASzB"}
@@ -0,0 +1,59 @@
1
+ import { existsSync, mkdirSync, realpathSync } from "node:fs";
2
+ import { dirname, isAbsolute, relative, resolve } from "node:path";
3
+ import { matchingFramePaths } from "./frame-preview.js";
4
+ export class MediaWorkspace {
5
+ root;
6
+ constructor(root) {
7
+ this.root = realpathSync(resolve(root));
8
+ }
9
+ input(value, name = "inputPath") {
10
+ const candidate = this.lexical(value, name);
11
+ if (!existsSync(candidate))
12
+ throw new Error(`${name} does not exist: ${value}`);
13
+ const real = realpathSync(candidate);
14
+ this.assertInside(real, name);
15
+ return real;
16
+ }
17
+ output(value, name = "outputPath") {
18
+ const candidate = this.lexical(value, name);
19
+ if (existsSync(candidate))
20
+ this.assertInside(realpathSync(candidate), name);
21
+ const parent = dirname(candidate);
22
+ const existingAncestor = this.existingAncestor(parent);
23
+ this.assertInside(realpathSync(existingAncestor), name);
24
+ mkdirSync(parent, { recursive: true });
25
+ this.assertInside(realpathSync(parent), name);
26
+ return candidate;
27
+ }
28
+ outputPattern(value) {
29
+ const pattern = this.output(value, "outputPattern");
30
+ for (const existingPath of matchingFramePaths(pattern)) {
31
+ this.assertInside(realpathSync(existingPath), "outputPattern");
32
+ }
33
+ return pattern;
34
+ }
35
+ lexical(value, name) {
36
+ if (!value.trim())
37
+ throw new Error(`${name} must be a non-empty path.`);
38
+ const candidate = resolve(this.root, value);
39
+ this.assertInside(candidate, name);
40
+ return candidate;
41
+ }
42
+ assertInside(candidate, name) {
43
+ const relation = relative(this.root, candidate);
44
+ if (relation.startsWith("..") || isAbsolute(relation)) {
45
+ throw new Error(`${name} must stay inside ${this.root}.`);
46
+ }
47
+ }
48
+ existingAncestor(value) {
49
+ let candidate = value;
50
+ while (!existsSync(candidate)) {
51
+ const parent = dirname(candidate);
52
+ if (parent === candidate)
53
+ break;
54
+ candidate = parent;
55
+ }
56
+ return candidate;
57
+ }
58
+ }
59
+ //# sourceMappingURL=workspace.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace.js","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@assemblyline-agents/ffmpeg",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": {
7
+ "types": "./dist/index.d.ts",
8
+ "default": "./dist/index.js"
9
+ }
10
+ },
11
+ "dependencies": {
12
+ "@modelcontextprotocol/sdk": "^1.25.2",
13
+ "@assemblyline-agents/core": "0.1.0"
14
+ },
15
+ "description": "Official OpenEve workspace-scoped FFmpeg connection plugin.",
16
+ "files": [
17
+ "dist",
18
+ "skills"
19
+ ],
20
+ "engines": {
21
+ "node": ">=22.19.0"
22
+ },
23
+ "license": "MIT",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/jasonbadeaux/openeve.git",
27
+ "directory": "packages/ffmpeg"
28
+ },
29
+ "bugs": {
30
+ "url": "https://github.com/jasonbadeaux/openeve/issues"
31
+ },
32
+ "homepage": "https://github.com/jasonbadeaux/openeve#readme",
33
+ "author": "OpenEve contributors",
34
+ "keywords": [
35
+ "openeve",
36
+ "ai",
37
+ "agents",
38
+ "ffmpeg",
39
+ "video",
40
+ "audio",
41
+ "media",
42
+ "plugin"
43
+ ],
44
+ "publishConfig": {
45
+ "access": "public"
46
+ },
47
+ "scripts": {
48
+ "build": "tsc -p tsconfig.json",
49
+ "check": "tsc -p tsconfig.json --noEmit"
50
+ }
51
+ }
@@ -0,0 +1,35 @@
1
+ ---
2
+ name: ffmpeg
3
+ description: Inspect, trim, normalize, stitch, re-audio, extract, and vertically reframe media with typed FFmpeg tools.
4
+ ---
5
+ # FFmpeg
6
+
7
+ Use `probe_media` before editing unfamiliar media. Record the duration, video
8
+ dimensions, frame rate, codecs, audio layout, and rotation metadata so edits
9
+ are based on the actual streams.
10
+
11
+ For a social recut:
12
+
13
+ 1. Build a paper edit with source paths and exact in/out seconds.
14
+ 2. Trim each selected beat to a new clip.
15
+ 3. Normalize clips from different sources with `transcode_media`.
16
+ 4. Stitch normalized clips in narrative order with `concat_media`.
17
+ 5. Replace or add the intended audio track.
18
+ 6. Use `reframe_vertical` for a 9:16 delivery.
19
+
20
+ Prefer native video understanding for review. If the selected model cannot
21
+ process video natively, say so and stop; do not silently approximate the video
22
+ with sampled frames. Call `extract_frames` with an explicit `previewFrames`
23
+ value only when the user specifically requests or approves a frame-based
24
+ review. Its default is zero, so ordinary frame extraction returns paths only.
25
+
26
+ Prefer short shots with a clear visual purpose. Keep the hook readable in the
27
+ first seconds, preserve speech boundaries, and avoid jump cuts that clip words
28
+ or gestures. Do not claim to have watched a video when only still frames were
29
+ provided, and do not treat sampled frames as equivalent to temporal review.
30
+
31
+ Every editing tool writes files and requires the connection's configured write
32
+ authority. Never attempt to escape the media workspace or work around the typed
33
+ operations with arbitrary FFmpeg flags. Do not remove watermarks, attribution,
34
+ or rights-management signals, and only transform media the user is authorized
35
+ to use.