Communitygithub.com

glass-edit : polish short vertical verre-et-papier

Monte une talking-head en short vertical style verre liquide + papier : tuiles logo, fonds papier, PiP pop-out, captions cinétiques.

Qu'est-ce que glass-edit : polish short vertical verre-et-papier ?

Étape 2 (polish) de la recette Short-form Editing, keemfinity/glass-edit (2026-10-10). README preview.gif + frames.png. Applique tuiles logo glass, fonds papier, PiP et captions cinétiques en 9:16.

Compatible avec✓Claude Code✓Codex CLI~Cursor
npx skills add https://github.com/keemfinity/glass-edit/tree/HEAD/skills/glass-edit

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Glass Edit

Turns a raw talking-head clip into a finished 1080x1920 / 30 fps short in the style reverse-engineered frame-by-frame from a reference short (57.6 s, 1729 frames, 27 scenes). Everything is rendered programmatically by a Remotion engine (React -> headless Chromium -> MP4): no templates, no stock footage, no music.

Source, updates and issues: https://github.com/keemfinity/glass-edit (MIT).

Work order: engine setup -> media prep (scripts) -> creative step (beat sheet -> edit.json) -> QA -> render -> deliver. The creative step is where the style lives - follow the Style Bible (2) and Beat Grammar (3) exactly.


0. Engine setup (once per session, ~3-5 min)

The engine (39 files: Remotion 4.0.533 project + Python scripts, ~300 KB) ships INSIDE this skill: engine/ next to this file, plus the face-detector model in assets/. Find the skill folder and copy the engine out:

SK="${CLAUDE_SKILL_DIR}"
for d in "$SK" /root/.claude/skills/synced/*/glass-edit ~/.claude/skills/glass-edit /mnt/skills/*/glass-edit; do
  [ -f "$d/engine/src/Main.tsx" ] && SK="$d" && break; done
[ -f "$SK/engine/src/Main.tsx" ] || SK=$(find / \( -path /proc -o -path /sys -o -name node_modules \) -prune -o \
  -path '*glass-edit/engine/src/Main.tsx' -print 2>/dev/null | grep -v "^$HOME/glass-edit/" | head -1 | sed 's|/engine/src/Main.tsx$||')
echo "skill folder: $SK"
[ -f $HOME/glass-edit/src/Main.tsx ] || { mkdir -p $HOME/glass-edit && cp -r "$SK/engine/." $HOME/glass-edit/; }
mkdir -p ~/models && cp -n "$SK"/assets/*.tflite ~/models/ 2>/dev/null; ls $HOME/glass-edit/src/Main.tsx

If no copy of the skill has engine/ (it was saved as text only), fetch it from GitHub:

git clone --depth 1 https://github.com/keemfinity/glass-edit /tmp/glass-edit-src
mkdir -p $HOME/glass-edit ~/models && cp -r /tmp/glass-edit-src/skills/glass-edit/engine/. $HOME/glass-edit/ \
  && cp -n /tmp/glass-edit-src/skills/glass-edit/assets/*.tflite ~/models/

Last resort: ask the user to attach glass-edit.zip (from the repo's Releases) and copy its glass-edit/engine/ folder.

Install + assets (needs code execution with network access to npm, PyPI, GitHub and huggingface.co (speech model); fontshare.com gives the Satoshi caption font (else DM Sans), jsDelivr gives brand logos (else monograms); storage.googleapis.com is only needed if Chromium must be downloaded):

cd $HOME/glass-edit && chmod +x scripts/*.sh
npm install --no-audit --no-fund >/dev/null            # exact versions from package.json (Remotion 4.0.533)
pip install --break-system-packages -q faster-whisper mediapipe onnxruntime opencv-python-headless scipy pillow
bash scripts/assets.sh       # fonts -> public/fonts, models -> ~/models (RVM ResNet50 107 MB), paper -> public/bg_*.jpg, github logo
node scripts/stills.mjs scripts/demo_edit.json work/qa 10   # smoke test: must write work/qa/f_00010.png
  • Chromium: CHROME=$(ls /opt/pw-browsers/chromium_headless_shell-*/chrome-linux/headless_shell 2>/dev/null | head -1). Pass --browser-executable=$CHROME to npx remotion render when set (stills.mjs finds it itself). If empty, run npx remotion browser ensure once and omit the flag.
  • Facts (typical Claude cloud sandbox: 2 CPU, no GPU): matting ~1.3 fps (ResNet50 + edge clean-up: ~25 min per minute of video), rendering ~7 fps. matte.py --model ~/models/rvm_mobilenetv3_fp32.onnx is ~2.5x faster for drafts but turns mics / held objects into smoke - never ship it. faster-whisper is fed raw PCM by the script (its own decoder breaks on this PyAV). If Satoshi can't be fetched, assets.sh falls back consistently (DM Sans).
  • Remotion is free for individuals and companies of up to 3 people; larger companies need a Remotion company license.

