Skip to content

bartlomiejborzucki/social-video-agent

v2.10.1Apache-2.0

Create project-aware social clips through persistent stages, deterministic FFmpeg editing, and planned Remotion motion design.

Changelog

All notable changes follow Keep a Changelog and semantic versioning.

[Unreleased]

[2.10.1] - 2026-11-04

Fixes from reviewing 2.10.0 before its release.

Fixed

  • A text story never passed brand QA when the project's speech captions are phrase, lit or dimmed: its centred sentences were compared with those settings. Rendering, the render evidence and brand QA now use the story caption style.
  • With step_numbers, the number joined to a cue's first word ("2. Krok") hid that word from at_word anchors and from the punch_in_words check.
  • An end_teaser with its own start and end was moved to the last 4 s; only a teaser without times (or with from_end) is placed from the end.
  • reel-check flagged phrase cues the caption builder made itself: the opening cue that carries the whole first sentence, and cues held through a pause. It now measures how long a cue's words take to say, after the first.
  • hook check kept failing an announced list after a list, steps or process was added; it now reads the motion plan.
  • A word starting with a numeral ("Trzymaj") counted as a number in the hook title check.
  • deliver --description-template wrote over a template already filled in; it now refuses.
  • motion accept failed on an effect proposed before the hook title existed and running under it; that candidate is skipped and named.
  • The tear effect left a strip of paper on its last frame, and a tilt on a landscape frame showed black corners.

[2.10.0] - 2026-11-04

Forms of a restless talking head, all opt-in: a title over the speaker, a teaser instead of an end card, phrase captions, picture effects on a static camera, a second shot under a continuing voice, props on their word, and text stories without speech. Nothing changes for a project that does not ask for them.

Added

  • hook_title motion element: the reel's title over the speaker from about 0.4 s, two lines at most in capitals, entering word by word or with a pop, on accent marker fields (title_style: highlight), a pill or plain, with a subtitle box (secondary_text, series: {part, total}) and emphasis_words. It is not a card: opening QA never refuses it and warns when it enters after 1.0 s; hook check reads it (at most 8 words; a question, an instruction, a number or a choice). title_font and title_font_file set its font apart from the captions'.
  • end_teaser motion element: what comes next over the speaker in the last from_end seconds (default 4), placed before any held end card. The new end_style config key (overlay, end_card, both) is checked by brand QA against the new ending_applied render evidence.
  • caption_style.mode: phrase: cues cut where the sentence breaks (any comma, before a conjunction, at most 2.5 s on screen), never lit word by word. outline_width_pct draws the outline as a share of the font size, outside the glyph; step_numbers puts "1." / "2." in front of a cue that starts a step. The punchy-caps brand profile is a test variant.
  • Picture effects in the motion plan: frame_in_frame, device_frame, split_mirror, triptych, whip_blur, tear, bw_flash, halftone_bg, speed_lines and tilt, drawn by Remotion from CSS and SVG. Never in the first second or under the hook title, one at a time, at most 0/2/4 per 30 s by motion_energy, and only when the new motion_effects key allows (off, manual, auto). Without segmentation, halftone and speed lines are a translucent layer over the whole frame, recorded as mask: none.
  • insert motion element: a second shot over the whole frame for 0.5-2.5 s while the main recording's voice goes on, cut or whipped in, with rights.
  • prop motion element and a project prop library (assets/props/ with props.yaml, or prop_library): a large picture on its word, refused without rights or with a child's face; props_applied records each file's hash.
  • punch_in_words: motion suggest proposes a push in on each listed word (PunchIn now takes at_word), and motion qa notes one said without.
  • motion suggest proposes effects on sentence boundaries every 6-10 s under motion_effects: auto, and props by their keywords.
  • Text stories: brief story turns "Sentence." -> clip.mp4 lines into an edit plan with mode: text_story; compile builds muted shots and one centred caption per shot (story_caption) without transcribing anything.
  • deliver --description-template writes publish.template.md: the hook, body, share ask (share_cta), credibility line (author_signature) and source in order, the rest as [[placeholders]] that --publish refuses.
  • hook check suggests a list with item_at_words when "3 kroki" or the like is said in the first 5 s.

Changed

  • opening-qa adds advisory opening_warnings: no word heard in the first 0.5 s, or a hook title after 1.0 s. The delivery manifest carries them.
  • reel-check warns when the last 4 s have no call to action, teaser, end card or spoken ask, when a phrase cue stays over 2.5 s, and when a text story does not end on a question.
  • motion qa counts effects as picture changes, reports effects, shakes and transitions per 30 s and the gaps between effects, and warns on an effect that starts mid-sentence or a loud one on a sensitive reel.

[2.9.0] - 2026-10-29

Fixes from editing with 2.7.1 and 2.8.0: a Claude Motion end card that keeps its end, sound effects and caption fixes that survive a recompile, and end cards that follow one another.

Changed

  • Several end_cards with hold: true follow one another after the last word (an explanation, then a call to action), up to 10 s in all, instead of the second one being refused. The earlier card stays under the next one while it enters, and QA counts the whole run as the end of the reel.

Fixed

  • A Claude Motion scene imported in place of a held end_card lost its end: motion film import takes the card out of the plan, and the next render or compile removed the hold, so the film started after the last frame. The hold now stays while a motion_film overlay starts where the speech ends and lasts the hold (within 0.05 s). The transition budget is counted over the whole reel again: after the import it was counted without the end card and refused transitions the same plan had passed before.
  • A sound effect on the entrance of a held end_card broke the edit: motion sfx failed with "sound effect at … lands after the … output" when the end of the speech rounded up, and any effect in the hold was refused. Effects are now checked against the whole edit, hold included; the effect on the card's entrance moves with the card, and one the shorter edit no longer plays is dropped with a note.
  • compile --force silently dropped every captions fix. Each fix is now recorded in the caption track; a rebuilt track (a recompile or an opening variant) gets it back, and compile lists any fix whose words the new edit no longer says.
  • SRT files had no blank line between captions, which some platforms reject.
  • A range moved to the end of the speech could run past the end of a phone recording's picture: the render came out shorter than the EDL, later shots moved and a held end_card was refused. compile now holds the last frame under the remaining words (up to 0.75 s) or ends the range with the picture, and warns.
  • Two brief rows with the same label crashed compile with a pydantic error (elements.0.end Input should be greater than 0) and left edl.json without captions. Rows of one label that meet are now one label; two different labels on one scene get a plain error, and the EDL is written only after the labels fit.
  • motion sfx placed the built-in click about 20 dB under the speech, where it could not be heard: a quiet effect is now measured and raised to the level of the built-in whoosh. It also skipped the scenes of an imported Claude Motion film; each now gets a sound like any graphic entering.
  • A first line said off screen and written in quotes in the brief was captioned with its quotes. The pair around the whole line is now dropped; quotes inside the line stay.

[2.8.0] - 2026-10-28

Graphics on the spoken word: each accent illustrates what is said at the moment it is said, within the same accent budget.

