Changelog
What actually shipped.
This page renders the repo's real CHANGELOG.md directly, not a rewritten copy.
Changelog
All notable changes to openvidstudio are documented here.
Format based on Keep a Changelog. Versioning follows Semantic Versioning.
[Unreleased]
Changed
- The Claude plugin now pins server 1.4.0 and is version 1.1.0. 1.4.0 is on npm, so a new plugin install runs the server with
install,fit_beatsand the canvas explainer tools.
[1.4.0] - 2026-10-24
Fixed
-
The
supernovaskill now loads. Its frontmatter description held an unquoted:, which is invalid YAML, so Claude Code dropped the skill silently. A test now checks that no skill description does this. -
init_projectno longer copies untracked files from the template'soutput/folder (#139).copy-templatenow copies the template from what git tracks (and, outside a checkout, allows onlyoutput/README.mdandoutput/.gitignore), andinit_projectskips any other file underoutput/, so a render left on one machine does not land in every new project. -
render_videono longer blocks every Supernova project at the brand-lock gate (#136). A project withproduction.mode: "supernova"skips the gate and reportsbrandLocked: falsewith abrandLockSkipReason; a standard project withoutsrc/brand.tsstill gets the gate. TheskipBrandLock: trueworkaround is gone from SUPERNOVA.md and the supernova skill. -
validate_scenesno longer warns about camera keyframes on every Supernova scene (#137). In a lesson the camera checks (fewer than two keyframes, a camera that never moves) are dropped; a standard project keeps both. The "ignore it" lines are gone from SUPERNOVA.md and the skill.
Added
- A Claude Code plugin:
/plugin marketplace add AnayDhawan/openvidstudio, then/plugin install openvidstudio@openvidstudio. The repo is the marketplace; the plugin inplugins/openvidstudioregisters the MCP server and the four owned skills (openvidstudio,openvidstudio-director,bgmusic,supernova) together. A small launcher pins the exact server version, installs it once into the plugin's data folder, answers the MCP handshake at once on the first start (a status tool, then the real tools viatools/list_changed), and starts the server directly after that..claude/skillsstays the single source;scripts/sync-skills.mjsnow also writes the plugin's skills and--checkfails CI if they drift. Adds a privacy page, aclaude plugin validate --strictCI job, and three test files (claude-plugin,plugin-launcher,install-cli). openvidstudio-mcp install <client>writes the server into a client's own MCP config. Clients:claude-desktop,cursor,vscode, andclaude-code(prints the plugin commands). It adds only theopenvidstudiokey, keeps every other server, refuses an unparsable file, writes a timestamped backup, and wrapscmd /con Windows.--dry-runand--printwrite nothing. Run asnpx -y @openvidstudio/mcp-server install claude-desktop.preflightchecks that edge-tts can synthesize, and a refusal is reported with its cause (#140). With the edge-tts engine,preflightshows the installed version and speaks one word to a temp file; on failure it shows the error line andpip install -U edge-tts.generate_narration's "no usable audio" warning now ends with edge-tts's last stderr line. The version notes in NARRATION.md, SUPERNOVA.md and the skill now point atpreflight.validate_sceneschecks a lesson's arc:diagram-lateandstatic-stretch(#138). In render mode, from the frames it already samples (no extra render pass), a Supernova lesson gets a warning when the first thing drawn beyond the background and the captions is later than 15 s (an error after 25 s), and a warning when sampled frames do not change, captions aside, for more than 8 s. The thresholds are the optionsdiagramWarnSeconds,diagramErrorSecondsandstaticSeconds. Captions are ignored by theirdata-ovs-captionsbox, or the bottom 22 percent.fit_beats, a tool that sizes the beats from the measured voice (#135). Aftergenerate_narrationit sets each beat to the spoken length plus a pad (default 0.5 s), clamped into the 2.3 to 2.9 words per second window and rounded to whole frames, moves the later beats' starts, never changesvo, lists inunfittableany line that cannot fit with the words to cut or the frames to lengthen to, supportsdryRun, and returnsvalidate_beatson the result. The tool count is now 42 (43 with Higgsfield).generate_narration's overrun note, NARRATION.md and the supernova skill point at it.LessonCaptions, word-timed captions that break at sentence ends (#134). One line in a scene,<LessonCaptions beatId text={vo} />: takes the text from the beat'svoso punctuation survives (the word timings carry bare words), breaks at a sentence end, at a comma after three words and atmaxWords, picks out the word being said, and stays out of the Reels safe zone. The SUPERNOVA.md snippet is replaced by the component and the supernova skill says to use it.Derivation, a formula built line by line, each step spoken (#133). Steps withtex,spokenand an optionalbecause, revealed at the word they are spoken at; the line before moves up and dims (a stack of three), carried terms stay put, new terms arrive in the accent and gone terms fade out.check_scriptnow readsDerivationstep strings and fails a step that is not in the narration, or is said out of order.- Supernova primitives, round 2:
Wave,Orbit,VectorField(#141).Waveis a travelling or standing sine wave with wavelength, amplitude and phase labelled, exact crest spacing and speed, and an optional Spark on a crest.Orbitis a body on a circular or elliptical orbit (Kepler's equation) with velocity and force arrows exactlymagnitude * scalepx long and scale bars.VectorFieldis arrows on a grid for(x, y) => [fx, fy], each exactly|f| * scalepx, with optional deterministic field lines. Each has real-frame tests at both frame sizes, an API.md entry with a still, and a sample scene. - Supernova primitives, round 3:
MatrixGrid,GraphViz,TreeViz,ComplexityCurve(#142).MatrixGridshows a 2x2 matrix acting on a grid, the unit square and i-hat and j-hat.GraphVizcolours nodes by search role with a Spark walking them, plusbfsOrder,shortestPathandbfsSteps.TreeVizbuilds a binary search tree by insert with a Spark walking to each new node and in, pre, post and level order highlighting.ComplexityCurveplots n, n log2 n and n squared on shared axes with a marker at a chosen n. Each has real-frame tests at both frame sizes, an API.md entry with a still, and a sample scene. render_canvas_explainer, a tool that turns an HTML canvas explainer into a video (#151). A file with a canvas that exposeswindow.seek(t),window.DURATIONandwindow.scoreSound(kit)is loaded in headless Chromium, walked frame by frame, piped into ffmpeg (H.264) and muxed with its score rendered throughSOUND.renderOffline; 1280x720 or 720x1280,asyncthroughjob_status, a file without the promises refused,Math.randomwarned about, and first, middle and last frame hashes returned so a second render can prove the frames are a function oft. The logic isdist/canvasExplainer.jsand also runs standalone.- Voice-over mode for canvas explainers (#152). With
videoName,beats.jsonbecomeswindow.BEATSand setsDURATION, the narration clips are mixed at each beat's start (to the sample) over the score rendered withvoiceover: trueand ducked at least 6 dB under the voice, and the beats' text is burned in as captions that break at sentence ends inside the safe areas, through awindow.CAPTIONS.drawhelper an explainer can call to caption in its own style.narration: "none"renders with the full-mix sound. validate_canvas_explainer, size, safe-area, purity and arc checks for a canvas explainer (#155). Loads the file in headless Chromium and checks that frames are a pure function oft, text size (labels 28px, headlines 44px, key numbers 64px at 720 on the shorter side, scaled), safe areas on a tall canvas, the lesson arc throughlessonArc.ts(diagram-late,static-stretch), the sound (most simultaneous sources, peak level) andMath.random. Findings havevalidate_scenes' shape. It also runs as the pre-flight ofrender_canvas_explainer`. The tool count is now 44 (45 with Higgsfield).check_scriptunderstands a story-first lesson arc (#154). The concept card gainsarc("seven-beat"by default, so existing lessons behave as before, or"story"),situation,character,formulas,practiceQuestionandanswer, with the checksstory-situation,story-formula,story-practice,story-comment,story-subscribeandstory-answer. SCRIPT.md documents both arcs.write_descriptionfor lessons adds the practice question and disclaimers (#156). A concept card withpracticeQuestionandansweradds the question and "Drop your answer in the comments." to the description and writes the answer tooutput/<name>-ANSWER.md(optionalanswerWorking), never into the description; adisclaimersarray in the project config is appended to every lesson description, each line once, at the end.@openvidstudio/styles, a pixel narrator and a depth-feel background kit (#153). A new workspace package of plain 2D canvas modules, every one a pure function oftwith a seeded generator and noMath.random: palette-indexed string-grid sprites drawn with integer-scalefillRect(drawSprite), one recurring narrator with idle, talk, point, think and celebrate poses and a prop set lessons can extend,Parallaxlayers, a perspective floor grid, depth fog, a camera dolly, drop shadows and pre-blurred far layers, per-subject accent tokens, a bitmap pixel font and a chip sound palette. Gallery stills in API.md.- Vertical 720x1280 layout helpers in
@openvidstudio/styles(#157).safeArea(W, H)keeps the top 14% and bottom 22% clear (and a 5% side margin),fitTextreturns the largest size that fits and never goes below the role minimum (headline 44, label 28, key number 64), and a caption band sits at the bottom of the safe area. Reframe-by-cropping is not offered.
Security
- Twelve Dependabot alerts closed, including one critical. The vulnerable packages (
hono,proxy-addr,fast-uri,qs,ip-address,source-map-js,postcss-selector-parser) are transitive, sopnpm.overridesin the rootpackage.json(and the same list inpnpm-workspace.yaml, which pnpm 11 reads) now require the patched versions, each bounded to its current major. Remotion is on 4.0.536 and React on 19.3.1 across every manifest at once (the three split Dependabot PRs for these are superseded).
Changed
- The install is
npx -y @openvidstudio/mcp-server. All four packages are on npm at 1.3.1 (@openvidstudio/capture,core,stemandmcp-server), so the README,llms.txtand the package READMEs lead with thenpxconfig and keep the clone as the way to run from source. The note saying the npm package was stale is gone.@openvidstudio/capturecan be installed alone withnpm install @openvidstudio/capture.
Docs
- The README's status list still said lesson videos "do not work today". It now says Supernova is built and tested and no lesson has been published yet.
[1.3.1] - 2026-10-19
The packages are ready for npm: each now ships a README, and @openvidstudio/mcp-server and @openvidstudio/stem
ship the Apache-2.0 LICENSE file, which they did not. Without them an npm page would have been blank. No code changes.
Added
- A README in
packages/mcp-server,packages/coreandpackages/stem, written for the npm page: what it is, how to add it to an MCP client withnpx -y @openvidstudio/mcp-server, what the machine needs, where the docs are, and the Remotion license note. - The LICENSE file in
packages/mcp-serverandpackages/stem, ascaptureandcorealready had.
[1.3.0] - 2026-10-19
Lessons get one shape. A Supernova lesson is now one concept in up to two minutes, in seven fixed beats,
and check_script checks the length and the chapter reference. No lesson has been published yet. Install
from a clone: the npm package is still 0.1.0 while publishing is blocked.
Added
- Every lesson has the same shape, and
check_scriptholds two more lines of it. A Supernova lesson is now one concept in up to two minutes, always in seven beats: hook, picture, set up, derive, use it, aha, close. SCRIPT.md gives the arc with times for a 120 second lesson, the rules (start the diagram inside 15 seconds, derive each formula line by line with a spoken form per line, say where the lesson sits, end on the check question) and a correctness routine (writederivation.md, check the symbolic result, the worked example and the units with code, name the assumptions, have someone who knows the subject read it). The concept card takes three optional fields:reference(a chapter code or "Part 2 of 4", as shown on screen),referenceSpoken(as said aloud) andmaxSeconds(default 120).check_scriptnow reports alengtherror when the beats add up to more than the budget, and areferenceerror when the first beat does not sayreferenceSpoken(a warning when it is missing, because then it cannot be checked). Ten new checks incheck-script.test.mjs; the example script in SUPERNOVA.md is now the seven-beat arc and draws no finding. - SUPERNOVA.md says how to fit beats to the voice and how to caption them. The voice reads plain prose at about 3 words per second and number-heavy lines at about 2.2, so beats are sized from each clip's
spokenSeconds, then regenerated. Word timings carry no punctuation, so captions take their text from the beat'svoand break at sentence ends (a snippet is included). It also notes thatrender_videoneedsskipBrandLock: truefor a lesson, and thatedge-tts7.2.8 gets a 403 from Microsoft: use 7.3 or newer.
Changed
- The lesson length is up to two minutes, not 60 to 90 seconds, in the
supernovaskill (now the arc and a derive-and-verify step), SUPERNOVA.md, README andllms.txt. click.wavis now a recorded double click (Mixkit, "Fast double click on mouse") instead of a 30 ms synthesized tick. Trimmed so the first click lands on the cue frame, mono 48 kHz like the rest of the pack, peak -1.5 dBFS.gen-sfx.shno longer generates it,public/sfx/CREDITS.mdrecords the source and the Mixkit licence, and the docs no longer say the whole pack is licence-free: a rendered video still needs no credit. The licence text could not be read when this was added, so confirm its terms on redistribution before republishing the pack on its own.
Docs
- OVERVIEW.md's doc map listed six of the thirteen docs. It now lists them all (CREATIVE, PRESETS, NARRATION, SUPERNOVA, API and TOOLS were missing), which is also where the site takes its index descriptions from.
[1.2.0] - 2026-10-19
Supernova mode, the engine for short animated STEM lessons (built by @yakew7), a new tagline, and tools that reject inputs they do not take. The engine is tested, but no finished lesson has been built and listened to yet (that is #107). Install from a clone: the npm package is still 0.1.0 while publishing is blocked.
Changed
- The tagline is now "Code-based video generation", always with the line "Every frame of your product is a real capture, never a generated mockup." The README headline,
llms.txtand the GitHub description say it. The second line stays with it because "video generation" alone reads as AI video, and nothing here is generated by a model. @openvidstudio/stemis now version 1.1.0, in step with the other packages, and its peer range on@openvidstudio/coreis^1.1.0.release-version.test.mjschecks all five packages share one version.
Fixed
- Three Windows test failures that were portability bugs, not product bugs.
equation.test.mjsrannode_modules/.bin/tsc, which on Windows is atsc.CMDshim that needs a shell.scaffold-vertical.test.mjscompared generated files with golden files that git had checked out with CRLF line endings; it now normalises them.graph.test.mjsfailed on one anti-aliased amber pixel from the "range 40 m" label, found past x = 40 in the 1080x1920 render; it now allows fewer than 8 amber pixels there (the curve would add dozens) and has a control that counts more than 40 where the curve is, between x = 30 and 35.
Docs
- The API gallery now has a picture for every new cinematic primitive, and the
DirectedShotstill has sharp text (#121).build-api-gallery.mjsrendersTransitionOverlay(one still per type: fade, whip, push, wipe, light-leak),LayeredShot,MotionBlur(a fast move, mid-way) and the six named shots (pushIn, pullOut, pan, whip, orbit, dutch), andAPI.mdembeds each with alt text that says what it shows.directedshot-start.pnganddirectedshot-end.pngwere re-rendered with the new sharp default (the old end still was rendered with a depth of field of 6 and smeared its text).api-gallery.test.mjschecks every embedded still exists, the new ones are embedded and described, each image is under 160KB, and measures the text in theDirectedShotstill: the steepest edges in its text boxes are 198 of 255, against 41 in the old one.
Fixed
scaffold_scenefollows the project's frame size (#117). The starter and every template hard-coded a 960,540 camera and a 1920x1080 layout, so in a 1080x1920 project scenes rendered off centre and cropped with no warning. Both now readvideoConfigand generate the camera centre, frame position and safe width from it, and the split-panel and comparison templates stack in a portrait frame. 1920x1080 output is byte-identical to before (golden files inscripts/tests/fixtures/scaffold-landscape/).ChapterHudwith no design now takes the grid for the composition size instead of the 1920x1080 one. A real-frame test scaffolds every template into a 1080x1920 project, finds no crop finding invalidate_scenesrender mode, and measures the camera centre at 540,960.plan_production's estimate now includes the terms it left out (#120). The director-mode eval ran 56 minutes against an estimate of 18.5. The benchmark composition renders about 3.7x faster than a finished peak video, so the render terms now divide the measured fps by a per-level scene weight (peak 3.7 from that run, mid 2, literal 1 as before). New lines report a bundle per review round, the draft pass and the final render (preflight's benchmark now measuresbundleSeconds), and capture time with a retry allowance. Each term is its own phase; the authoring term keeps its "engineering estimate" label. On the Acme run (60 s, peak, 3 rounds) the estimate is 38.2 minutes. The weights are calibrated on that one run and need recalibrating as more runs are timed.- Tools now reject an input they do not take, instead of silently dropping it (#119). The MCP SDK builds a zod object from each tool's input shape and zod strips unknown keys, so
render_video { quality: "draft" }(the option isdraft: true) ran a full-quality render of about 15 minutes. Every tool is now registered with a strict schema: the error names the unknown key, lists the valid ones and suggests the closest ("did you meanprojectRoot?"). write_beats_filenow returns thevalidate_beatswarnings on a successful write. The result is{ written: true, path, warnings }, so an agent that goes straight to the write still sees notes such as "shot lengths are nearly uniform". Warnings never block the write (#118).
Added
CollageBroll, a halftone paper-collage b-roll primitive (#122). A drop-in entry in the motion primitives catalog (catalog_motion_primitivesfinds it,add_motion_primitiveinstalls it; it imports only react, remotion and core): a flat colour field, black and white halftone cut-outs of images or built-in shapes, cardstock accents, cream keylines, soft shadows and fixed-seed paper grain, with pieces assembling structure first, then people, connectors and the result, each entering from empty space on a spring with no fade or zoom. A pure function of the frame, placed as fractions of the composition. A real-frame test checks the empty field before the first piece, every piece at its target at the end, the arrival order against a deliberately scrambled listing, no fade and no zoom, and identical re-renders, at both frame sizes. Documented inAPI.mdwith a still. Written from scratch; inspired by the look ofgbro-collage-broll, not its prompts or text.- Supernova mode, part 9: the
supernovaskill,SUPERNOVA.mdand a concept-card template (#116)..claude/skills/supernova/SKILL.md(mirrored to the five tool directories bysync-skills) walks a lesson frominit_projecttowrite_descriptionin order;SUPERNOVA.mdholds the design system, the primitives, the fixed lesson voice (edge-tts,en-US-AndrewNeural, a proposal for the handle's owner to confirm), the Reels safe zones checked against current guidance (top 14% agrees across sources; the bottom varies from about 340 px to 35%, which is for ads), a concept-card template and a worked projectile-motion card and script. A test checks every tool the skill names is registered, that the example script passescheck_script, and runs the steps in order on a real project through avalidate_scenesrender. - Supernova mode, part 8:
validate_scenesand the disclosure rules know about lessons, plus a glow budget (#115). In a supernova project the "numbers typed into copy withoutfacts.json" check accepts a number that is in the concept card's worked example (concept.json) or in anEquation'stex, and still flags one that appears nowhere; a standard project is judged exactly as before (asserted). The disclosure rule is replaced:write_briefneeds no real or constructed split in a lesson's shot list, andwrite_descriptionwrites one line (the video is a constructed explanation, not footage) in place of the per-shot sample-content section. New in render mode, for lessons: a glow budget that counts the glowing elements (data-ovs-glow, from the helper in@openvidstudio/stem) in each sampled frame, warns above one, errors above two, and exempts a beat that holds a Nova.ValidateScenesResultgainsmode. - Supernova mode, part 7:
generate_narrationwrites word timings (#114). With the edge-tts engine each clip getspublic/audio/vo/<beatId>.words.json,[{ word, startMs, endMs }], timed to the clip as written (every time is divided by the same 0.9 to 1.12 stretch factor). The word boundaries come from theedge_ttsPython module through a new helper,py/edge-words.py: theedge-ttscommand in version 7 only writes sentence-level subtitles, not the word-level ones the issue assumed. Other engines write nothing and the result'swordTimingssays so.stitch_compositioncopies the words next to the composition (only when some beat has them, so other compositions are unchanged) and mounts them with the newNarrationWordsProvider; the newuseNarrationWords(beatId)hook in core giveswordAt(frame)andframeOfWord(index). Tested against the realedge-ttsservice (skipped with a clear message when it is not installed), and the hook is rendered in a real browser. Documented inNARRATION.mdandAPI.md. - Supernova mode, part 6:
check_script, a lint for lesson scripts (#113). A new tool (no model call) that readsbeats.jsonand a small concept card (concept.json:{ goal, terms, example }) and reports findings with a severity, each pointing at a beat: sentence length, Flesch-Kincaid reading level, concept-card terms defined at first use, no more than two new terms a beat, eachEquation'sspokenstring present in its beat's narration, a hook that is a question or holds a number and does not open "in this video", words per second against the beat length (sharingvalidate_beats' own budget through a new exportedvoPaceCheck), and a last beat that ends with a question. A good script draws no finding; a deliberately bad one fires each of the eight checks once, and fixing one removes only its finding. The lesson script rules are inSCRIPT.md. Tools: 41 (42 with Higgsfield). - Supernova mode, part 5:
FreeBodyandArrayViz(#112). In@openvidstudio/stem:FreeBody(an object with labelled force arrows, each exactlymagnitude * scalepx long with a scale bar so the picture is honest, arriving one at a time, with an optional net-force arrow and a tilt for a block on a slope) andArrayViz(cells holding values, named pointers, a highlighted range, a dimmed range, and a pointer that can be the Spark). Real-frame tests at 1920x1080 and 1080x1920 measure arrow lengths against the magnitudes, the highlight against the cells it was given, and pointer positions against the cell centres. The two sample scenes are a block on a slope and binary search. Documented inAPI.mdwith stills. - Supernova mode, part 4:
Equation, with a spoken form, and the maths font decision (#111).Equationin@openvidstudio/stemtypesets TeX in sans-serif maths and reveals it term by term as a pure function of the frame.spoken(the equation as it is said) is required by the types and at runtime and is exposed asdata-spokenfor the script lint. Renderer: MathJax 4 with its Fira maths font (SVG output) over KaTeX, which leaves serifsin,cosand theta with\mathsf; the comparison, with two screenshots, is inAPI.md. It runs from the project's ownpublic/mathjax/(copied byinit_project, generated from the installed packages at build time, not checked in), with its speech worker switched off, so a render needs no network.scripts/offline-render-check.mjsnow also renders an Equation and a Spark in a supernova project with the network blocked. A fully revealed equation matches an unrevealed reference render pixel for pixel; the real-frame tests run at 1920x1080 and 1080x1920. - Supernova mode, part 3:
Axes,PlotandNumberLine(#110). In@openvidstudio/stem:Axes(labelled axes, ticks, gridlines in the dim token, and a graph-to-screen mapping exposed throughuseAxes()),Plot(a sampledy = f(x)with a draw-on, a tangent at a given x, a shaded area between two x values and marked points) andNumberLine(ticks, labelled points, open or closed intervals, an arrow, anduseNumberLine()so a Spark can ride it). Sizes come from the composition and no stroke is thinner thanlineWeight(). Real-frame tests at 1920x1080 and 1080x1920 work out each expected pixel independently (y = x squaredthrough (2, 4), the draw-on half-way and complete, a tangent slope measured from two points, projectile motion with its range and peak marked as the fixture). Documented inAPI.mdwith stills. - Supernova mode, part 2:
SparkandNova(#109). In@openvidstudio/stem:Spark(a bright core, a bloom and a fading trail along a path of local time or keyframes) with the behaviourstrace,carry,orbit,split,fallandwalk, andNova(nothing beforeat, a three-frame flash, a decelerating shockwave ring, the children condensing from particles;ator a named impact from the music clock). Both draw their glow through oneGlowhelper taggeddata-ovs-glow, so the glow budget can be counted from a rendered frame. Scatter comes from a seeded hash, sizes fromuseVideoConfig(). Real-frame tests at 1920x1080 and 1080x1920 (positions, trail falloff, flash brightness, ring expansion, formed content, byte-identical re-renders). Documented inAPI.mdwith stills;build-api-gallery.mjsnow also renders a 1080x1920 stem gallery. - Supernova mode, part 1:
packages/stem, a production mode flag and the Void + Spark design system (#108). New workspace package@openvidstudio/stemwithsupernovaDesignSystem(subject)(Void base#07060B, warm white text that is never pure white, one accent per subject: math, physics, cs, chemistry, biology), a newvoid-sparkmotif (added indesignSystem.tsandwriteBrief.ts), and Space Grotesk (SIL OFL) loaded from local files.production.mode("standard"by default,"supernova") is accepted byinit_projectand read byvalidate_beats,write_briefandplan_production; a lesson needs no creative level.init_projectwithmode: "supernova"vendors stem into the project (copy-template.mjs, as for core), copies the font, and defaults to 1080x1920; a standard project's files, config andpackage.jsonare unchanged (asserted).
[1.1.0] - 2026-10-19
Director mode, a cinematic camera, native 9:16, and a score that does not tick. Lesson videos (Supernova, tracked in #107) are not in this release. Install from a clone: the npm package is still 0.1.0 while publishing is blocked.
Docs
- README and CONTRIBUTING rewritten to be plainer and more welcoming. The README now explains
what the tool does in five steps, leads the install with the clone route, and says outright that
npx -y @openvidstudio/mcp-serverinstalls 0.1.0 from September (about 27 tools, no director mode) while publishing is blocked. It no longer claims@openvidstudio/captureis published (it is not on npm) or thatnpxgives forty tools. Node is "20 or newer" everywhere, matching the packages'enginesfield and CI. CONTRIBUTING covers setup, where things live, how to run one test or all of them, and what a good pull request looks like.llms.txtand the package descriptions match. - README and llms.txt now disclose Remotion's own license, not just openvidstudio's. openvidstudio's code is Apache-2.0 and free for anyone, but it renders through Remotion, which is free only for individuals, non-profits, evaluation use, and for-profit companies with up to 3 employees; a larger company needs a paid Remotion Company License (remotion.pro) to render with it. The README's badge and llms.txt's tagline said openvidstudio was free without that caveat, which reads as "free to use" to a company evaluator when the Remotion dependency underneath it isn't, for them. Tech stack and License sections now state the split plainly.
Fixed
-
validate_scenesrender mode read the wrong colour for SVG text. SVG text is painted withfill, but the contrast check read the inherited CSScolor, so axis labels in a graph were reported at 1.04:1 contrast. It now readsfillfor SVG text (found by running the lesson steps end to end). -
The brand palette now reaches the render (#61). The template's
sideEffects: ["*.css"]let the bundler dropsrc/brand.ts, soapplyBrandnever ran and every scene rendered in the default navy. It now lists./src/brand.ts, andvalidate_sceneswarns when none ofbrand.ts's colours appears in a rendered frame. -
stitch_compositionkeeps a hand-picked music track on re-stitch (#64). A chosen track and its level are preserved; an auto-detected bed still upgrades when a better one is added. -
capture_terminalon Windows (#66). Python output is unbuffered and UTF-8 (PYTHONUNBUFFERED,PYTHONUTF8,PYTHONIOENCODINGin the child's environment), and a barebash, which resolves to the WSL launcher, is flagged. -
spawnCapturequotes a.cmdshim path that contains a space (#77). -
The tool counts in the README were wrong in three places (#69, #70, #81) and are now generated and checked.
-
contact_sheetno longer always fails. Its filtergraph fedtileone input label per frame ([t0][t1]...tile=...), buttiletakes exactly one input stream, so every call errored with "More input link labels specified for filter 'tile' than it has inputs." Every scaled input is now concatenated into one stream first (concat=n=N:v=1:a=0), and only that single stream feedstile. Verified with a real ffmpeg run producing correctly-dimensioned 4-beat and 16-beat sheets. -
qc_extract_frames's end-frame seek no longer lands on the next beat, and no longer reports a path for a frame it never wrote. Timestamp-based seeking rounds a target frame to a time that accurate-seek can land just past, returning the following frame instead (and, for the last beat, writing nothing at all). Extraction is now by frame index (select=eq(n,N)); every output is verified to exist and be non-empty before being reported, with amidError/endErrorinstead of a phantom path otherwise. Verified with a synthetic 3-beat render: every beat's mid and end stills show their own color, not a neighbor's, including the final beat. -
CursorActor's click rings no longer follow the cursor past the click. A ring was drawn at the cursor's live position on every frame it's visible, so a path that kept moving after a click dragged the ring with it (~100px off target in a real repro). Rings now anchor at the cursor's position at its own click frame. Scaffolded recording scenes also now make the cursor arrive ~4 frames before a click and dwell ~7 frames after. -
scaffold_scene's cta template no longer shipsyour-org/your-repoplaceholders when real info is available: owner/name now come from the git remote or package.json'srepositoryfield, chips fromlicenseand a simple language guess.validate_scenesalso no longer false-positives "fewer than two camera keyframes" onRepoCtaand other components with their own internal camera. -
preflight's Chromium check no longer guesses fixed OS cache directories. It ignoredPLAYWRIGHT_BROWSERS_PATH(a false "no chromium" when browsers lived there instead) and could report OK on a different build than real capture would actually launch (a version mismatch between this server's playwright and an older build the project already had). It now resolveschromium.executablePath()from the exact same@openvidstudio/captureimport real capture uses, checks that path exists, and on failure names the exact resolved (missing) path and the fix command. Verified: passes with browsers only under a customPLAYWRIGHT_BROWSERS_PATH; fails with the right path and fix named when that directory is empty; passes again once the same build is copied there. -
Four small ones:
Drop,Bong, andSoftClickare now exported from@openvidstudio/core's index; they existed insfx.tsxbut weren't exported, so importing them threw a React error.- README's tool count corrected to the real registered count (33, 34 with Higgsfield);
a new check in
manual-test.mjsfails if the two ever disagree again. find_music_bedfetches only the requested catalog whensourceis"lofi"or"cc0"; with"any"(the default), each catalog is fetched independently, and one failing no longer fails the whole call; the result carries awarningsentry naming what degraded, falling back to a stale cached fetch of the failed catalog when one exists. (The earlier report of open-lofi's catalog.json 404ing was a sandbox proxy artifact; it's reachable on a normal network.)- New
render.browserExecutableconfig:render_videopasses it to Remotion as--browser-executableso a render reuses an already-installed browser (typically the same Playwright Chromium capture uses) instead of Remotion downloading its own, which fails with no network.preflightchecks the configured path too, separately from its Playwright Chromium check.
-
docs_driftno longer treats a reference screenshot's pixel size as the CSS viewport to re-capture at. A 1440x900 capture at the default 2x scale is a 2880x1800 PNG on disk; re-navigating to a 2880-wide viewport is a different, much wider layout, not the page that was actually captured, so every default-scale capture reported 26-40% drift on pages that never changed.capture_screenshot,capture_screen_recording, andrelease_diffnow write a<asset>.capture.jsonsidecar (url, viewport, deviceScaleFactor, zoom, interactions, capturedAt) next to every capture;docs_driftresolves the viewport to re-capture at from an explicit binding, then that sidecar, and only falls back to a narrow heuristic (the reference's pixel size is exactly double a common CSS viewport) for a legacy asset with neither, otherwise asking for an explicit binding instead of guessing. Verified: v1.1 captures compared against v1.1 itself report 0 drift; compared against v1.0, only the route an actual code change touched reports drift (9.70% changed), everything else 0. -
extract_brandnow finds a palette on a repo with no CSS design tokens, if the logo carries one. It previously resolved nothing on such a repo even when the logo SVG visibly carried the brand's real colors. When no CSS token or Tailwind config yields a palette, it now parses the chosen logo SVG's fill colors, ranks them by pixel area, and maps the top ones to accent/accentAlt/success/danger (plus a background token for an exact full-bleed rect), naming the logo as the source. Fonts also now fall back to@fontsource/*package.json dependencies when no CSS variable ornext/fontimport named one. Verified against this repo's ownbrand/logo-lockup.svg: resolves#8b5cf6,#f87171,#3b82f6,#2dd4bfon#0a0a0a, each attributed to the logo. -
Renders no longer require network access.
TerminalReplay,RepoCta, andChecklistPaneleach called a@remotion/google-fontsloader at module scope, and every scaffolded scene imports the core index, so any render with no route to fonts.gstatic.com (an offline machine, a corporate proxy) crashed before producing a single frame. Inter (400-900) and JetBrains Mono (400, 700) now ship as bundled woff2 files (templates/default/public/fonts/, sourced from@fontsource/interand@fontsource/jetbrains-mono, regenerate withpackages/core/scripts/gen-fonts.sh), loaded by a newpackages/core/src/localFonts.tsviaFontFaceplusdelayRender/continueRender. Every network font loader is gone from core. Verified with a real render, network blocked, of all three components: the delayRender handle clears in under 100ms per frame and the output shows real Inter/JetBrains Mono, not fallback glyphs. CI gained anoffline-renderjob (scripts/offline-render-check.mjs) that fails if this regresses. -
render_video's incremental path no longer drifts audio out of sync with video. Each per-beat segment's AAC track ran slightly longer than its video (encoder padding), and stream-copying every segment's audio under one concat pass stacked that drift beat over beat: a 16-beat, 5400-frame video came out ~0.85s audio-behind by the end. Video now concatenates from per-segment files with their audio isolated into a fresh container first (a segment's own container duration otherwise still reflects its longer audio track even after dropping the audio stream, so the old approach left a silent gap at every join instead of drift); audio is decoded to raw PCM per segment, trimmed/padded to exactly duration/fps samples, concatenated as samples, and encoded to AAC once at the end. Verified on a synthetic 16-beat composition: joined video and audio both land within 1ms of frames/fps, and a transient placed at a known offset lands within 1 frame of its expected time. -
capture_screen_recordingat the defaultdeviceScaleFactor(2) no longer leaves 3/4 of the frame grey. Playwright'srecordVideo.sizeis in the same CSS-pixel units asviewport, not physical pixels, and it only ever scales a painted frame down to fit that size, never up. The recording context was asking for a canvas pre-multiplied bydeviceScaleFactor, i.e. larger than the page ever paints into, so the extra area was never filled.recordVideo.sizeis now the unscaled viewport;deviceScaleFactoralone drives the actual resolution Chromium renders at, same ascapture_screenshot. Output dimensions change for anyone already calling this. A 1280x800 viewport at the defaultdeviceScaleFactorused to produce a 2560x1600 file with content in one quarter of it; it now produces a filled 1280x800 file. Verified by recording a four-quadrant page before and after: before, three quadrants read flat grey (rgb 128,128,128); after, all four carry their real colours.
Added
-
Director mode. At the peak and mid creative levels the agent works as a director, not a template filler: a creative brief first (
write_brief), a design system as code (defineDesignSystem), an original score (compose_score), scenes written from core's primitives, a mandatory review loop (render_stills), render-mode quality gates (validate_sceneswithrender: true), a facts pipeline (collect_facts), and a final assembly (finalize_video,write_description). Production modes: peak, mid, literal.compose_scorecomposes an original, arc-matched score from the brief's music plan, with a cues file exact by construction, mastered to -15 LUFS under narration or -14 for music-only (modeis required, no default), and numeric checks an agent can read: band balance, clicks, per-section loudness, a spectrogram. Needs Python with numpy, scipy, soundfile, pyloudnorm and matplotlib.useMusicClock(beatId)in core gives a sceneat(bar, beat),beatPulse(),onBeat(),onDownbeat(),impact(label)andsectionAt(frame)as scene-local frames.stitch_compositioncopies the bed's cue grid next to the composition and mounts the provider.plan_beatsgainssnapToBarsto land every beat boundary on a downbeat.DirectedShotin core turns a flat capture into a push-in with tilt, depth of field and a cursor.Words,DotText,PixelLogo,PixelWipe,Atmosphere,DotGrid,Flash,Black,Letterbox,ChapterHud,Window,JsonView,ToolChip,Pill,Check,Cross,Glass,useTypedandCaretare documented in API.md with a real still each.scaffold_scenegainsmode: "template" | "starter" | "blank", and a beat'sscenefield pointsstitch_compositionat any scene file.render_stillstakesperBeat(a number, or"review"for five frames per beat that straddle every cut), exactframes, andround; it writes a labelled contact grid and returns the numeric findings, so an agent without image input can review.validate_sceneswithrender: truemeasures cropping, text size, reading time (3.5 words/s plus 0.8 s, shared withvalidate_beats), dead frames, em dashes, contrast against the rendered pixels, the title-safe area, HUD overlap and whether the brand palette reaches a frame. A brief may mark a beatdeliberateHold. Withoutrender, a static check warns about numbers typed into a scene's copy instead of read fromfacts.json.write_descriptionwrites YouTube chapters from the beats and a credits section read from disk, andfinalize_videoalways writes it.finalize_videoalso normalises loudness to -14 LUFS / -1.5 dBTP and writes a poster and a size-capped share copy.CREATIVE.md(principles),TOOLS.md(generated), and theopenvidstudio-directorskill, mirrored to the six agent skill directories.
-
A per-tool test suite.
scripts/manual-test.mjsis now a runner over one file per tool inscripts/tests/, each runnable alone. CI runs the ones that need only node. -
The tool list and every tool-count claim are generated from the server's registry (
scripts/generate-tool-docs.mjs), and CI fails when they drift. -
render_videoacceptsasync: true, and a newjob_statustool. A real render can run 40+ minutes, well past an MCP client's own request timeout (Claude Code'sMCP_TOOL_TIMEOUT, or the SDK client default), which killed it before it finished. Input is still validated synchronously (a bad call still fails immediately), then the render starts detached and the call returns{ jobId, estimate }right away.job_statuspolls for progress (frames rendered, current beat, a log tail) and the eventual result, the same shape a synchronous call would have returned. The synchronous path also now emits real MCP progress notifications for a client that attached aprogressToken. Verified with a real render through a client enforcing a 3000ms timeout on the initial call: it returned in under 3s with a jobId, and polling reachedstatus: "done"with a correct result. -
Music-only videos:
narration: "none",stitch_composition'smusicBed, andoverrides.tsx.validate_beatsrequired every beat'svoand held it to the spoken words/sec budget, so a text-and-music video with no narration at all could never passwrite_beats_file.beats.jsongains an optional top-levelnarration: "voice" | "none"("voice" assumed when omitted, so every existing file stays valid); under"none",vois optional and unpaced, and a new optional per-beatonScreenTextis checked against a reading-time budget instead (seeSCRIPT.md).stitch_compositiongainsmusicBed: { file, volume }(volume defaults to 0.18 with narration, 1.0 without), and importssrc/videos/<name>/overrides.tsxwhen present as a dev-authored extension point it only ever references, never writes, so a hand-added layer survives a re-stitch. -
deterministiconcapture_screenshot/capture_screen_recording, for a capture that is the same twice. Opt-in (default off): freezesDate.now()/new Date()(page.clock.setFixedTime, which "keeps all the timers running" rather than pausing them, sosettleand any real animation are unaffected) and seedsMath.random()via an init script, both before the page's own scripts run. Verified the way the issue asked: a page that printsDate.now(),Math.random()and eight randomly sized bars captures to two different PNGs with the flag off and to two byte-identical PNGs with it on. Screenshots are the provable half; an MP4 also depends on x264's own run-to-run behaviour, so a recording is closer to reproducible than it was, not guaranteed bit-exact. Does not force a CSS/JS animation to a specific phase -- that stays a separate, harder problem.visual_regressiongained a matchingexactflag that gates on any pixel difference (pixelThreshold/changedRatio/meanDeltaall 0, compared at the baseline's own resolution instead of downscaled), meaningful once the beats being diffed were captured withdeterministic: true. beats.json'sCaptureVisual.deterministicmirrors the same flag on the manifest. -
catalog_motion_primitivesandadd_motion_primitive, a first batch of reusable motion pieces.scaffold_scene's 10 templates are whole scenes; below that there was nothing reusable, so a beat that wanted a count-up number or a fanned card layout got that animation hand-derived frommotion.ts's tween/spring math again every time (thestattemplate's own counter is a literal hardcoded"0"for exactly this reason).catalog_motion_primitivessearches a small, bundled, local registry (a count-up stat card, a fanned card stack, a logo outro, a typewriter line, a beat-timeline strip) by free-text query;add_motion_primitivewrites the named entry's real.tsxsource intosrc/videos/<videoName>/primitives/, ready to import into a scene unmodified. No hosted tier or account, same as the built-in SFX pack. -
openvidstudioandbgmusicskills now mirrored to every tool directory, not just.claude/skills:.agents/skills,.cursor/skills,.github/skills,.opencode/skillsand.windsurf/skillsnow carry them alongside the vendoredremotion-*skills.scripts/sync-skills.mjsgenerates the copies from.claude/skills(their one source of truth) and a--checkrun is wired into CI, so a skill added later can't silently stay.claude-only again. -
find_music_bedandimport_music_bed, real music without a licence problem. Two CC0-1.0 catalogs are searchable from the server:btahir/open-lofi(166 lo-fi tracks with category metadata) andSoundSafari/CC0-1.0-Music(~9,200 tracks aggregated from freepd, chosic, Free Music Archive, freesound and Pixabay).find_music_bedmatches mood words against titles and categories and caches its index atoutput/music-index.json;import_music_bedfetches the chosen track, trims it from a start point, fades both ends and normalizes to a LUFS target so the bed sits under narration, then writes a<file>.source.jsonand aMUSIC-SOURCES.mdrow. Both catalogs are public domain, so that record is provenance rather than an attribution obligation. Nothing is vendored: open-lofi ships its tracks only inside a 554MB release zip, so an import reads the zip's central directory over HTTP range requests and pulls just the chosen member's bytes. Abgmusicskill in.claude/skills/covers when to reach for this over the synthesized ambient bed. -
Three CC0 sounds join the built-in pack.
bong.wav,drop.wavandclick_soft.wavcome from Kenney under CC0 1.0, converted to the pack's 48kHz mono format, withSoftClick,BongandDropcomponents in@openvidstudio/coreand entries inplan_sound_effects. The pack stays free of any obligation, synthesized or public domain, andpublic/sfx/CREDITS.mdnow records which file is which and why. -
plan_music_cues: beats and strong cues, in seconds, for a music track. Reads the cue gridcompose_scorewrites next to a track straight off disk. For any other track it decodes the audio and estimates a grid from an RMS onset envelope and one autocorrelation pass, caching the result next to the track as<stem>.cues.json. SeeSTYLE.md's new "Beat-locked cuts" section for how a scene cut should use the result. -
export_renditiongains a"poster"format. Picks a beat's midpoint (or an exact timestamp) as the video's poster frame, writes it as a jpg, and, unless disabled, writes a second copy of the render with that frame baked in as the file's embedded thumbnail (anattached_picstream, copied not re-encoded, so quality is untouched). Fixes the default idle thumbnail being whatever a video's first frame happens to be, usually black or mid-transition. -
tone, an optional top-levelbeats.jsonfield. One ofSCRIPT.md's new Tone presets (default,release,walkthrough,quiet), read by nothing but documented so a multi-session video's narration reads as one register instead of drifting beat to beat. SeePLANNING.md§7. -
@openvidstudio/capture, a package of its own. Zoom compensated browser capture, interaction replay, and the desktop, mobile and terminal backends, with no dependency on Remotion, React, or@openvidstudio/core. The hard, defensible part of this project is getting a true frame out of a running product, and it was welded to a video pipeline, so a team that wanted reliable capture and no video had to take everything or rebuild the hard part.@openvidstudio/mcp-servernow consumes it. -
Per-beat render caching.
render_videogainsincremental: every beat renders to its own segment underoutput/segments/<video>/, and the segments are joined with ffmpeg's concat demuxer under stream copy, so a reused segment's bytes reach the final file untouched. A segment is reused only when the beat's JSON, its scene source, every artifact it references, its narration, and the project-wide inputs (brand tokens, the generated composition, the music bed) all hash identical. This is what makesdiff_beatsactionable: editing one caption in a ten beat video re-renders one beat. -
visual_regression: each beat's midpoint frame against a stored baseline, with a per-beat verdict, a diff image, and markdown ready to post on a pull request. The first run writes the baseline instead of failing.templates/defaultships the workflow and the script, rendering in draft and incremental mode. -
docs_drift: re-captures the pages a video's beats came from and reports which clips have gone stale. Bindings are inferred from the manifest, since a browser screenshot beat already records its url and interactions. -
release_diff: given two refs and a running instance of each, works out which routes changed, captures both sides, drops the routes that did not actually change on screen, and drafts a before/after manifest. The filtering is the point: a what's-new clip showing four identical pages is worse than no clip. -
export_rendition: a README GIF through a real two-pass palette, a docs screenshot set with an index built from each beat's own narration, or App Store and Play Store frames at the exact dimensions each store demands. All three read the finished render, so the docs images cannot drift from the video. -
reformat_vertical: a 9:16 cut with the crop chosen per beat from the manifest. A terminal beat crops left, because a terminal is text pinned to the left margin and centring it cuts the command in half; a mobile recording is fitted and padded rather than cropped. Overridable per beat withvertical.focusorvertical.crop, both validated. Active speaker tracking is deliberately out of scope. -
plan_shots: ranks what a demo should show from the repository's own emphasis, with the evidence for every entry: README order, the newest changelog section, which routes exist and how often the docs link them, whether the package ships a command, and where the recent commits went. It feedsPLANNING.md's intake rather than replacing it. -
Multilingual narration.
generate_narrationtakeslanguages, reading translated lines from each beat'svoTranslationsand writing every non-base language topublic/audio/vo/<lang>/<beatId>.mp3.stitch_compositionemits one composition per language over a single shared scene tree, withrequireNarrationenforced per language. Theomnivoiceengine covers 600+ languages zero-shot and, given a reference clip, uses one cloned voice across all of them. Translations are not drafted here: a translation is content and goes through the same approval the original script did. -
Wayland capture.
capture_desktopreadsXDG_SESSION_TYPEand routes a Wayland session through ffmpeg'spipewiregrab, checking the local ffmpeg actually has the filter first. Wayland has no window-title selector, sowindowis refused there rather than silently capturing the whole screen, and the portal consent prompt is documented rather than engineered around. -
A real pty for
capture_terminal, when the optionalnode-ptymodule is installed, so a program keeps its colour and cursor addressing. Falls back to pipes otherwise, and reports which mode produced the cast. -
A programmatic export surface on
@openvidstudio/mcp-server, for callers with no agent in the loop (CI, cron). Deliberately short: the tools that draft or write a manifest need an agent's judgement and a human's approval. -
Capture without a browser. Three new tools remove the assumption that filmable software has a URL, which previously forced every beat about a desktop app, a phone, or a CLI to degrade into a hand-authored
dom-demopanel:capture_desktoprecords a window, a region, or a whole display through ffmpeg, using the right device per platform (gdigrab on Windows, avfoundation on macOS, x11grab or pipewiregrab on Linux depending on the session type). Output is forced to even dimensions, since h264/yuv420p cannot encode an odd-width region and a hand-picked rectangle very often is one.capture_mobilerecords an Android device via adb screenrecord, or an iOS Simulator viaxcrun simctl, remuxing both so Remotion can seek them. Refuses Android recordings over 180 seconds rather than returning the silently truncated filescreenrecordwould produce.capture_terminalrecords a real command run as timed text rather than pixels, writing a JSON cast topublic/terminal/<beatId>.jsonthat replays throughTerminalReplay, so one capture stays sharp at any resolution and picks up the brand palette. It uses a real pty whennode-ptyis installed and pipes stdout/stderr otherwise, reporting which.
-
sourceon a recording beat:"browser"(default, so every existingbeats.jsonis unchanged),"desktop","mobile", or"terminal", each with its own validated required fields. SeePLANNING.md§4.6. -
existing-assetcaptureMethod, for real screenshots a project already publishes.attributionis required: an unattributed borrowed frame reads as a real capture, which is the exact dishonesty the rest of the pipeline is built to avoid. -
Brand-lock gate:
render_videonow refuses to run unlesssrc/brand.tsexists (written byextract_brand), so a video never ships wearing openvidstudio's own default navy/Inter look by accident. PassskipBrandLock: truewhen a project deliberately has no brand to extract. Result now also reportsbrandLocked. -
PRESETS.md: fixed recipes over the existing tool chain for five common video shapes. gif-demo (single-beat, no narration/music, capped under 15s, real capture only, GIF output via the CLI's extension-based codec inference), screenrec-only (one continuouscapture_screen_recordingspanning the whole flow, one beat, no per-beat choreography, light captions), remotion-only (no capture step at all, puredom-demo/higgsfieldmotion graphics), 2min-demo (fixed 6-beat structure capped at 2:00, narration+captions mandatory), 5min-demo (extends the real six-chapter structureEA/launch_plans/TODO.mdproved out for the project's own launch video into a fixed ~5:00 beat arc for any project). A sixth candidate, vertical reformat with active-speaker tracking, is flagged as real and validated (r/reactjs feedback) but only half scoped: the manifest-driven crop shipped asreformat_vertical, and active-speaker tracking stays out, because it needs real face and audio analysis rather than a recipe over the existing tools. -
diff_beats: compares an editedbeats.jsondraft against the version already on disk and returns a per-beat rerun plan (added/removed/unchanged/changed, which fields changed, whether capture orscaffold_sceneneed to rerun for that beat, whether its output files exist on disk). Read-only, writes nothing. Makes editing one beat in an already-built video a targeted rerun instead of redoing the whole pipeline. Superseded in part byrender_video'sincrementalmode above: the whole-videorerenderNeededflag is now something that can actually be acted on per beat rather than only reported. -
Two optional per-beat
beats.jsonfields,transition("cut"|"whip"|"fade","cut"assumed if omitted) andartifacts(screenshotPath/recordingPath/voPathoverrides of the convention output paths), both validated byvalidate_beats. First step of hardening the manifest into a fully reviewable product boundary: a dev can now readbeats.jsonalone and know the transition and asset path for every beat, without opening capture.ts or relying on the path convention from memory. SeePLANNING.md§4.5. Every existingbeats.jsonstays valid unchanged, both fields are additive. -
templates/defaultnow ships the official Remotion agent-skills bundle (remotion-best-practices,remotion-markup,remotion-captions,remotion-render,remotion-saas,remotion-multimedia,remotion-interactivity,remotion-maps,remotion-studio,remotion-upgrade,remotion-create,remotion-docs) for Claude Code, Cursor, Windsurf, OpenCode, and GitHub Copilot, so a coding agent writing scenes in a scaffolded project has the same Remotion knowledge base an agent working directly in a hand-built Remotion project would. -
public/imported_audios/: a scaffolded project's own folder for sound effects or music dropped in by hand or fetched viaplan_sound_effects, kept separate from the synthesized, rights-free pack inpublic/sfx/so it's always clear which sounds carry licence terms. Documented inNARRATION.mdandPIPELINE.md. -
Cinematic camera. A shot vocabulary for
DirectedShot(shot="pushIn" | "pullOut" | "pan" | "whip" | "orbit" | "dutch" | "hold" | "drift", explicit props still win), handheld sway that eases to exactly zero inside hold windows and an impact shake that decays (handheldOffset,useHandheld,impactShake,useImpactShake),LayeredShotfor a capture split into depth layers, andMotionBlur, aCameraMotionBlurthat is only mounted while the camera moves (frames outside the window are byte-identical to a render without it).Layeris now true Z parallax from the rig's perspective instead of a fixed offset, and takes its centre from the stage rather than 960 by 540.CursorActorsegments arc slightly and overshoot on arrival.E.sineandE.expojoin the easing set. Adds@remotion/motion-blur4.0.534. -
beat.transitionnow does something.cut,whip,fade,push,wipeandlight-leakare drawn by aTransitionOverlayover the cut, so every beat start, duration and audio join is unchanged and a file with only cuts is generated byte for byte as before. Measured on real renders: at the cut afadeframe has mean luma 13.8 against 72.9 for the plain cut, and frames 20 before and after the cut are 0.000% different. -
capture_screenshotlayers. Pass selectors and get a background with those elements hidden, one transparent PNG per element and a manifest (<name>.layers.json) in capture pixels, whichLayeredShotreads. Compositing them back reproduces the screenshot (mean error 0.0010 of 255, worst pixel 2). -
render_stillscrop. A higher-scale look at a region next to each still, for checking small text. -
validate_beatswarns about a metronome. Four or more beats whose longest is under 2.5 times the shortest read as uniform; the result now carries an always-presentwarningsarray. -
Native 9:16. A project can be 1080x1920 from
init_project'svideoConfig, and the core primitives follow the composition instead of assuming 1920x1080:CinematicScene's stage,DotGrid,Letterbox(bars are 64/1080 of the frame height) andRepoCta's camera.defineDesignSystemtakes astageoption, so the grid margins scale to the frame (84/231/196 at 1080x1920; 150/130/110 at 1920x1080 exactly as before).validate_scenesjudges framing against the project's own frame size, so a 1500px block is an error in a 1080 wide frame and fine in a 1920 one. Landscape renders are byte-identical to before (checked on dots, bars and the end card). -
export_renditionreelsformat. 1080x1920, H.264 and AAC, 30 fps, faststart, from a native vertical render. A 1920x1080 or 4:5 source is refused with nothing written and a pointer to rendering natively vertical or toreformat_vertical. An unknown format is now an error instead of silently falling through to store frames.
Changed
-
preflightasks for Node 20, not 18. The packages declare>=20and CI runs 20, so a Node 18 machine passedpreflightand then failed later. The check is nowcheckNodeVersion, exported and tested. -
Depth of field is off by default and never on text.
DirectedShot'sdepthOfFielddefaulted to 6, which blurred product text outside a small ellipse. It now defaults to 0; when it is on,protectrectangles stay sharp (edge energy 1.000x against 0.006x unprotected), the cursor is never blurred, andrackToslides the sharp region. This is what the eval reviewer called smeared text. -
Captions settle, move and fit.
Captionhasposition(bottom,top,left,right,auto) andavoid, finishes its reveal within 8 frames ofatwhatever the word count (a 14-word caption used to take 31), and sizes to the composition. The scene templates vary where it sits and cap the browser-capture push at 1.6. -
render_video,contact_sheet, andqc_extract_framesnow write tooutput/by default instead ofout/, a more discoverable name for the folder a finished render, contact sheet, or QC stills get picked up or uploaded from.output/ships with its own README and stays gitignored (renders are build output, not source). -
plan_sound_effectsnow saves and checks imported sounds inpublic/imported_audios/instead of alongside the built-in pack inpublic/sfx/; itssfxDirresult field is renamedimportedAudiosDir. -
Typing sound is now one recording.
typing-effect.mp3, a copyright-free typing recording, replaces the four synthesized key sounds.TypingSfxplays it across each typed run instead of firing one sample per character, and loops it for long runs. The rest of the built-in pack is synthesized or CC0, so a rendered video still carries no audio obligations. -
compose_scorehas no percussion by default. The score used to fire a tick on every beat in tension sections, an off-beat hat in every groove bar, a four-on-the-floor kick, a clap roll and a sixteenth-note arp on a fixed grid. Under a voiceover that reads as a ticking clock.percussionnow defaults to"none"(no kick, clap, hat or tick; the bass and arp play uneven rhythms with small timing, level and decay changes), and"full"keeps the old arrangement as an opt-in. A newchecks.percussionTickmeasures periodic transients above 5 kHz andcompose_scorewarns when it finds them. -
finalize_videowarns when the final audio ticks (audio-tick). The same measurement runs on the mixed audio, so a ticking track you brought yourself is flagged too. -
TitleSlam's per-word blip is off by default (sfx={false}). A blip every four frames is a rapid tick under any title. Click and checklist sounds stay on, since they follow real events. -
stitch_compositiononly auto-picksmusic-bed.mp3. A leftoverpulse-bed.mp3in a project is ignored unless you pass it asmusicBed.file.
Removed
key_a.wav,key_b.wav,key_c.wavandkey_enter.wav, with theKeySoundandEnterKeycomponents that played them. UseTypingSfxfor typed text.pulse-bed.mp3, its cue file andgenerate-pulse-bed.mjs. It was a kick and hat click track and the default bed, so it put a ticking clock under most videos. Usecompose_score,find_music_bed, or the ambientmusic-bed.mp3.
[1.0.0] - 2026-09-02
Initial public release: the pnpm monorepo, the MCP server, and the public site, built end to end.
Added
- pnpm monorepo scaffold, with
@openvidstudio/coreextracted from the original private vidstudio pipeline and atemplates/defaultproject shell. @openvidstudio/mcp-server: the MCP server itself, exposinginit_project,validate_beats,write_beats_file,scaffold_scene,stitch_composition,render_video, andqc_extract_frames.capture_screenshotandcapture_screen_recordingtools, with zoom-desync compensation so a capture comes out pixel-accurate regardless of a Playwright profile's per-origin zoom level.import_higgsfield_clip, gated behind a project's ownhasHiggsfieldconfig flag, plusHIGGSFIELD.mddocumenting that tier's scope and prerequisites.apps/site: the public Next.js site, built directly frompackages/docs/*.mdrather than a hand-copied duplicate./docs, rendering all seven pipeline docs from their real source files./gallery, with a real sample video built end to end by the pipeline itself (init_projectthroughrender_video), not hand-edited.- Landing page: hero, feature tiers, and quickstart, with a composition pass tying every section to the rest of the site.
Fixed
- Windows
npx.cmdspawnEINVALinrender_video. templates/default's tsconfiglibmismatched againstpackages/core;@openvidstudio/core's package.jsontypefield.@openvidstudio/corenow vendored (not monorepo-relative) so a scaffolded project actually installs standalone, outside this repo.beats.json'sInteractionschema unified across tools; a failing worked example inPLANNING.mdcorrected.- An undisclosed second hero gradient removed from the landing page.
/docs' doc list now derived from a real directory read instead of a hardcoded list, so it can't silently go stale againstpackages/docs/.- Final-review findings across
apps/siteand themcp-servertest suite.
Changed
- Root workspace
workspacesfield corrected; em dashes stripped from@openvidstudio/core's comments. - Asset conventions (screenshot, recording, VO, music-bed paths)
documented directly in
PIPELINE.md;BrowserFrame's provenance claim corrected to match what it actually renders. - vidstudio's original private protocol docs ported into
packages/docs/and generalized for a public, multi-project audience.
Docs
- Review findings addressed across the doc set; the Motion.so comparison note restored; minor cross-reference fixes.
Tests
- Real
render_videoinvocation tests added to the mcp-server test suite.