1. Pipeline

INPUT = the user's video: chat attachments land in /mnt/user-data/uploads/ (or ~/.claude/uploads/<id>/); a file on the user's computer is staged first (device_stage_files). Ask nothing up front unless the brief is unclear - the style is fixed; optional inputs worth using if offered: brand list, screenshots / b-roll for media scenes, CTA keyword.

cd $HOME/glass-edit; mkdir -p public/src work out
# 1 ingest: constant 30 fps, long side <= 1920, keep audio
ffmpeg -v error -y -i "$INPUT" -vf "scale='if(gt(iw,ih),min(1920,iw),-2)':'if(gt(iw,ih),-2,min(1920,ih))',fps=30" \
  -c:v libx264 -crf 16 -preset medium -pix_fmt yuv420p -c:a aac -b:a 192k -ar 48000 public/src/raw.mp4
# 2 transcribe - list every brand / product / name you expect in --prompt (fixes spellings);
#   English by default (small.en) - for any other language add --model small (multilingual)
python3 scripts/captions.py transcribe public/src/raw.mp4 work/words_raw.json --prompt "Ollama, ChatGPT, ..."
# 3 tighten pacing like the reference (pauses > 0.40 s -> 0.28 s); remaps the word times
python3 scripts/trim_silence.py public/src/raw.mp4 work/words_raw.json public/src/clip.mp4 work/words.json
# 4 person cut-out in the background (ResNet50 RVM + clean edges, ~25 min per minute of video) -> transparent VP9 webm
nohup python3 scripts/matte.py public/src/clip.mp4 public/src/clip_fg.webm > work/matte.log 2>&1 &
# 5 speaker framing (eye line + scale for full-screen and PiP; detects missing headroom and off-shots)
python3 scripts/framing.py public/src/clip.mp4 > work/framing.json
# 6 logos for every brand you will show: slug[:domain] (lobe-icons / simple-icons / favicon fallback)
python3 scripts/fetch_logos.py public/logos github openai:openai.com notion:notion.so
# 7 captions: --serif emphasis words/phrases (word@t0-t1 scopes one occurrence), --accent CTA keyword (lime),
#   --fix spelling corrections "heard=written,..."
python3 scripts/captions.py chunk work/words.json work/captions.json --serif "never,zero,no monthly" --accent "local" --fix "olama=ollama"
# 8 read the transcript, write the beat sheet, then work/plan_edit.py (sections 3-4) -> work/edit.json
python3 -c "import sys;sys.path.insert(0,'scripts');from plan import Words;[print(f'{a:6.2f}-{b:6.2f} {t}') for a,b,t in Words('work/words.json').sentences()]"
python3 work/plan_edit.py
# 9 validate + QA stills (frame = seconds*30) + contact sheet - LOOK at it
python3 scripts/validate.py work/edit.json
node scripts/stills.mjs work/edit.json work/qa 0 45 120 ... && python3 scripts/sheet.py work/qa work/qa_sheet.png
# 9b matte check (after work/matte.log says "done"): every 64th frame over magenta - LOOK at it
ffmpeg -v error -y -f lavfi -i color=c=magenta:s=1080x1920:r=30 -c:v libvpx-vp9 -i public/src/clip_fg.webm \
  -filter_complex "[0][1]overlay=shortest=1,select='not(mod(n\,64))',scale=270:-2,tile=7x1" -frames:v 1 work/matte_check.jpg