Added

  • item_at_words on list, steps and process: each point enters on the word that names it. motion suggest anchors the steps it proposes; compile and the render move every anchor to its caption word and print each move. A word the captions do not hold is an error.
  • caption_style.upcoming_word_opacity (0.3–1.0, default 1.0): the words of the caption on screen that are not spoken yet are drawn dimmed, with the active-word highlight, emphasis marks and rounded_lines. Remotion only; the FFmpeg route refuses it and brand QA checks it was drawn.
  • strike: a habit crossed out in the accent on one word and replaced on another, with an optional scissors or cross mark.
  • bubble: a speech bubble (tail at the speaker or off frame) or a message with a notification dot, popping in over the head on its word.
  • icon: a small flat icon over the head on the word that names it, from Tabler Icons (MIT), downloaded once at a pinned version and hash. The render manifest records set, version, licence, source and file hash (icons_applied). A project maps words to icons in icon_dictionary. At most 0, 2 or 4 per reel by energy.
  • process: 2–5 stages in the brand colours entering on their words, a pointer through them and a closing state with a tick.
  • kinetic_word: one key word built letter by letter with sparks in the brand colours; one per reel, never at calm.
  • inset: a project picture or short clip as a card over the speaker, with its rights declared and never a child's face; motion qa warns when it hides a talking mouth for more than 2 s.
  • custom: a Remotion component written for one sentence in motion/custom/<id>.tsx, given only the brand tokens, its zone, the safe margins and its words' times, and allowed only react and a list of remotion names. Every render checks its imports, forbidden calls and types (tsc --noEmit); one that fails is left out and recorded in custom_rejected, and the rest of the reel renders. motion custom init writes the template; motion custom check checks it and renders its range to qa/.
  • motion suggest proposes a strike for "zamiast X powiedz Y", "nie mów X", "lepiej: Y"; a bubble for a quote ("mówi: …", "mówi, że …", a line the brief quotes); a process for "najpierw… potem… na końcu"; and an icon for a word in the icon dictionary. The Polish words are in a data file a project replaces by category with .social-video/accent-words.yaml.
  • sensitive_topic: true in the motion plan refuses every playful graphic (stickers and the word graphics above) for a reel about death, illness or grief; motion suggest --sensitive proposes none.
  • motion qa measures the word graphics where they are drawn (top zone by default) against faces, the caption band and the scene frame, and counts each as an accent.

[2.7.1] - 2026-10-26

Fixes from the first manual run of reference analysis and opening variants.

Fixed

  • workflow status and workflow resume take the workspace as -w, and workflow complete N takes it as an argument, so every workflow command accepts both.
  • workflow status names the command that makes each missing artifact (next_commands in --json) and lists what the stage already has, so a resumed session does not have to guess how to write the motion plan or whether to compile again.
  • motion variants previews no longer fail when the EDL has sound effects after the preview's end; a first-shot variant moves the effects with the change in length.
  • An on-screen opening line is no longer hidden behind the first scene's label: the label gives way for the line's seconds, and changes records the text it replaced. A line too long for the scene frame's title tab is refused before any variant renders, not after the first one.
  • A first-shot span that ends mid-sentence is warned about, with the end that finishes the sentence; the old first shot's label is left off the new one and listed in changes; motion that runs past the first cut keeps its length.
  • A second motion variants run on another axis no longer loses the first set: each axis keeps analysis/hook-variants-<axis>.json, and previews are named opening-<axis>-<name>.
  • motion variants --full renders at final quality unless --quality says otherwise.
  • A variant equal to the edit's first caption is recorded as the control, with no change.
  • Preview voice repair is decided on the whole variant, so a 6 s preview is cleaned (or not) the way the full render is.
  • The light_leak transition takes the brand accent's hue; it was turned the other way (a turquoise accent came out violet).
  • A list draws its text as a title, like steps and charts.
  • compile --force warns when it drops sound effects from motion sfx.
  • style analyze --editorial says when the reference has no speech and which transcription model it used.

[2.7.0] - 2026-10-25

An editorial read of a reference video from stills and its transcript, and opening variants that change one thing and say what they test.

Added

  • style analyze --editorial: besides the rhythm, writes stills of a reference (the first frame, every 0.5 s of the first 3 s, just after each cut, the middle of each shot, the last frame), its timed transcript and the limits of that sample under context/reference/. The agent reads them in its own session and writes context/reference-analysis.json: timed observations, hypotheses that cite them, stretches to watch and what not to copy. style check-analysis checks every claim is anchored to a moment that exists and writes context/reference-analysis.md. No model API is called.
  • motion variants --axis text|first_shot: opening variants that change one thing. Each records a --hypothesis, the exact changes, what stays shared and the hash of its audio; the set keeps the hashes of the edit it varies. first_shot replaces the first range with another span of the footage, keeps the other ranges, rebuilds the captions and moves the motion by the change in length; --pickup records a variant that needs footage filmed first.
  • deliver --hook-variants runs opening QA on each variant's own file and records its axis, hypothesis and result, plus a caveat that two organic posts are not an A/B test.
  • MCP tools motion_variants, style_analyze and style_check_analysis; deliver takes hook_variants.

