{
  "markdown": "![ffmpeg-render-pro](https://raw.githubusercontent.com/beeswaxpat/ffmpeg-render-pro/main/assets/banner.svg)\n\n# ffmpeg-render-pro\n\n[![npm version](https://img.shields.io/npm/v/ffmpeg-render-pro.svg)](https://www.npmjs.com/package/ffmpeg-render-pro)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n[![Platform: Cross-platform](https://img.shields.io/badge/Platform-Win%20%7C%20Mac%20%7C%20Linux-brightgreen)](https://github.com/beeswaxpat/ffmpeg-render-pro)\n[![Node.js](https://img.shields.io/badge/Node.js-%3E%3D18-339933?logo=node.js&logoColor=white)](https://nodejs.org/)\n[![MCP Server](https://img.shields.io/badge/MCP-Server-purple)](https://modelcontextprotocol.io)\n\nRender video from code, in parallel. You write one function that paints a frame; ffmpeg-render-pro splits the frame range across worker threads, encodes one MP4 segment per worker, joins the segments with stream copy (no re-encode), and shows a live dashboard in your browser while it runs. It also detects GPU encoders, grades color, merges audio, and ships as a CLI, a Node library, an MCP server for AI agents, and a Claude Code skill.\n\nBuilt by [Beeswax Pat](https://github.com/beeswaxpat). Free and open source.\n\n## Start here\n\nThree commands. You need Node.js 18 or newer and [ffmpeg](https://ffmpeg.org/download.html) on your PATH.\n\n```bash\n# 1. Prove the setup works: a 5 second test render. The dashboard opens in your browser.\nnpx ffmpeg-render-pro benchmark\n\n# 2. Write a starter worker script into the current folder.\nnpx ffmpeg-render-pro init my-worker.js\n\n# 3. Render it. Output lands in output.mp4.\nnpx ffmpeg-render-pro render my-worker.js --duration=5\n```\n\nOpen `my-worker.js`. The only function you need to change is `renderFrame(frameNum, buffer)`: fill the buffer with your pixels (B, G, R, A, one row after another) and everything else is already done. Derive any randomness from the `seed` it receives and parallel output stays identical to a sequential render.\n\nInstall it globally if you would rather not type `npx`:\n\n```bash\nnpm install -g ffmpeg-render-pro\n```\n\n## Using an ffmpeg that is not on PATH\n\n```bash\nFFMPEG_RENDER_PRO_FFMPEG=/opt/ffmpeg/bin/ffmpeg     # ffmpeg binary\nFFMPEG_RENDER_PRO_FFPROBE=/opt/ffmpeg/bin/ffprobe   # optional; the sibling ffprobe is found automatically\nFFMPEG_RENDER_PRO_CACHE_DIR=~/.ffmpeg-render-pro    # optional; where GPU probe results are cached\n```\n\nThe variables are read at call time, so a long-running process such as the MCP server picks up changes without a restart.\n\n## CLI\n\n```bash\nffmpeg-render-pro init [my-worker.js]   # write the starter worker (--force overwrites)\nffmpeg-render-pro benchmark             # 5 second test render with the bundled worker\nffmpeg-render-pro render <worker.js>    # render with your worker\nffmpeg-render-pro info                  # cores, RAM, recommended workers, ffmpeg version, GPU\nffmpeg-render-pro detect-gpu            # probe hardware encoders (--cpu / --gpu force a mode)\nffmpeg-render-pro version\n```\n\nRender and benchmark flags: `--width=1920 --height=1080` (must be even), `--fps=60`, `--duration=60` (fractions allowed), `--output=out.mp4`, `--workers=N`, `--max-workers=8`, `--seed=42`, `--title=\"...\"`, `--crf=20` (0-51, lower is higher quality), `--encoder-preset=fast` (any x264 preset). Dashboard flags: `--no-dashboard`, `--no-open`, `--port=8080`, `--linger-ms=30000` (`0` exits as soon as the render finishes). Run `ffmpeg-render-pro` with no arguments for the full list.\n\nAn unknown flag warns and continues. A value that does not parse, such as `--fps=abc`, exits 1 instead of rendering at the default.\n\nInstalled binaries: `ffmpeg-render-pro` (this CLI) and `ffmpeg-render-pro-mcp` (the MCP server). The older `ffmpeg-render-mcp` name still works so existing MCP configs never break.\n\n## How a render works\n\n1. `renderParallel` checks ffmpeg, validates the resolution, and picks a worker count from your CPU cores and RAM (never more workers than frames).\n2. It starts the dashboard server on `127.0.0.1` and opens your browser.\n3. Each worker thread runs your script with a frame range in `workerData`, pipes raw BGRA frames into its own ffmpeg process, and writes one MP4 segment.\n4. Segments are joined with the concat demuxer and `-c copy`, which takes seconds regardless of length.\n5. Temp files are removed. A failed worker's range is retried once before the render fails.\n\n## Your worker\n\nA worker is a Node script that runs in a `worker_threads` thread. `init` gives you one where only `renderFrame` needs editing; `examples/basic-worker.js` in the installed package is a larger reference with a particle system and seeded RNG.\n\nFields the renderer injects through `workerData`:\n\n| Field | Meaning |\n|---|---|\n| `width`, `height`, `fps` | Frame size and rate |\n| `seed` | Derive every random value from this |\n| `startFrame`, `endFrame` | Render exactly `[startFrame, endFrame)` |\n| `segmentPath` | Write this worker's MP4 here |\n| `workerId` | Include it in every message you post |\n| `totalFrames`, `duration` | Whole-video totals, for global effects such as a progress bar |\n| anything in `renderParallel({ workerData })` | Your own extra keys (the bundled workers honor `codecArgs`) |\n\nMessages the worker posts with `parentPort.postMessage`:\n\n| Message | When | Fields |\n|---|---|---|\n| `{ type: 'progress' }` | periodically | `workerId`, `pct`, `fps`, `frame`, `eta` |\n| `{ type: 'fast-forward-start' }` | optional, before replaying state to reach `startFrame` | `workerId`, `frames` |\n| `{ type: 'done' }` | once, after the segment is fully written | `workerId` |\n| `{ type: 'error' }` | on failure, never followed by `done` | `workerId`, `error` |\n\nEvery worker must encode with the same codec, resolution, framerate, and pixel format, because the segments are stream-copied together.\n\n## Library\n\n```js\nconst {\n  renderParallel,       // the render engine\n  createEncoder,        // pipe raw frames into ffmpeg with backpressure\n  detectGPU,            // hardware encoder discovery, cached 7 days\n  getConfig,            // worker count and codec choice for a resolution\n  computeTotalFrames,   // float-safe fps x duration\n  concatSegments,       // stream-copy join (validates inputs by default)\n  colorGrade,           // presets or a custom -vf chain\n  mergeAudio,           // add a soundtrack without re-encoding video\n  startDashboard,       // the local progress server\n  ProgressTracker,      // per-worker progress plus dashboard JSON\n  saveCheckpoint, loadCheckpoint, restoreCheckpoint, generateCheckpoints,\n  getEncoderIO,         // encoder recipe as { inputArgs, filter, outputArgs }\n  getCodecArgs,         // the same recipe as one flat array\n  ffmpegBin, ffprobeBin // resolved binaries, env-var aware\n} = require('ffmpeg-render-pro');\n```\n\n### renderParallel(options)\n\n```js\nconst controller = new AbortController();\n\nconst result = await renderParallel({\n  workerScript: './my-worker.js',   // required\n  outputPath: './output.mp4',       // required\n  width: 1920, height: 1080,        // even numbers, up to 7680x4320\n  fps: 60, duration: 60,\n  seed: 42,\n  title: 'My Render',               // shown in the dashboard\n  workerCount: undefined,           // exact count; omit to auto-detect\n  maxWorkers: 8,                    // cap for auto-detect\n  dashboard: true, autoOpen: true, dashboardPort: 8080,\n  dashboardLingerMs: 0,             // 0 resolves as soon as the render ends (the CLI keeps it up 30s)\n  quiet: false,                     // true keeps stdout byte-clean; status goes to stderr\n  signal: controller.signal,        // abort() stops workers and removes temp files\n  workerData: {},                   // extra keys for your worker\n});\n// result: { outputPath, elapsed, totalFrames, avgFps }\n```\n\nAbort rejects with an error whose `name` is `'AbortError'`. In library use set `dashboardLingerMs: 0` so the call returns without holding the process open. Set `FFMPEG_RENDER_PRO_DEBUG=1` for full stack traces from the CLI.\n\n### Post-processing\n\n```js\n// Color grade with a preset (noir, warm, cool, cinematic, vintage) or a custom -vf chain\nawait colorGrade({ inputPath: 'raw.mp4', outputPath: 'graded.mp4', preset: 'cinematic' });\nawait colorGrade({ inputPath: 'raw.mp4', outputPath: 'graded.mp4', filter: 'eq=contrast=1.08:saturation=0.9', crf: 18 });\nawait colorGrade({ inputPath: 'final.mp4', outputPath: 'graded.mp4', preset: 'noir', keepAudio: true }); // default strips audio\n\n// Merge audio: video is stream-copied, audio becomes AAC. loop and normalize (loudnorm) are optional.\nawait mergeAudio({ videoPath: 'graded.mp4', audioPath: 'track.mp3', outputPath: 'final.mp4', bitrate: 320, loop: true, normalize: true });\n\n// Join same-codec, same-size videos with stream copy. Inputs are probed with ffprobe first; pass { validate: false } to skip.\nawait concatSegments(['part-000.mp4', 'part-001.mp4'], 'joined.mp4');\n```\n\n`colorGrade` accepts any encoder name in `codec`; encoders that need their own filter (VA-API) get it merged into the grade chain automatically.\n\n### Checkpoints for long renders\n\nFor multi-hour renders, snapshot your simulation state every N frames once, so each worker replays only the frames since the nearest snapshot instead of starting from frame 0.\n\n```js\ngenerateCheckpoints({ systems, totalFrames: 432000, fps: 60, checkpointDir: './.checkpoints', interval: 60000 });\n\n// inside a worker\nconst cp = loadCheckpoint('./.checkpoints', startFrame);\nif (cp) {\n  const resumeFrame = restoreCheckpoint(cp, systems);\n  // fast-forward from resumeFrame to startFrame, then render\n}\n```\n\n`systems` is an object of named modules with `getState()`, `setState()`, and `update(dt)`. A checkpoint labeled frame F holds exactly F updates. `_frame` and `_timestamp` are reserved keys.\n\n## MCP server (for AI agents)\n\nSeven tools over stdio, usable from Claude Code, Claude Desktop, or any MCP client.\n\n```bash\n# Claude Code, no install needed\nclaude mcp add --transport stdio ffmpeg-render-pro -- npx --yes --package=ffmpeg-render-pro ffmpeg-render-pro-mcp\n\n# Claude Code, after npm install -g ffmpeg-render-pro\nclaude mcp add --transport stdio ffmpeg-render-pro -- ffmpeg-render-pro-mcp\n```\n\nClaude Desktop (`claude_desktop_config.json`):\n\n```json\n{\n  \"mcpServers\": {\n    \"ffmpeg-render-pro\": {\n      \"command\": \"npx\",\n      \"args\": [\"--yes\", \"--package=ffmpeg-render-pro\", \"ffmpeg-render-pro-mcp\"]\n    }\n  }\n}\n```\n\n| Tool | What it does |\n|---|---|\n| `get_worker_template` | Returns the worker contract, the starter worker source, and paths to both bundled workers. Start here. |\n| `render_video` | Parallel render from a worker script, with progress notifications and cancellation |\n| `detect_gpu` | Probe hardware encoders (NVENC, VideoToolbox, AMF, VA-API, QSV) |\n| `system_info` | Cores, RAM, recommended worker count, ffmpeg version |\n| `color_grade` | Presets or a custom filter |\n| `merge_audio` | Add a soundtrack, video stream-copied |\n| `concat_videos` | Stream-copy join, inputs validated by default |\n\nThe agent recipe: call `get_worker_template`, copy `starterSource` to a file and replace `renderFrame`, then call `render_video` with that file as `worker_script` (`dashboard: false`, `auto_open: false` for headless runs). To render without writing code, pass the returned `starterPath` or `templatePath` straight to `render_video`. Post-process with `color_grade`, `merge_audio`, and `concat_videos`.\n\nEvery tool declares an `outputSchema` and returns `structuredContent`, so parse JSON instead of text. Writers overwrite `output_path`. `render_video` defaults to 30 fps (the CLI defaults to 60), emits `notifications/progress` every 2 seconds when the client sends a `progressToken` (turn on `resetTimeoutOnProgress` for long renders), and stops all workers on client cancellation. stdout carries only JSON-RPC frames. Missing ffmpeg returns an error that names the install page and the env var.\n\nThe tarball also ships `llms.txt` at the package root and a Claude Code skill:\n\n```bash\n# from a global install (macOS / Linux)\ncp -r \"$(npm root -g)/ffmpeg-render-pro/.claude/skills/ffmpeg-render-pipeline\" ~/.claude/skills/\n# from a repo clone (Windows)\nxcopy .claude\\skills\\ffmpeg-render-pipeline %USERPROFILE%\\.claude\\skills\\ffmpeg-render-pipeline\\ /E /I\n```\n\n## NVENC quick reference\n\nThe renderer detects NVENC by itself. For one-off encodes outside it:\n\n```bash\n# confirm the encoder exists before relying on it\nffmpeg -y -f lavfi -i testsrc=size=256x256:rate=30:d=1 -c:v h264_nvenc -cq 23 probe.mp4\n\n# encode: presets p1 (fastest) to p7 (best); -cq works like CRF, lower is better\nffmpeg -i in.mp4 -c:v h264_nvenc -preset p5 -cq 21 -pix_fmt yuv420p -c:a aac -b:a 192k -movflags +faststart out.mp4\n```\n\n`h264_nvenc` rejects very narrow frames (145px minimum on a Turing card) by writing a zero-byte file and exiting, so keep probes at 256x256. Both commands come from the [ffmpeg Render Cookbook](https://store.chronoverify.com/l/ffmpeg-render-cookbook?utm_source=npm&utm_medium=npm&utm_campaign=ffmpeg-render-cookbook) ($12): 29 recipes, each run on ffmpeg 8.0.1 before publication.\n\n## Security notes\n\n- Releases are published to npm by GitHub Actions through npm trusted publishing (OIDC). There is no publish token, and every version from 1.5.2 on carries a provenance attestation that ties the tarball on npm to the exact commit and workflow run that built it (see the Provenance panel on the npm page).\n- The dashboard binds to `127.0.0.1` only and loads nothing from the network. No telemetry.\n- `render_video` and `renderParallel` execute the worker script you name with the privileges of the current user. Only run workers you wrote or trust.\n- The MCP server reads and writes files anywhere the current user can. Run it with a trusted agent, and consider restricting its working directory when prompts are untrusted.\n- A custom `filter` string is file access: ffmpeg filters such as `movie=` and `subtitles=` read local files. Treat filter input the way you treat a file path.\n- Concat list files are written under `os.tmpdir()`; output paths are written exactly where you point them.\n\n## Tests\n\n`npm test` runs 12 zero-dependency suites (255 checks): unit, smoke, a real MCP session over stdio with a byte audit of stdout, and end-to-end renders verified with ffprobe and `framemd5`. It skips the render suites cleanly on machines without ffmpeg. CI runs the same on Ubuntu, Windows, and macOS against Node 18, 20, 22, and 24.\n\n## Changelog and license\n\nSee [CHANGELOG.md](CHANGELOG.md). MIT.\n",
  "bytes": 14529,
  "sha": "c2a756bebda4633d67760c3581d151e8d6b321edbe86d3e2c3aaccf0bfed4c71",
  "repo_slug": "beeswaxpat/ffmpeg-render-pro",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_beeswaxpat_ffmpeg_render_pro_47d85a1f/readme"
}