← Files Cecil-IA Labs FFmpegARCHIVED FILE
docs/development/runtime.md
4.09 KB · Sep 30, 2026 · 23:17 UTC
# Milestone 2 — FFmpeg Core Runtime
## Status
Implemented for `v0.1.0-alpha.2`.
## Purpose
Milestone 2 creates the single execution boundary used by future media-domain commands. No video, audio, conversion, composition, repair, diagnostic, or streaming command is allowed to invoke FFmpeg or FFprobe directly.
```text
CLI / Skill scripts / package API
│
▼
domain operation
│
▼
runFFmpeg / runFFprobe
│
▼
runCommand()
│
▼
node:child_process.spawn
│
▼
ffmpeg / ffprobe
```
## Runtime modules
```text
src/core/
├── binary-resolver.ts
├── cancellation.ts
├── capabilities.ts
├── command-result.ts
├── errors.ts
├── ffmpeg-runner.ts
├── ffprobe-runner.ts
├── progress.ts
├── temp-files.ts
└── index.ts
```
## Binary resolution
Resolution order is deterministic:
1. explicit API/CLI override (`--ffmpeg-path`, `--ffprobe-path`);
2. `FFMPEG_PATH` / `FFPROBE_PATH` environment override;
3. `PATH` lookup.
Resolved values are verified as executable files. No shell `which`, `where`, `command -v`, or equivalent is used.
## Execution safety
`runCommand()` is the only production source module allowed to import `node:child_process`.
All invocations use:
```ts
spawn(binary, args, {
shell: false,
...
});
```
The runtime never uses:
```text
eval
exec
execFile
shell interpolation
concatenated command strings for execution
```
A shell-like command string can be rendered for diagnostics, but it is display-only and is never executed.
## Structured execution result
```ts
interface CommandExecution {
binary: string;
args: string[];
cwd?: string;
exitCode: number | null;
signal?: NodeJS.Signals;
durationMs: number;
stdout?: string;
stderr?: string;
executed: boolean;
stdoutTruncated: boolean;
stderrTruncated: boolean;
}
```
Non-zero exit codes are intentionally returned by the generic runtime. The FFmpeg and FFprobe adapters classify them as:
- `E_FFMPEG_EXECUTION_FAILED`;
- `E_PROBE_FAILED`.
This keeps process mechanics separate from media-domain semantics.
## Output capture
stdout and stderr are captured incrementally with bounded tail buffers.
Default per-stream capture limit:
```text
4 MiB
```
If a stream exceeds the limit, the oldest bytes are discarded and the result marks:
```text
stdoutTruncated: true
stderrTruncated: true
```
Callers may additionally subscribe to chunks or tee them to the terminal without disabling capture.
## Dry run
`dryRun: true` performs binary resolution and invocation construction but does not spawn the child process.
The returned result contains:
```json
{
"exitCode": 0,
"executed": false
}
```
This is the execution primitive behind the public CLI `--dry-run` contract.
## Cancellation
Every runner accepts an `AbortSignal`.
On abort:
1. send `SIGINT` to the child (`SIGTERM` on Windows);
2. wait a configurable grace interval (default 1500 ms);
3. use `SIGKILL` if the process remains alive;
4. reject with `E_ABORTED`.
CLI entrypoints can bridge operating-system signals through `createProcessSignalController()`.
## Temporary workspace
`TemporaryWorkspace` creates isolated directories under the operating system temp directory and rejects path traversal outside the workspace.
By default it cleans recursively. `keep: true` implements the future `--keep-temp` behavior.
## Progress parser
`FFmpegProgressParser` incrementally parses machine-readable output generated by:
```text
-progress pipe:N
```
The parser is transport-independent and can consume arbitrary chunks without assuming that one chunk equals one line.
## Capability foundation
Milestone 2 includes version-line parsing and shared capability types. Actual capability discovery (`-encoders`, `-decoders`, `-filters`, hardware acceleration) belongs to Milestone 3.
## Dependency decision
Milestone 1 provisionally included `execa`. Milestone 2 replaced it with native `spawn` to provide a smaller and more auditable process boundary with explicit streaming and cancellation control. See ADR 0002.
SHA-256: 65f453d1fb5013eef0369655b5b21c7a1ee7be4e72b3627bfb189fd78476a54e