Skip to content

Effects and themes

Effects are styled by the active design theme (editorial by default; also mono, soft, night, xhs-pop); see Themes and looks and the style rules. Browse on your machine with python3 -m vstudio.effects --list or --show <id>.

Paths are relative to the repo root ($VSTUDIO). The Engine column says how the effect is drawn:

  • PIL: a per-frame Python pass on numpy frames (PIL, OpenCV or numpy), piped to ffmpeg.
  • ffmpeg: a filter graph only.
  • HF: HyperFrames HTML + GSAP, rendered by npx hyperframes render.
  • audio: numpy audio or an ffmpeg audio filter.

Most effects can only be reused inside the same engine family, so pick the engine first. A HyperFrames project uses vstudio.hf. A per-frame Python compositor uses vstudio.overlays / vstudio.draw images or photo-story’s frame functions. An ffmpeg graph uses the filter strings. Transitions are the exception: vstudio.xfade gives every transition name (the 11 HF types, the photo-story kinds, common ffmpeg xfade names) an implementation in all three engines (xfade.blend, xfade.ffmpeg_transition, xfade.hf_transitions); see the coverage matrix in section 7.

Find effects from code: effects.find(engine="ffmpeg", energy="low", text="zoom"), effects.get("light-leak"), or python -m vstudio.effects --list --engine hyperframes / --show <id>.

HyperFrames rules (vstudio.hf already follows them):

  • Overlays start at opacity: 0 in CSS.
  • Never use fromTo twice on one target; later moves use to(), or immediateRender: false with an identity tl.set at t=0.
  • The outgoing scene or clip must stay alive (longer data-duration) for the whole transition.
  • Asset paths are project-root-relative (assets/x.png), never ../.

Full list: workflows/explainer/references/pitfalls.md.

The vstudio.hf API. Every function returns {"css", "html", "js"}:

  • Put the css in one <style>, the html in the root composition, and the js after hf.prelude(), which defines tl and $.
  • Numbers and lists are inlined as JSON. Wrap a value in hf.JS("D.X") to reference your own data object instead.
  • hf.indent(s, n) nests a snippet inside a block.

Worked users: workflows/promo-recut/scripts/build_promo.py (almost every generator) and workflows/explainer/scripts/make_index.py (transitions).


The tables below are generated from the registry in lib/vstudio/effects.py. Do not edit them by hand: change the registry, then run PYTHONPATH=lib python3 -m vstudio.effects --write-md. tests/test_effects.py fails if the block is stale. To add an effect or port one to another engine, see ADDING_EFFECTS.md.