Changed

  • motion variants makes 6 s previews by default and names variants a, b…; render the chosen one with --full --only NAME. Previews are written as previews/opening-<name>-<N>s.mp4. --from-brief gives two lines (the brief's on-screen text and first spoken sentence); the generated question is gone.
  • A text variant that is not said word for word is drawn as on-screen text over the opening, never as the first caption; a line that is said keeps its real word timings. Text variants must share one audio track, or the run fails.
  • Rendering a variant no longer writes the workspace EDL or motion plan, and its Remotion base gets its own file instead of replacing the edit's base-<quality>.mp4.
  • hook check keeps only general Polish word lists. The concrete-situation list is empty by default and comes from the project's .social-video/hook-words.yaml, which can replace any list; a child: list there still reads as situation:.
  • A brief line said off screen (zza kadru, poza kadrem, off screen) is shown as text only unless the recording carries it, whoever says it. The fields are voice_off_screen and first_line_off_screen; briefs saved with child_off_screen / first_line_is_child still load.
  • Documentation examples are neutral and invented.

[2.6.0] - 2026-10-24

More of Remotion in the reel: TikTok-style caption boxes, hand-drawn emphasis, light leaks and animated emoji, built-in sound effects, and a faster render.

Added

  • caption_style.background_style: rounded_lines: one rounded box per caption line, joined where the lines meet, drawn with @remotion/rounded-text-box from the widths Python measured. Remotion only; the FFmpeg route refuses it.
  • caption_style.emphasis_mark: highlight | underline | circle: a hand-drawn mark (@remotion/rough-notation) on each emphasis word as it is spoken. Remotion only; the FFmpeg route refuses it and brand QA checks it was drawn.
  • motion sfx -w WORKSPACE without a folder uses a built-in set of CC0 effects listed by @remotion/sfx (whoosh, whip, mouse click), downloaded once into the cache and verified against pinned SHA-256 hashes.
  • animated_emoji: true on an emoji sticker plays its Noto Animated Emoji clip (@remotion/animated-emoji), downloaded once from a pinned commit into the cache. CC BY 4.0: the credit is recorded in stickers_applied and goes in the post description.
  • light_leak transition: a WebGL film light leak (@remotion/effects) over the cut, its hue turned to the brand accent.
  • SOCIAL_VIDEO_REMOTION_CONCURRENCY and SOCIAL_VIDEO_REMOTION_GL tune the Remotion render (browser tabs, OpenGL backend); unset keeps the defaults.

Changed

  • Music and sound effects are no longer gated: audio_bed and sound_effects need no license_confirmed, motion sfx no longer needs --licensed (still accepted), and music_policy/sfx_policy are recorded preferences that the render and brand QA no longer enforce.
  • Remotion 4.0.538: frames are encoded while later frames render (parallel encoding, on by default since 4.0.534).

Fixed

  • Remotion draws logos, plates and image stickers with <Img>, so a frame is never captured before its image has loaded.

[2.5.0] - 2026-10-20

A stronger, livelier opening without breaking the brand: no card at frame 0, no recognisable children's faces, colours and font from the contract.

Added

  • sticker motion element: an emoji from sticker_allowlist (drawn only from Noto Color Emoji, SIL OFL; recorded in the render manifest) or a project PNG/WebP/SVG, anchored to a spoken word (at_word, occurrence), animated with pop, shake, pulse or drop for 0.6-1.5 s; at most 0/1/2 per reel by motion_energy, never in the opening frame or on a full-frame card.
  • shakes in the motion plan shake the picture on a word, in both renderers.
  • motion qa measures stickers against faces, captions and the scene frame.
  • hook check WORKSPACE: a Polish heuristic for the first caption and first second (situation with a child, viewer or tension, length, first word, first movement, no greeting, no title or book) with concrete fixes; word lists in qa/data/hook_pl.yaml; results in analysis/hook-check.json and the delivery manifest.
  • motion suggest proposes starting the first shot where it moves, and with --hook-punch a 1.16 push-in on the first key word at 1.0-1.5 s.
  • motion variants --from-brief [--seconds N] renders the brief's on-screen text, first sentence and a question as alternative first captions; deliver --hook-variants delivers them by name.
  • A project b-roll library: broll_library, broll_synonyms and broll.yaml (description, keywords, child_face_visible, rights).

Changed

  • broll suggest never proposes a clip with a child's face, matches Polish endings and synonyms across every transcript, keeps a clip over speech to the camera to 2 s, spaces clips 3 s apart and honours broll_density.
  • image_generation_policy defaults to the new ask: each image needs the user's yes. The docs describe the native image-tool route and the Remotion fallback when a session has no Claude Motion type.

Fixed

  • The consent message named a --consent user-request flag that does not exist; it is --user-request.

[2.4.0] - 2026-10-20

What reels cut on 2.3.0 had to work around by hand, fixed in the tool.

Added

  • compile moves each range's outer edges from whisper's word times to where the speech is heard to start and stop (RMS against the local floor, within 0.4 s of the word), keeps a 0.08-0.12 s breath, never crosses a neighbouring word, prints every move and warns when speech carries on past the final out-point. --word-edges keeps the transcript's edges.
  • Transcript quality lists suspect_words (longer than 1.2 s, or holding 0.3 s of measured silence) with a ready span per passage, and transcribe --refine START-END --model large-v3 re-transcribes only that passage and splices its words into the workspace transcript.
  • sources add FILE -w WS [--id ID] and sources list manage the recordings of an existing workspace without touching transcripts or state; workflow init --id names the first one.
  • Plan items carry zoom, speed and grade onto their ranges, so a fix survives compile --force; compile lists qa-editorial.json EDL fixes the rebuilt EDL no longer carries.
  • Named grades mono, mono_contrast and shake (combinable with +) on a range or the whole EDL, drawn in the base render both renderers share.
  • motion suggest proposes a jump -- a sharp 1.1 range zoom -- instead of a transition on a cut inside one take with speech on both sides; motion accept writes it to the plan item and the EDL. motion qa warns when a transition runs over speech with a face on screen.
  • end_card with hold: true is placed after the last word and extends the edit by its length in silence (end_hold in the EDL); layout: image_top puts a cover image above the text.
  • brief import posts.docx --match TITLE reads a reel from the month's Word document, with tracked deletions left out and insertions kept.
  • deliver --max-size-mb 30 re-encodes a larger video in two passes with the audio stream copied, checks A/V against the file it replaces and records the limit and bitrate per item.
  • Technical QA measures audio/video sync against base for composited renders and fails beyond 20 ms.

Changed

  • transcripts/<id>.json is the source of truth for every recording; the cache only fills it, and stages no longer re-transcribe over it with their own default model.
  • compile takes the source argument as optional with --workspace and accepts a picture-only source when the plan's sound is transcribed.
  • A scene title is fitted to its tab by width (shrinking to 60 % at most) on both routes, replacing the fixed 48-character label limit; a title that still does not fit is refused with the length that would.
  • End card text is measured in Python: links and addresses are never split and the font shrinks to fit the width.

Fixed

  • Remotion finals played their audio 40-45 ms late: the compositor re-encoded the base AAC without trimming encoder priming. It now renders pictures only and the base audio stream is muxed back unchanged.
  • speed under an audio_source slowed the voice with the picture; only the picture is retimed now.
  • An element that runs to the final frame (an end card) no longer fades out on it.
  • A graded range (shake) no longer breaks concatenation with ungraded ones.
  • A brief whose first row narrates the situation before a quote opens on the situation, not on the quote; abbreviations such as "np." no longer end the first sentence.
  • --refine keeps a word the span's edge cuts through, and a word heard twice across the edge only once.

[2.3.0] - 2026-10-14

What the 13.10.2026 reel needed a local patch and hand-written scripts for, as configurable features. Workspaces started on 2.2 must be recreated.

Added

  • label_style: scene_frame draws each label as the title of a frame around that scene's captions; the next label starting where one ends keeps the frame and flips the title, with an accent flash. Both renderers draw it from one geometry (motion/layout.py) that scales with the output height; the render records scene_frame among the caption features.
  • secondary_color (default: the second brand colour) for the frame tab, the top list card and the CTA text, compiled into the contract as brand.secondary_colour.
  • position on cta (above_captions or top) and on list and steps (center or top).
  • compile builds an edit from several recordings: plan items carry order, audio_source/audio_start/audio_end (a voice under another file's picture) and reframe; every transcript the plan keeps words from is loaded, one numbered caption track (JSON, SRT, VTT, ASS) is built from all of them, and the motion plan's labels are pinned to the scenes the edit made, by beat or to the nearest cut.
  • captions fix -w WS --at T --from OLD --to NEW corrects caption words and their per-word data, keeps the timing, and rewrites JSON, SRT, VTT and ASS.
  • caption_hold_through_pauses (or caption_style.hold_through_pauses) keeps a caption up through a pause of up to 2 s within one range.
  • config init asks for motion energy, style pack, label style, karaoke, word animation and brand words (or takes them as options) and writes them all.
  • config validate names every style decision left to a schema default.
  • SOCIAL_VIDEO_YUNET_MODEL points face detection at a local model file.
  • doctor checks the project's brand font and prints how to install it.

Changed

  • The chip label no longer uses the caption box colour: a white pill with text in the second colour and an accent dot.
  • Motion QA measures labels, CTAs, lists and steps where they are drawn, and reports a graphic that crowds a scene frame.
  • A render logs each motion-plan colour, font or style the brand contract replaces.

Fixed

  • config validate without --workspace no longer creates edit/ in the project root; it writes nothing.
  • A slide transition no longer exposes a black edge.
  • On the FFmpeg route, rounded-box caption text and the scene-frame tab are sized as the browser sizes them: libass measured a font by its line height, so the same box held visibly smaller type than in Remotion.

[2.2.1] - 2026-10-13

Fixed

  • Transcription no longer breaks when PyAV 19 is installed: av is pinned below 19 until a faster-whisper release stops passing metadata_errors.
  • Scene detection reads FrameTimecode.seconds instead of the deprecated get_seconds().

Changed

  • Upstreams reviewed on 2026-10-13: video-use, faster-whisper, PySceneDetect (lock moves to 0.7.2) and auto-editor. Nothing needed porting.
  • Development dependencies: hypothesis 6.168.5 and the dev-tooling group.

[2.2.0] - 2026-10-13

Short-form practices that measure, from 2026 creator research.

Added

  • reel-check measures what loses short-form viewers: the first word heard after more than a second, a length outside the platform's completion window, dead air inside the speech, a tail after the last word, a picture unchanged for more than five seconds, an end card longer than two seconds, speech without captions, and publishing copy that never asks for a save or a send. Findings are advisory, written to analysis/reel-check.json and .md, and recorded at delivery as reel_check and reel_check_warnings.
  • Platform specs carry a directional completion window (sweet_spot_min, sweet_spot_max) with its sources: Reels 7-45 s, TikTok 15-60 s, Shorts 15-50 s.
  • references/short-form.md: the practices, what each check means, and the sources, flagged as creator research rather than platform documentation.

[2.1.0] - 2026-10-13

Content first: a short opens on the scene, never on a card.

Added

  • opening_policy: content_first (the default when the key is absent), with cover_in_timeline: false, first_cue_full_sentence: true, first_cue_max_words: 8 and max_leading_silence_s: 0.3, compiled into the brand contract. opening_policy: free keeps the old behaviour.
  • The first caption starts at 0.0 s and carries the whole first sentence, up to first_cue_max_words, as the one exception to the cue limits; brand QA exempts it from the length check, and it still has to fit two lines.
  • brief import reads a montage brief, as text or from the monthly package JSON, into context/brief.json and brief: lines in edit-plan.json user_overrides. Its first row becomes the first caption, a child's off-screen line the recording lacks is shown as text and never voiced, its short texts become label graphics over the shot (--labels), and its cover text feeds cover --from-brief.
  • A label graphic: a short text in the top safe zone, white on the brand accent, drawn by Remotion.
  • opening-qa and delivery measure the opening: frame 0 is a moving scene, the first caption at 0.0 s, the leading silence, a cover or card at the start, when the first sentence is heard, end cards only at the end. The report goes to analysis/opening-qa.json and .md with first-frame.png.
  • delivery-manifest.json gains cover_path, first_frame_is_content, first_frame_note, first_cue_start_s, first_cue_text, leading_silence_s, cover_in_timeline, time_to_first_situation_s, end_card_only_at_end, opening_ready and opening_issues; manifests written before keep loading.

Changed

  • Under content_first the render refuses a timeline that opens on a title card, a cover image or still clip, a logo or a Claude Motion card, or that places an end card before the end; delivery is not ready, and Stage 5 cannot complete, while the measured opening breaks the rule; the composed cover is always delivered as cover.jpg; motion suggest proposes no hook card.

[2.0.0] - 2026-10-13

The workflow stages as MCP tools. The CLI, the artifacts and the workflow state are unchanged; the major version marks the new interface.

Added

  • social-video-agent mcp serve (the mcp extra) serves the workflow as MCP tools over stdio: status, init and batch, stage completion, transcription, planning, cuts, motion, b-roll, Claude Motion films, rendering, technical, brand and motion QA, caption translation, editorial fixes, delivery and export. Each tool runs the matching CLI command with --json in a child process and returns its result or its error, so every gate is the CLI's.
  • The Remotion licence declaration, fetch --rights, cloud image calls and brand-file upload consent are deliberately not tools.
  • social-video-agent mcp tools lists the tools and the commands they run; python -m social_video runs the CLI.
  • references/mcp.md tells an agent how to use the tools.

Changed

  • Skill files that describe judgement only moved from 2.0 to "Under consideration": hosts without the MCP tools still need the commands.

[1.8.0] - 2026-10-13

Several recordings, several languages.

Added

  • workflow batch DIR -w PARENT gives every recording in a folder its own workspace through the ordinary Stage 0. workflow status PARENT reports where each one stands, which to work on next, and whether they still share one brand contract.
  • captions translate --to LANG writes the cues for the agent to translate, with the line limit; captions apply --to LANG refuses a translation that adds, drops or retimes a cue, leaves one empty or runs over the limit, and writes the track, ASS, SRT and VTT on the original timings. The CLI makes no translation call, and nothing is dubbed.
  • deliver --language LANG renders a variant with those captions burned in, and with --srt/--vtt its sidecars.
  • Caption tracks record whether their word timings were measured or, for a translation, estimated across each cue.

Changed

  • Shorts reusing the parent's motion suggestions moved from 1.8 to "Under consideration": the candidates are positions on the parent's cut.

[1.7.0] - 2026-10-13

Measured motion QA: the Stage 3 questions about every graphic, as numbers.

Added

  • motion qa writes qa/qa-motion.json: for every graphic and Claude Motion segment, its time on screen against the time to read it, its overlap with the caption band while words are spoken, and the share of a face it covers on the base render; then accents per minute against the energy level and the longest stretch with no cut, graphic, punch-in or transition. Every finding is a warning with measured and expected values.
  • motion sheet colours each tile green, amber or red by what motion qa found at that moment.
  • Platform specs record reviewed_at and their sources.

Changed

  • On the Remotion route Stage 3 requires qa/qa-motion.json before it accepts the editorial review.
  • Reserved zones keep the larger of the project's estimate and the Claude Motion type's guidance: Reels and TikTok reserve 13.1% at the top, TikTok 21.9% at the bottom, and Shorts 34.9% at the bottom, where it reserved 15%. Projects posting to Shorts with a smaller bottom margin now get a platform warning.

[1.6.0] - 2026-10-13

Claude Motion segments: full-frame graphics designed in Claude Motion, imported and checked against the brand.

Added

  • motion film spec writes a film spec, Motion's own scenes.json and base.css from the motion plan and the brand contract: the edit's size and a frame rate Motion exports (24, 30 or 60), the brand's colours in Motion's four roles, the font family, safe margins and platform reserved zones in pixels, and the letters the player's built-in faces lack. Only hook cards, chapters, calls to action, quotes and end cards qualify; a scene under one second is refused.
  • motion film import cuts the user's export into one segment per scene, refusing another size or a length that does not match, and places each as a full-frame overlay in place of the graphic it stands for, with hashes and brand facts in motion-films.json. motion film remove puts the graphic back; motion film status and workflow status list scenes still waiting for an export.
  • Renders record the Motion segments they composite and fail when one is missing or changed since import. Brand QA fails a segment of the wrong size, changed, or set in another font, and warns when the brand changed after the spec or the segment's colours are not the brand's. The motion sheet shows every segment.
  • brand_asset_upload in the project config (none by default) decides whether the logo and brand font file may be uploaded to a film; --allow-brand-upload records the user's agreement for one film. The recording is never uploaded.
  • references/motion-film.md tells a Claude session how to write the film, and when the bridge is unavailable.

[1.5.1] - 2026-10-13

Hygiene after 1.3, and the roadmap to 2.0.

Fixed

  • Beat detection compared each frame with a neighbourhood that counted frames beyond either end of the music bed as silence, so the noise floor of the first and last quarter second read as beats and could pull a punch-in onto them. The neighbourhood now averages only the frames that exist.

Changed

  • Model routing recommends the current Claude family: Claude Opus 5.5, Claude Sonnet 5.5 and Claude Haiku 5.5 where it named Opus 5, Sonnet 5 and Haiku 4.5. Claude Fable 5.1 stays the escalation tier.
  • upstreams.yml and .env.example describe WhisperX as the implemented optional backend it has been since 1.0; SKILL.md numbers its routing steps without a gap.

Tests

  • The motion and broll commands end to end: b-roll found by file name and sidecar tags, accepted once, unknown ids named; accents filtered by kind and confidence; hook variants refused off Remotion; the motion sheet.
  • Property tests: accents never exceed the energy level's budget or spacing, beats move only punch-ins and only within the snap tolerance, and onsets of a steady click tempo over a noise floor are found with nothing else.

Documentation

  • The roadmap is reviewed against 1.5.0 and runs from a 1.5.1 hygiene release through Claude Motion segments (1.6), measured motion QA (1.7), batch and caption translation (1.8) to stages as MCP tools (2.0).
  • ADR-005 (proposed) records Claude Motion as a source of full-frame segments the user exports and the CLI imports, not as a renderer.

[1.5.0] - 2026-09-29

Covers that survive the profile grid, render time estimates, and a session that knows which edits are in progress.

Added

  • Render timings: every render records how long it took in its manifest (render_seconds) and in a local render-log.jsonl (sizes, durations and seconds only). render estimates the next one from comparable earlier renders on the same machine and reports the time it took. SOCIAL_VIDEO_RENDER_LOG moves or disables the log.
  • Cover layouts: cover --layout bottom|top|boxed|number (--figure for the big number), each with guidance on when to use it. Repeating --layout draws the candidates side by side in qa/cover-sheet.png without choosing one. cover.json records the layout, the drawn text box and grid_safe.
  • workflow brief and a Claude Code SessionStart hook: a session opened in a video project is told which edits are in progress, at which stage and what they are missing, including workspaces kept in the app home for Windows-drive sources. Silent outside a video project and bounded by a timeout. doctor and validate_distribution.py validate hooks/hooks.json.

Fixed

  • The default cover could put its last line below the 3:4 centre a profile grid shows, where it is cut off. Cover type is now measured as drawn and kept inside that crop and the safe margins.
  • Every Remotion render left its ~30 MB webpack bundle in the system temp directory, often RAM-backed tmpfs, until it filled. The render script now removes the bundle when it finishes or fails.

[1.4.0] - 2026-09-29

Reference rhythm and transcript trust: match the feel of a reference video, and know when a transcript is not fit to cut on.

Added

  • Transcript quality check at Stage 1: music notes and sound annotations, low-confidence words, decoding loops, an English-only model on other speech and an uncertain language are reported with the model to retry with. transcribe prints it, --json carries it, and Stage 1 writes analysis/<source>.transcript-quality.json. It warns and never blocks.
  • style analyze REFERENCE -w WORKSPACE: measures a reference video's shot lengths, cut rate, pauses, speaking pace (--transcribe) and hook timing, and maps them onto motion_energy, a target shot length and a hook deadline. Writes context/style-reference.json and a contact sheet of one frame per shot for choosing the style pack. Only numbers are kept; project config is not changed.

Changed

  • The motion review sheet and the reference contact sheet share one frame-grid helper in imaging.py.

[1.3.1] - 2026-09-25

Fixed

  • The hybrid-mode Stage 0 test simulated the engine inside WSL with a native Windows temporary directory, which only a real wslpath could translate, so the Windows unit job failed. It now runs where the engine runs, off Windows.

[1.3.0] - 2026-09-25

Motion design: energy levels, style packs, transitions and accents proposed from the transcript.

Added

  • motion_energy (calm, lively, bold) and style_pack (editorial, bold-social, tech-minimal) in project config: one setting sizes punch-ins, transitions, accent density and caption word animation, and the render refuses movement above its limits.
  • Remotion transitions on cuts (zoom, slide, flash), pop and box active-word animation, and four graphics: hook_card, chart, compare and steps.
  • motion suggest/accept: accent candidates found in the transcript (hook, figures, steps, questions, punchlines, turns, jumping cuts), spaced to the energy level and snapped to the beats of a licensed music bed.
  • motion sfx (sound under the motion from the user's own library), motion variants (hook A/B previews) and motion sheet (a contact sheet of every animated moment).
  • broll suggest/accept: clips from the user's own library matched to the transcript by file name and tags, tolerant of Polish inflection.
  • Seven more Remotion graphics: quote, stat (counts a figure up), list (reveals two to five points), chapter, cta, progress and logo_reveal, in the brand's typography and colours, inside the safe margins and clear of the caption area.
  • workflow renderer remotion switches an existing workspace, legacy FFmpeg ones included, to Remotion through the Stage 0 licence gate.

Changed

  • Rounded caption boxes no longer require Remotion: on the FFmpeg route libass draws them from the same measured layout, with the brand font loaded from the project, and the render records that layout for brand QA.

[1.0.1] - 2026-09-25

Repairs the Windows agent to WSL2 engine mode.

Fixed

  • Windows adapter: with more than one wsl.exe on PATH (System32 and the WindowsApps alias), their paths were joined into one command that does not exist. System32 is preferred, otherwise the first match is used.
  • Windows adapter: social-video-agent.ps1 doctor bound doctor to the -Distribution parameter. Adapter options are now parsed by hand, only before the first engine argument, so every engine argument passes through.
  • The engine in ~/.local/bin was not found by a non-interactive wsl.exe call. The adapter and the runtime check now locate it by absolute path and start it with wsl.exe --exec -- no shell, no login PATH, no manual link; SOCIAL_VIDEO_WSL_ENGINE names a non-standard location.
  • install_skills.py run in WSL put a Linux symlink on the Windows drive, which a native Windows agent could not follow. A Windows destination now gets an atomic copy, replacing an earlier install and nothing else.
  • Diagnostics run through the adapter reported wsl-native. The adapter shares SOCIAL_VIDEO_AGENT_PLATFORM=windows through WSLENV, so doctor and runtime.json record windows-agent-wsl-runtime.
  • wsl --list --verbose output decoded with NULs between characters was never parsed by the Python runtime check.

[1.0.0] - 2026-09-23

Added

  • An installed wheel can render with Remotion. The wheel now carries the compositor sources (never Remotion's own packages), and social-video-agent doctor --install-remotion installs the locked npm dependencies and headless browser into the app cache. Until now the default renderer only worked from a repository checkout.

  • docs/ROADMAP.md.

  • CI tests Python 3.10 through 3.13, type-checks the 3.10 floor in its own environment, reports branch coverage, and lints the PowerShell adapter with PSScriptAnalyzer. Property tests generate Windows, WSL mount and UNC paths the example tests never listed.

  • caption_style in the project config gains active_word_highlight, highlight_color, emphasis_words and emphasis_color. Brand keywords are coloured by both renderers, and brand QA fails a render that was asked for emphasis and did not draw it.

  • cuts find lists hesitations, doubled words, restarted phrases and long pauses, each with an id, a confidence and the quoted words; cuts accept turns the chosen ones into plan drops. compile builds edl.json from the plan and removes accepted cuts exactly, after word snapping, even inside kept spans. It refuses to overwrite an existing EDL without --force.

  • --backend whisperx (the align extra) aligns every word with WhisperX after faster-whisper recognises it; words it cannot place keep their timing.

  • transcribe --diarize (the diarize extra and your own HF_TOKEN) labels every word with its speaker using the pinned free pyannote weights. Speaker reframing then follows diarized turns and cuts between faces at turn changes.

  • split_stack layouts: two or three source regions stacked as bands, each cover or contain, for two speakers or a screen above the presenter. With split_stack as the default reframe, two people in shot get a pane each.

  • export WORKSPACE writes the cut as FCPXML, Premiere/FCP7 XML, OpenTimelineIO and CMX 3600 EDL, frame for frame with the render, and lists what the formats cannot carry.

  • fetch URL --rights "..." (the fetch extra) downloads one recording the user has the right to edit, with a provenance record beside it.

  • The release workflow can publish to PyPI through trusted publishing once the PyPI project is configured; CI runs twine check on every build.

  • motion-plan.json gains punch_ins: timed pushes on the picture that ease in, hold and ease out while captions and graphics stay put.

Changed

  • The guided route now recommends OpenAI Astra where it recommended Sol (Stages 1 and 3) and Sol where it recommended Terra (Stages 2 and 4); Terra is no longer recommended. Claude alternatives are unchanged.
  • Rendering refuses any zoom above the brand's punch_in_max, and any punch-in when the project sets punch_in_intensity: 0.
  • The transcript cache is keyed on the backend as well, so switching backends no longer returns another backend's transcript. Default-backend caches keep their names and stay valid.
  • The CLI is a package with one module per command group; social_video.cli:app and python -m social_video.cli are unchanged, and every command's help text is identical.
  • render_edl is split into input collection, loudness planning and the encode step.
  • Every finished file is published through one atomic writer with a unique staging name, so two concurrent runs can no longer collide on a shared .partial file. Artifact JSON uses it too.

Removed

  • The cloud extra, which nothing used.

Fixed

  • Voice-cleanup measurement used its own filter-path escaping, which broke the filtergraph when the temp directory contained ,, ; or =. It now uses the shared, tested escaper.
  • The Remotion bridge handed node a relative staging path while running it from the compositor directory; the staging root is now resolved first.
  • Two transcription backends that do not exist were still being imported.
  • Burned-in active-word captions lit every word not yet spoken and left spoken ones plain, the opposite of the Remotion route. The spoken word is now the highlighted one, with 1 ms colour transforms because libass ignores zero-length ones.
  • Render manifests recorded Remotion 4.0.525 whatever was installed; they now record the version the compositor pins.
  • split_stack was a declared reframe mode that rendered as a plain centre crop.
  • Every artifact write failed on Windows: the atomic writer flushed through a read-only handle, which Windows refuses with EBADF.
  • Several required doctor checks could fail without saying what to do.
  • Registering a JPEG or WebP plate from the agent's image tool failed: Pillow inferred the format from the .partial staging name.
  • EDL validation turned an unreadable source into an uncaught ffprobe error on its second probe; it is now a named problem, and each source is probed once.
  • A Windows unit test expected a rootless output path on the cwd's drive; it is resolved against the project root, so it lands on the project's drive.

[0.6.3] - 2026-09-22

Fixed

  • CI could not finish on any host but Linux. The repository-hygiene scan ran git ls-files while collecting tests, so in the container job -- where the workspace belongs to the runner and git runs as root, which git reads as dubious ownership -- one refusal aborted the entire run instead of one check. The job now trusts its workspace, and a checkout git cannot read skips rather than breaking collection.
  • The caption-fit fixture asked fc-match for a font and inspected its return code, which a Windows runner never reaches: with no fontconfig the call itself raises, so fifteen tests errored instead of skipping.
  • The Windows/WSL path tests asserted POSIX resolution of paths that only exist inside WSL. Resolving is native and rightly so -- on Windows a rootless /mnt/d/out lands on the current drive, and on macOS /home crosses an autofs firmlink -- so those tests are Linux-only and the path recognisers, which are pure string logic, still run on every host.
  • A migration message was asserted verbatim against wrapped console output, which failed on runners whose temporary directory is long enough to push the sentence onto two lines.

[0.6.2] - 2026-09-22

Changed

  • Refreshed the dependency floor and the pinned toolchain so the project builds on current releases: Remotion 4.0.527, React 19.3, TypeScript 7.0, and the matching @types packages; actions/setup-node v7 and astral-sh/setup-uv v10.2.0 in CI. The Python lock moved to current ruff, urllib3, protobuf, filelock, fsspec and platformdirs builds.

Fixed

  • Python 3.11+ installs silently resolved to onnxruntime 1.23 -- the last release with CPython 3.10 wheels. Only the 3.10 ceiling was declared, so a universal resolution satisfied every environment with that one version and never reached a current build. A matching >=1.30 floor for 3.11+ forces the fork, and 3.10 stays installable.

[0.6.1] - 2026-09-22

Fixed

  • The repository-hygiene scan rejected every /home/<user>/projects/... path, which failed CI on its own test suite: the WSL UNC tests have to name the POSIX path they resolve to. Placeholder user names are now allowed on POSIX exactly as they already were on Windows, and a real user name is still rejected.

[0.6.0] - 2026-09-21

Fixed

  • An end card with a generated plate rendered without its own words. The plate is an absolutely positioned background image and the text is in-flow, so the image painted over it and the end card came out as a wordless gradient -- silently defeating the rule that image tools draw backgrounds while every word is drawn locally. The text is now positioned above the plate, and a pixel test on a rendered frame asserts it.

Added

  • A second runtime mode, so a native Windows Codex agent can drive the Linux engine. wsl-native is unchanged and remains the default outcome for an agent already inside WSL; windows-agent-wsl-runtime is new; linux-native and macos-native are unchanged. runtime_mode: auto decides from the real platform of the agent process, whether wsl.exe runs, whether the chosen distribution reports WSL 2, and whether the engine is installed inside it -- never from a terminal preference, which is configured separately and says nothing about where the agent runs. runtime_mode and wsl_distribution are new optional project-config fields, and SOCIAL_VIDEO_RUNTIME_MODE and SOCIAL_VIDEO_WSL_DISTRIBUTION override them.
  • scripts/windows/social-video-agent.ps1, the one supported bridge. It passes arguments as an array rather than building a command string, uses no Invoke-Expression and no sh -lc, propagates stdout, stderr and the exit code, forwards Ctrl+C, and refuses to fall back to ffmpeg.exe, Windows Python or Windows Node -- a run that mixed Windows and Linux binaries would not be the run that was reviewed. Missing WSL, a broken WSL, no distribution, WSL 1 only, an ambiguous choice and a missing engine are five distinct diagnostics, and it installs nothing.
  • Path normalisation across the boundary: quoted paths, and \\wsl$\<distro>\... / \\wsl.localhost\<distro>\... UNC paths, in addition to the drive letters, spaces, Polish characters, brackets, OneDrive folders and /mnt/<drive> paths already handled. Normalising twice is the same as normalising once, which is what stops /mnt/c/mnt/c/.... A UNC path for a different distribution is refused rather than silently resolved.
  • Stage 0 writes runtime.json: the agent platform, the runtime mode, the distribution and its WSL version, the project path on each side, the Linux cache root, which engine binaries exist, the image policy and the Remotion licence declaration. Capabilities only the agent can see -- native ImageGen, Canva -- are written in a separate session block as unknown_to_cli and marked session-scoped, so a later run re-checks rather than trusting yesterday's tool list.
  • doctor reports the agent side and the engine side separately in the hybrid mode, with one cause per line.
  • references/hybrid-runtime.md, read only when the agent is on Windows. The blanket "no PowerShell/WSL bridge" rule became a precise one: no bridge in wsl-native, only the reviewed adapter in windows-agent-wsl-runtime, and never Windows and Linux binaries in the same render.
  • install_skills.py also installs the skill folder into the Windows user's home when run from WSL, resolving that home from Windows rather than guessing a path under /mnt/c/Users. Only the skill crosses; no media dependency is duplicated on Windows.
  • remotion-license attest | status | refresh | revoke: the Remotion license declaration can now be recorded once for a project instead of once per edit. It lives in .social-video/remotion-license.json, next to the branding config but deliberately not inside it, and records the declaration, the explicit acknowledgement, the moment, the component versions and the terms URL -- nothing else. Eligibility is never derived from company size, revenue or any other signal, and there is no field in which such a signal could be supplied. workflow init and render take an explicit --remotion-license flag first, then the edit's own state when resuming, then the project declaration, and otherwise stop with the whole instruction. The CLI asks again after a revocation, a change of terms URL, a new release line, or a year; refresh re-confirms and never reinstates a revoked statement. Workflow state records remotion_license_source, and an older state without that field keeps its own declaration and is never asked again.
  • Native image tools are supported without an API key of the user's own. Detection used to be credential-only, and reported "a ChatGPT plan does not include API image generation and Codex CLI has no image tool" when no key was set -- which is false on a host that hands its agent an ImageGen tool, and which turned a missing key into a claim that imagery was impossible. There are now four recognised sources: the host's native tool and a Canva MCP connection, both of which belong to the agent and cannot be called from Python; and the OpenAI and Gemini image APIs, which the CLI still calls itself. image status reports cli_can_generate plus checked: local_api_integrations_only and native_imagegen: unknown_to_cli, and never presents its own blindness as a verdict. New image prompt hands the agent the guarded no-text prompt to pass to its own tool verbatim; image register adopts the resulting file, copies it into the workspace, hashes it and records the tool that really drew it, leaving the model empty when the tool reports none; image capabilities records what the session can do, the source the agent chose and why, and refuses a record that contradicts itself. Consent is unchanged and now includes --user-request for one image the user asked for by name; image_generation_policy: none outranks every one-off, including the native tool. Nothing picks a source automatically, because availability is not a reason to use something.
  • Measured voice cleanup, and audio_cleanup_policy to govern it. Until now the only thing done to the audio was loudness normalisation, so a recording with rumble, mains hum or an audible noise floor shipped with them. The render now measures the cut speech -- the per-window RMS distribution, so the noise floor is the 10th percentile and speech the 90th; energy below 60 Hz, where no voice lives; narrow bands at 50 and 60 Hz; 5-9 kHz; peak level -- and applies only what crosses its own threshold, each with a ceiling: a two-pole high-pass at 80 Hz, a notch at the hum fundamental only, at most 10 dB of afftdn, a de-esser at intensity 0.15, and at most 2:1 compression. A well-recorded voice comes out untouched and the manifest records that every check ran below its threshold. This is not an "enhance voice" button: the same chain on every clip is how a good recording ends up sounding managed. Clipping is reported and never repaired, and hum harmonics are left alone because notching them thins the voice. Every render discloses what it changed and how to undo it (--no-audio-cleanup, or audio_cleanup_policy: none), the measurement and the ceilings land in the render manifest, and brand QA checks the repair against them. Policy defaults to measured, so an existing project gets it after re-validating; its source media is untouched either way.
  • config migrate: a read-only report of every pre-0.4 value in a project config that still needs a human decision, with the old value, why it cannot be converted, and what to write instead.

Fixed

  • Remotion shortened long captions and made up an ellipsis. The compositor laid captions out with -webkit-line-clamp and overflow: hidden, which is a UI idiom for shortening a label, and the project config could not set font_size_pct or max_chars_per_cue even though CaptionStyle had both -- so every project compiled to a 96-pixel caption at 1080x1920 and nikomu, udowadniając na siłę rendered as nikomu, udowadniając na.... Both keys are now part of caption_style, the reference 9:16 values (font_size_pct: 3.6, max_words_per_cue: 4, max_chars_per_cue: 24, outline_or_shadow: none, corner_radius: 24, bottom_margin_pct: 22) are the defaults for any key a config omits, and config validate refuses a geometry whose own text cannot be drawn at the configured resolution. Layout is measured in Python against the project font and shipped to the compositor as explicit lines, so there is no wrapping, clamping or ellipsis rule in the browser at all: a cue that does not fit is wrapped at word boundaries, then shrunk to at most 72% of the contracted size, then refused by name. Brand QA checks the geometry that was drawn -- lost text, an added ellipsis, a line wider than the box, a cue longer than the contract, a style that drifted from it -- rather than the geometry that was asked for.
  • Migrating a pre-0.4 project config followed a recipe that could not work. The documented steps (add schema_version, rename two fields) left punch_in_intensity: restrained, broll_density: low, descriptive music_policy/sfx_policy/default_fps_policy and caption_style.outline_or_shadow: subtle to fail with a raw pydantic dump, and silently accepted fractional safe_margins: under the executable contract 0.15 is 0.15% of the frame, not 15%, so a config that validated rendered captions against the frame edge. Renames and YAML scalar shapes are still carried over silently; every value that encodes an editorial decision now raises ConfigMigrationError naming the field and the choices. Nothing is mapped for you, because a plausible default would overrule the editor.
  • context inspect rejected configs that config validate compiles. The discovery model did not know image_generation_policy or target_platforms, so a valid 0.5 config failed discovery as "extra field", and it read only the pre-0.4 logo/font spellings when reporting logos and fonts. The two models are now locked to the same field set by a test, discovery keeps unknown keys as context instead of failing, and the executable contract stays the only gate that rejects a value.
  • Context discovery pulled in files it had no business reading: anything under docs/ scored as a candidate on location alone, so report archives whose name mentioned the brand, unrelated PDFs, and instruction files from .codex/skills became style sources. Dot directories other than .social-video and installed skill trees are skipped, candidates must have a readable or reusable extension, and a real brand signal is required rather than a directory name. Keyword inspection now looks for brand-specific phrases instead of words like "video" that appear in every status report, and a file named by the project config outranks the filename heuristic.

[0.5.0] - 2026-09-18

Added

  • Optional cover and end-card plates from a cloud image model, gated on real credentials rather than on a subscription: OpenAI gpt-image-1-mini or Gemini gemini-2.5-flash-image, selected by image status, refused on a host with no image API. The model draws backgrounds only; every word is composed locally from the brand contract. Consent is image_generation_policy or --allow-cloud-image, and visuals.json records provider, model, the exact prompt, consent basis and SHA-256.
  • social-video-agent cover: a composed cover still over a generated plate, a real frame, or a solid brand colour, with cover.json provenance, plus a --cover delivery variant. --poster remains the raw frame grab.
  • end_card motion elements accept an image_asset background with image_dim_pct; no other element type may, and plates are staged into the private render directory like fonts and logos.
  • Platform specs (reels, tiktok, shorts) as editable JSON, a platforms command, target_platforms project config, and qa/deliver --platform checks for estimated feed-UI safe zones, aspect ratio, hard duration limits and the editorial length ceiling.
  • Agent-authored publish.json (title, description, hashtags, alt text, spoken hook, CTA), validated against the target platforms and carried into the delivery manifest with its hash.
  • Opening QA: opens on an image, captions start immediately, and qa/opening-contact-sheet.png, so the hook is measured the way the ending was.
  • Music beds and sound effects, making music_policy and sfx_policy executable instead of decorative. audio_bed mixes one local track under the edit with sidechain ducking, fades and an optional declared loop; effects are placed by hand at named moments with a reason, never on a timer. Both require license_confirmed, both are refused when the project policy is none, the mix runs before loudness normalisation, and brand QA checks the mix that happened against the policy and warns when a bed is too loud or not ducked.
  • speaker framing is now implemented rather than falling back with a warning. It tracks each face across sampled frames using YuNet's mouth landmarks, measures mouth motion, and correlates it with the speech envelope. When no face is convincingly ahead of the runner-up it declines to pick and records the fallback in the reframe reason. This is a correlation heuristic, not neural ASD; LR-ASD remains the recorded next step.
  • shorts list and shorts create: the mechanical half of turning one long recording into several standalone clips. Each selected candidate gets its own workspace, single-range EDL and a copy of the parent's transcripts, so nothing is ever re-transcribed per clip. Selection stays editorial: an unreviewed candidate set produces nothing, and anything under three seconds is refused.

Fixed

  • Renders came out short of their own EDL on ffmpeg 7. Frame padding was expressed as a duration, and 0.033333333 is a hair under 1/30 of a second, so ffmpeg's floor turned "clone one frame" into "clone none". Any range whose source did not quite cover its allocated frames lost one, which put an ordinary multi-cut edit past the 45 ms QA tolerance on a current distribution. Padding is now counted in whole frames, and CI runs the render-timing suite on ffmpeg 7 as well so the next such regression is visible.
  • The default Remotion renderer silently dropped the contracted active-word caption highlight: word timings were excluded from its props, so the bold-caption contract rendered as flat text while brand QA passed. Remotion now draws the highlight, applies the contract's case per word as the ASS writer does, and render manifests record the caption features actually applied. Brand QA fails when a contracted feature was not drawn instead of comparing the contract with a copy of itself.

[0.4.0] - 2026-09-17

Added

  • Versioned project config commands and compiled brand-contract.json with deterministic Remotion caption boxes, resolved fonts/logos, exact colors, margins, resolution and CFR.
  • Blocking brand QA, automatic structured-caption discovery, atomic multi-artifact editorial fixes, privacy-boundary review sheets, and a native Stage 5 delivery command with per-file QA and SHA-256 manifest entries.
  • CLI/skill/plugin/package compatibility checks and a documented migration path.

[0.3.0] - 2026-09-17

Added

  • Default Remotion motion-design composition for new staged workflows, with exact locked Node dependencies, Chrome/bootstrap/doctor checks, a validated motion-plan.json, project-aware restrained graphic primitives, and a real offline H.264/AAC smoke render in CI.
  • A blocking Stage 0 license declaration before discovery or media work, plus an explicit FFmpeg-only opt-out and backwards-compatible legacy workspaces.

Changed

  • FFmpeg remains responsible for deterministic cuts, captions, audio timing and the base edit; Remotion composites the planned visual layer; FFmpeg/ffprobe still provide blocking technical QA and full-stream decode validation.

[0.2.1] - 2026-09-17

Fixed

  • Show persisted OpenAI and Claude model recommendations at every guided boundary, including Stage 4 finalization and Stage 5 delivery, while keeping version 1 workflow states resumable.

[0.2.0] - 2026-09-17

Added

  • WSL2-first bootstrap, diagnostics, path normalization, plugin marketplace, release checks, and acceptance documentation.
  • Blocking timestamp/CFR/duration QA, atomic stable render artifacts, explicit split A/V and privacy-safe ending strategies, editorial QA handoff, and repeated-frame continuity analysis for the final ten seconds.
  • Bounded target-project discovery with cached, source-traceable context; optional project configuration; and Unicode-safe asset discovery.
  • Persistent guided/continuous workflow state, budget-aware conceptual model tiers, validated stage handoffs, status/resume commands, and progressive skill references for multi-model editing.

[0.1.0] - 2026-09-16

  • Initial public alpha of the local transcript-first social video editor.