bartlomiejborzucki/social-video-agent
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 fromat_wordanchors and from thepunch_in_wordscheck. - An
end_teaserwith its ownstartandendwas moved to the last 4 s; only a teaser without times (or withfrom_end) is placed from the end. reel-checkflagged 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 checkkept failing an announced list after alist,stepsorprocesswas 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-templatewrote over a template already filled in; it now refuses.motion acceptfailed on an effect proposed before the hook title existed and running under it; that candidate is skipped and named.- The
teareffect left a strip of paper on its last frame, and atilton 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_titlemotion 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}) andemphasis_words. It is not a card: opening QA never refuses it and warns when it enters after 1.0 s;hook checkreads it (at most 8 words; a question, an instruction, a number or a choice).title_fontandtitle_font_fileset its font apart from the captions'.end_teasermotion element: what comes next over the speaker in the lastfrom_endseconds (default 4), placed before any held end card. The newend_styleconfig key (overlay,end_card,both) is checked by brand QA against the newending_appliedrender 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_pctdraws the outline as a share of the font size, outside the glyph;step_numbersputs "1." / "2." in front of a cue that starts a step. Thepunchy-capsbrand profile is a test variant.- Picture
effectsin the motion plan:frame_in_frame,device_frame,split_mirror,triptych,whip_blur,tear,bw_flash,halftone_bg,speed_linesandtilt, 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 bymotion_energy, and only when the newmotion_effectskey allows (off,manual,auto). Without segmentation, halftone and speed lines are a translucent layer over the whole frame, recorded asmask: none. insertmotion 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.propmotion element and a project prop library (assets/props/withprops.yaml, orprop_library): a large picture on its word, refused without rights or with a child's face;props_appliedrecords each file's hash.punch_in_words:motion suggestproposes a push in on each listed word (PunchInnow takesat_word), andmotion qanotes one said without.motion suggestproposes effects on sentence boundaries every 6-10 s undermotion_effects: auto, and props by their keywords.- Text stories:
brief storyturns"Sentence." -> clip.mp4lines into an edit plan withmode: text_story;compilebuilds muted shots and one centred caption per shot (story_caption) without transcribing anything. deliver --description-templatewritespublish.template.md: the hook, body, share ask (share_cta), credibility line (author_signature) and source in order, the rest as[[placeholders]]that--publishrefuses.hook checksuggests alistwithitem_at_wordswhen "3 kroki" or the like is said in the first 5 s.
Changed
opening-qaadds advisoryopening_warnings: no word heard in the first 0.5 s, or a hook title after 1.0 s. The delivery manifest carries them.reel-checkwarns 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 qacounts 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 withhold: truefollow 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_cardlost its end:motion film importtakes 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 amotion_filmoverlay 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_cardbroke the edit:motion sfxfailed 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 --forcesilently dropped everycaptions fix. Each fix is now recorded in the caption track; a rebuilt track (a recompile or an opening variant) gets it back, andcompilelists 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_cardwas refused.compilenow 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
compilewith a pydantic error (elements.0.end Input should be greater than 0) and leftedl.jsonwithout 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 sfxplaced 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_wordsonlist,stepsandprocess: each point enters on the word that names it.motion suggestanchors the steps it proposes;compileand 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 androunded_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 inicon_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 atcalm.inset: a project picture or short clip as a card over the speaker, with its rights declared and never a child's face;motion qawarns when it hides a talking mouth for more than 2 s.custom: a Remotion component written for one sentence inmotion/custom/<id>.tsx, given only the brand tokens, its zone, the safe margins and its words' times, and allowed onlyreactand a list ofremotionnames. Every render checks its imports, forbidden calls and types (tsc --noEmit); one that fails is left out and recorded incustom_rejected, and the rest of the reel renders.motion custom initwrites the template;motion custom checkchecks it and renders its range toqa/.motion suggestproposes astrikefor "zamiast X powiedz Y", "nie mów X", "lepiej: Y"; abubblefor a quote ("mówi: …", "mówi, że …", a line the brief quotes); aprocessfor "najpierw… potem… na końcu"; and aniconfor 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: truein the motion plan refuses every playful graphic (stickers and the word graphics above) for a reel about death, illness or grief;motion suggest --sensitiveproposes none.motion qameasures 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 statusandworkflow resumetake the workspace as-w, andworkflow complete Ntakes it as an argument, so every workflow command accepts both.workflow statusnames the command that makes each missing artifact (next_commandsin--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 variantspreviews 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
changesrecords 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 variantsrun on another axis no longer loses the first set: each axis keepsanalysis/hook-variants-<axis>.json, and previews are namedopening-<axis>-<name>. motion variants --fullrenders atfinalquality unless--qualitysays 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_leaktransition takes the brand accent's hue; it was turned the other way (a turquoise accent came out violet). - A
listdraws itstextas a title, like steps and charts. compile --forcewarns when it drops sound effects frommotion sfx.style analyze --editorialsays 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 undercontext/reference/. The agent reads them in its own session and writescontext/reference-analysis.json: timed observations, hypotheses that cite them, stretches to watch and what not to copy.style check-analysischecks every claim is anchored to a moment that exists and writescontext/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_shotreplaces 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;--pickuprecords a variant that needs footage filmed first.deliver --hook-variantsruns 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_analyzeandstyle_check_analysis;delivertakeshook_variants.
Changed
motion variantsmakes 6 s previews by default and names variantsa,b…; render the chosen one with--full --only NAME. Previews are written aspreviews/opening-<name>-<N>s.mp4.--from-briefgives 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 checkkeeps 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; achild:list there still reads assituation:.- 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 arevoice_off_screenandfirst_line_off_screen; briefs saved withchild_off_screen/first_line_is_childstill 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-boxfrom 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 WORKSPACEwithout 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: trueon 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 instickers_appliedand goes in the post description.light_leaktransition: a WebGL film light leak (@remotion/effects) over the cut, its hue turned to the brand accent.SOCIAL_VIDEO_REMOTION_CONCURRENCYandSOCIAL_VIDEO_REMOTION_GLtune the Remotion render (browser tabs, OpenGL backend); unset keeps the defaults.
Changed
- Music and sound effects are no longer gated:
audio_bedandsound_effectsneed nolicense_confirmed,motion sfxno longer needs--licensed(still accepted), andmusic_policy/sfx_policyare 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
stickermotion element: an emoji fromsticker_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 withpop,shake,pulseordropfor 0.6-1.5 s; at most 0/1/2 per reel bymotion_energy, never in the opening frame or on a full-frame card.shakesin the motion plan shake the picture on a word, in both renderers.motion qameasures 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 inqa/data/hook_pl.yaml; results inanalysis/hook-check.jsonand the delivery manifest.motion suggestproposes starting the first shot where it moves, and with--hook-puncha 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-variantsdelivers them by name.- A project b-roll library:
broll_library,broll_synonymsandbroll.yaml(description, keywords,child_face_visible, rights).
Changed
broll suggestnever 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 honoursbroll_density.image_generation_policydefaults to the newask: 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-requestflag 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
compilemoves 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-edgeskeeps 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, andtranscribe --refine START-END --model large-v3re-transcribes only that passage and splices its words into the workspace transcript. sources add FILE -w WS [--id ID]andsources listmanage the recordings of an existing workspace without touching transcripts or state;workflow init --idnames the first one.- Plan items carry
zoom,speedandgradeonto their ranges, so a fix survivescompile --force;compilelistsqa-editorial.jsonEDL fixes the rebuilt EDL no longer carries. - Named grades
mono,mono_contrastandshake(combinable with+) on a range or the whole EDL, drawn in the base render both renderers share. motion suggestproposes ajump-- a sharp 1.1 range zoom -- instead of a transition on a cut inside one take with speech on both sides;motion acceptwrites it to the plan item and the EDL.motion qawarns when a transition runs over speech with a face on screen.end_cardwithhold: trueis placed after the last word and extends the edit by its length in silence (end_holdin the EDL);layout: image_topputs a cover image above the text.brief import posts.docx --match TITLEreads a reel from the month's Word document, with tracked deletions left out and insertions kept.deliver --max-size-mb 30re-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 basefor composited renders and fails beyond 20 ms.
Changed
transcripts/<id>.jsonis 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.compiletakes the source argument as optional with--workspaceand 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.
speedunder anaudio_sourceslowed 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.
--refinekeeps 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_framedraws eachlabelas 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 recordsscene_frameamong 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 asbrand.secondary_colour.positiononcta(above_captionsortop) and onlistandsteps(centerortop).compilebuilds an edit from several recordings: plan items carryorder,audio_source/audio_start/audio_end(a voice under another file's picture) andreframe; 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, bybeator to the nearest cut.captions fix -w WS --at T --from OLD --to NEWcorrects caption words and their per-word data, keeps the timing, and rewrites JSON, SRT, VTT and ASS.caption_hold_through_pauses(orcaption_style.hold_through_pauses) keeps a caption up through a pause of up to 2 s within one range.config initasks for motion energy, style pack, label style, karaoke, word animation and brand words (or takes them as options) and writes them all.config validatenames every style decision left to a schema default.SOCIAL_VIDEO_YUNET_MODELpoints face detection at a local model file.doctorchecks the project's brand font and prints how to install it.
Changed
- The
chiplabel 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 validatewithout--workspaceno longer createsedit/in the project root; it writes nothing.- A
slidetransition 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:
avis pinned below 19 until a faster-whisper release stops passingmetadata_errors. - Scene detection reads
FrameTimecode.secondsinstead of the deprecatedget_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-checkmeasures 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 toanalysis/reel-check.jsonand.md, and recorded at delivery asreel_checkandreel_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), withcover_in_timeline: false,first_cue_full_sentence: true,first_cue_max_words: 8andmax_leading_silence_s: 0.3, compiled into the brand contract.opening_policy: freekeeps 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 importreads a montage brief, as text or from the monthly package JSON, intocontext/brief.jsonandbrief:lines inedit-plan.jsonuser_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 becomelabelgraphics over the shot (--labels), and its cover text feedscover --from-brief.- A
labelgraphic: a short text in the top safe zone, white on the brand accent, drawn by Remotion. opening-qaand 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 toanalysis/opening-qa.jsonand.mdwithfirst-frame.png.delivery-manifest.jsongainscover_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_readyandopening_issues; manifests written before keep loading.
Changed
- Under
content_firstthe 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 ascover.jpg;motion suggestproposes 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(themcpextra) 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--jsonin 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 toolslists the tools and the commands they run;python -m social_videoruns the CLI.references/mcp.mdtells 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 PARENTgives every recording in a folder its own workspace through the ordinary Stage 0.workflow status PARENTreports where each one stands, which to work on next, and whether they still share one brand contract.captions translate --to LANGwrites the cues for the agent to translate, with the line limit;captions apply --to LANGrefuses 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 LANGrenders a variant with those captions burned in, and with--srt/--vttits 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 qawritesqa/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 sheetcolours each tile green, amber or red by whatmotion qafound at that moment.- Platform specs record
reviewed_atand theirsources.
Changed
- On the Remotion route Stage 3 requires
qa/qa-motion.jsonbefore 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 specwrites a film spec, Motion's ownscenes.jsonandbase.cssfrom 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 importcuts 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 inmotion-films.json.motion film removeputs the graphic back;motion film statusandworkflow statuslist 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_uploadin the project config (noneby default) decides whether the logo and brand font file may be uploaded to a film;--allow-brand-uploadrecords the user's agreement for one film. The recording is never uploaded.references/motion-film.mdtells 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.ymland.env.exampledescribe WhisperX as the implemented optional backend it has been since 1.0;SKILL.mdnumbers its routing steps without a gap.
Tests
- The
motionandbrollcommands 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 localrender-log.jsonl(sizes, durations and seconds only).renderestimates the next one from comparable earlier renders on the same machine and reports the time it took.SOCIAL_VIDEO_RENDER_LOGmoves or disables the log. - Cover layouts:
cover --layout bottom|top|boxed|number(--figurefor the big number), each with guidance on when to use it. Repeating--layoutdraws the candidates side by side inqa/cover-sheet.pngwithout choosing one.cover.jsonrecords the layout, the drawn text box andgrid_safe. workflow briefand a Claude CodeSessionStarthook: 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.doctorandvalidate_distribution.pyvalidatehooks/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.
transcribeprints it,--jsoncarries it, and Stage 1 writesanalysis/<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 ontomotion_energy, a target shot length and a hook deadline. Writescontext/style-reference.jsonand 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
wslpathcould 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) andstyle_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),popandboxactive-word animation, and four graphics:hook_card,chart,compareandsteps. 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) andmotion 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,progressandlogo_reveal, in the brand's typography and colours, inside the safe margins and clear of the caption area. workflow renderer remotionswitches 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.exeon 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 doctorbounddoctorto the-Distributionparameter. Adapter options are now parsed by hand, only before the first engine argument, so every engine argument passes through. - The engine in
~/.local/binwas not found by a non-interactivewsl.execall. The adapter and the runtime check now locate it by absolute path and start it withwsl.exe --exec-- no shell, no login PATH, no manual link;SOCIAL_VIDEO_WSL_ENGINEnames a non-standard location. install_skills.pyrun 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 sharesSOCIAL_VIDEO_AGENT_PLATFORM=windowsthroughWSLENV, sodoctorandruntime.jsonrecordwindows-agent-wsl-runtime. wsl --list --verboseoutput 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-remotioninstalls 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_stylein the project config gainsactive_word_highlight,highlight_color,emphasis_wordsandemphasis_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 findlists hesitations, doubled words, restarted phrases and long pauses, each with an id, a confidence and the quoted words;cuts acceptturns the chosen ones into plan drops.compilebuildsedl.jsonfrom 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(thealignextra) aligns every word with WhisperX after faster-whisper recognises it; words it cannot place keep their timing. -
transcribe --diarize(thediarizeextra and your ownHF_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_stacklayouts: two or three source regions stacked as bands, eachcoverorcontain, for two speakers or a screen above the presenter. Withsplit_stackas the default reframe, two people in shot get a pane each. -
export WORKSPACEwrites 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 "..."(thefetchextra) 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 checkon every build. -
motion-plan.jsongainspunch_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 setspunch_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:appandpython -m social_video.cliare unchanged, and every command's help text is identical. render_edlis 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
.partialfile. Artifact JSON uses it too.
Removed
- The
cloudextra, 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_stackwas 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
doctorchecks 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
.partialstaging 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-fileswhile 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-matchfor 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/outlands on the current drive, and on macOS/homecrosses 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
@typespackages;actions/setup-nodev7 andastral-sh/setup-uvv10.2.0 in CI. The Python lock moved to currentruff,urllib3,protobuf,filelock,fsspecandplatformdirsbuilds.
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.30floor 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-nativeis unchanged and remains the default outcome for an agent already inside WSL;windows-agent-wsl-runtimeis new;linux-nativeandmacos-nativeare unchanged.runtime_mode: autodecides from the real platform of the agent process, whetherwsl.exeruns, 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_modeandwsl_distributionare new optional project-config fields, andSOCIAL_VIDEO_RUNTIME_MODEandSOCIAL_VIDEO_WSL_DISTRIBUTIONoverride them. scripts/windows/social-video-agent.ps1, the one supported bridge. It passes arguments as an array rather than building a command string, uses noInvoke-Expressionand nosh -lc, propagates stdout, stderr and the exit code, forwards Ctrl+C, and refuses to fall back toffmpeg.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 separatesessionblock asunknown_to_cliand marked session-scoped, so a later run re-checks rather than trusting yesterday's tool list. doctorreports 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 inwsl-native, only the reviewed adapter inwindows-agent-wsl-runtime, and never Windows and Linux binaries in the same render.install_skills.pyalso 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 initandrendertake an explicit--remotion-licenseflag 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;refreshre-confirms and never reinstates a revoked statement. Workflow state recordsremotion_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 statusreportscli_can_generatepluschecked: local_api_integrations_onlyandnative_imagegen: unknown_to_cli, and never presents its own blindness as a verdict. Newimage prompthands the agent the guarded no-text prompt to pass to its own tool verbatim;image registeradopts 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 capabilitiesrecords 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-requestfor one image the user asked for by name;image_generation_policy: noneoutranks 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_policyto 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 ofafftdn, 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, oraudio_cleanup_policy: none), the measurement and the ceilings land in the render manifest, and brand QA checks the repair against them. Policy defaults tomeasured, 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-clampandoverflow: hidden, which is a UI idiom for shortening a label, and the project config could not setfont_size_pctormax_chars_per_cueeven thoughCaptionStylehad both -- so every project compiled to a 96-pixel caption at 1080x1920 andnikomu, udowadniając na siłęrendered asnikomu, udowadniając na.... Both keys are now part ofcaption_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, andconfig validaterefuses 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) leftpunch_in_intensity: restrained,broll_density: low, descriptivemusic_policy/sfx_policy/default_fps_policyandcaption_style.outline_or_shadow: subtleto fail with a raw pydantic dump, and silently accepted fractionalsafe_margins: under the executable contract0.15is 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 raisesConfigMigrationErrornaming the field and the choices. Nothing is mapped for you, because a plausible default would overrule the editor. context inspectrejected configs thatconfig validatecompiles. The discovery model did not knowimage_generation_policyortarget_platforms, so a valid 0.5 config failed discovery as "extra field", and it read only the pre-0.4logo/fontspellings 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/skillsbecame style sources. Dot directories other than.social-videoand 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-minior Geminigemini-2.5-flash-image, selected byimage status, refused on a host with no image API. The model draws backgrounds only; every word is composed locally from the brand contract. Consent isimage_generation_policyor--allow-cloud-image, andvisuals.jsonrecords 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, withcover.jsonprovenance, plus a--coverdelivery variant.--posterremains the raw frame grab.end_cardmotion elements accept animage_assetbackground withimage_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, aplatformscommand,target_platformsproject config, andqa/deliver --platformchecks 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, andqa/opening-contact-sheet.png, so the hook is measured the way the ending was. - Music beds and sound effects, making
music_policyandsfx_policyexecutable instead of decorative.audio_bedmixes 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 requirelicense_confirmed, both are refused when the project policy isnone, 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. speakerframing 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 listandshorts 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.033333333is 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-captioncontract 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.jsonwith 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.