Effect What Engine Where Params When to use Energy Hold / duration Max uses Reuse Tested
Punch-in (eased) punch-in Talking head scales up for a window, then back HF lib/vstudio/hf.py:punch_in scale=1.14, in_dur=0.45, out_dur=0.5 Stress a sentence in a talking head without cutting med 0.45 s in, window 2-5 s, 0.5 s out ~1 per 20 s of talk; vary windows hf.punch_in([[12.0, 15.5]]) on an inner wrapper around your video test_hf.py
Punch-and-stay punch-and-stay One punch that holds (e.g. on the punchline) HF hf.py:punch_at scale=1.16, dur=0.5, origin=50% 38% Land a punchline or a reveal and stay there high 0.5 s move, holds to the next cut 1-2 per video (A7) hf.punch_at("#ow", 41.2), then hf.stamp(..., at=42.1) test_hf.py
Wrapper zoom entrance wrapper-zoom-entrance Settles from 1.15x and transparent HF hf.py:enter_zoom from_scale=1.15, dur=0.6 Bring in any section wrapper (outro, new section) med 0.6 s 1 per section Use it to bring in any section wrapper (promo’s outro) test_hf.py
Per-sentence punch-in (hard cut) per-sentence-punch Face-centred zoom per sentence: EMPH sids get emph_zoom, odd sids get alt_zoom PIL workflows/talkinghead/scripts/vertical/compose.py:zoom emph_zoom=1.32, alt_zoom=1.16 Vertical talking head with jump cuts between sentences med one sentence (2-6 s) every other sentence; EMPH <= 1 in 4 Copy zoom(); in HF use hf.punch_in -
Ken Burns (8 motions) ken-burns Slow pan / zoom / 3D turn on one photo (in, out, panL, panR, up, down, still, flip) PIL workflows/photo-story/scripts/photostory/shots.py:ImageShot motion=in, z=1.12, c=(0.5, 0.5) Any still photo held >= 2 s low 3-6 s per photo unlimited, but alternate directions ImageShot(C, sh, name).frame(lt) with a Ctx; in HF use the hyperframes-keyframes skill -
Code-zoom cut-in code-zoom Tighter crop on the code block, centred on the code-colour centroid, with a seamless join ffmpeg workflows/longform-to-short/scripts/zoom_targets.py, build_timeline.py:zoom_crop zoom.box=[736, 336], zoom.tol=colour tolerance Screen recordings where the code / panel is unreadable at full frame low the whole window being read as needed (it is legibility, not a flourish) crop=w:h:x:y,scale=W:H on the window; zoom_targets finds any flat-coloured panel -
Zoom-through into a framed screen zoom-through The shot scales 1.35x and blurs out while the framed screen lands, then drifts HF hf.py:zoom_through (+ framed_screen) scale_in=0.84, drift=0.85 Go from a talk into a highlights reel or demo high ~1 s move, then the reel 1 per video (A7) hf.zoom_through(at, until) + hf.framed_screen(...) test_hf.py
Stabilize stabilize deshake + crop ffmpeg workflows/vlog/scripts/build_vlog.py:stab_prefix stab_rx/ry=32, stab_zoom=0.93 Handheld phone / walking clips n/a whole clip unlimited Prepend deshake=rx:ry:edge=clamp,crop=iw*k:ih*k to any chain -
Loupe loupe A magnifier travels a path over the photo PIL photostory/overlays.py:overlays (loupe=) loupe=[(x, y), ...], loupe_mag=2.3 Show a detail inside a photo (inscription, brushwork) low 3-5 s 1-2 per video photo-story shot opt; elsewhere copy the block (needs C.b) -
Effect What Engine Where Params When to use Energy Hold / duration Max uses Reuse Tested
Split screen split-screen Full-frame face clipped to an inset() and slid aside per window; windows < 0.2 s apart are bridged HF hf.py:split_screen; geometry in build_promo.py:GEO dur=0.7, bridge=0.2 Talking head + something to show (screenshot, recording) med 0.7 s in, window >= 3 s the main device of a promo; 1 star use + repeats Any HF project with a talking-head wrapper; fill the freed side with screenshot_cards or a <video> test_hf.py
Vertical face band + card vertical-face-band 9:16 promo: face band on top, card below HF build_promo.py:GEO["vertical"] split_inset=inset(200px 40px 1000px 40px round 28px) Vertical promo with screenshots med same as split windows as split screen --orientation vertical, or pass GEO values to hf.split_screen -
Call layouts call-layouts 2 tiles stacked, trio (2 masked guests + host), landscape pair, landscape trio (6 s-smoothed reframe) (vertical, trio, landscape, landscape-trio) PIL workflows/call-clips/scripts/render_vertical.py, render_trio.py, render_landscape.py, render_landscape_trio.py TILE_H=608, REFRAME_S=6 Zoom / Meet / Teams recordings low whole clip one layout per clip build_clips.py --renderer <file>; --guest-region / --host-region -
Fit: crop / pad / blur-pad / stretch fit-modes Fit any aspect into the canvas (crop, pad, blur, stretch) ffmpeg lib/vstudio/cut.py:fit_chain; vlog/scripts/build_vlog.py:fit_chain fit=crop Mixed-aspect sources in one timeline n/a whole clip unlimited cut.fit_chain(size, mode) or xfade_assemble(fit=...) test_core.py
Browser-chrome crop browser-chrome-crop Detects doc / browser headers per span and crops them plus the bookmark bar, then fits and pads ffmpeg longform-to-short/scripts/geometry.py; render.py browser_header_px=115 Screen-share recordings n/a per span unlimited Run geometry.py on any screen recording and use the crop spans -
Photo layouts photo-layouts Multi-photo compositions in the picture box (collage, film, split, grid, rows, tilt, deck, quote, route, medal) PIL photostory/shots.py (CollageShot … MedalShot; factory build(C, sh, k)) src="collage:a,b,c", yaw=tilt angle Comparisons, sets, routes, quotes inside a photo story med 3-6 s vary kinds; each kind 1-2 per video shots.build(C, sh, k).frame(lt) -
Video in a photo story video-in-photo-story Pre-cut, graded clip read frame by frame PIL + ffmpeg photostory/shots.py:VideoShot, prep_video speed=1.0, grade=VEQ Short live moments between photos med 2-5 s unlimited photo-story spec src="v1234" -
Effect What Engine Where Params When to use Energy Hold / duration Max uses Reuse Tested
3D screenshot card screenshot-card-3d A card slides in with rotationY -28 to -6 deg, drifts, then leaves HF hf.py:screenshot_cards card_w=760, accent=theme Show a screenshot while the speaker talks about it med card on screen >= 3 s 1 card per claim; the 3D entry stars once, later cards can enter flatter hf.screenshot_cards([dict(id="c1", img=..., w, h, s, e, scroll=[[3, 0]])]) test_hf.py
Card scroll keyframes card-scroll The image scrolls to bring row y to the top at time t HF hf.py:screenshot_cards (scroll=[[t, y], ...]) scroll=[[t, y_img_px]] Long screenshots where the line being discussed is below the fold low 0.6-1.2 s per move as needed Find rows with promo-recut/scripts/find_rows.py test_hf.py
Chips row chips-row Pills pop in one by one; a star chip is gold; all fade at end HF hf.py:chips items=[[t, text, star]], gold=#F4D35E List of features / tags med pop 0.3 s each, hold >= 1 s full 1 row per section hf.chips([[3, "Writing", 0], [4, "Video", 1]], end=9) test_hf.py
Chip / badge / tag / stamp images overlay-images Themed RGBA images for PIL / ffmpeg compositors (outline, filled, star, tag, ghost) PIL + ffmpeg lib/vstudio/overlays.py:chip, badge, tag, stamp style=outline, scale=1.0 Any per-frame or ffmpeg compositor low hold >= 1 s unlimited (each style stars once) Paste with draw.alpha_paste; ffmpeg can overlay as -loop 1 PNGs test_visual.py
Badge (jingxuan / preview) badge Solid label that slides in HF + PIL hf.py:badge; overlays.badge accent=theme Mark a section as highlights / preview low whole section 1 per section HF: hf.badge("...", s, d, at); PIL: overlays.badge("...") test_hf.py
Outlined tag outlined-tag Fading pill (e.g. ‘full version / excerpt / 1.1x’) HF hf.py:tag pos_css=right:160px; top:956px; Context labels low section 1-2 per video hf.tag(...) test_hf.py
Framed screen framed-screen Rounded, shadowed screen playing a video on a grid backdrop; shrinks away at exit HF hf.py:framed_screen, grid_backdrop_css rate=1.0 Any ‘video inside a monitor’ beat med length of the reel 1 per video hf.framed_screen(src, start, dur, exit_at) test_hf.py
Notes panel notes-panel Light theme card: small label, title, hairline rule, bullets (legacy: coloured header) (paper, notes-red, notes-yellow, teal, navy) PIL + ffmpeg overlays.py:notes_panel; per-row reveal in talkinghead compose.py (PANELS) width=620, theme=paper Summarise 2-4 points the speaker is listing low reveal 0.3 s per row; hold >= 2 s after the last row 1 per topic PIL image; burn with ffmpeg overlay=...:enable='between(t,a,b)'; for HF save a PNG test_visual.py
Callout bubble callout-bubble Speech bubble that slides up 24 px PIL overlays.py:callout; compose.py (CALLOUTS) max_w=560 Side comments, asides low 2-4 s ~1 per 30 s Same as the notes panel test_visual.py
Node card node-card ‘NEXT’ seam card PIL overlays.py:node_card; call-clips/.../render_vertical.py:render_node_card eyebrow Topic change inside a clip low fade 0.35 s, hold ~1.5 s 1 per topic change Paste at a cut and fade over 0.35 s test_visual.py
Quote card quote-card Typographic quote: weight contrast, hanging opening mark, short accent rule PIL overlays.py:quote_block; call-clips render_trio.py:render_quote width=980, size=80 Pull a strong line from a call low hold >= 2 s 1-2 per clip overlays.quote_block([(main, 'main'), (sub, 'sub')], width) test_theme.py
Chapter rule chapter-rule A hairline draws across with a small tracked label (02 · name) and optional title PIL overlays.py:chapter_rule (progress 0-1) progress=1.0, title A quiet section change instead of a full-frame chapter card low 2-3 s 1 per section overlays.chapter_rule("第二部分", "先跑通,再优化", index=2, progress=p) test_theme.py
Lower third lower-third Theme card with name and role, short accent rule PIL overlays.py:lower_third scale=1.0 Introduce a speaker low 3 s 1 per speaker overlays.lower_third("Name", "Role") test_theme.py
Circle-face list scene circle-face-list Blurred bg, ringed circle crop of the face, title, popping tokens PIL compose.py (blurbg, circle_inset, token_img) R=290 Listing items while still seeing the speaker med the list span (4-10 s) 1-2 per video Copy the 3 helpers -
Shrink-to-card scene shrink-to-card Frame shrinks to a 0.55x rounded card over a blurred copy, with title and lines PIL compose.py:card s=0.55, r=36 A section summary over the speaker med 4-8 s 1-2 per video Copy card() + blurbg() -
Privacy sticker privacy-sticker Face-tracked sticker covering a participant, with a coverage proof PIL call-clips/scripts/track_face.py -> apply_sticker.py; verify_coverage.py scale=2.4, smooth=0.25 Hide a participant’s face n/a whole clip unlimited track_face.py VIDEO --region ..., then apply_sticker.py VIDEO --track --sticker cat.png -
Camera-off avatar camera-off-avatar Full-tile cat avatar PIL + HTML call-clips/assets/cat_avatar.html, verify_avatar.py --tile=960x540 A participant with camera off n/a whole clip unlimited Render with render_sticker.py --size 960x540 -
Photo overlays photo-overlays Develop, shimmer, dust, pounce holes, red pen, triangle, loupe, timeline, count-up, label (develop, shimmer, dust, prick, hl, tri, loupe, tl, count, label) PIL photostory/overlays.py:overlays(C, sh, f, lt); label in render.py + subtitles.make_label fx=(), hl_t=0.3 Point at something inside a photo story shot med per shot each overlay stars once (A7) Use them in a photo-story spec -
Polaroid / taped card polaroid Taped polaroid with a shadow and caption PIL lib/vstudio/cover.py:polaroid; photostory/shots.py:make_card rot=0 Scrapbook / travel looks, covers low hold >= 1 s unlimited in a scrapbook section cover.polaroid(img, 600, rot=-4, cap="...") test_visual.py
Effect What Engine Where Params When to use Energy Hold / duration Max uses Reuse Tested
Number counter number-counter A figure counts up with ease-out, small label under it PIL overlays.py:counter(value, t) count=0.9, suffix A number the speaker says that matters (fans, years, money) low 0.9 s count, hold >= 1 s 1-2 per video overlays.counter(7, t, label="粉丝", suffix="万") test_theme.py
Keyword subtitles (HF) keyword-subtitles-hf Cue pops up 14 px and fades; highlighted term in the highlight colour HF hf.py:subtitles + overlays.cue_html + overlays.hf_cue_css geo=horizontal Every narrated HF video low per cue always on overlays.cue_html(...), then hf.subtitles(cues) test_hf.py
Burned subtitles (PIL) burned-subtitles-pil Bold line with stroke, keywords in yellow, balanced CJK wrap PIL lib/vstudio/draw.py:text_layer; talkinghead compose.py stroke=6, sub_size=54 Per-frame compositors low per cue always on draw.text_layer("...", draw.load_font("cjk-bold", 54)) test_visual.py
ASS subtitles ass-subtitles libass burn-in with highlight and bilingual alt line ffmpeg lib/vstudio/subs.py:ass_write; longform-to-short/scripts/burn_final.py alt_scale=0.72, outline=2.4 Pure ffmpeg pipelines low per cue always on subs.ass_write(cues, "s.ass"), then -vf ass=s.ass test_core.py
Bilingual EN/ZH subtitles bilingual-subtitles EN above ZH; word in gold PIL photostory/subtitles.py:make_sub f2 size=0.72x Bilingual stories low per cue always on photo-story; elsewhere draw.bilingual_layer test_lib_requests.py
Pop words pop-words Big stroked word with a -4 deg bounce PIL compose.py:pop_img + ease_pop size=per pop, hold=per pop Punctuate a key word high pop 0.25 s, hold >= 1 s <= 1 per 15 s talkinghead; in HF use hf.stamp with angle=-4 -
Title card title-card Centred boxed title + gold sub line; back.out pop, lifts away HF hf.py:title_card hold=1.9, size=96 Section openers med pop 0.4 s + hold 1.9 s 1 per section hf.title_card("...", s, at, sub="...") test_hf.py
Hook title (talkinghead) hook-title 2-line hook title (line 2 has keywords) + badge PIL compose.py (HOOK_TITLE, HOOK_BADGE_TEXT) hook_badge=STYLE Opening seconds of a short high first 2-4 s 1 per video talkinghead config -
Step labels step-labels Numbered ‘01 …’ labels swap in place over a reel HF hf.py:step_labels labels=[{s, e, n, t}] Tutorial steps, reels low >= 1.5 s per label 1 set per reel hf.step_labels(labels, start, dur) test_hf.py
End card end-card Serif kicker, big line, dim CTA; staggered fade-ups HF hf.py:end_card size=80 Outro / CTA low 3-4 s 1 per video hf.end_card(E, 3.2, "kicker", "main", "CTA") test_hf.py
Chapter card chapter-card Full-frame ‘02 / 05 + title’ card PIL overlays.py:chapter_card; longform-to-short/scripts/make_assets.py; photo-story subtitles.make_chapter cards.dur=1.6 Chapter boundaries in long videos med 1.6 s 1 per chapter Render as a PNG and insert as a -loop 1 still test_visual.py
Running header running-header Section names, per-section progress, titles PIL photostory/subtitles.py:Header SECTIONS=spec Photo stories with sections low whole video always on photo-story -
3b1b scene techniques 3b1b-techniques Draw-on lines, count-ups, type-in, clip wipes, axis grow, math built term by term (draw-on, count-up, type-in, clip-wipe, axis-grow, stagger, svgOrigin, drift, math-terms) HF workflows/explainer/references/design-truth.md, assets/reference-scene.html ease=power2.out, drift=<= 6 px Explainer scenes med per scene each technique stars once per scene Copy from the reference scene -
Slides slides Square / vertical slide presets as PNG or animated clip (s-title, s-contrast, s-three, s-punch, s-bars, s-recap) HTML workflows/slides/templates/slides_vertical.template.html, scripts/render_slides.py, record_slides.py --accent=persona B-roll for vertical videos low 3-6 s per slide unlimited Drop PNGs or clips into any edit -
Effect What Engine Where Params When to use Energy Hold / duration Max uses Reuse Tested
Marker sweep marker-sweep A soft highlighter band sweeps in behind the keyword of one line PIL overlays.py:marker_line (sweep 0-1) sweep=0.45, size=0.06 Land the one keyword of a sentence quietly low 0.45 s sweep, hold >= 1.5 s 1 per 20 s (S2) overlays.marker_line("先做【减法】", 64, sweep=p) test_theme.py
Highlighter rows highlighter-rows A yellow multiply bar sweeps across a screenshot row HF hf.py:screenshot_cards (hl=[[t, y0, y1, frac]]) frac=0.6 Point at one line in a screenshot low sweep 0.5 s, hold >= 1 s 1-2 per card Rows from find_rows.py test_hf.py
Red box red-box Accent rounded box pops (back.out) around a region HF hf.py:screenshot_cards (box=[t, y0, y1]) accent=theme Frame a block in a screenshot med pop 0.4 s, hold >= 1 s 1 per card Same as highlighter rows test_hf.py
Stamp stamp-hf Bordered word slams in from 2.2x HF hf.py:stamp angle=-12, size=96 Verdict words (‘works’, ‘tested’) high slam 0.3 s, hold >= 1 s 1-2 per video (A7) hf.stamp("...", s, d, at) test_hf.py
Stacking stamps stacking-stamps White plate + accent stamp slam-ins; same end time = stack PIL overlays.py:stamp; compose.py (STAMPS) angle=8, size=56 Per-frame verdicts high slam 0.25 s each 1 stack per video overlays.stamp("...", 8) test_visual.py
Red-pen ellipse + dim red-pen-ellipse Double ellipse drawn on, outside dimmed 50 % PIL photostory/overlays.py (hl=) hl_t=0.3 Point at a region of a photo med draw 0.3 s, hold >= 1.5 s 1-2 per video photo-story; for covers cover.redpen_ellipse test_lib_requests.py
Gold count-up gold-count-up Big number counting up, with a caption PIL photostory/overlays.py (count=) count=(n, "EN", "ZH") Numbers worth remembering med count 1 s, hold >= 1 s 1-2 per video photo-story; in HF use the explainer count-up proxy pattern -
Shimmer / develop / sketch reveal reveal-fx Gold light sweep; develop from a warm wash; pencil-to-colour (shimmer, develop, sketch) PIL photostory/overlays.py, shots.py:pencil fx=("shimmer",) A hero photo’s first appearance med 1-2 s light effects <= 2 per video photo-story spec -
Effect What Engine Where Params When to use Energy Hold / duration Max uses Reuse Tested
Freeze-frame split freeze-split Video split into clip + still + clip; resumes with data-media-start HF hf.py:freeze_clips hold=2.6 Pause on a moment to explain it med hold 2-3 s 1-2 per video hf.freeze_clips(src, freeze, start, cut_at, hold, media_dur, rate) test_hf.py
Freeze hold + flying zoomed card freeze-hold-card Dims the freeze; a zoomed card flies out, holds, flies back HF hf.py:freeze_hold hold=2.6, fly_from=(380, -150) Enlarge a prompt / a line from a screen high fly 0.6 s, hold 2-3 s 1 per video (A7) hf.freeze_hold(T, 2.6, (380, -150), "label", "assets/img/prompt.png") test_hf.py
Transient freeze transient-freeze Holds the frame from 0.4 s before an accidental screen flash; live audio continues ffmpeg longform-to-short/scripts/transient_scan.py -> freezes, build_timeline.py, render.py white_thresh=0.55 Screen recordings with flashes n/a length of the flash as needed transient_scan works on any screen recording -
Effect What Engine Where Params When to use Energy Hold / duration Max uses Reuse Tested
11 scene transitions hf-scene-transitions GSAP scene-to-scene transitions on wrapper divs (blur, fade, push, vpush, iris, zoom, focus, blocks, chroma, flip, zoomout) HF hf.py:scene_transitions, hf.transition, hf.TRANSITIONS; menu in workflows/explainer/references/transitions.md d=per type (0.45-1.1 s), blocks=8 Scene changes in HF projects; one calm family + iris for the core reveal + blocks for chapters med blur .8, fade .45, push .7, vpush .7, iris 1.1, zoom .8, focus .9, blocks 1.1, chroma .8, flip .9, zoomout 1.0 hard cut is the default; styled ones only where place / day / mood changes (A5) hf.scene_transitions([...]), or xfade.hf_transitions for any bridge name test_hf.py
12 photo transitions photo-transitions Per-frame transitions inside the photo-story picture box (fade, push, whip, flash, zoom, iris, leak, ink, blinds, tear, slideup, cut) PIL photostory/transitions.py:transition(C, P, N, p, kind); durations TRD trd=TRD[kind] (0.32-0.7 s) Photo stories med 0.32-0.7 s vary; <= 2 styled per minute xfade.blend(name, P, N, p) (no Ctx) or the photo-story function -
Cross-engine transitions (bridge) xfade-bridge One transition name in all three engines: HF GSAP, ffmpeg xfade (built-in or custom expr), PIL per-frame (blur, fade, push, vpush, iris, zoom, focus, blocks, chroma, flip, zoomout, whip, flash, fadeblack, light-leak, ink, blinds, tear, slideup, cut, wipe, dissolve, pixelize, radial) HF + ffmpeg + PIL lib/vstudio/xfade.py (blend, ffmpeg_transition, ffmpeg_expr, hf_transitions, coverage) name=fade, duration=xfade.default_duration(name) Any time the same look must exist in two engines (a HF explainer and its ffmpeg cut-down) med per name (0-1.1 s) same as the source effect cut.xfade_assemble(pieces, transition=xfade.ffmpeg_transition("iris")) test_effects.py
Light-leak transition light-leak Cross-dissolve under a warm film light leak HF + ffmpeg + PIL xfade.blend("light-leak", ...), xfade.ffmpeg_transition("light-leak"), xfade.hf_transitions([("light-leak", ...)]) strength=0.9, duration=0.7 Travel / memory pieces, a change of day or place med 0.7 s <= 2 per video (light effects cheapen fast) xfade.blend("light-leak", A, B, p) per frame of the overlap test_effects.py
Dissolve joins (xfade) xfade-joins Any ffmpeg xfade between pieces, with acrossfade and mute pads ffmpeg lib/vstudio/cut.py:xfade_assemble; vlog build_vlog.py (transition, xfade=.8) xfade=0.3, transition=fade Joining pieces in any ffmpeg workflow low 0.15-0.8 s unlimited for plain fades cut.xfade_assemble(pieces, xfade=0.3, transition="fadeblack") test_core.py
Hook montage hook-montage Sped-up hooks with crossfades into the body ffmpeg workflows/talkinghead/scripts/montage.py:Montage hook_speed=1.3, body_speed=1.1 Cold open of a short high 3-8 s 1 per video Montage(...) is importable; use .graph -
Speed ramps speed-ramps Per-segment setpts (+ atempo) ffmpeg vlog/build_vlog.py:seg_speed; media.atempo_chain speed=1.2 Tightening or energy ramps med per segment ramps as an effect: 1 star use setpts=PTS/s + media.atempo_chain(s) test_core.py
End fade (no fade-in) end-fade fade=t=out + afade; never a black first frame ffmpeg vlog/build_vlog.py fade_out=1.5 Last pass of any video low 1.5 s 1 per video Use in any final pass -