# 10 render
npx remotion render src/index.ts Main out/final.mp4 --props=work/edit.json --concurrency=2 --crf=18 --browser-executable=$CHROME

Deliver: cp out/final.mp4 /mnt/user-data/outputs/<name>_glass.mp4, SendUserFile it, and when a folder is connected also write it there (device_commit_files). Summarise the beat sheet in 5-10 lines and offer tweaks (swap a scene type, move a cue, change accent words) - edits are cheap: change plan_edit.py, re-run, re-render. If the matte failed, drop fg (full-screen shows the raw footage, PiP loses the pop-out) and say so. framing.py prints "no headroom" when the head touches the top of the source: it then uses a tighter full-screen crop and a PiP without pop-out (expected, not an error). Off-shots (framing.json -> offShots, also printed as notes): stretches where the face is missing or framed very differently - another camera angle, a whip/blur frame, b-roll. Podcast clips often OPEN on one. Never show the speaker there: for an opening off-shot give scene 1 pipIn = the first clean frame (the hook card stands alone, then the PiP rises in and the column glides up to make room); elsewhere use a speaker-less scene. Confirm the exact frame with a strip: ffmpeg -v error -i public/src/clip.mp4 -vf "select='lt(n,30)',scale=216:-2,tile=10x3" -frames:v 1 work/first30.jpg. validate.py warns when a pip/full scene overlaps an off-shot. Cut-out quality (matte.py v3): ResNet50 RVM keeps whatever the speaker talks into or holds (podcast mic, shock mount, cable) solid; specks and haze are removed, the outline is snapped to the image with a guided filter and tightened, and rim pixels take the subject's own colour, so the source wall never shows as a grey (on black paper) or dark (on cream) halo. In work/matte_check.jpg the outline must be crisp with no coloured rim, objects in front of the speaker solid, no floating specks. If an edge still looks soft, re-run with --tight 0.2,0.8; to keep fine flyaway hair use --tight 0.05,0.95 --gf 4. A failed or missing ResNet50 download falls back to mobilenet with a printed note.


2. Style Bible (measured from the reference)

2.1 Pacing and structure

  • 1080x1920, 30 fps, voice only (no music, no SFX), pauses trimmed to ~0.3 s; cuts land mid-sentence on word onsets.
  • A new visual beat every ~2 s (27 scenes / 57.6 s, range 0.7-3.6 s). A scene may run longer only if something new happens inside it every <= 1.5 s (focus pull, stat, strike, X, typing, orbit, world switch).
  • ~50 % light world / ~50 % dark world. Speaker visible ~45 %: PiP ~30 %, full-screen ~15 %.
  • Full-screen speaker = short breathers (0.7-1.7 s, every ~6-8 s), always hard-cut, on connective / personal lines.
  • Every element appears on the word that names it (0-4 frames after onset): numbers when said, sub-labels on their word, X when the competitor is named, strike-through on the negated word.

2.2 Two worlds

worldlookfor
lightwarm cream paper #EAE3D6, faint mottling, soft vignettehook, lists, comparisons, demos, explanations
darkcrumpled black paper, warm-brown top-left, olive bottom-rightintroducing a product/tool/person, caveats, payoff, outro; every full-screen speaker shot
  • light <-> dark is ALWAYS enter: "dissolve" (4-frame blur-dissolve). Same-world changes: cut, whips or morph.
  • A world can flip inside a scene with worldAt while the hero element stays (dark title card -> light orbit).

2.3 Speaker treatments

speakerlookcaption centre
piprounded card (x 70-1010, top 1400, r 34) with the ORIGINAL footage running off the bottom; the cut-out head pops out above the card edgey 1143
fullcut-out person over dark paper, eyes at y~1051, eye distance ~290 pxy 1425 (over the chin)
nonegraphics onlyy 1425
  • PiP slides in (5 f) / out (3 f) with motion blur on whips and morphs; appears/disappears instantly on cuts. The caption glides 1143 <-> 1425 (~9 f) when the PiP leaves/returns.

