Complete karaoke subtitle system and advanced animated text effects. PROACTIVELY activate for: (1) Karaoke-style highlighted lyrics, (2) ASS/SSA advanced subtitle styling, (3) Scrolling credits (horizontal/vertical), (4) Typewriter text animation, (5) Bouncing/moving text, (6) Text fade in/out effects, (7) Word-by-word text reveal, (8) Kinetic typography, (9) Lower thirds animation, (10) Countdown timers and dynamic text. Provides: ASS karaoke timing format, drawtext with time expressions, scrolling text patterns, text animation formulas, kinetic typography techniques, subtitle styling reference, multi-line animated text.
/plugin marketplace add JosiahSiegel/claude-plugin-marketplace/plugin install ffmpeg-master@claude-plugin-marketplaceThis skill inherits all available tools. When active, it can use any tool Claude has access to.
MANDATORY: Always Use Backslashes on Windows for File Paths
When using Edit or Write tools on Windows, you MUST use backslashes (\) in file paths, NOT forward slashes (/).
| Effect | Command |
|---|---|
| Scrolling credits | -vf "drawtext=textfile=credits.txt:y=h-80*t" |
| Typewriter | -vf "drawtext=text='Hello':enable='gte(t,n*0.1)'" |
| Fade in text | -vf "drawtext=text='Title':alpha='min(1,t/2)'" |
| Bouncing text | -vf "drawtext=text='Bounce':y='h/2+50*sin(t*5)'" |
| Karaoke ASS | Use ASS format with \k timing tags |
| Moving text | -vf "drawtext=text='Move':x='w-mod(t*100,w+tw)'" |
Use for animated text and karaoke:
Complete guide to karaoke subtitles, kinetic typography, scrolling credits, and advanced text animation with FFmpeg.
ASS (Advanced SubStation Alpha) supports karaoke timing with \k tags.
[Script Info]
Title: Karaoke Example
ScriptType: v4.00+
PlayResX: 1920
PlayResY: 1080
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Karaoke,Arial,72,&H00FFFFFF,&H000000FF,&H00000000,&H80000000,1,0,0,0,100,100,0,0,1,3,2,2,10,10,50,1
[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 0,0:00:01.00,0:00:05.00,Karaoke,,0,0,0,,{\k50}Twinkle {\k50}twinkle {\k50}little {\k50}star
Dialogue: 0,0:00:05.00,0:00:09.00,Karaoke,,0,0,0,,{\k50}How {\k50}I {\k50}wonder {\k50}what {\k50}you {\k50}are
| Tag | Effect | Example |
|---|---|---|
\k | Fill from left | {\k100}Word = 1 second fill |
\kf or \K | Smooth fill (fade) | {\kf100}Word |
\ko | Outline highlight | {\ko100}Word |
Duration is in centiseconds (100 = 1 second).
ASS uses TWO DIFFERENT time unit systems - this is a common source of bugs!
| Tag | Unit | Example | Duration |
|---|---|---|---|
\k | centiseconds | {\k50} | 0.5 seconds |
\kf | centiseconds | {\kf100} | 1.0 second |
\ko | centiseconds | {\ko25} | 0.25 seconds |
Conversion: seconds × 100 = centiseconds
| Tag | Unit | Example | Duration |
|---|---|---|---|
\t(t1,t2,...) | milliseconds | \t(0,500,...) | 0-500ms animation |
\fad(in,out) | milliseconds | \fad(200,300) | 200ms in, 300ms out |
\move(x1,y1,x2,y2,t1,t2) | milliseconds | \move(0,0,100,100,0,1000) | 1 second move |
Conversion: seconds × 1000 = milliseconds
| Expression | Unit | Example |
|---|---|---|
t | seconds | t=2.5 means 2.5 seconds |
enable='between(t,0,3)' | seconds | 0-3 seconds |
alpha='min(1,t/2)' | seconds | fade over 2 seconds |
| Seconds | Centiseconds (karaoke) | Milliseconds (animation) |
|---|---|---|
| 0.1s | 10 | 100 |
| 0.25s | 25 | 250 |
| 0.5s | 50 | 500 |
| 1.0s | 100 | 1000 |
| 2.0s | 200 | 2000 |
; WRONG - mixing units!
Dialogue: 0,0:00:00.00,0:00:02.00,Style,,0,0,0,,{\k100\t(0,100,...)}Word
; {\k100} = 1 second (centiseconds)
; \t(0,100,...) = 0-100ms = 0.1 seconds (milliseconds) - NOT THE SAME!
; CORRECT - consistent timing
Dialogue: 0,0:00:00.00,0:00:02.00,Style,,0,0,0,,{\k100\t(0,1000,...)}Word
; {\k100} = 1 second
; \t(0,1000,...) = 1 second - NOW THEY MATCH!
| BPM Range | Syllable Duration | Tag Value | Notes |
|---|---|---|---|
| 60-80 (Slow ballad) | 600-800ms | \kf60-80 | Long, emotional |
| 80-100 (Moderate) | 400-600ms | \kf40-60 | Standard pop |
| 100-120 (Upbeat) | 300-500ms | \kf30-50 | Most common |
| 120-140 (Fast) | 250-400ms | \kf25-40 | Energetic |
| 140+ (Rapid) | 200-300ms | \kf20-30 | Rap, EDM |
Based on syllable/word duration and desired effect:
\k (Instant Fill) - Best For:; Rap example (fast succession):
{\k25}Yo {\k20}this {\k15}is {\k30}rapid {\k25}fire {\k35}flow
\kf (Progressive Fill) - Best For:; Ballad example (sustained notes):
{\kf150}Loooooove {\kf120}yoooou {\kf180}foreeeever
Recommended: Use \kf for any syllable >100 centiseconds for smoothest visual effect.
\ko (Outline Reveal) - Best For:; Neon karaoke style:
[V4+ Styles]
Style: NeonKaraoke,Arial,80,&H00000000,&H0000FF00,&H0000FF00,&H00000000,1,0,0,0,100,100,0,0,3,6,0,2,10,10,50,1
[Events]
Dialogue: 0,0:00:01.00,0:00:05.00,NeonKaraoke,,0,0,0,,{\ko50}Electric {\ko60}neon {\ko40}glow
Create dynamic color changes during karaoke fill:
; Gradient karaoke: Yellow → Orange → Red
[V4+ Styles]
Style: GradientKaraoke,Impact,80,&H0000FFFF,&H000000FF,&H00000000,&H00000000,1,0,0,0,100,100,0,0,1,5,0,2,10,10,50,1
; Primary^ Secondary^
; Yellow(start) Red(filled)
; Add intermediate color transition with \t tags:
[Events]
Dialogue: 0,0:00:01.00,0:00:05.00,GradientKaraoke,,0,0,0,,{\kf80\t(0,400,\2c&H0000A5FF&)}Word1 {\kf100\t(0,500,\2c&H0000A5FF&)}Word2
; ↑ Orange midpoint
Color progression formula:
Start: &H0000FFFF (Yellow)
Mid: &H0000A5FF (Orange) - 50% through karaoke fill
End: &H000000FF (Red)
For very precise timing (character-by-character):
; Each character gets individual timing (manual, tedious)
{\k10}H{\k8}e{\k12}l{\k10}l{\k15}o {\k20}w{\k18}o{\k15}r{\k12}l{\k10}d
; Best practice: Use tools like Aegisub Karaoke Timer
; Manual character timing only for special effects
When to use character-level karaoke:
Create natural bounce effects with exponential decay:
# Vertical spring bounce (decaying oscillation)
# Formula: y_offset = amplitude * e^(-decay*t) * sin(frequency*t)
-vf "drawtext=text='BOUNCE':fontsize=80:fontcolor=white:\
x=(w-tw)/2:\
y='(h-th)/2-30*exp(-t*2.5)*sin(t*15)'"
# Breakdown:
# -30*exp(-t*2.5) = amplitude decays from 30px to 0
# sin(t*15) = oscillates at 15 rad/s (≈2.4 Hz)
# Combines: bounces 2-3 times over ~1 second, settling at center
Parameter tuning:
Simulate elastic material with multiple overshoots:
# Elastic scale effect (rubber band)
# Overshoots target size multiple times before settling
-vf "drawtext=text='ELASTIC':fontsize='72*(1+0.4*exp(-t*3)*sin(t*20))':\
fontcolor=yellow:x=(w-tw)/2:y=(h-th)/2"
# Scale oscillates: 72px → 101px → 65px → 80px → 70px → 72px (settled)
# Mathematical basis: e^(-3t) * sin(20t) creates damped oscillation
Elastic parameters:
FFmpeg doesn't support bezier directly, but we can approximate with piecewise functions:
# Ease-out approximation (fast start, slow end)
# Cubic bezier (0, 0, 0.2, 1) approximation
-vf "drawtext=text='EASE OUT':\
alpha='if(lt(t,0.5),1-exp(-t*6),1)':\
fontsize=80:fontcolor=white:x=(w-tw)/2:y=(h-th)/2:\
enable='lt(t,1)'"
# Ease-in approximation (slow start, fast end)
# Cubic bezier (0.8, 0, 1, 1) approximation
-vf "drawtext=text='EASE IN':\
alpha='if(lt(t,0.5),exp(-(1-t)*6),0)':\
fontsize=80:fontcolor=white:x=(w-tw)/2:y=(h-th)/2:\
enable='between(t,1,2)'"
# Ease-in-out (S-curve) using sigmoid approximation
-vf "drawtext=text='SMOOTH':\
alpha='1/(1+exp(-10*(t-0.5)))':\
fontsize=80:fontcolor=white:x=(w-tw)/2:y=(h-th)/2:\
enable='lt(t,1)'"
Mathematical reasoning:
1 - e^(-kt) creates natural deceleration1/(1+e^(-k(t-0.5))) creates smooth S-curve (ease-in-out)Create organic, unpredictable motion:
# Dual-frequency wobble (complex motion)
# Combines two sine waves at different frequencies for organic feel
-vf "drawtext=text='WOBBLE':fontsize=80:fontcolor=white:\
x='(w-tw)/2+8*sin(t*7)+4*sin(t*17)':\
y='(h-th)/2+6*cos(t*7)+3*cos(t*13)'"
# Breakdown:
# Primary wobble: 8*sin(t*7) = 8px amplitude, 7 rad/s (1.1 Hz)
# Secondary wobble: 4*sin(t*17) = 4px amplitude, 17 rad/s (2.7 Hz)
# Result: Complex, organic motion pattern
# Drunk/unstable effect (low frequency, large amplitude)
-vf "drawtext=text='UNSTABLE':fontsize=80:fontcolor=white:\
x='(w-tw)/2+20*sin(t*2.5)+10*sin(t*6.3)':\
y='(h-th)/2+15*cos(t*3.1)+8*cos(t*7.2)'"
Wobble design principles:
Create heartbeat or alarm-style pulsing:
# Heartbeat pulse (two quick beats, pause)
# Pattern: THUMP-thump ... pause ... THUMP-thump
-vf "drawtext=text='♥ HEARTBEAT ♥':\
fontsize='80+25*max(0,sin(t*15)*exp(-mod(t,1.2)*10))':\
fontcolor=red:x=(w-tw)/2:y=(h-th)/2"
# Breakdown:
# sin(t*15) = rapid oscillation
# exp(-mod(t,1.2)*10) = decay envelope every 1.2 seconds
# max(0, ...) = only positive pulses
# Result: Pulse decays quickly, resets every 1.2s
# Alarm pulse (constant urgent rhythm)
-vf "drawtext=text='⚠ ALERT ⚠':\
fontsize='80+20*abs(sin(t*8))':\
fontcolor=yellow:x=(w-tw)/2:y=(h-th)/2"
# abs(sin(t*8)) creates continuous pulsing at 8 rad/s (1.3 Hz)
# Clip text progressively from left
# Uses drawbox mask to reveal text over time
-filter_complex "\
[0:v]drawtext=text='REVEALED':fontsize=100:fontcolor=white:\
x=(w-tw)/2:y=(h-th)/2[txt];\
[txt]drawbox=x=0:y=0:w='min(w,t*500)':h=h:c=black@0:t=fill:replace=1[v]" \
-map "[v]"
# Breakdown:
# w='min(w,t*500)' = width grows at 500 pixels/second
# Creates left-to-right reveal effect
# Text rises from bottom
-vf "drawtext=text='RISING':fontsize=100:fontcolor=white:\
x=(w-tw)/2:\
y='if(lt(t,1),h-t*h,0)'"
# y position: starts at h (bottom), moves to 0 (top) over 1 second
# Countdown that pulses on each second change
-vf "drawtext=text='%{eif\\:10-floor(t)\\:d}':\
fontsize='200+50*abs(sin(floor(t)*50))*exp(-(t-floor(t))*8)':\
fontcolor=white:x=(w-tw)/2:y=(h-th)/2"
# Breakdown:
# %{eif\\:10-floor(t)\\:d} = countdown number (10, 9, 8, ...)
# abs(sin(floor(t)*50)) = trigger pulse on integer seconds
# exp(-(t-floor(t))*8) = decay within each second
# Result: Number "pops" at each second change
# Simulates old-school flip counter with vertical offset
-vf "drawtext=text='%{eif\\:10-floor(t)\\:d}':\
fontsize=200:fontcolor=white:\
x=(w-tw)/2:\
y='(h-th)/2-(t-floor(t))*100*step(t-floor(t))'"
# y offset creates "flipping" motion at second boundaries
| Genre | Syllable Timing | Animation Style | Color Scheme |
|---|---|---|---|
| Ballad | 100-200 centiseconds | \kf smooth fill | White → Soft Blue |
| Pop | 40-80 centiseconds | \kf with bounce | White → Bright Pink |
| Rap | 15-40 centiseconds | \k instant | White → Red |
| Rock | 50-100 centiseconds | \k with shake | White → Orange |
| EDM | 30-60 centiseconds | \ko outline | Neon colors |
# Slow, clear typewriter for learning
# 80ms per character = comfortable reading pace
-vf "drawtext=textfile=lesson.txt:\
fontsize=48:fontcolor=white:\
x=50:y=100:\
enable='gte(n,eif(n/24,80))'"
# Each character appears every 80/24 ≈ 3.3 frames at 24fps
# Professional credits scroll
# Speed: 60-80 pixels/second for comfortable reading
-vf "drawtext=textfile=credits.txt:\
fontsize=42:fontcolor=white:\
x=(w-tw)/2:\
y='h-60*t':\
line_spacing=25"
# Calculation:
# 60 px/s at 42px font = ~1.4 lines/second
# For 50 lines: 50/1.4 = ~36 seconds total duration
Credits readability formula:
scroll_speed = font_size * 1.2 to 1.5 (pixels/second)
total_duration = (line_count * line_height) / scroll_speed
Example:
font_size = 42px
line_height = 42 + 25 (spacing) = 67px
scroll_speed = 42 * 1.4 = 59 px/s
50 lines = 50 * 67 = 3350 pixels
duration = 3350 / 59 = 56.8 seconds
Enhanced with research from:
# Burn karaoke subtitles into video
ffmpeg -i input.mp4 \
-vf "ass=karaoke.ass" \
-c:v libx264 -crf 18 -c:a copy \
output_karaoke.mp4
# With specific fonts directory
ffmpeg -i input.mp4 \
-vf "ass=karaoke.ass:fontsdir=/path/to/fonts" \
output.mp4
[V4+ Styles]
; Gradient karaoke (yellow to red)
Style: KaraokeGradient,Impact,80,&H0000FFFF,&H000000FF,&H00000000,&H80000000,1,0,0,0,100,100,0,0,1,4,2,2,10,10,60,1
; Glow effect karaoke
Style: KaraokeGlow,Arial Black,72,&H00FFFFFF,&H00FF00FF,&H00FF00FF,&H00000000,1,0,0,0,100,100,0,0,4,0,20,2,10,10,50,1
; Outline only (neon style)
Style: KaraokeNeon,Arial,80,&H00000000,&H0000FF00,&H0000FF00,&H00000000,1,0,0,0,100,100,0,0,3,4,0,2,10,10,50,1
[Events]
; Word-by-word with effects
Dialogue: 0,0:00:01.00,0:00:05.00,KaraokeGradient,,0,0,0,,{\k50\fad(200,0)}Never {\k50}gonna {\k50}give {\k50}you {\k50}up
; With positioning
Dialogue: 0,0:00:05.00,0:00:09.00,KaraokeGlow,,0,0,0,,{\pos(960,900)\k50}Never {\k50}gonna {\k50}let {\k50}you {\k50}down
ASS uses &HAABBGGRR format (Alpha, Blue, Green, Red):
&H00FFFFFF = White (fully opaque)&H000000FF = Red&H0000FF00 = Green&H00FF0000 = Blue&H80000000 = 50% transparent black[Events]
; Bounce effect per word
Dialogue: 0,0:00:01.00,0:00:05.00,Karaoke,,0,0,0,,{\k50\t(0,500,\fry360)}Word1 {\k50\t(0,500,\fry360)}Word2
; Scale pop on highlight
Dialogue: 0,0:00:01.00,0:00:05.00,Karaoke,,0,0,0,,{\k50\t(0,100,\fscx120\fscy120)\t(100,200,\fscx100\fscy100)}Pop
; Color transition
Dialogue: 0,0:00:01.00,0:00:05.00,Karaoke,,0,0,0,,{\k100\t(0,1000,\c&H0000FF&)}Red {\k100\t(0,1000,\c&H00FF00&)}Green
| Tag | Effect |
|---|---|
\t(t1,t2,tags) | Animate tags from t1 to t2 |
\move(x1,y1,x2,y2) | Move text from point to point |
\fad(fadein,fadeout) | Fade in/out (milliseconds) |
\fscx, \fscy | Scale X/Y percentage |
\frx, \fry, \frz | Rotate on axis |
\pos(x,y) | Position text |
\an1-9 | Alignment (numpad positions) |
# Basic scrolling credits
ffmpeg -i input.mp4 \
-vf "drawtext=textfile=credits.txt:\
fontfile=/path/to/font.ttf:\
fontsize=48:\
fontcolor=white:\
x=(w-tw)/2:\
y=h-80*t" \
-c:v libx264 -crf 18 output.mp4
# y=h-80*t: Start at bottom (h), move up at 80 pixels/second
DIRECTED BY
John Smith
PRODUCED BY
Jane Doe
STARRING
Actor One
Actor Two
Actor Three
MUSIC BY
Composer Name
SPECIAL THANKS
Everyone
# Multi-speed credits (slower for names)
ffmpeg -i input.mp4 \
-vf "drawtext=textfile=credits.txt:\
fontsize=48:\
fontcolor=white:\
x=(w-tw)/2:\
y='h-60*t':\
line_spacing=20" \
output.mp4
# Right to left scroll
ffmpeg -i input.mp4 \
-vf "drawtext=text='BREAKING NEWS... This is a scrolling ticker message':\
fontsize=36:\
fontcolor=white:\
y=h-50:\
x='w-mod(t*150,w+tw)'" \
ticker.mp4
# x='w-mod(t*150,w+tw)': Continuous scroll at 150 pixels/second
# Seamless looping ticker
ffmpeg -i input.mp4 \
-vf "drawtext=text=' LIVE NEWS BREAKING STORY UPDATES ':\
fontsize=40:\
fontcolor=yellow:\
y=h-60:\
x='w-mod(t*200\\,w+tw)':\
box=1:boxcolor=red@0.8:boxborderw=10" \
news_ticker.mp4
# Typewriter effect using enable
ffmpeg -f lavfi -i "color=c=black:s=1920x1080:d=10" \
-vf "\
drawtext=text='H':x=100:y=500:fontsize=72:fontcolor=white:enable='gte(t,0.0)',\
drawtext=text='e':x=150:y=500:fontsize=72:fontcolor=white:enable='gte(t,0.1)',\
drawtext=text='l':x=200:y=500:fontsize=72:fontcolor=white:enable='gte(t,0.2)',\
drawtext=text='l':x=250:y=500:fontsize=72:fontcolor=white:enable='gte(t,0.3)',\
drawtext=text='o':x=300:y=500:fontsize=72:fontcolor=white:enable='gte(t,0.4)'" \
typewriter.mp4
# Blinking cursor during typing
ffmpeg -f lavfi -i "color=c=black:s=1920x1080:d=5" \
-vf "\
drawtext=text='Hello':fontsize=72:fontcolor=white:x=100:y=500:\
enable='gte(t,0)':alpha='min(1,(t-0)/0.5)',\
drawtext=text='|':fontsize=72:fontcolor=white:\
x='100+72*min(5,floor(t/0.1))':y=500:\
alpha='lt(mod(t,0.5),0.25)'" \
typewriter_cursor.mp4
# Simple fade in
ffmpeg -i input.mp4 \
-vf "drawtext=text='Title':fontsize=100:fontcolor=white:\
x=(w-tw)/2:y=(h-th)/2:\
alpha='if(lt(t,2),t/2,1)'" \
fade_in.mp4
# Explanation: alpha goes from 0 to 1 over 2 seconds
# Fade out (assuming 10 second video)
ffmpeg -i input.mp4 \
-vf "drawtext=text='Goodbye':fontsize=100:fontcolor=white:\
x=(w-tw)/2:y=(h-th)/2:\
alpha='if(gt(t,8),1-(t-8)/2,1)'" \
fade_out.mp4
# Title card with fade in/out
ffmpeg -f lavfi -i "color=c=black:s=1920x1080:d=6" \
-vf "drawtext=text='Chapter One':fontsize=120:fontcolor=white:\
x=(w-tw)/2:y=(h-th)/2:\
alpha='if(lt(t,1),t,if(gt(t,5),1-(t-5),1))'" \
title_card.mp4
# Vertical bounce
ffmpeg -i input.mp4 \
-vf "drawtext=text='BOUNCE':fontsize=80:fontcolor=yellow:\
x=(w-tw)/2:\
y='(h-th)/2+50*sin(t*5)'" \
bounce.mp4
# Horizontal bounce
ffmpeg -i input.mp4 \
-vf "drawtext=text='PING PONG':fontsize=60:fontcolor=cyan:\
x='(w-tw)/2+200*sin(t*3)':\
y=(h-th)/2" \
horizontal_bounce.mp4
# Text moving in circle
ffmpeg -i input.mp4 \
-vf "drawtext=text='ORBIT':fontsize=60:fontcolor=white:\
x='(w/2)+200*cos(t*2)-tw/2':\
y='(h/2)+200*sin(t*2)-th/2'" \
orbit.mp4
# Slide in from right
ffmpeg -i input.mp4 \
-vf "drawtext=text='SLIDE IN':fontsize=80:fontcolor=white:\
x='if(lt(t,1),w-tw*(t),w-tw)':\
y=(h-th)/2" \
slide_in.mp4
# Slide in from left
ffmpeg -i input.mp4 \
-vf "drawtext=text='FROM LEFT':fontsize=80:fontcolor=white:\
x='if(lt(t,1),-tw+tw*t,0)':\
y=(h-th)/2" \
slide_left.mp4
# Words appearing with scale
ffmpeg -f lavfi -i "color=c=black:s=1920x1080:d=8" \
-vf "\
drawtext=text='THIS':fontsize='72*(1+0.3*exp(-3*(t-1)))':fontcolor=white:\
x=(w-tw)/2:y=h/2-100:enable='gte(t,1)',\
drawtext=text='IS':fontsize='72*(1+0.3*exp(-3*(t-2)))':fontcolor=white:\
x=(w-tw)/2:y=h/2:enable='gte(t,2)',\
drawtext=text='KINETIC':fontsize='72*(1+0.3*exp(-3*(t-3)))':fontcolor=red:\
x=(w-tw)/2:y=h/2+100:enable='gte(t,3)'" \
kinetic_pop.mp4
# Shaking text (impact effect)
ffmpeg -i input.mp4 \
-vf "drawtext=text='IMPACT':fontsize=120:fontcolor=white:\
x='(w-tw)/2+10*sin(t*50)*exp(-t*2)':\
y='(h-th)/2+10*cos(t*50)*exp(-t*2)'" \
shake.mp4
# Spinning text (requires recent FFmpeg)
# Note: drawtext doesn't support rotation natively
# Use ASS subtitles for rotation:
# Create spinning.ass
cat << 'EOF' > spinning.ass
[Script Info]
ScriptType: v4.00+
PlayResX: 1920
PlayResY: 1080
[V4+ Styles]
Style: Spin,Arial,72,&H00FFFFFF,&H00FFFFFF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,0,5,0,0,0,1
[Events]
Dialogue: 0,0:00:00.00,0:00:05.00,Spin,,0,0,0,,{\an5\pos(960,540)\t(0,5000,\frz1080)}SPINNING
EOF
ffmpeg -i input.mp4 -vf "ass=spinning.ass" output.mp4
# Name and title lower third
ffmpeg -i input.mp4 \
-vf "\
drawbox=x=50:y=h-150:w=500:h=100:c=blue@0.7:t=fill,\
drawtext=text='John Smith':fontsize=36:fontcolor=white:\
x=70:y=h-140,\
drawtext=text='CEO, Company Inc.':fontsize=24:fontcolor=white@0.8:\
x=70:y=h-100" \
lower_third.mp4
# Slide-in lower third
ffmpeg -i input.mp4 \
-vf "\
drawbox=x='if(lt(t,0.5),-500+1000*t,50)':\
y=h-150:w=500:h=100:c=blue@0.7:t=fill:\
enable='between(t,0,8)',\
drawtext=text='John Smith':fontsize=36:fontcolor=white:\
x='if(lt(t,0.5),-430+1000*t,70)':y=h-140:\
alpha='min(1,(t-0.3)/0.3)':\
enable='between(t,0.3,8)',\
drawtext=text='CEO, Company Inc.':fontsize=24:fontcolor=white:\
x='if(lt(t,0.5),-430+1000*t,70)':y=h-100:\
alpha='min(1,(t-0.5)/0.3)':\
enable='between(t,0.5,8)'" \
animated_lower_third.mp4
# 10 second countdown
ffmpeg -f lavfi -i "color=c=black:s=1920x1080:d=10" \
-vf "drawtext=text='%{eif\\:10-t\\:d}':fontsize=200:fontcolor=white:\
x=(w-tw)/2:y=(h-th)/2" \
countdown.mp4
# Pulsing countdown
ffmpeg -f lavfi -i "color=c=black:s=1920x1080:d=10" \
-vf "drawtext=text='%{eif\\:10-t\\:d}':\
fontsize='200+30*sin(t*10)*exp(-mod(t,1)*3)':\
fontcolor=white:\
x=(w-tw)/2:y=(h-th)/2" \
pulsing_countdown.mp4
# Count up timer (MM:SS)
ffmpeg -f lavfi -i "color=c=black:s=1920x1080:d=120" \
-vf "drawtext=text='%{eif\\:floor(t/60)\\:d\\:2}\\:%{eif\\:mod(t,60)\\:d\\:2}':\
fontsize=100:fontcolor=green:\
x=(w-tw)/2:y=(h-th)/2" \
stopwatch.mp4
# Show frame number
ffmpeg -i input.mp4 \
-vf "drawtext=text='Frame\\: %{frame_num}':\
fontsize=24:fontcolor=white:\
x=10:y=10" \
frame_numbers.mp4
# Show timecode
ffmpeg -i input.mp4 \
-vf "drawtext=text='%{pts\\:hms}':\
fontsize=24:fontcolor=white:\
x=10:y=10" \
timecode.mp4
# With milliseconds
ffmpeg -i input.mp4 \
-vf "drawtext=text='%{pts}':\
fontsize=24:fontcolor=white:\
x=10:y=10" \
timecode_ms.mp4
# Show filename
ffmpeg -i input.mp4 \
-vf "drawtext=text='%{metadata\\:title}':\
fontsize=24:fontcolor=white:\
x=10:y=h-40" \
metadata.mp4
# Multi-line centered text
ffmpeg -i input.mp4 \
-vf "drawtext=text='Line One\nLine Two\nLine Three':\
fontsize=48:fontcolor=white:\
x=(w-tw)/2:y=(h-th)/2:\
line_spacing=20" \
multiline.mp4
# Use text file for long text
ffmpeg -i input.mp4 \
-vf "drawtext=textfile=message.txt:\
fontsize=36:fontcolor=white:\
x=50:y=50:\
line_spacing=10" \
from_file.mp4
| Expression | Result |
|---|---|
t | Current time in seconds |
t*100 | Speed factor |
mod(t,5) | Loop every 5 seconds |
sin(t*3) | Oscillate 3 times/second |
exp(-t) | Exponential decay |
if(lt(t,2),expr1,expr2) | Conditional |
min(a,b) / max(a,b) | Min/max |
floor(t) / ceil(t) | Round down/up |
| Variable | Meaning |
|---|---|
w | Video width |
h | Video height |
tw | Text width |
th | Text height |
n | Frame number |
frame_num | Same as n |
# Appear after 2 seconds
enable='gte(t,2)'
# Visible between 2-5 seconds
enable='between(t,2,5)'
# Fade in over 1 second
alpha='min(1,t)'
# Center horizontally
x='(w-tw)/2'
# Center vertically
y='(h-th)/2'
# Loop position
x='mod(t*100,w)'
# Smooth oscillation
y='h/2+50*sin(t*3)'
# Decay animation
fontsize='100+50*exp(-t*2)'
This guide covers FFmpeg karaoke and animated text. For basic subtitles see ffmpeg-captions-subtitles, for shapes see ffmpeg-shapes-graphics.
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.