Transition coverage across engines (vstudio.xfade)

Section titled “Transition coverage across engines (vstudio.xfade)”

exact = same look; near = same idea, small visual difference; approx = closest stand-in.

Transition Default s HyperFrames ffmpeg xfade PIL blend Gaps
blur 0.8 blur (exact) hblur (approx) exact ffmpeg: xfade hblur smears horizontally only; no 2-D defocus
fade (crossfade, xfade) 0.45 fade (exact) fade (exact) exact -
push (slideleft) 0.7 push (exact) slideleft (near) exact ffmpeg: no 4 px motion blur
vpush 0.7 vpush (exact) slideup (near) exact ffmpeg: outgoing is not dimmed to 40 %
iris (circleopen) 1.1 iris (exact) circleopen (near) exact ffmpeg: centre is 50 %/50 % (HF 50 %/45 %); outgoing does not shrink to 0.94
zoom (zoomin) 0.8 zoom (exact) zoomin (approx) near ffmpeg: only the outgoing zooms; no blur, incoming static; pil-frame: incoming settles from 1.18x (photo-story) instead of 0.7x
focus 0.9 focus (exact) fade (approx) exact ffmpeg: xfade cannot defocus; pre-blur the tail/head with gblur or render in HF
blocks (panels) 1.1 blocks (exact) custom expr (near) exact ffmpeg: no 1 px panel edge line
chroma 0.8 chroma (exact) pixelize (approx) near ffmpeg: no RGB split; for the real look pre-render with rgbashift; pil-frame: split is a channel roll, not a drop-shadow
flip 0.9 flip (exact) squeezeh (approx) near ffmpeg: squeeze, no perspective or dimming; pil-frame: horizontal squeeze, no perspective foreshortening
zoomout 1 zoomout (exact) custom expr (near) exact ffmpeg: no blur on the outgoing
whip (whip-pan, whippan) 0.32 bridge GSAP (near) custom expr (near) exact hyperframes: CSS blur is 2-D, not directional; ffmpeg: 5-tap blur, slight ghosting at 1080p; slow (per-pixel expr)
flash (fadewhite, white-flash) 0.5 bridge GSAP (exact) fadewhite (near) exact ffmpeg: pure white, not warm white
fadeblack (dip, dip-to-black) 0.6 bridge GSAP (exact) fadeblack (exact) exact -
light-leak (leak, lightleak) 0.7 bridge GSAP (near) custom expr (near) exact hyperframes: CSS radial gradients with screen blend; ffmpeg: leak added in YUV (luma + warm chroma shift)
ink 0.6 blur (approx) custom expr (near) exact hyperframes: no organic mask; needs an SVG feTurbulence mask; ffmpeg: sine-field blotches instead of noise
blinds 0.55 blocks (approx) custom expr (exact) exact hyperframes: blocks covers with colour instead of revealing B
tear 0.55 push (approx) wipeleft (approx) exact hyperframes: no torn edge; ffmpeg: straight edge, no paper band or shadow
slideup (coverup) 0.45 bridge GSAP (exact) coverup (near) exact ffmpeg: outgoing is not dimmed
cut (hard, none) 0 bridge GSAP (exact) fade (near) exact ffmpeg: use xfade=0 in xfade_assemble for a true cut (concat)
wipe (wipeleft) 0.5 bridge GSAP (exact) wipeleft (exact) exact -
dissolve 0.6 fade (approx) dissolve (exact) exact hyperframes: no per-pixel noise
pixelize 0.6 chroma (approx) pixelize (exact) exact hyperframes: stepped glitch instead of pixel blocks
radial (clock, clock-wipe) 0.8 iris (approx) radial (exact) exact hyperframes: circle grows; no conic sweep
Effect What Engine Where Params When to use Energy Hold / duration Max uses Reuse Tested
Vlog grade vlog-grade eq + warm colorbalance + unsharp ffmpeg vlog/build_vlog.py:grade_chain sat=1.18, contrast=1.12, warm=on Travel / lifestyle footage n/a whole video one grade per video Paste the chain into any -vf -
Talking-head grade talkinghead-grade Denoise + eq + colorbalance + CAS sharpen ffmpeg talkinghead config GRADE eq brightness=-0.06 Talking heads n/a whole video one grade Paste it -
HDR to SDR hdr-to-sdr zscale/tonemap for iPhone HLG ffmpeg lib/vstudio/media.py:hdr_to_sdr_args force=False iPhone HDR sources n/a whole clip always for HDR sources media.hdr_to_sdr_args(src) test_core.py
Film look film-look Grain always; strong = warm desat, flicker, vignette, scratches, dust PIL photostory/looks.py:film_look(C, box, gt, strong) strong=False Archive / memory sections low section strong look 1 section Call it on any (BOX_H, BOX_W, 3) float32 frame with a Ctx -
Texture generators texture-generators grain, light leak, ink field, torn edge, dark radial bg, paper (grain, leak, ink, jag, bg, paper) PIL photostory/looks.py:make_grain/make_leak/make_ink/make_jag/make_bg/paper_bg seed=fixed Building blocks for other effects n/a - - Need a Ctx -
Portrait retouch portrait-retouch Face slim, eyes, de-shine, skin/makeup, body slim PIL lib/vstudio/retouch.py:retouch slim=0.05, smooth=0.6, makeup=0.5 Covers and talking heads (creator decides) n/a whole clip always subtle retouch.retouch(img) test_retouch.py
Effect What Engine Where Params When to use Energy Hold / duration Max uses Reuse Tested
SFX bank sfx-bank Synthesised pop, whoosh, stamp/thud, ding, … audio lib/vstudio/audio.py:sfx_bank, SFX_GAINS, write_sfx gains=pop .32, whoosh .4, thud .45, ding .35 Any on-screen action that needs a sound (A11) n/a 0.1-1.6 s repeats must alternate and step down (A12) audio.write_sfx("assets/sfx") test_beats.py
SFX placement sfx-placement Mix events into a voice track audio audio.py:place_sfx; talkinghead compose.py:mix_audio events=[(t, name)] After picture lock (A12) n/a - <= 2 per 2 s audio.place_sfx(x, [(3.2, "pop")], sr) test_beats.py
Card stinger card-stinger 1.6 s noise whoosh + 110 Hz thump at -19 dBFS audio longform-to-short/scripts/make_audio_assets.py -> card_sting.wav level=-19 dBFS Under a chapter card med 1.6 s 1 per chapter Copy the WAV under any chapter card -
Music bed + ducking music-bed Loops music, ducks it under the voice, optional carve EQ, fades audio audio.py:mix_bed; vlog/scripts/add_music.py; explainer make_bgm_bed.py + carve.mjs duck_db=-10, music_lufs=-30 Every narrated video n/a whole video 1 bed (+ a no-music version, A13) audio.mix_bed(voice, music, out) test_core.py
Loudness loudness Two-pass loudnorm to -14 LUFS audio audio.py:loudnorm_2pass, normalize_stem lufs=-14, tp=-1.5 Last step of every workflow n/a - always audio.loudnorm_2pass(src, dst) test_core.py
Studio sound (voice enhance) studio-sound Denoise, dereverb, voice EQ, de-ess, gentle compression (local) audio lib/vstudio/studiosound.py strength=standard Phone / laptop / room recordings, 降噪, 人声增强 n/a whole video 1 studiosound.enhance(src, dst, strength="standard") test_studiosound.py
Voice anonymize voice-anonymize Pitch shift, duration preserved audio + ffmpeg audio.py:pitch_shift_filter; longform pitches.windows semitones=-3 Privacy for a voice n/a window as needed -af with audio.pitch_shift_filter(-3) on the window test_core.py
Effect What Engine Where Params When to use Energy Hold / duration Max uses Reuse Tested
HF chapter progress bar hf-progress Bar + ticks + chapter labels on a scrim; the active label lights up HF lib/vstudio/overlays.py:hf_progress geo=horizontal Long videos with chapters low whole video always on p = overlays.hf_progress(chs, 0, total); paste css/html/js test_visual.py
Line / refined / classic bar (per frame) progress-bar-pil Hairline theme bar (default), or segmented gradient bar with knob and pill, or notes-board bar (line, refined, classic) PIL overlays.py:progress_bar(style=None|"line"|"refined"|"classic") style=line Per-frame compositors low whole video always on Paste progress_bar(...) each frame test_visual.py
Static bar + ffmpeg fill progress-ffmpeg Dim bar PNG + labels + drawbox fill / playhead expression ffmpeg overlays.py:progress_static, progress_fill; talkinghead build_filter.py y=1000 Pure-ffmpeg landscape passes low whole video always on Use it for a pure-ffmpeg pass test_visual.py
Effect What Engine Where Params When to use Energy Hold / duration Max uses Reuse Tested
Split cover split-cover Retouched photo + quote + title + thumbnail + chips + stamp, several sizes PIL lib/vstudio/cover.py:split_cover title_highlight Talking-head covers n/a still 1 per video split_cover(cfg) test_visual.py
Notes cover notes-cover Frame + sticky-note panels + kicker PIL cover.py:notes_cover, sticky_note mute_bottom=150 Talkinghead horizontal cover n/a still 1 talkinghead H cover test_visual.py
Framed cover framed-cover Tilted framed screenshot + eyebrow/big/sub + chips PIL cover.py:framed_cover, framed tilt=-3 Longform / course covers n/a still 1 longform cover test_visual.py
Collage cover (pattern A) collage-cover 4-frame diagonal collage, X-slash, play diamond HTML workflows/cover/templates/cover_collage.template.html --cell-w/h=boxes Short-video covers n/a still 1 python -m vstudio.render -
Face on quadrants (pattern B) face-quadrants-cover Matted face over 4 slide quadrants HTML cover/templates/cover_face_quadrants.template.html, scripts/matte.py --engine=mediapipe Talk with slides n/a still 1 Same -
Torn-paper scrapbook torn-paper-cover Torn-edge photo pieces with a taped title HTML vlog/scripts/make_cover.py:build_html roughness=13 Vlog covers n/a still 1 Use it with any stills -
Photo-story cover photo-story-cover Title zone + hero split polaroid + taped polaroid row with red circles PIL photostory/cover.py COVER=spec Photo stories n/a still 1 polaroid(), circled() -
Frame scoring frame-scoring Picks smiling, eyes-open, centred frames PIL cover.py:score_frames, contact_sheet top_n=6, min_gap=2.0 Choosing a cover frame n/a - - Run on any talking video test_visual.py