2.4 Captions (always on)

  • lowercase, 1-2 words per chunk (3 if all tiny), punctuation kept, continuous except across pauses; 3-frame blur-in.
  • Satoshi Medium 54 px, #2B2723 on cream, #ECE9E4 on dark / over the speaker.
  • Instrument Serif Italic 66 px accents: the hook words, the paid competitor in "instead of X", negations and money ("never", "zero", "no monthly", "bill.", "api key."). Max ~3 per 10 s. CTA keyword in lime #C6E33C.

2.5 Type

rolefontsize
tool / entity titleAnybody 500, wdth 112, -0.02em155 px (<= 800 px wide), centre y 433, mask-rise
heavy display ("STOP", "no API key")Archivo 800 (wdth 100 caps / 104 lower)110-200 px
big numbers ("$60", "$0")Archivo 800290 px; hero payoff ~550 px
thin partner word ("paying" under "STOP")Anybody 200, wdth 118~160 px, tight under the heavy word
descriptor linesSatoshi 40070 px white / 66 px grey #BDBCB6; stat 76 px
pillsArchivo 800 100 px, cream pill h168chips: Satoshi 500 60 px on #141312
micro labelsJetBrains Mono 27-31 px uppercase, tracking 0.32em

2.6 Colour - the whole palette

cream paper #EAE3D6 · crumpled black paper · ink #141210 · white on dark #F3F1ED · greys #BDBCB6 / #6F6A64 · lime #C6E33C (the only accent: highlight pill, "after", typed keyword, cloned-voice wave) · orange strike #E8643C · red #E8343A (X marks, old price) · chip #141312 · pill #F3EEE6 · UI card #1C1C1C. Brand colours appear only inside clear glass tiles; on dark tiles logos are white.

2.7 Liquid-glass components

  • Dark tile (smoked glass app icon): radius 20 %, bright top-left bevel, light bottom lip, cast shadow, white mono logo at 62 %. Hero 300 px; rows: 3 = 280, 4 = 215. Colourful app icons with no mono version are shown as-is.
  • Clear tile (thick clear glass): green-tinted rim with specular streaks, frosted body, logo in brand colour, optional word under it or a row lockup; warm rim = emphasised. Single 545, pair 388, orbit 110-200 px.
  • Sheen sweeps every glass surface (14 f) ~20 f after it lands, then every ~3 s.
  • Cream repo pills (GitHub icon + label, labels blurred to tease), lime "+1" pill, dark chips, dark UI cards.

2.8 Motion (frames @ 30 fps)

blur-in 6 f (opacity, 16 px blur, +12 px rise, 0.97 scale) · mask-rise 7 f (titles, heavy text, $0) · focus pull 5 f (placeholders sit blurred until named) · strike 7 f L->R · X: two 4 f strokes, 2nd starts +3 f · connector 6 f then descriptor · typing ~0.9 char/f, CTA keyword 1 char / 2.5 f · orbit: satellites spring out from behind the hero (front first, 3 f apart), hero sinks 160 px, named ones grow x1.42 to the front · list fly-in 7 f from the right with motion blur · list drop: reverse cascade 1.6 f apart with spring · before/after wipe 12 f with lime handle.

2.9 Transitions (enter of the incoming scene)

enteruseeffect
cutdefault; all full-screen speaker in/outhard cut
dissolveonly light <-> darkold graphics gone in 2 f; paper blends 50/85/97 % with 14 px blur
whipLefthook / list -> next light layoutgraphics shoot left (1/3 gone on the cut frame), PiP drops
whipUpsame topic -> PiP statementPiP rises; graphics accelerate up ~300 px, smear and vanish (8 f)
morphin-place layout change (teaser -> list -> CTA)old graphics blur out in 2 f, new blur in, PiP slides

3. Beat Grammar - script -> scenes

Read the sentences, mark the words that carry a new idea, then map each idea to a recipe:

