Definitive reference for FFmpeg and ASS/SSA animation timing units, optimal durations, and best practices. PROACTIVELY activate for: (1) Animation timing questions, (2) ASS subtitle timing, (3) Karaoke timing tags, (4) Caption duration calculation, (5) Transition duration selection, (6) Fade/zoom timing, (7) Frame rate considerations, (8) Platform-specific timing (TikTok/Shorts/Reels), (9) Readability formulas, (10) Audio-video sync tolerances. Provides: Complete time unit reference tables, optimal duration guidelines, psychology-based timing recommendations, caption readability formulas, and platform-specific timing profiles.
Install
npx skillscat add josiahsiegel/claude-plugin-marketplace/ffmpeg-animation-timing-reference Install via the SkillsCat registry.
This skill provides reference information about time units, durations, and best practices for FFmpeg filters and ASS/SSA subtitle animations. It addresses confusion between different timing systems, particularly the distinction between centiseconds used in karaoke tags and milliseconds used in animation tags. Developers working with video transitions, subtitle timing, or platform-specific animation durations can use this reference to ensure correct timing implementation.
CRITICAL GUIDELINES
Windows File Path Requirements
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 (/).
Documentation Guidelines
NEVER create new documentation files unless explicitly requested by the user.
FFmpeg Animation Timing Reference (2025-2026)
Quick Reference Card
| Context | Unit | Example |
|---|---|---|
| FFmpeg filters (fade, xfade, drawtext) | Seconds | duration=1.5 |
FFmpeg zoompan d= parameter |
Frames | d=150 (150 frames) |
FFmpeg time variable t |
Seconds | enable='gte(t,2)' |
| ASS karaoke tags (\k, \kf, \ko) | Centiseconds | {\k100} = 1 second |
| ASS animation tags (\t, \fad, \move) | Milliseconds | \t(0,500,...) = 0.5s |
| ASS dialogue timestamps | H:MM:SS.CC | 0:00:05.50 |
CRITICAL: ASS Has TWO Different Time Units!
This is the most common source of confusion in subtitle animations.
ASS/SSA Time Unit Disambiguation
| Tag Type | Unit | Conversion | Example |
|---|---|---|---|
| Karaoke tags (\k, \kf, \ko, \K) | Centiseconds (1/100s) | × 100 | {\k50} = 0.5 seconds |
| Animation tags (\t) | Milliseconds (1/1000s) | × 1000 | \t(0,200,...) = 0.2 seconds |
| Fade tags (\fad) | Milliseconds | × 1000 | \fad(300,0) = 0.3s fade in |
| Move tags (\move) | Milliseconds | × 1000 | \move(x1,y1,x2,y2,0,500) = 0.5s |
| Dialogue timestamps | H:MM:SS.CC | Centiseconds | 0:00:01.50 = 1.5 seconds |
Example: Same Duration, Different Units
; Both animations last 0.5 seconds but use DIFFERENT UNITS:
; Karaoke: 50 CENTISECONDS = 0.5 seconds
{\k50}Word
; Animation: 500 MILLISECONDS = 0.5 seconds
{\t(0,500,\fscx120)}WordSection 1: FFmpeg Time Units (All Filters)
Complete FFmpeg Time Unit Reference
| Filter/Context | Parameter | Unit | Example | Notes |
|---|---|---|---|---|
fade |
duration (d) |
Seconds | fade=t=in:d=2 |
Floating point |
fade |
start_time (st) |
Seconds | fade=st=5:d=2 |
When fade begins |
afade |
duration (d) |
Seconds | afade=d=3 |
Audio fade |
xfade |
duration |
Seconds | duration=1.5 |
Transition length |
xfade |
offset |
Seconds | offset=4 |
When transition starts |
acrossfade |
duration (d) |
Seconds | acrossfade=d=1 |
Audio crossfade |
drawtext |
t variable |
Seconds | enable='gte(t,5)' |
Time since start |
drawtext |
alpha |
0-1 scale | alpha='min(1,t/2)' |
Opacity over time |
zoompan |
d |
Frames | d=150 |
Duration in frames |
zoompan |
t variable |
Seconds | z='1+0.1*t' |
Time in expressions |
fps |
fps |
Frames/second | fps=30 |
Frame rate |
trim |
start, end |
Seconds | trim=start=2:end=10 |
Cut points |
setpts |
PTS |
Timestamps | setpts=PTS-STARTPTS |
Presentation timestamps |
Frame Rate Conversion
| Frame Rate | 1 Frame Duration | 30 Frames | 60 Frames |
|---|---|---|---|
| 24 fps | 41.67 ms | 1.25 s | 2.5 s |
| 25 fps | 40 ms | 1.2 s | 2.4 s |
| 30 fps | 33.33 ms | 1 s | 2 s |
| 50 fps | 20 ms | 0.6 s | 1.2 s |
| 60 fps | 16.67 ms | 0.5 s | 1 s |
Zoompan Duration Calculation
# zoompan d= parameter is in FRAMES, not seconds!
# For 2-second zoom at 30fps:
ffmpeg -i input.mp4 -vf "zoompan=z='1.2':d=60:s=1080x1920" output.mp4
# d=60 frames ÷ 30fps = 2 seconds
# For 2-second zoom at 60fps:
ffmpeg -i input.mp4 -vf "zoompan=z='1.2':d=120:s=1080x1920" output.mp4
# d=120 frames ÷ 60fps = 2 seconds
# Formula: frames = seconds × fpsViral Video Timing Optimizations (2025-2026)
Based on the 1.3-second attention threshold research, hook animations must be fast and dramatic:
| Effect Type | FPS | Effect Duration | Frame Count | d= Parameter |
|---|---|---|---|---|
| Hook zoom punch | 60 | 0.4-0.5s | 24-30 frames | d=1 (use time conditional) |
| Hook flash | 60 | 0.2-0.3s | 12-18 frames | d=1 (use time conditional) |
| Continuous zoom | 30 | Entire video | N/A | d=1 (recalc per frame) |
| Text animations | 60 | 0.3-0.5s | 18-30 frames | d=1 (use time conditional) |
Important: Always use d=1 for continuous per-frame processing. Limit effect duration using time conditionals like if(lt(t,0.5),effect,1) rather than setting d= to a frame count (which would freeze the video after that many frames).
Critical Rule: Hook animations MUST complete within 0.5 seconds to fit in the 1.3-second attention window with room for text/content.
Optimal Zoom Parameters by Platform
| Platform | Hook Zoom | Hook Duration | Continuous Zoom | Recommended FPS |
|---|---|---|---|---|
| TikTok | 1.5x (50%) | 0.4-0.5s | +0.2%/sec | 60fps for hooks |
| YouTube Shorts | 1.5x (50%) | 0.5-0.6s | +0.15%/sec | 60fps throughout |
| Instagram Reels | 1.4x (40%) | 0.5-0.6s | +0.18%/sec | 60fps for hooks |
Optimized Hook Effect Formulas
# 1.5x zoom punch over 0.5s at 60fps (RECOMMENDED):
# Always use d=1 for continuous processing; time conditional limits effect duration
fps=60,zoompan=z='if(lt(t,0.5),1.5-t,1)':d=1:s=1080x1920
# 8% zoom pulse at ~2Hz for 1.5s (sin(t*12) = 12 rad/s = 1.91 Hz):
fps=60,zoompan=z='if(lt(t,1.5),1+0.08*sin(t*12),1)':d=1:s=1080x1920
# Subtle continuous zoom for TikTok (0.2%/sec - minimum perceptible):
zoompan=z='1+0.002*t':d=1:s=1080x1920
# Subtle continuous zoom for YouTube Shorts (0.15%/sec - larger screens):
zoompan=z='1+0.0015*t':d=1:s=1080x1920Section 2: ASS/SSA Subtitle Timing
Karaoke Tags (Centiseconds)
| Tag | Name | Unit | Effect |
|---|---|---|---|
\k |
Karaoke | Centiseconds | Instant highlight (no fill) |
\kf |
Karaoke Fill | Centiseconds | Progressive fill left-to-right |
\ko |
Karaoke Outline | Centiseconds | Outline wipe effect |
\K |
Karaoke (alt) | Centiseconds | Same as \k |
Karaoke Timing Examples
; Each word highlights for the specified duration
; Values are in CENTISECONDS (100 = 1 second)
; 0.5 second per word:
{\k50}This {\k50}is {\k50}a {\k50}test
; Variable timing matching speech:
{\k30}The {\k25}quick {\k40}brown {\k35}fox
; Karaoke fill (progressive highlight):
{\kf100}Slowly {\kf150}highlighting {\kf80}each {\kf120}wordConversion Table: Centiseconds
| Centiseconds | Seconds | Common Use |
|---|---|---|
| 10 | 0.1s | Very fast syllable |
| 25 | 0.25s | Quick word |
| 50 | 0.5s | Normal word |
| 100 | 1.0s | Long word/pause |
| 150 | 1.5s | Extended emphasis |
| 200 | 2.0s | Dramatic pause |
Animation Tags (Milliseconds)
| Tag | Format | Unit | Effect |
|---|---|---|---|
\t |
\t(t1,t2,tags) |
Milliseconds | Animate between t1 and t2 |
\fad |
\fad(in,out) |
Milliseconds | Fade in/out duration |
\move |
\move(x1,y1,x2,y2,t1,t2) |
Milliseconds | Move from t1 to t2 |
Animation Timing Examples
; Animation over 300 milliseconds (0.3 seconds):
{\t(0,300,\fscx120\fscy120)}Pop effect
; Scale animation: 0-200ms scale up, 200-400ms scale down
{\fscx80\fscy80\t(0,200,\fscx110\fscy110)\t(200,400,\fscx100\fscy100)}Bounce
; Fade in over 500ms, no fade out:
{\fad(500,0)}Fade in text
; Move over 800ms:
{\move(0,1920,540,960,0,800)}Slide in from bottomConversion Table: Milliseconds
| Milliseconds | Seconds | Common Use |
|---|---|---|
| 50 | 0.05s | Instant flash |
| 100 | 0.1s | Very fast animation |
| 200 | 0.2s | Quick pop/snap |
| 300 | 0.3s | Standard UI animation |
| 500 | 0.5s | Smooth transition |
| 800 | 0.8s | Noticeable movement |
| 1000 | 1.0s | Slow, deliberate |
| 2000 | 2.0s | Very slow, dramatic |
Dialogue Timestamps (H:MM:SS.CC)
; Format: Hours:Minutes:Seconds.Centiseconds
; Dialogue line from 1.5s to 5.0s:
Dialogue: 0,0:00:01.50,0:00:05.00,Default,,0,0,0,,Your text here
; Examples:
0:00:00.00 = 0 seconds (start)
0:00:01.50 = 1.5 seconds
0:00:10.00 = 10 seconds
0:01:00.00 = 60 seconds (1 minute)
0:05:30.25 = 5 minutes, 30.25 secondsSection 3: Optimal Animation Durations
Human Perception Thresholds
| Timing | Brain Response | Application |
|---|---|---|
| <16ms | Sub-perceptual | Cannot perceive (below 60fps frame) |
| 16-50ms | Preattentive | Flash effects, subliminal |
| 50-100ms | Motion detection | Pattern interrupts, glitches |
| 100-200ms | Conscious recognition | Quick animations, snappy UI |
| 200-400ms | Attention capture | Standard animations |
| 400-800ms | Processing time | Complex transitions |
| 800ms-2s | Contemplation | Dramatic, emotional effects |
| >2s | Extended focus | Deliberate, artistic pacing |
Recommended Durations by Effect Type
Text Animations
| Effect | Fast | Standard | Slow | Notes |
|---|---|---|---|---|
| Word pop (scale) | 100-150ms | 150-250ms | 250-400ms | Material Design: 200ms |
| Fade in | 200-300ms | 300-500ms | 500-800ms | Smooth appearance |
| Fade out | 150-250ms | 200-400ms | 400-600ms | Slightly faster than fade in |
| Slide in | 200-300ms | 300-500ms | 500-800ms | Distance affects perception |
| Bounce | 300-400ms | 400-600ms | 600-800ms | Needs overshoot time |
| Typewriter | 30-50ms/char | 50-80ms/char | 80-120ms/char | Per character |
Video Transitions (xfade)
| Transition | Fast | Standard | Slow | Best For |
|---|---|---|---|---|
| Fade/Dissolve | 0.3-0.5s | 0.8-1.2s | 1.5-2.5s | Universal |
| Wipe | 0.2-0.4s | 0.5-0.8s | 1.0-1.5s | Directional energy |
| Slide | 0.3-0.5s | 0.6-1.0s | 1.2-2.0s | Dynamic movement |
| Circle/Iris | 0.5-0.8s | 1.0-1.5s | 2.0-3.0s | Dramatic reveal |
| Zoom | 0.4-0.6s | 0.8-1.2s | 1.5-2.5s | Impact, intensity |
| Pixelize | 0.3-0.5s | 0.5-0.8s | 1.0-1.5s | Glitch aesthetic |
Hook Effects (First 1-3 Seconds)
| Hook Type | Effect Duration | Total Hook | Timing Pattern |
|---|---|---|---|
| Pattern Interrupt | 50-150ms | 0.5-1.5s | Instant grab |
| Flash/Brightness | 80-200ms | 0.3-0.8s | Quick pulse |
| Zoom Punch | 200-400ms | 1-2s | Fast zoom-in, hold |
| Glitch | 100-300ms | 0.5-1.5s | Multiple bursts |
| Text Slam | 150-250ms | 1-2s | Quick appear, hold |
Karaoke/Caption Animations
| Content | Duration Formula | Example |
|---|---|---|
| Single word | Word length × 50-100ms | "Hello" = 250-500ms |
| Short phrase | 1-2 seconds | "Check this out" |
| Sentence | Words ÷ 3 per second | 9 words = 3 seconds |
| Reading captions | See readability formula | Based on WPM |
Section 4: Caption Readability Formula
Reading Speed Standards
| Audience | Words Per Minute (WPM) | Use Case |
|---|---|---|
| Slow readers | 120-140 WPM | Accessibility, elderly |
| Average | 160-180 WPM | Recommended default |
| Fast readers | 200-220 WPM | Experienced viewers |
| Skim reading | 250-300 WPM | Not recommended for video |
Caption Duration Calculation
Formula
# Basic formula:
caption_duration = (word_count / words_per_minute) * 60
# With minimum duration (accessibility):
minimum_duration = 1.5 # seconds - WCAG recommendation
duration = max(minimum_duration, caption_duration)
# Conservative formula (recommended):
duration = max(1.5, (word_count / 160) * 60)Quick Reference Table
| Word Count | @ 160 WPM | @ 180 WPM | Recommended |
|---|---|---|---|
| 1-2 words | 0.75s | 0.67s | 1.5s (minimum) |
| 3 words | 1.13s | 1.0s | 1.5s (minimum) |
| 4 words | 1.5s | 1.33s | 1.5s |
| 5 words | 1.88s | 1.67s | 1.9s |
| 6 words | 2.25s | 2.0s | 2.3s |
| 8 words | 3.0s | 2.67s | 3.0s |
| 10 words | 3.75s | 3.33s | 3.8s |
| 15 words | 5.63s | 5.0s | 5.6s |
Python Implementation
def calculate_caption_duration(text: str, wpm: int = 160) -> float:
"""
Calculate optimal caption display duration.
Args:
text: Caption text
wpm: Words per minute (default 160 for comfortable reading)
Returns:
Duration in seconds
"""
word_count = len(text.split())
calculated = (word_count / wpm) * 60
# WCAG minimum: 1.5 seconds for any caption
minimum = 1.5
# Add 0.5s buffer for cognitive processing
buffer = 0.5
return max(minimum, calculated + buffer)
# Examples:
print(calculate_caption_duration("Hello")) # 1.5s (minimum)
print(calculate_caption_duration("This is a test")) # 2.0s
print(calculate_caption_duration("Check out this incredible transformation")) # 2.88sAccessibility Guidelines (WCAG 2.2)
| Guideline | Requirement |
|---|---|
| Minimum duration | 1.5 seconds for any caption |
| Maximum reading speed | 200 WPM (3.3 words/second) |
| Character limit | 42 characters per line (readability) |
| Lines per caption | Maximum 2 lines |
| Persistence | Caption visible for full duration |
Section 5: Platform-Specific Timing
TikTok (Fast, Energetic)
| Element | Timing | Notes |
|---|---|---|
| Cut/transition | 0.5-1.5s between clips | Fast pacing |
| Text animation | 100-200ms | Snappy, punchy |
| Hook window | 1-1.5s | Immediate grab |
| Caption duration | 0.8-1.5s per phrase | Short, readable |
| Zoom effects | 200-400ms | Quick, impactful |
| Optimal video length | 15-30s (sweet spot) | Completion rate focus |
YouTube Shorts (Medium-Fast)
| Element | Timing | Notes |
|---|---|---|
| Cut/transition | 1-2s between clips | Slightly slower than TikTok |
| Text animation | 150-300ms | Smooth but quick |
| Hook window | 2-3s | Slightly more time |
| Caption duration | 1.2-2s per phrase | More readable |
| Zoom effects | 300-600ms | Noticeable but smooth |
| Optimal video length | 50-60s | Algorithm favored |
Instagram Reels (Aesthetic, Polished)
| Element | Timing | Notes |
|---|---|---|
| Cut/transition | 1.5-2.5s between clips | More polished |
| Text animation | 150-250ms | Stylish timing |
| Hook window | 2-3s | Visual-first platform |
| Caption duration | 1-1.8s per phrase | Instagram style |
| Zoom effects | 400-800ms | Cinematic feel |
| Optimal video length | 15-30s (trending) | Engagement focused |
Professional/Broadcast
| Element | Timing | Notes |
|---|---|---|
| Cut/transition | 2-4s between clips | Deliberate pacing |
| Text animation | 300-500ms | Smooth, professional |
| Lower third entrance | 400-600ms | Broadcast standard |
| Caption duration | 2-4s per phrase | Full readability |
| Zoom effects | 800-1500ms | Subtle, cinematic |
| Typical segment | 30-90s | Standard TV timing |
Section 6: Easing Functions and Natural Motion
Material Design Timing Standards
| Animation Type | Duration | Easing | Use Case |
|---|---|---|---|
| Small (icon, button) | 100ms | ease-out | Micro-interactions |
| Medium (card, dialog) | 200-300ms | ease-in-out | Standard UI |
| Large (page, panel) | 300-500ms | ease-out | Major transitions |
| Complex (multi-element) | 400-700ms | ease-in-out | Choreographed |
FFmpeg Easing Expressions
Ease Out (Deceleration) - Elements Entering
# Ease out: fast start, slow finish
# Good for: Elements appearing, sliding in
-vf "drawtext=text='Hello':alpha='1-exp(-t*5)':fontsize=48:fontcolor=white:x=(w-tw)/2:y=(h-th)/2"Ease In (Acceleration) - Elements Leaving
# Ease in: slow start, fast finish
# Good for: Elements disappearing, sliding out
-vf "drawtext=text='Goodbye':alpha='exp(-(5-t)*5)':fontsize=48:fontcolor=white:x=(w-tw)/2:y=(h-th)/2:enable='lt(t,5)'"Bounce Effect
# Bounce: overshoot then settle
# Duration: 400-600ms recommended
-vf "drawtext=text='Bounce':y='h/2-50+50*exp(-t*3)*sin(t*15)':fontsize=48:fontcolor=white:x=(w-tw)/2"Elastic Effect
# Elastic: spring-like oscillation
# Duration: 600-1000ms recommended
-vf "drawtext=text='Elastic':fontsize='48*(1+0.3*exp(-t*2)*sin(t*10))':fontcolor=white:x=(w-tw)/2:y=(h-th)/2"Smooth Oscillation (Continuous)
# Pulse/float effect
# Period: 2-4 seconds per cycle
-vf "drawtext=text='Float':y='h/2+20*sin(t*2)':fontsize=48:fontcolor=white:x=(w-tw)/2"Recommended Easing by Context
| Context | Function | Duration |
|---|---|---|
| Element appearing | 1-exp(-t*5) |
200-400ms |
| Element disappearing | exp(-t*5) |
150-300ms |
| Emphasis/attention | 1+0.2*sin(t*6) |
Continuous |
| Bounce entrance | exp(-t*3)*sin(t*15) |
400-600ms |
| Smooth transition | Linear (no easing) | Any |
Section 10: Mathematical Animation Formula Reference
Complete Easing Function Formulas
Based on standard animation libraries (CSS, anime.js, React Spring) and adapted for FFmpeg.
Linear (No Easing)
f(t) = tFFmpeg: alpha='t' or x='t*100'
Use for: Constant speed motion, mechanical effects
Quadratic Easing
Ease In (Acceleration):
f(t) = t²FFmpeg: alpha='t*t'
Ease Out (Deceleration):
f(t) = 1 - (1-t)²FFmpeg: alpha='1-(1-t)*(1-t)'
Ease In-Out:
f(t) = if(t < 0.5, 2*t², 1 - 2*(1-t)²)FFmpeg: alpha='if(lt(t,0.5),2*t*t,1-2*(1-t)*(1-t))'
Cubic Easing
Ease In:
f(t) = t³FFmpeg: alpha='t*t*t'
Ease Out:
f(t) = 1 - (1-t)³FFmpeg: alpha='1-(1-t)*(1-t)*(1-t)'
Ease In-Out (Material Design Default):
f(t) = if(t < 0.5, 4*t³, 1 - 4*(1-t)³)FFmpeg: alpha='if(lt(t,0.5),4*t*t*t,1-4*(1-t)*(1-t)*(1-t))'
Exponential Easing
Ease In:
f(t) = 2^(10*(t-1))FFmpeg: alpha='exp(10*(t-1)*0.693)'
(Note: 2^x = e^(x*ln(2)) where ln(2) ≈ 0.693)
Ease Out (Recommended for text appearing):
f(t) = 1 - 2^(-10*t)FFmpeg: alpha='1-exp(-10*t*0.693)'
Sine Easing (Smooth, Gentle)
Ease In:
f(t) = 1 - cos(t*π/2)FFmpeg: alpha='1-cos(t*1.571)'
(Note: π/2 ≈ 1.571)
Ease Out:
f(t) = sin(t*π/2)FFmpeg: alpha='sin(t*1.571)'
Ease In-Out:
f(t) = -(cos(π*t) - 1)/2FFmpeg: alpha='-(cos(3.1416*t)-1)/2'
Circular Easing (Sharp Curve)
Ease In:
f(t) = 1 - sqrt(1 - t²)FFmpeg: alpha='1-sqrt(1-t*t)'
Ease Out:
f(t) = sqrt(1 - (1-t)²)FFmpeg: alpha='sqrt(1-(1-t)*(1-t))'
Elastic Easing (Overshoot with Oscillation)
Ease Out (Most Common):
f(t) = 2^(-10*t) * sin((t*10 - 0.75)*2π/3) + 1FFmpeg approximation:
alpha='exp(-10*t*0.693)*sin((t*10-0.75)*2.094)+1'(Note: 2π/3 ≈ 2.094)
Ease In:
f(t) = -2^(10*(t-1)) * sin((t*10 - 10.75)*2π/3)FFmpeg:
alpha='-exp(10*(t-1)*0.693)*sin((t*10-10.75)*2.094)'Bounce Easing (Multiple Bounces)
Ease Out (Complex, piecewise):
For FFmpeg, simplified bounce approximation:
# Single bounce (easier implementation):
alpha='if(lt(t,0.4),
7.5625*t*t,
if(lt(t,0.8),
7.5625*(t-0.6)*(t-0.6)+0.75,
7.5625*(t-0.9)*(t-0.9)+0.9375))'Simplified damped oscillation bounce:
# More natural, continuous bounce:
alpha='1-abs(cos(t*4.5*3.1416))*exp(-t*6)'Back Easing (Overshoot)
Ease Out (Slight overshoot then settle):
f(t) = 1 + c3 * (t-1)³ + c1 * (t-1)²
where c1 = 1.70158, c3 = c1 + 1FFmpeg approximation:
# Simplified overshoot (10% beyond target):
alpha='if(lt(t,0.7),t*1.3,1+(1-t)*0.3)'Spring Physics Mathematical Reference
Standard Spring System (Mass-Spring-Damper)
Differential equation:
m * x''(t) + c * x'(t) + k * x(t) = 0
Where:
m = mass
c = damping coefficient
k = spring stiffness (Hooke's constant)
x(t) = position at time t
x'(t) = velocity
x''(t) = accelerationKey Parameters
Natural frequency:
ω_n = sqrt(k/m)Damping ratio:
ζ = c / (2 * sqrt(k * m))Damping Cases
Under-damped (0 < ζ < 1) - Bouncy, overshoots:
x(t) = e^(-ζ*ω_n*t) * [A*cos(ω_d*t) + B*sin(ω_d*t)]
Where:
ω_d = ω_n * sqrt(1 - ζ²) (damped frequency)Critically damped (ζ = 1) - No overshoot, fastest settle:
x(t) = (A + B*t) * e^(-ω_n*t)Over-damped (ζ > 1) - Slow, sluggish:
x(t) = A*e^(r1*t) + B*e^(r2*t)
Where r1, r2 are real rootsSimplified Spring for UI Animation
Two-parameter approach (bounce + duration):
# Convert bounce and duration to spring parameters
mass = 1 # Normalized
stiffness = (2*π / duration)²
if bounce >= 0:
damping = (1 - bounce) * 4*π / duration
else:
damping = 4*π / (duration * (1 + bounce))Bounce parameter mapping:
bounce = 0: Critically damped (no overshoot)0 < bounce < 1: Under-damped (1 = undamped oscillation)bounce < 0: Over-damped (negative = slower settling)
FFmpeg Spring Implementation
Basic under-damped spring (stiffness=100, damping=10, mass=1):
# Calculate natural frequency and damping ratio:
# ω_n = sqrt(100/1) = 10
# ζ = 10 / (2*sqrt(100*1)) = 10/20 = 0.5 (under-damped)
# ω_d = 10 * sqrt(1 - 0.5²) = 10 * 0.866 = 8.66
# Position formula:
-vf "drawtext=text='SPRING':\
y='(h-th)/2-50*exp(-5*t)*cos(8.66*t)':\
fontsize=80:fontcolor=white:x=(w-tw)/2"
# Breakdown:
# -50 = initial displacement (50px below center)
# exp(-5*t) = exponential decay (damping term: ζ*ω_n = 0.5*10 = 5)
# cos(8.66*t) = oscillation at damped frequencyTunable spring parameters for FFmpeg:
# Low bounce (ζ=0.8, quick settle):
y='(h-th)/2+amplitude*exp(-8*t)*sin(10*t)'
# Medium bounce (ζ=0.5, balanced):
y='(h-th)/2+amplitude*exp(-5*t)*sin(12*t)'
# High bounce (ζ=0.3, very bouncy):
y='(h-th)/2+amplitude*exp(-3*t)*sin(14*t)'
# Critical damping (ζ=1, no overshoot):
y='(h-th)/2+amplitude*exp(-10*t)*(1+10*t)'Oscillation and Wave Formulas
Basic Trigonometric Functions
Sine wave (smooth oscillation):
y = A * sin(ω*t + φ)
Where:
A = amplitude (peak height)
ω = angular frequency (rad/s)
t = time (seconds)
φ = phase offset (radians)Frequency conversion:
ω = 2πf
where f = frequency in Hz (cycles/second)
Examples:
1 Hz = 6.28 rad/s (ω = 2π * 1)
2 Hz = 12.56 rad/s (ω = 2π * 2)
3 Hz = 18.85 rad/s (ω = 2π * 3)FFmpeg examples:
# 2 Hz oscillation (2 cycles/second):
y='50*sin(12.56*t)'
# 1 Hz with phase offset (starts at peak):
y='50*sin(6.28*t+1.571)' # φ = π/2 = 1.571
# Cosine (90° phase shifted sine):
y='50*cos(6.28*t)' # equivalent to sin(t + π/2)Amplitude Modulation (AM)
Variable amplitude sine wave:
y = A(t) * sin(ω*t)FFmpeg decaying oscillation:
# Exponential amplitude decay:
y='50*exp(-2*t)*sin(10*t)'
# Linear amplitude decay:
y='50*max(0,1-t)*sin(10*t)'
# Pulsing amplitude (low frequency modulates high frequency):
y='(30+20*sin(2*t))*sin(20*t)'
# ↑ envelope ↑ carrier waveFrequency Modulation (FM)
Variable frequency sine wave:
y = A * sin(ω(t)*t)FFmpeg examples:
# Accelerating oscillation (siren effect):
y='50*sin(5*t*t)' # frequency increases linearly
# Decelerating oscillation:
y='50*sin(20*t-5*t*t)' # frequency decreases
# Vibrato (musical wobble):
y='50*sin(10*t+2*sin(3*t))'
# ↑ base ↑ vibrato modulationLissajous Curves (2D Parametric Motion)
Circular motion:
x = r * cos(ω*t)
y = r * sin(ω*t)FFmpeg circular orbit:
-vf "drawtext=text='○':\
x='(w/2)+200*cos(2*t)':\
y='(h/2)+200*sin(2*t)':\
fontsize=40:fontcolor=white"Elliptical motion:
# a=200 (horizontal radius), b=100 (vertical radius)
x='(w/2)+200*cos(2*t)'
y='(h/2)+100*sin(2*t)'Figure-8 motion (Lissajous 1:2 ratio):
x='(w/2)+150*sin(1*t)'
y='(h/2)+150*sin(2*t)'Noise and Randomness Functions
Pseudo-Random with Sine
FFmpeg doesn't have true random(), but we can approximate:
# Pseudo-random using high-frequency sine:
'10*sin(t*137.5)' # 137.5 is irrational, appears random
# Multi-layered pseudo-random:
'5*sin(t*137.5)+3*sin(t*241.3)+2*sin(t*89.7)'Perlin-like Noise (Smooth Random)
# Smooth noise using multiple sine waves:
noise = 'sin(t*2.3)+0.5*sin(t*5.1)+0.25*sin(t*11.7)'
# Normalized to 0-1 range:
normalized_noise = '(sin(t*2.3)+0.5*sin(t*5.1)+0.25*sin(t*11.7)+1.75)/3.5'Jitter/Shake Effects
Continuous shake:
# High frequency, small amplitude:
x='(w-tw)/2+5*sin(t*50)'
y='(h-th)/2+5*cos(t*47)' # Different freq for 2D randomnessDecaying shake (impact):
# Shake that settles over time:
x='(w-tw)/2+10*exp(-t*3)*sin(t*50)'
y='(h-th)/2+10*exp(-t*3)*cos(t*47)'Triggered shake (specific moments):
# Shake only between t=2s and t=2.5s:
x='(w-tw)/2+if(between(t,2,2.5),15*sin(t*60),0)'Practical Animation Cookbook
Fade Transitions
Fade in (0 to 1 over duration):
# Linear: alpha='min(1,t/duration)'
alpha='min(1,t/0.5)' # 0.5s fade in
# Ease out: alpha='1-exp(-k*t)'
alpha='1-exp(-5*t)' # Smooth fade in
# Cubic: alpha='min(1,t/duration)^3'
alpha='min(1,(t/0.5)*(t/0.5)*(t/0.5))'Fade out (1 to 0):
# Assuming video duration = 10s, fade out in last 2s:
alpha='if(gt(t,8),1-(t-8)/2,1)'
# Exponential fade out:
alpha='if(gt(t,8),exp(-(t-8)*3),1)'Fade in and out:
# Fade in 0-1s, hold 1-9s, fade out 9-10s:
alpha='if(lt(t,1),t,if(gt(t,9),10-t,1))'Scale Animations
Pop in (scale 0 to 100%):
# FFmpeg drawtext (no scale support, use ASS instead)
# ASS:
{\fscx0\fscy0\t(0,300,\fscx100\fscy100)}TextPulse (continuous breathing):
# ASS:
fontsize='72+8*sin(t*6.28)' # 1 Hz pulse, ±8px
# ASS pulse with hold:
{\t(0,300,\fscx110\fscy110)\t(300,600,\fscx100\fscy100)}Bounce scale:
# ASS overshoot bounce:
{\fscx50\fscy50\t(0,150,\fscx115\fscy115)\t(150,300,\fscx95\fscy95)\t(300,450,\fscx100\fscy100)}Position Animations
Slide in from right:
x='if(lt(t,1),w-(w-target_x)*t,target_x)'
# Example: target_x = (w-tw)/2 (center)
x='if(lt(t,1),w-((w-(w-tw)/2)*t),(w-tw)/2)'Slide in with ease-out:
x='if(lt(t,1),w-((w-(w-tw)/2)*(1-exp(-5*t))),(w-tw)/2)'Bounce in from bottom:
# ASS:
{\move(540,1920,540,960,0,400)\t(0,150,\fscy115)\t(150,300,\fscy95)\t(300,400,\fscy100)}Circular path:
x='(w/2)+radius*cos(speed*t)'
y='(h/2)+radius*sin(speed*t)'Rotation Animations
FFmpeg drawtext doesn't support rotation. Use ASS:
; Continuous spin (360° over 2 seconds):
{\t(0,2000,\frz360)}Text
; Wobble rotation:
{\t(0,200,\frz15)\t(200,400,\frz-10)\t(400,600,\frz5)\t(600,800,\frz0)}Timing Psychology and Perception
Human Perception Thresholds
| Threshold | Duration | Perception | Use Case |
|---|---|---|---|
| Flicker fusion | 16-20ms | Below this: perceived as continuous | 60fps = 16.67ms/frame |
| Preattentive | 20-50ms | Detected but not consciously processed | Subliminal cues |
| Attention capture | 50-100ms | Minimum for conscious recognition | Flash effects |
| Pattern interrupt | 100-200ms | Breaks expectation, grabs attention | Hook effects |
| Comfortable transition | 200-400ms | Natural UI timing | Standard animations |
| Deliberate motion | 400-800ms | Noticeable, purposeful | Dramatic emphasis |
| Sustained attention | 800ms-2s | Holds focus | Title cards, captions |
| Cognitive processing | 2-5s | Complex information | Infographic reveals |
Animation Duration Guidelines by Distance
Relationship between distance and duration for perceived constant speed:
duration = sqrt(distance) * k
Where:
distance = pixels traveled
k = speed constant (typically 5-15)
Example:
100px move: duration = sqrt(100) * 10 = 100ms
400px move: duration = sqrt(400) * 10 = 200ms
(Appears same speed despite 4x distance)FFmpeg implementation:
# Move text 500px in 224ms (sqrt(500)*10):
-vf "drawtext=text='MOVE':\
x='if(lt(t,0.224),(w-500)+500*t/0.224,w)':\
y=(h-th)/2:fontsize=80:fontcolor=white"Biological Rhythms
Natural timing that feels "right":
| Rhythm | Rate | Use Case |
|---|---|---|
| Resting heartbeat | 60-80 BPM (1-1.3 Hz) | Calm, meditative pulse |
| Walking pace | 100-120 BPM (1.7-2 Hz) | Comfortable, steady rhythm |
| Excited/anxious | 120-160 BPM (2-2.7 Hz) | Energetic, urgent feeling |
| Breathing | 12-20/min (0.2-0.33 Hz) | Slow, natural oscillation |
FFmpeg examples:
# Heartbeat pulse (70 BPM = 1.17 Hz):
fontsize='72+6*max(0,sin(t*7.33))'
# Walking pace pulse (110 BPM = 1.83 Hz):
fontsize='72+4*abs(sin(t*11.5))'
# Breathing (15 breaths/min = 0.25 Hz):
fontsize='72+10*sin(t*1.57)'Sources
This comprehensive timing reference compiled from:
- FFmpeg Expression Evaluation Documentation
- Spring Physics for UI Animations
- Easing Functions Mathematical Reference
- Material Design Motion Guidelines
- React Spring Physics Documentation
- ASS Subtitle Format Specification
- WCAG 2.2 Animation Accessibility
- Human Perception Studies - Accessible Animations
Section 7: Audio-Video Sync Tolerances
Synchronization Thresholds
| Sync Type | Maximum Tolerance | Recommended | Notes |
|---|---|---|---|
| Lip sync | ±80ms | ±40ms | Human perception limit |
| Music beat sync | ±50ms | ±20ms | Musical precision |
| Karaoke lyrics | ±100ms | ±30ms | Comfortable singing |
| Sound effects | ±30ms | ±10ms | Impact synchronization |
| Transition + audio | ±100ms | ±50ms | Cross-fade sync |
Practical Guidelines
Lip Sync
Tolerance: ±80ms (audio can lead or lag video by 80ms)
Recommendation: Keep within ±40ms for professional quality
Audio Leading (negative offset): Less noticeable, up to -80ms acceptable
Audio Lagging (positive offset): More noticeable, keep under +40msMusic Sync
# Calculate beat timing:
# 120 BPM = 2 beats/second = 500ms per beat
# 140 BPM = 2.33 beats/second = 429ms per beat
# Align transitions to beat:
# At 120 BPM, beats fall at: 0ms, 500ms, 1000ms, 1500ms, 2000ms...Karaoke Timing
Word highlight should START when word begins (not before)
Early highlight: Confusing, breaks immersion
Late highlight: Frustrating for singers
Recommendation: Highlight 0-50ms AFTER word starts (slight lag preferred)Section 8: Frame Rate Considerations
Timing Precision Limits
| Frame Rate | Frame Duration | Precision Limit |
|---|---|---|
| 24 fps | 41.67ms | ~40ms increments |
| 25 fps | 40ms | 40ms increments |
| 30 fps | 33.33ms | ~33ms increments |
| 60 fps | 16.67ms | ~17ms increments |
Implications
Animation Smoothness
For smooth animation, use durations that are multiples of frame duration:
30fps:
- 100ms = 3 frames (smooth)
- 200ms = 6 frames (smooth)
- 333ms = 10 frames (smooth)
- 150ms = 4.5 frames (may stutter)
60fps:
- 100ms = 6 frames (smooth)
- 200ms = 12 frames (smooth)
- 167ms = 10 frames (smooth)Minimum Visible Duration
Animation duration must be at least 2-3 frames to be perceived:
- 30fps minimum: ~67-100ms
- 60fps minimum: ~33-50ms
Very short effects (<50ms) may not render consistentlyFrame-Safe Duration Table
| Desired Duration | 30fps Safe | 60fps Safe |
|---|---|---|
| ~100ms | 100ms (3f) | 100ms (6f) |
| ~150ms | 167ms (5f) | 150ms (9f) |
| ~200ms | 200ms (6f) | 200ms (12f) |
| ~250ms | 233ms (7f) | 250ms (15f) |
| ~300ms | 300ms (9f) | 300ms (18f) |
| ~500ms | 500ms (15f) | 500ms (30f) |
Section 9: Quick Copy-Paste Reference
FFmpeg Common Timing Patterns
# 2-second fade in:
-vf "fade=t=in:d=2"
# Text visible from 1s to 5s:
-vf "drawtext=text='Hello':enable='between(t,1,5)':..."
# 1.5s xfade starting at 4s:
-filter_complex "[0:v][1:v]xfade=duration=1.5:offset=4"
# 2-second zoom at 30fps (60 frames):
-vf "zoompan=z='1.5':d=60:s=1080x1920"
# Fade text in over 0.5s, hold, fade out over 0.3s:
-vf "drawtext=text='Hello':alpha='if(lt(t,0.5),t/0.5,if(lt(t,4.7),1,(5-t)/0.3))':enable='lt(t,5)':..."ASS Common Timing Patterns
; Word karaoke (centiseconds): 0.5s + 0.4s + 0.6s + 0.5s
{\k50}This {\k40}is {\k60}the {\k50}test
; 200ms pop animation (milliseconds):
{\fscx80\fscy80\t(0,200,\fscx100\fscy100)}Pop
; 500ms slide + 300ms fade:
{\move(0,1920,540,960,0,500)\fad(300,0)}Slide in
; Bounce: 150ms up, 100ms overshoot, 150ms settle (total 400ms)
{\fscx90\fscy90\t(0,150,\fscx110\fscy110)\t(150,250,\fscx95\fscy95)\t(250,400,\fscx100\fscy100)}BounceDuration Cheat Sheet
| Duration | Use For |
|---|---|
| 50-100ms | Glitch, flash |
| 100-200ms | Quick UI feedback |
| 200-300ms | Standard animation |
| 300-500ms | Smooth transition |
| 500-800ms | Noticeable movement |
| 800-1500ms | Dramatic effect |
| 1.5-3s | Caption display |
| 3-5s | Long text reading |
Related Skills
ffmpeg-karaoke-animated-text- Karaoke subtitle creationviral-video-animated-captions- CapCut-style animationsffmpeg-transitions-effects- Video transitionsviral-video-hook-templates- Hook timing patternsffmpeg-captions-subtitles- Subtitle fundamentals