Count: 92 registry entries in 11 sections (197 counting named variants). By engine: PIL 44, ffmpeg 20, HF 26, HTML 5, audio 7. Parameter feel, pitfalls and entry points: python -m vstudio.effects --show <id>.


  1. “Zoom in on what I’m pointing at” (talking head + screen)

    • HF: hf.punch_in([[s, e]]) on #face-zoom. To also show the thing, add hf.split_screen([[s, e]], inset, x)
      • hf.screenshot_cards([...]) with scroll to the row.
    • Screen recording in ffmpeg: a code-zoom cut-in (zoom_targets.py finds the centre; crop + scale on the window).
    • Per-frame vertical: talkinghead EMPH + emph_zoom.
  2. “Show a screenshot and highlight a line”

    • Run find_rows.py shot.png to get the row y’s.
    • Call hf.screenshot_cards([dict(id="c1", img=…, w, h, s, e, scroll=[[s,0],[t,y-120]], hl=[[t+0.3, y0, y1, 0.6]], box=[t2, y0, y1])]).
    • Add hf.split_screen over the same window so the face moves aside.
  3. “Freeze and enlarge a prompt”

    • Extract the frame at the pause: ffmpeg -ss T -frames:v 1.
    • Lay out the clips with hf.freeze_clips(src, "assets/img/freeze.jpg", start, T, 2.6, media_dur, rate).
    • Add the card with hf.freeze_hold(at, 2.6, fly_from=(dx, dy), label="我给 agent 的 prompt", image="assets/img/prompt.png").
    • Shift every later time by the hold. promo-recut config hold: does all of this.
  4. “Split screen: me + screen recording”

    • HF: hf.split_screen(windows, "inset(40px 520px 120px 500px round 28px)", x=-440), plus a <video class="clip"> in the freed right half. Give it its own track, start it at opacity 0, and fade it with the windows.
    • 9:16: use GEO vertical (face band on top).
    • Calls: call-clips render_vertical / render_landscape layouts.
  5. “Insert a highlights reel”

    • In a talk: hf.zoom_through(at=M, until=O) + hf.framed_screen("assets/video/montage.mp4", M, dur + 0.5, exit_at=O, rate=1.1)
      • hf.step_labels(...) + hf.badge("精选", …) + hf.title_card("成片精选", …). Cut the montage with cut.xfade_assemble.
    • At the start (cold open): talkinghead Montage(hooks=…), or call-clips hooks with the 高光预告 badge.
  6. “Chapter progress bar”

    • HF: overlays.hf_progress(chapters, start, total, geo).
    • Per-frame: overlays.progress_bar(chs, t, total, style="refined").
    • Pure ffmpeg: overlays.progress_static + progress_fill.
    • Add a chapter card at each boundary: overlays.chapter_card(i, n, title) + card_sting.wav. In HF, use the blocks transition.
  7. “Stamp a punchline”

    • HF: hf.punch_at("#ow", t) then hf.stamp("好用", start, dur, at=t + 0.9). Add the SFX with audio.write_sfx() → stamp.wav as an <audio> at the same time.
    • Per-frame: overlays.stamp(text, angle) with an ease_pop scale-in, and place_sfx([(t, "thud")]).
  8. “Explainer-style scene change”

    • hf.scene_transitions([hf.transition("blur", "w-a", "w-b", T, 0.8), …]). Use one calm family (blur/push), with iris for the core reveal and blocks for a new chapter.
    • Stretch each outgoing scene’s data-duration by d (make_index.py --patch-scenes).
    • Photo / image sequences: photostory.transitions.transition(C, P, N, p, "ink"), or Ctx-free xfade.blend("ink", P, N, p).
    • The same transition in an ffmpeg cut-down: cut.xfade_assemble(pieces, transition=xfade.ffmpeg_transition("iris")).
  9. “Hide a guest’s face”

    • Run track_face.py → apply_sticker.py → verify_coverage.py --min-coverage 1.0.
    • For a camera-off tile, use cat_avatar.html + verify_avatar.py.