what is saidrecipe
Hook (first 3-4 s): "Stop X-ing", "N tools that...", "You're doing X wrong"column light + pip, frame 0 already built: heavy key word + thin partner word + a row of 3-4 dark tiles as blurred placeholders that focus-pull on each name
"these N things / here are N..."list fly, light, whipLeft, one pill per item on its beat, labels blur (tease)
"The first / next is X" (product, app, person, concept)tool, dark, dissolve: title + hero dark tile + connector + 1-2 descriptor lines on cue (stat + unit, or what it is + what it's for)
X works with / supports A, B, Csame tool + worldAt -> light + satellites (orbit; emphasise the first two named)
"without / no more / don't need X"column heavy text with strikeAt on that word (light + pip, often whipUp)
"instead of paying for X", "replaces X"column clear tile(s) of X, xAt when X is named
prices / cost comparisoncolumn: label brand, big red price + strikeAt + strikeTilt: 3, chip new thing, big "$0"
demo of an AI / chat / promptchat (types the prompt, sends, thinking dots, optional reply)
UI features ("tools, masks, layers")media + highlights cycling on each word (needs a screenshot)
input -> output (voice, audio, before/after image)wave cards, or media + compare
requirement / caveat ("you need A or B")column dark + pip: small label, pill A, italic "or", pill B
payoff / punchlinecolumn dark, no speaker: struck heavy + giant big (~550 px) + small grey label
teaser ("one more I couldn't fit")column dark + pip, repo lime pill, blur, badge: "+1", y: 640
"want all of them?"list drop (dark, morph) of all items above the lime pill
CTA "comment WORD"cta dark + pip (morph): mono label, box types WORD in lime, italic follow-up line
personal / connective line, sign-offfull, hard cut, 0.7-1.7 s; also the final sentence

For other kinds of talking heads (story, opinion, tutorial) use the same kit: claims and numbers -> column heavy/big (+strike for "not X"), named things -> tool cards or tiles, steps -> list fly with icon pills ({icon:'check'}, monograms {text:'1'}), feelings / asides -> full breathers, payoff -> dark column, CTA -> cta.

Rules: start every scene ON its first word (W.at), never in a pause · cue elements to their exact words · alternate worlds, never > ~8 s in one world, a full breather every 6-8 s · one idea per scene, <= 5 text elements · frame 0 must already look finished · end on full for the last sentence · no emoji; without a brand logo use a monogram {text:"F5"} or a line icon {icon:"bolt"} (bolt dollar clock chart trend check x lock mic play image code globe heart star user users chat cpu cloud search doc spark rocket video mail link gear phone shield fire) · without a screenshot use chat / wave / column / tool instead of media - never invent a product UI beyond its logo.

3.0 Worked example for a non-tool video (generalising the grammar)

Script: "I saved ten thousand dollars in six months. And no, I didn't get a raise. First, I cancelled every subscription I wasn't using - Netflix, Spotify, Adobe - that was 240 dollars a month. Second, I moved my savings to a high-yield account. Third, I automated everything, so the money leaves before I can spend it. Want my spreadsheet? Comment SAVE and I'll send it to you."

 0.0 column/light/pip  big "$10,000" (built at frame 0) + label "in 6 months" on "six months"
 2.4 full              cut       "And no, I didn't get a raise."  (aside -> breather)
 4.1 column/light/pip  cut       mono "step 01" + heavy "cancel" + 3 clear tiles netflix/spotify/adobe, xAt on each name
 7.9 column/light      cut       label "subscriptions" / big red "$240" + strikeAt "240" / chip "every month"
 9.6 tool/dark         dissolve  title "high-yield", logo {icon:'trend'}, lines "4.5% APY" on "high-yield", "vs 0.01%" sub
12.8 column/dark/pip   cut       mono "step 03" + heavy "automate" + italic "before you can spend it" on "before"
15.4 full              cut       "so the money leaves before I can spend it" tail / breather
16.9 cta/dark/pip      morph     label "comment below", word "SAVE", line "and I'll send you the sheet."
19.5 full              cut       "...send it to you."

captions: --serif "no,didn't,never" --accent "save"; logos: netflix spotify adobe (fetch_logos.py).

3.0b Field-tested: 13 s quote clip, no brands (podcast guest quoting an author, opens on another camera angle; names changed)

Script: "Ava Moreno says, accept that all of your heroes are full of shit. Your heroes aren't gods, they're just regular people who got particularly good at one thing by sacrificing literally everything else. That's the price that you pay for success, and most people wouldn't pay it."

 0.00 column/light/pip  cut       dark tile {text:'AM'} + heavy "AVA MORENO" (built) + thin "says" on "says"; y 650,
                                  pipIn 0.47 (opening off-shot: close-up angle + whip frame)
 1.64 column/light/pip  whipUp    italic "accept that" / heavy "YOUR HEROES" on "your" / thin "are full of sh*t" on "full"
 3.45 column/dark       dissolve  mono "your heroes" / big "GODS", strikeColor orange, strikeAt "aren't"
 4.47 tool/dark         cut       title "regular people", {icon:'users'} tileAt "people"; "particularly good" / sub "at one thing"
 7.17 column/light/pip  dissolve  italic "by sacrificing" / 3 clear tiles heart, clock, globe - xAt literally, everything,
                                  else / heavy "EVERYTHING" on "everything" / thin "else." on "else"
 9.47 column/dark       dissolve  dark tile {icon:'dollar'} / heavy "THE PRICE" on "price" / thin "you pay" / lime italic
                                  "for success." on "success" (the edit's single lime moment)
11.71 full              cut       "and most people wouldn't pay it."

captions: --serif "[email protected],aren't gods,everything else,wouldn't". Lessons: a quote -> attribution on frame 0, the line itself in the next beat; a person with no logo -> monogram tile; abstract nouns ("everything else") -> line-icon tiles you can X; a payoff builds top-down, one element per spoken word (no element idle > ~1.2 s); profanity -> soften in the graphics ("sh*t"), keep it in the captions as spoken unless the user asks; the same icon never means two things.

3.1 The reference edit as a beat sheet (calibration)

 0.0 column/light/pip  STOP paying + 3 blurred dark tiles (chatgpt, midjourney, elevenlabs focus on their words)
 3.5 list/light        whipLeft  5 blurred repo pills fly in
 6.6 tool/dark         dissolve  "ollama" + llama tile; 180k / GitHub stars; worldAt 10.5 -> light orbit of 6 model tiles (gemma + deepseek grow)
14.4 column/light/pip  whipUp    "no API key", struck on "api key"
16.6 tool/dark         dissolve  "open webui"; a chat app / on top of ollama
18.7 full              cut       "it sits on top of ollama and works"
20.0 chat/light/pip    cut       llama3.2 card types "Write me a business plan", sends
22.7 tool/dark         dissolve  "invokeai"; local image generation / with a full editing canvas
24.7 column/light/pip  dissolve  big clear midjourney tile, red X on "midjourney"
26.3 full              cut
27.8 media/light       cut       screenshot; tools -> masks -> layers highlights + zoom card
29.8 tool/dark         dissolve  "f5-tts" (F5 monogram); voice cloning / from a short clip
32.0 full              cut
33.1 wave/light/pip    cut       SHORT AUDIO CLIP -> CLONED VOICE (lime)
35.2 column/light      cut       elevenlabs / $60 red struck / f5-tts chip / $0
37.0 tool/dark         dissolve  "upscayl"; 50k / GitHub stars
40.6 media/light       dissolve  before/after wipe
43.3 full              cut
44.4 column/light      cut       Topaz + Magnific clear tiles, X on each name; upscayl chip; $0
46.3 full              cut
47.3 column/dark/pip   cut       some of these want / a decent GPU / or / an M-series Mac
49.3 column/dark       cut       monthly bill (struck) / $0 (550 px) / after that
50.5 full              cut
51.2 column/dark/pip   cut       lime +1 pill (blurred name)
53.4 list/dark (drop)  morph     all repos drop above the lime pill
54.5 cta/dark/pip      morph     COMMENT BELOW, types LOCAL; "and the link lands in your dms."
56.0 full              cut       "...send them to you directly."

4. Writing the edit - scripts/plan.py

# work/plan_edit.py
import sys; sys.path.insert(0, 'scripts')
from plan import *
W = Words('work/words.json')      # W.at("phrase", after=sec, offset=..) -> start time of the spoken phrase
S = [
  scene('column', 0, 'light', 'pip', 'cut', items=[heavy('STOP'), thin('paying'),
        tiles('dark', [tile('openai', W.at('chatgpt')), tile('midjourney', W.at('midjourney'))], placeholder=True)]),
  scene('list', W.at('these five'), 'light', 'none', 'whipLeft', mode='fly', blurLabels=True,
        items=[{'label': 'ollama', 'at': W.at('these')}, {'label': 'open webui', 'at': W.at('five')}]),
  scene('tool', W.at('the first'), 'dark', 'none', 'dissolve', title='ollama', logo=logo('ollama'),
        lines=[line('180k', W.at('180,000')), line('GitHub stars', W.at('stars'))]),
  scene('full', W.at('it sits'), 'dark', 'full', 'cut'),
]
write_edit('work/edit.json', S, W, captions='work/captions.json', framing='work/framing.json',
           logos='public/logos/logos.json', video='src/clip.mp4', fg='src/clip_fg.webm')

Builders: scene, logo, tile, tiles, heavy, thin, big, label, italic, mono, pill, chip, repo, line (extra keywords become props). write_edit makes scenes contiguous. Paths in edit.json are relative to public/.

5. edit.json reference

Top: {duration, speaker:{video, fg?, srcW, srcH, full:{scale,x,y}, pip:{scale,x,y}}, logos, captions:[{text,s,e,style}], scenes}. Scene: type, start, end (s), world: light|dark, speaker: none|pip|full, enter: cut|dissolve|whipUp|whipLeft|morph, optional worldAt:[{t,world}], hideCaptions, captionDy, y (vertical centre), pipIn (s; pip scenes: the PiP rises in at that time instead of at the scene start - a column then sits at yFree (default 770) and glides to y). Top-level offShots:[[t0,t1,why]] is copied from framing.json by write_edit (validate.py checks it). Logo: {slug} | {text:"F5"} | {icon:"bolt"} + word, row, wordmark, mono, src, color.

typeprops
columnitems stacked, centred (y 600 with PiP, 770 without). kind: heavy, thin, big, label, italic, mono, pill, chip, repo, tiles, spacer. Item props: text, at, size, color, gap, anim:"rise"/"blur"/"none", strikeAt, strikeColor, strikeTilt. tiles: {style:"dark"/"clear", placeholder, size, tiles:[{logo, at, xAt}]}. repo: {label, variant:"cream"/"lime", blur, badge, icon, w, h}
tooltitle, logo, titleAt, tileAt, titleSize, lines:[{text, at, kind:"stat"/"text"/"sub"}], satellites:{at, items:[{logo:{slug,word}}], focus:{at, idx:[i,j]}} (6 satellites: 0 = top, clockwise, 3 = bottom front)
listmode:"fly"/"drop", items:[{label, at, blur, icon, badge}], blurLabels, pillH, y; drop: dropAt, highlight:{label, fromY, badge, blur}
chatmodel, tag, logo, greeting, prompt, typeAt, sendAt, reply, replyAt, cps, placeholder, at, h, y
wavetop:{label, at}, bottom:{label, at}, growAt, y
mediasrc (public/), aspect (w/h), at, w, h, y, highlights:[{box:[x,y,w,h] 0-1, label, at}], compare:{at, dur, before?, labels}
ctalabel, word, typeAt, line, lineAt, button, at, y
full-
Cue times (at, xAt, strikeAt, typeAt, sendAt, lineAt, growAt, dropAt, titleAt, tileAt, pipIn, worldAt.t) are absolute
seconds inside their scene; omitted at = scene start. Defaults are tuned to the reference - override sparingly.

6. QA (always)

  1. validate.py has no ERROR; read every WARN (an off-shot WARN means the speaker shows a wrong angle - fix it).
  2. Stills at frame 0, mid-scene for each scene, +2 f after every dissolve/whip, and on each cue. Contact sheet -> look: text inside x 70-1010, nothing under the caption or the PiP head, logos right (white on dark tiles), captions readable, no empty frames, matte clean (no holes / halos / smoky objects) on full-screen shots and on the PiP head (work/matte_check.jpg). Frame 0 is the thumbnail: finished text, finished caption, no half-cut head or foreign camera angle in the PiP.
  3. After rendering: ffprobe (1080x1920, 30 fps, audio, duration = clip) and 3-4 extracted frames.

Skills associés