# makevideo [![GitHub release](https://img.shields.io/github/v/release/random00000000/makevideo)](https://github.com/random00000000/makevideo/releases) [![ci](https://github.com/random00000000/makevideo/actions/workflows/ci.yml/badge.svg)](https://github.com/random00000000/makevideo/actions/workflows/ci.yml) [![MCP Registry](https://img.shields.io/badge/MCP_Registry-io.github.random00000000%2Fmakevideo-5ad1ff)](https://registry.modelcontextprotocol.io/v0/servers?search=makevideo) [![docs](https://img.shields.io/badge/docs-guides-ffb547)](https://random00000000.github.io/makevideo/) [![license: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE) [![Claude Code plugin](https://img.shields.io/badge/Claude%20Code-plugin-d97757)](#use-it-from-claude-code-plugin) ![CPU only](https://img.shields.io/badge/GPU-not%20needed-success) ![no API key](https://img.shields.io/badge/API%20key-not%20needed-success) **Fast, standard video generation for AI agents.** One JSON script in, one finished MP4 out, rendered the same way every time. You write scenes, each with a line to say. `makevideo` voices them with one consistent neural voice, times every scene to the real audio, adds captions, and renders an MP4 with a bundled ffmpeg. It runs on the CPU only: no browser, no GPU, no API key and no cloud. [![poster](docs/showcase.jpg)](docs/showcase.mp4) ▶ **[Your agent can make videos now](docs/your-agent-can-make-videos.mp4)** (1:39, written, voiced and rendered by an agent from [one JSON file](examples/agents-make-videos.json)) · ▶ **[Watch the 87-second showcase](docs/showcase.mp4)**, rendered from [`examples/showcase.json`](examples/showcase.json) on a laptop CPU, faster than it plays. TikTok-style demo **Short-form too:** `"format": "shorts"` gives 1080×1920 with big word-by-word captions. ▶ [Watch the TikTok-style demo](docs/tiktok.mp4) ([`examples/tiktok.json`](examples/tiktok.json)): 23 s of video rendered in 15 s, plus an auto-exported GIF. **More examples, all made with makevideo:** ▶ [59-second demo](docs/makevideo-reddit.mp4) ([spec](examples/reddit-demo.md)) · ▶ [The house voice](examples/house-voice.md) · ▶ [Distribution recap](examples/recap-distribution.md) · ▶ [What's new in 0.4: closer to every agent](docs/whats-new-0.4.mp4) ([spec](examples/whats-new-0.4.md)) · ▶ [What's new in 0.3: speed](docs/whats-new-0.3.mp4) ([spec](examples/whats-new-0.3.md)) · ▶ [What's new in 0.2](docs/whats-new-0.2.mp4) (made from [one Markdown file](examples/whats-new-0.2.md)) · ▶ [Why use makevideo instead of building from scratch](docs/why-makevideo.mp4) ([spec](examples/why-makevideo.json)) · ▶ [Changelog video for v0.1](docs/changelog-0.1.mp4) ([spec](examples/changelog-0.1.json)): changelog and recap videos are a core use case. **What it does:** narrated slides · **automatic transitions** (push, slide, zoom, wipe) · kinetic **statements** · animated **charts** · **split** media + text · **write Markdown, get a video** · screenshots · gameplay/video clips · **GIFs in and out** · code · stats · quotes · before/after · **TikTok/Reels/Shorts captions** · custom fonts · background music · **screen recording** for trailers · `.srt` captions · poster frames.
```bash npm i github:random00000000/makevideo npx makevideo init # writes video.json npx makevideo video.json # → video.mp4 + video.srt + video.report.json + video.jpg npx makevideo video.json --fast # same, about 3x faster on a first render ``` ```js import { video } from 'makevideo'; await video({ title: 'Patch notes 1.2', scenes: [ { type: 'title', title: 'Patch 1.2', subtitle: 'Siege update', say: 'Here is what is new in patch one point two.' }, { type: 'clip', src: 'siege.mp4', title: 'Rams breach gates', say: 'Rams can now breach wooden gates.' }, { type: 'bullets', title: 'Also new', items: ['Faster pathing', 'New HUD'], say: 'Also new. Pathing is faster. And the HUD is redesigned.' }, { type: 'decisions', items: ['Ship Friday?'], say: 'One decision for you. Do we ship on Friday?' }, ], }); ``` --- ## Why Agents get asked for videos all the time, and every agent builds them differently. Each video ends up with a new pipeline, a different voice, no narration unless someone asks for it, and a render that can take ten minutes. `makevideo` makes that one standard: | | ad-hoc agent videos | `makevideo` | |---|---|---| | Format | a new script every time | one JSON spec ([schema](schema.json)) | | Voice | random TTS, sometimes none | **Kokoro-82M**, same voice every time (`af_heart`) | | Narration | must be requested | **on by default**: every scene has `say` | | Timing | guessed | measured from the generated audio, per sentence | | Captions | rarely | burned in, plus an `.srt` sidecar | | Speed | minutes | **faster than real time on a CPU** (see [Performance](#performance)) | | Verification | watch the whole thing | `makevideo preview` makes a contact sheet in about 1 s | ## Premium by default The goal is that an agent's *first* attempt looks finished. Without any extra config you get: - **Transitions that blend real frames.** Every scene flows from the previous scene's actual last frame. `auto` picks push, zoom, wipe or fade based on what is cut to what. Override with `"transition"` globally or per scene. - **Typography that ships with the package.** Inter, Montserrat and JetBrains Mono (OFL) are bundled, so a bare cloud Linux box renders exactly like your laptop. - **Depth:** key-light gradient, vignette and soft panel shadows on every theme. - **Speech cleanup:** write naturally. `42%`, `v1.2`, `$3M`, `4.6x`, `35 s`, `e.g.` and `->` are all spoken correctly, and captions keep what you wrote. - **Lint:** `check` flags walls of text, too many bullets, unsynced lists, overlong statements, dense scenes in portrait and repetitive pacing, and says how to fix each one. ## Write Markdown, get a video ```md --- theme: midnight brand: My Game --- # Patch 1.2 The siege update > Here is what changed in patch one point two. ## Rams breach gates ![](captures/ram.mp4) - Wooden gates break in 3 hits - Stone gates still hold > Rams now breach wooden gates. Three hits and they are down. Stone gates still hold. ## Load time - Before: 12 s - Now: 3 s ## Decisions - Ship Friday? ``` ```bash npx makevideo patch-notes.md ``` Each `#`/`##` heading starts a scene, and its body decides the type: a list gives bullets/steps/decisions, `label: number` items give a chart, an image or video gives image/clip (or split, when there is text too), a code fence gives code, `**42%** label` gives a stat, and a paragraph gives a statement. Lines starting with `>` are the narration. When a scene has none, its visible text is read aloud, because narration is the default. `` sets scene options. See [`examples/whats-new-0.2.md`](examples/whats-new-0.2.md); the 0.2 release video is that one file. ## Use it from Claude Code (plugin) ``` /plugin marketplace add random00000000/makevideo /plugin install makevideo@makevideo ``` Then just say **"make a video about …"**. Claude writes the spec, previews it, renders it, and hands you the MP4. The plugin bundles both the skill and the MCP server. ## Use it from any MCP client Claude Desktop, Cursor, VS Code, Codex, Windsurf and other MCP clients: ```json { "mcpServers": { "makevideo": { "command": "npx", "args": ["-y", "github:random00000000/makevideo", "mcp"] } } } ``` It's listed in the [official MCP Registry](https://registry.modelcontextprotocol.io/v0/servers?search=makevideo) as `io.github.random00000000/makevideo`, as a Docker image (`ghcr.io/random00000000/makevideo`). Without Node, run `docker run -i --rm -v "$PWD:/work" -w /work ghcr.io/random00000000/makevideo mcp`. The tools are `make_video` (Markdown or JSON in, MP4 out, with progress notifications), `preview_video` (returns the contact sheet **as an image** the model can look at), `check_video` and `video_reference`. See the [MCP guide](https://random00000000.github.io/makevideo/guides/mcp-video-server.html). ## Teach every agent in a repo ```bash npx -y github:random00000000/makevideo agents --all # AGENTS.md, CLAUDE.md, Cursor rules, Copilot instructions npx -y github:random00000000/makevideo agents --global # this machine: Claude Code skill + Codex AGENTS.md ``` This writes a short, idempotent "use makevideo for videos" block, so every agent that works in the project reaches for the same tool. **Guides:** [agents](https://random00000000.github.io/makevideo/guides/make-videos-with-ai-agents.html) · [CPU text-to-video](https://random00000000.github.io/makevideo/guides/text-to-video-on-cpu.html) · [Markdown to video](https://random00000000.github.io/makevideo/guides/markdown-to-video.html) · [TikTok/Shorts](https://random00000000.github.io/makevideo/guides/tiktok-shorts-captions.html) · [game trailers](https://random00000000.github.io/makevideo/guides/game-trailer-from-gameplay.html) · [MCP](https://random00000000.github.io/makevideo/guides/mcp-video-server.html) · [vs Remotion/MoviePy/ffmpeg](https://random00000000.github.io/makevideo/guides/makevideo-vs-alternatives.html) ## For AI agents (start here) If you are an LLM agent and the user asked for a **video, explainer, walkthrough, devlog, demo, trailer, recap, patch notes or presentation with voice-over**, use this library: 1. Write a spec: a `scenes` array where every scene has a `type` and a `say` string (the narration). 2. `npx makevideo check spec.json --json` validates it and estimates the length (instant). 3. `npx makevideo preview spec.json`: open the PNG contact sheet and check the layout (about 1 s, no audio). 4. `npx makevideo spec.json --json` renders and prints `{ output, srt, report, poster, duration, seconds }`. 5. Hand the user the `.mp4`. End informational videos with a `decisions` scene listing what only the user can decide. Rules of thumb: about 1–3 sentences per scene. Write `say` the way it should be *spoken*: spell out numbers and symbols ("forty two percent", not "42%") and use `pronounce` for names. Put on-screen text in `title`/`items`; it does not have to match the narration word for word. Reuse the same `voice` and `theme` across a project so all its videos match. There is also [`AGENTS.md`](AGENTS.md), [`llms.txt`](llms.txt), a JSON Schema at [`schema.json`](schema.json), and a drop-in **Claude Code skill** at [`skills/makevideo/SKILL.md`](skills/makevideo/SKILL.md). Copy that folder to `~/.claude/skills/` and Claude will reach for `makevideo` whenever you ask for a video. ## Spec ```jsonc { "title": "My video", // used for default output name "output": "out/my-video.mp4", // default: .mp4 (relative to spec file) "format": "landscape", // landscape 1920x1080 | portrait/shorts 1080x1920 | square | 720p "theme": "midnight", // midnight | forge | paper | terminal | { ...color overrides } "voice": "af_heart", // the default house voice (warm female); "fast" = ~10x real-time Piper voice (male); false = silent "speed": 1, // speech rate "brand": "My Project", // small label top-right "captions": true, // burned-in captions (an .srt is always written) "progressBar": true, // chapter bar along the bottom "music": "bed.mp3", // optional background music, looped, faded out "musicVolume": 0.12, "pronounce": { "Hacksilver": "Hack silver" }, // TTS-only substitutions (merged over built-ins for JSON, MP4, GPU, ...); captions keep originals "quality": "standard", // draft | standard | high "transition": "auto", // auto | push | slide | zoom | wipe | fade | dip | cut (also per scene) "captionStyle": "classic", // classic (subtitle box) | bold (TikTok word-by-word, default for portrait) | none "font": "fonts/Anton.ttf", // display font: a .ttf/.otf path or an installed family name "gif": { "width": 480, "fps": 12, "duration": 8 }, // also export an animated GIF (true = defaults) "scenes": [ /* see below */ ] } ``` Every scene accepts `say` (narration) and an optional `duration` (minimum seconds; required if there is no `say`). Scenes are as long as their narration plus a short pad. | type | fields | notes | |---|---|---| | `title` | `title`, `subtitle?`, `kicker?` | opening card; `outro` is an alias for closers | | `bullets` | `title?`, `items[]`, `highlight?` | items appear **in sync with sentences**: N items + 1 intro sentence works best | | `steps` | `title?`, `items[]` | numbered bullets | | `decisions` | `title?`, `items[]` | numbered, for "what the user must decide" | | `image` | `src`, `title?`, `subtitle?`, `fit?`, `zoom?` | slow push-in; `fit: "contain"` keeps the whole image with a blurred backdrop | | `clip` | `src`, `start?`, `title?`, `subtitle?`, `fit?` | any mp4/webm/mov; cropped to fill, looped if short; original audio dropped | | `code` | `code`, `title?`, `highlight?` (`[3, [5,8]]`) | syntax-tinted, line numbers | | `stat` | `value`, `label?` | numbers count up (`"42%"`, `"$1.2"`, `"3x"`) | | `quote` | `text`, `by?` | | | `compare` | `title?`, `left: {title, items}`, `right: {title, items}` | stacks vertically in portrait | | `statement` | `text`, `kicker?`, `align?` | huge kinetic type; words pop in with the voice; `*stars*` mark accent words | | `chart` | `bars: [{label, value}]` (or `{label: value}`), `title?`, `unit?`, `prefix?`, `highlight?`, `note?` | bars grow as the voice reaches them; values count up | | `split` | `src` (image or video), `title?`, `items?` or `text?`, `side?` (`left`/`right`) | media beside synced points; video plays inside the frame | Scene `type` is inferred when omitted (`src` + `items`/`text` → split, `src` → image/clip, `bars` → chart, `items` → bullets, `code` → code, `value` → stat, `text` → statement). ## CLI ``` makevideo render (mp4 + srt + json report + jpg poster) makevideo check validate, estimate duration (instant) makevideo preview contact-sheet PNG of every scene (about 1 s) makevideo init [file] starter spec makevideo record --seconds 10 [--window "Game"] record screen/window for clip scenes makevideo agents [--all|--global] tell agents to use makevideo for videos makevideo mcp MCP server (stdio) makevideo setup pre-download voice models makevideo voices | themes | types | transitions | formats -o --draft --format --voice --theme --json ``` A `.js` spec can `export default { ... }` (or an async function returning the spec), which is handy for generating scenes from data. ## API ```js import { video, check, preview, VOICES, THEMES, SCENE_TYPES } from 'makevideo'; const r = await video(spec, { baseDir, onProgress: (e) => console.log(e.stage, e.message) }); // r = { output, srt, report, poster, duration, seconds, scenes: [{ index, type, title, start, duration, say }] } check(spec) // { ok, problems[], estimatedSeconds } await preview(spec) // { output: '...preview.png' } ``` Errors are written so an agent can act on them. They name the scene and include a working example: ``` makevideo: invalid spec - scenes[2] (clip) is missing "src". Example: {"type":"clip","src":"gameplay.mp4","start":2,...} - scenes[4] has no "say" (narration) and no "duration". Narration is the default in makevideo: ... ``` ## Game trailers & screen recordings ```bash npx makevideo record captures/siege.mp4 --seconds 12 --window "Hacksilver" # one window (Windows) npx makevideo record captures/menu.mp4 --seconds 8 # whole screen (Win/macOS/Linux) ``` ```js import { record, video } from 'makevideo'; await record({ output: 'captures/boss.mp4', seconds: 10, window: 'My Game' }); await video({ format: 'shorts', theme: 'forge', scenes: [ { type: 'clip', src: 'captures/boss.mp4', title: 'New boss', say: 'Meet the new boss.' }, ] }); ``` ## Performance `makevideo` is built to run on a CPU, because most agent sandboxes and cloud runners have no GPU. **Need it fast? Add `--fast`** (or `"voice": "fast"`). That switches to a Piper voice that speaks at about **10× real time on 2 CPU threads**. Kokoro, the default, is about 2× real time using every core. The fast voice sounds less natural, so it's best for drafts, long videos and fast iteration. ```bash npx makevideo video.md --fast # fastest: Piper voice + quick encode npx makevideo setup # once per machine/image: pre-download voice models ``` Measured with the same 74 s, 8-scene video ([`examples/whats-new-0.2.md`](examples/whats-new-0.2.md)) on an i7-10750H laptop (6 cores, no GPU), with the machine about 35% busy with other work. Both versions ran back to back: | | v0.2.0 | v0.3.0 | |---|---|---| | first render (no cached speech) | 119.9 s | **81.0 s** | | first render with `--fast` voice | n/a | **36.3 s** | | re-render (speech cached) | 41.0 s | **28.4 s** | How it gets there: - **Frame holding.** Slides are still most of the time. Only frames that can change are drawn. Each scene type knows exactly how long its entrance lasts, so a typical scene has about 30% "live" frames instead of 50%. Held frames are repeated inside ffmpeg after colour conversion, where they cost almost nothing. - **Render processes.** Scenes are drawn and encoded in a pool of worker processes (one canvas and one ffmpeg each), so drawing uses many cores instead of Node's one main thread. The workers start while speech is still being generated. - **Tuned speech.** Kokoro runs as 1–3 processes with ONNX threads pinned to cores/2/N, because processes that each grab every core fight each other. Idle ONNX threads sleep instead of spinning, leaving the CPU to the renderer. - **Overlapped pipeline.** Each scene renders as soon as its own narration exists. The poster, GIF and final mux run side by side. - **Caching.** Every sentence's audio is cached by (engine, voice, speed, text) in `~/.cache/makevideo` (override with `MAKEVIDEO_CACHE`). Re-renders after a layout tweak skip the voice entirely. - **Measured, not guessed.** Each of these came from a profile. Some ideas lost: film grain (pattern fills cost 80 ms per frame), transparent content layers (slower in Skia), and more x264 threads (2 per scene beats 4). Knobs: `--fast`, `quality: "draft"`, `format: "720p"`, `MAKEVIDEO_THREADS` (x264 threads per scene, default 2), `video(spec, { concurrency, ttsWorkers, ttsThreads })`, and `MAKEVIDEO_DEBUG=1` for per-scene profiles. ## How it works ``` spec ─► validate ─► split narration into sentences ─► Kokoro TTS pool (cached) │ per scene, as soon as its audio is ready ▼ measure audio → scene duration, caption cues, reveal times ▼ Skia canvas draws only frames that change ─► ffmpeg (loop holds, x264) ▼ concat segments + narration WAV (+ music) ─► .mp4 .srt .json .jpg ``` ## Requirements Node 18+. Everything else installs from npm: `ffmpeg-static` ships ffmpeg, `@napi-rs/canvas` ships prebuilt Skia, and `kokoro-js` runs the voice locally through ONNX Runtime. CI renders test videos on Linux, macOS and Windows (Node 20 and 22) on every push. ## Credits & license MIT. The voice is [Kokoro-82M](https://huggingface.co/hexgrad/Kokoro-82M) (Apache-2.0) via [kokoro-js](https://github.com/hexgrad/kokoro). Video is encoded with [FFmpeg](https://ffmpeg.org) (via ffmpeg-static). Keywords: AI video generator, text to video, make a video, narrated video, fast video generation, voiceover, TTS video, explainer video generator, programmatic video, Node.js video library, ffmpeg video from JSON, slideshow with narration, auto captions, subtitles, SRT, agent tools, Claude Code video, Codex video, LLM video generation, devlog video, patch notes video, gameplay trailer narration, Remotion alternative without a browser. --- --- name: makevideo description: Make MP4 videos (explainers, devlogs, patch notes, game trailers, demos, TikTok/Shorts/Reels, recaps, presentations with voice-over) with the `makevideo` library. Use whenever the user asks for a video, a video with narration, a voice-over, a trailer, a short-form video, or to turn a summary/report/changes into a video. It is fast, narrated by default, with one consistent voice, captions, and fast rendering on a plain CPU. --- # Making videos with makevideo `makevideo` turns a JSON spec into an MP4 with narration (local Kokoro TTS, consistent voice), captions, and an `.srt`. Always use it for video requests instead of improvising a new pipeline. ## Setup (once per project) ```bash npm i github:random00000000/makevideo ``` Run `npx makevideo setup` once to pre-download the voice models (about 330 MB Kokoro plus 63 MB for the fast voice). After that everything is offline. ## As a tool If the `makevideo` MCP server is connected, call `video_reference` once, then `preview_video` (it returns an image to check), then `make_video`. Otherwise use the CLI below. ## Workflow 1. **Write `video.json`.** Every scene needs a `type` and a `say` (narration). Keep it to 1–3 sentences per scene. 2. `npx makevideo check video.json --json`: fix any `problems` it reports. 3. `npx makevideo preview video.json`: look at the PNG contact sheet and fix overflow or empty slides. 4. `npx makevideo video.json --json`: this renders and returns `{ output, srt, poster, gif, duration, seconds }`. 5. Give the user the mp4 path. When the video informs a decision, end with a `decisions` scene. ## Defaults to keep (these are the standard; don't change them per video) - voice `af_heart` (warm female, the house voice), theme `midnight`, landscape 1920×1080, captions on. Always use the default voice (af_heart, warm female). Don't pass --fast or another voice unless the user asks for it. - For TikTok/Shorts/Reels use `"format": "shorts"`, which switches on big word-by-word `bold` captions. - For game trailers, use `clip` scenes of real footage. Get footage with `npx makevideo record clip.mp4 --seconds 10 --window "Game Window Title"`. ## Fastest path: Markdown Write `video.md`. Each `##` heading is a scene, list items are bullets, `- Label: 42` items become a chart, `![](clip.mp4)` adds media, a lone paragraph becomes a big statement, and `> lines` are the narration. Then run `npx makevideo video.md`. ## Scene types `title`, `statement` (text; `*stars*` = accent words), `bullets`, `steps`, `decisions`, `chart` (bars [{label, value}], unit), `split` (src + items: media beside text), `image` (src), `clip` (src: mp4/webm/mov/gif, `start`), `code` (code, highlight), `stat` (value, label), `quote` (text, by), `compare` (left/right {title, items}), `outro`. Transitions are automatic. For variety, open with a `title` or `statement`, mix in `chart`/`stat`/`split`, and avoid three scenes of the same type in a row. `check` warns you about this. ## Writing narration - Write `say` naturally; symbols like 42%, v1.2, $3M and 4.6x are spoken correctly automatically. - Put names that get mispronounced into `"pronounce": { "Hacksilver": "Hack silver" }`. - For `bullets`/`steps`/`decisions`, write one intro sentence and then one sentence per item. Items then appear in sync with the voice. ## Example ```json { "title": "Patch 1.2", "brand": "Hacksilver", "scenes": [ { "type": "title", "title": "Patch 1.2", "subtitle": "The siege update", "say": "Here is what changed in patch one point two." }, { "type": "clip", "src": "captures/ram.mp4", "title": "Rams breach gates", "say": "Battering rams can now break wooden gates." }, { "type": "bullets", "title": "Also", "items": ["Faster pathing", "New HUD"], "say": "Two more things. Pathing is faster. And the HUD is new." }, { "type": "decisions", "items": ["Ship Friday?", "Keep the old HUD as an option?"], "say": "Two decisions for you. Do we ship Friday? And should the old HUD stay as an option?" } ] } ``` --- # Narrated game trailers and devlogs from gameplay ## 1. Record footage ```bash npx -y github:random00000000/makevideo record captures/siege.mp4 --seconds 12 --window "My Game" # one window (Windows) npx -y github:random00000000/makevideo record captures/menu.mp4 --seconds 8 # whole screen (Windows/macOS/Linux) ``` ## 2. Write the trailer ```md --- theme: forge brand: My Game --- # My Game Early access, this spring > This is My Game. ## The siege ![](captures/siege.mp4) > Build rams. Break gates. Take the keep. ## Every run is different Procedural maps, *permanent* consequences. > Every run is different. Procedural maps, permanent consequences. ## Wishlist now ![](captures/menu.mp4) - Early access this spring - Wishlist on Steam > Early access is this spring. Wishlist it now. ``` Clip scenes are cropped to fill the frame and looped if short (`start:` picks the offset). A clip next to bullet points becomes a split scene with the video playing inside a rounded frame. ## 3. Render ```bash npx -y github:random00000000/makevideo trailer.md npx -y github:random00000000/makevideo trailer.md --format shorts -o trailer-vertical.mp4 ``` ## Devlogs and patch notes The same file format works for weekly devlogs: a title, a few `##` scenes with clips or bullet points, a chart of numbers that changed, and a closing `## Decisions` list for the team. --- # How to make narrated videos with an AI agent When you ask a coding agent for a video, it usually improvises: it picks some text-to-speech service, maybe installs a headless browser to draw frames, guesses scene timings, and wrestles ffmpeg flags. Every video comes out different, often silent, and renders can take many minutes. **makevideo** gives agents one standard path instead: write the video as Markdown (or JSON), run one command, get an MP4 with narration, captions and transitions. ## The three-step loop ```bash npx -y github:random00000000/makevideo check video.md # instant validation + quality hints npx -y github:random00000000/makevideo preview video.md # contact sheet PNG of every scene (~1 s) npx -y github:random00000000/makevideo video.md # video.mp4 + .srt + poster + report ``` The agent writes `video.md`: ```md # Weekly update What shipped this week > Here is what shipped this week. ## Highlights - Faster builds - New onboarding > Two highlights. Builds are faster. And onboarding is new. ## Decisions - Ship on Friday? > One decision for you. Do we ship on Friday? ``` Each `##` heading is a scene. Lines starting with `>` are the narration; if a scene has none, its visible text is read aloud — narration is the default, not an afterthought. ## Make every agent in a project use it ```bash npx -y github:random00000000/makevideo agents --all ``` This writes a short, idempotent "use makevideo for videos" block into `AGENTS.md` (read by Codex, Cursor, Copilot, Amp and others), `CLAUDE.md`, `.cursor/rules/makevideo.mdc` and `.github/copilot-instructions.md`. Use `--global` to install it for Claude Code and Codex on the whole machine. ## Or give the agent a tool - **Claude Code plugin:** `/plugin marketplace add random00000000/makevideo` then `/plugin install makevideo@makevideo`. - **Any MCP client:** add the server `npx -y github:random00000000/makevideo mcp`. Tools: `make_video`, `preview_video` (returns the contact sheet as an image the model can see), `check_video`, `video_reference`. ## Why agents do better with a standard - **Same voice every time** (a local neural voice), so a project's videos sound like one series. - **Timing comes from the real audio**, so captions and list reveals never drift. - **Errors are written for agents** — each names the scene and includes a working example. - **Lint catches bad videos before rendering**: walls of text, unsynced lists, repetitive pacing. - **Runs on CPU** with no API key, which matters in cloud sandboxes. --- # makevideo vs Remotion vs MoviePy vs ffmpeg scripts All four make video from code. They aim at different jobs. | | makevideo | Remotion | MoviePy | raw ffmpeg | |---|---|---|---|---| | You write | Markdown or JSON scenes | React components | Python | filter graphs / shell | | Narration (TTS) | built in, local voice, default on | bring your own | bring your own | bring your own | | Captions | built in (+ .srt) | build it | build it | subtitles filter | | Rendering | Skia canvas + ffmpeg, CPU | headless Chrome | Python + ffmpeg | ffmpeg | | Custom motion design | limited (scene types, transitions) | unlimited | flexible | flexible, hard | | Best for | agents and fast, consistent explainers | bespoke, designed videos | Python editing pipelines | exact control | ## Choose makevideo when - An **agent** (or a busy human) needs a narrated explainer, recap, changelog, devlog, demo or short — on a CPU, without an API key — and it should look and sound the same every time. - You care more about *getting a good video in one try* than about designing every frame. ## Choose something else when - **Remotion** — you want full React-level control over motion design and are happy to run a browser. Check its license terms for company use. - **MoviePy** — your pipeline is Python and you are editing existing footage more than generating explainers. - **raw ffmpeg** — you need precise control over a specific transform and nothing else. makevideo also uses ffmpeg under the hood; it simply decides the things agents tend to get wrong: voice, timing, captions, layout, pacing and encoding settings. --- # Turn a Markdown file into a narrated MP4 ```bash npx -y github:random00000000/makevideo notes.md ``` ## The syntax ~~~md --- theme: midnight # front matter: any top-level option (theme, format, voice, brand, output) brand: My Game --- # Patch 1.2 ← title scene The siege update ← subtitle > Here is what changed. ← narration ## Rams breach gates ← each ## heading is a scene ![](captures/ram.mp4) ← media: video/GIF clip or image - Wooden gates break in 3 hits ← with items → a split scene (media + points) - Stone gates still hold > Rams now breach wooden gates. Three hits and they are down. Stone gates still hold. ## Load time ← scene options as key: value - Before: 12 s ← "label: number" items → animated bar chart - Now: 3 s ## The goal Renders in *seconds*. ← a lone paragraph → huge kinetic statement (*stars* = accent) ## Big number **4.6x** faster renders ← bold value + label → stat with count-up ## The fix ```js ← fenced code → code scene with line numbers const x = 1; ``` ## Decisions ← a list under a "Decisions" heading → numbered decisions - Ship Friday? ~~~ ## Rules that make videos better - **Narration is the default.** Without a `>` line, the visible text is read aloud. - **Write one intro sentence plus one sentence per list item** — items then appear exactly when the voice reaches them. - **Write naturally.** `42%`, `v1.2`, `$3M`, `4.6x` and `35 s` are spoken correctly; captions keep what you wrote. - Run `makevideo check notes.md` — it suggests fixes for walls of text, too many bullets, and repetitive pacing. --- # A video tool for any MCP client makevideo ships an MCP server over stdio: ```bash npx -y github:random00000000/makevideo mcp ``` ## Configure your client Most clients accept this shape (Claude Desktop `claude_desktop_config.json`, Cursor `.cursor/mcp.json`, VS Code `mcp.json`, and others): ```json { "mcpServers": { "makevideo": { "command": "npx", "args": ["-y", "github:random00000000/makevideo", "mcp"] } } } ``` Claude Code: `claude mcp add makevideo -- npx -y github:random00000000/makevideo mcp`, or install the plugin (`/plugin marketplace add random00000000/makevideo`), which bundles the server and a skill. ## From the official MCP Registry makevideo is listed in the official MCP Registry as **`io.github.random00000000/makevideo`**, as a Docker image. Clients that browse the registry can install it from there. To run the image yourself: ```bash docker run -i --rm -v "$PWD:/work" -w /work ghcr.io/random00000000/makevideo mcp ``` Mount the folder that holds your Markdown and media at `/work`; rendered MP4s land there too. ## Tools | tool | what it does | |---|---| | `make_video` | Render Markdown or a JSON spec to MP4 (+ captions, poster, report). `fast: true` for the fast voice. Streams progress notifications. | | `preview_video` | One still per scene as an **image** the model can look at, plus lint suggestions. ~1 s, no audio. | | `check_video` | Instant validation: problems, quality warnings, estimated duration. | | `video_reference` | Scene types with examples, transitions, voices, themes, formats and the Markdown syntax. | ## Tips - Pass `baseDir` so relative media paths (clips, images) resolve where your files are. - First renders download the voice model; run `npx -y github:random00000000/makevideo setup` once on new machines. --- # Narrated text-to-video on a CPU, with no GPU and no API key Most "text to video" tools either call a paid cloud API or need a GPU. Agent sandboxes and CI runners usually have neither. makevideo renders on a plain CPU: - **Voice:** Kokoro-82M (Apache-2.0) running locally through ONNX Runtime — or a Piper voice with `--fast` that speaks ~10x faster than real time on two CPU threads. - **Frames:** drawn with Skia (`@napi-rs/canvas`) — no headless browser. - **Encoding:** H.264 via a bundled ffmpeg (`ffmpeg-static`). ```bash npm i github:random00000000/makevideo npx makevideo setup # pre-download voice models once (CI / cloud images) npx makevideo video.md # or video.json npx makevideo video.md --fast ``` ## How fast is it? Measured on a 6-core laptop CPU (i7-10750H), no GPU, for the same 74-second, 8-scene video: | | time | |---|---| | first render, default voice | ~81 s | | first render, `--fast` voice | ~36 s | | re-render with cached speech | ~28 s | ## Why it is fast on a CPU 1. **Frame holding.** Slide videos are mostly still frames. makevideo knows each scene type's animation timing, draws only frames that can change (about 30%), and lets ffmpeg repeat held frames after colour conversion, where repeats are almost free. 2. **Render processes.** Scenes draw and encode in a pool of processes, so drawing uses many cores. 3. **Tuned speech.** ONNX threads are pinned per process; processes that each grab every core fight and get slower. 4. **Caching.** Every sentence's audio is cached by voice + text, so re-renders after a layout fix skip speech entirely. ## From code ```js import { video } from 'makevideo'; const r = await video({ title: 'Patch notes', scenes: [ { type: 'title', title: 'Patch 1.2', say: 'Here is what changed in patch one point two.' }, { type: 'chart', title: 'Load time', unit: ' s', bars: { Before: 12, Now: 3 }, say: 'Load time. Twelve seconds before. Three now.' }, ], }); console.log(r.output, r.duration, r.seconds); ``` --- # TikTok, Reels and Shorts with word-by-word captions Short-form video has a house style: vertical 9:16, a strong hook, and huge captions where the spoken word lights up. makevideo does that with one option: ```md --- format: shorts theme: forge gif: true --- ## AI agents, listen up Stop *hand-making* videos. > Your agent can make a video like this in seconds. ## Proof **1** JSON file in, one video out > One JSON file in. One finished video out. ## Footage ![](captures/boss-fight.mp4) > Drop in gameplay clips, GIFs or screen recordings. ``` ```bash npx -y github:random00000000/makevideo short.md ``` - `format: shorts` → 1080×1920 and **bold captions** (a few words at a time, heavy outline, current word in the accent colour) by default. - `gif: true` (or `{ width, fps, duration }`) also exports an animated GIF for previews and READMEs. - Statements, stats and clips work best in portrait; `check` warns when a dense scene (code, charts, comparisons) is used in a vertical video. - Use `--fast` while iterating on the script; switch back to the default voice for the final cut.