Turn a talking-head video into an animated explainer (split-screen or fullscreen layout) with 9 infographic scene templates and optional captions
Turn a talking-head video into an animated explainer in one of two layouts: split-screen (default; the speaker sits in the bottom half and the infographic panel plays in the top half) or fullscreen (the speaker fills the frame and each scene covers it while it plays). See Layouts. You describe the panel as a list of timed scenes, each using one of 9 templates, with optional burned-in captions. Output is a 1080x1920 MP4 with loudness normalised to -16 LUFS.
The endpoint returns a job_id. Poll it with the same status and result endpoints used for every other video job.
POST https://eranol.com/api/v1/ffmpeg/video/animate
Costs 10 credits per request (Standard Plan). Header: x-api-key: YOUR_KEY.
{
"video_url": "https://cdn.example.com/talking-head.mp4",
"type": "split_infographic",
"captions": {
"srt_url": "https://ffmpeg.eranol.com/files/captions.srt"
},
"scenes": [
{
"start": 0.0,
"end": 5.0,
"template": "icon_pair",
"kicker": "THE PROMISE",
"title": "Safe pension for life?",
"accent": "blue",
"icons": [
{
"name": "shield-check",
"accent": "green"
}
],
"symbol": "?",
"at": [
0.6,
1.4
]
},
{
"start": 5.0,
"end": 11.5,
"template": "logo_flow",
"kicker": "UK MINISTRY OF DEFENCE",
"title": "Now hunting down retired soldiers",
"accent": "orange",
"logo_domain": "mod.uk",
"to_icon": "users",
"at": [
5.5,
6.6,
7.3
]
},
{
"start": 11.5,
"end": 16.8,
"template": "stat_countup",
"kicker": "REPAYMENT DEMANDED",
"title": "Up to",
"accent": "orange",
"value": 100000,
"prefix": "£",
"bar_max": 100000,
"label": "TOP-END DEMAND",
"at": 12.2
},
{
"start": 16.8,
"end": 21.3,
"template": "hbar_compare",
"kicker": "HE WAS TOLD HE OWES",
"accent": "orange",
"hero": {
"value": 16000,
"prefix": "£"
},
"bars": [
{
"label": "MICHAEL",
"value_text": "£16,000",
"ratio": 0.16,
"accent": "orange"
},
{
"label": "TOP-END DEMAND",
"value_text": "£100,000",
"ratio": 1.0
}
],
"at": 17.3
},
{
"start": 21.3,
"end": 27.3,
"template": "ring_percent",
"kicker": "THE SPLIT",
"title": "Who was at fault",
"accent": "green",
"percent": 46,
"label": "AT FAULT",
"stamp": {
"text": "PUNISHED ANYWAY",
"at": 25.3
},
"at": 22.3
},
{
"start": 27.3,
"end": 42.3,
"template": "rows",
"kicker": "TREASURY RULES",
"title": "Every overpayment is collected",
"accent": "blue",
"items": [
{
"text": "TREASURY RULES STATE",
"at": 27.6
},
{
"text": "EVERY SINGLE OVERPAYMENT",
"at": 29.5
},
{
"text": "MUST BE COLLECTED BY LAW",
"at": 32.1
},
{
"text": "REGARDLESS OF WHO WAS AT FAULT",
"at": 35.7
}
]
},
{
"start": 42.3,
"end": 53.3,
"template": "bar_columns",
"kicker": "A BRUTAL CUT",
"title": "£414 less every month",
"accent": "orange",
"columns": [
{
"label": "J",
"value_text": "−£414"
},
{
"label": "F",
"value_text": "−£414"
},
{
"label": "M",
"value_text": "−£414"
},
{
"label": "A",
"value_text": "−£414"
},
{
"label": "M",
"value_text": "−£414"
},
{
"label": "J",
"value_text": "−£414"
},
{
"label": "J",
"value_text": "−£414"
},
{
"label": "A",
"value_text": "−£414"
},
{
"label": "S",
"value_text": "−£414"
},
{
"label": "O",
"value_text": "−£414"
},
{
"label": "N",
"value_text": "−£414"
},
{
"label": "D",
"value_text": "−£414"
}
],
"note": "≈ £4,968 A YEAR",
"at": 43.5
},
{
"start": 53.3,
"end": 59.3,
"template": "growing_bars",
"kicker": "QUIETLY OVERPAYING",
"title": "Thousands of retired troops, for years",
"accent": "orange",
"count": 8,
"left_label": "YEAR 1",
"right_label": "OVERPAYMENTS ADD UP →",
"illustrative": true,
"at": 54.3
},
{
"start": 59.3,
"end": 69.2,
"template": "progress_track",
"kicker": "UNTIL THE STATE STEPS IN",
"title": "Life savings vanish",
"accent": "orange",
"label": "LIFE SAVINGS",
"direction": "drain",
"text": "DRAINING…",
"illustrative": true,
"at": 60.3
}
]
}
[
{
"job_id": "1406c589-a1f4-45d5-8745-aae07bc4de38",
"status": "pending",
"message": "Job created. Poll status_url for progress.",
"result_url": "https://eranol.com/api/v1/ffmpeg/result/1406c589-a1f4-45d5-8745-aae07bc4de38",
"status_url": "https://eranol.com/api/v1/ffmpeg/status/1406c589-a1f4-45d5-8745-aae07bc4de38"
}
]
When the job completes, GET result_url returns url, filename, duration, resolution (1080x1920), type, scene_count, dropped_scenes, logos_used and captions.
| Field | Type | Default | Description |
|---|---|---|---|
video_url | string | required | Source talking-head video |
scenes | array | required | 1 to 80 scenes |
type | string | split_infographic | Layout: split_infographic (half-screen) or fullscreen_overlay. See Layouts. Any other value returns 422 |
crop_y | int | 360 | split_infographic only (ignored by fullscreen_overlay). Top offset of the 1080x960 speaker crop. The video is scaled to 1080 wide first; the job fails if crop_y + 960 does not fit |
captions | object | none | Omit for no captions. Burned under the speaker (split) or over the whole frame (fullscreen) |
captions.srt_url | string | required if set | SRT file URL |
captions.color | string | #2a78d6 | Fill colour, #RRGGBB |
captions.outline_color | string | #ffffff | Outline colour, #RRGGBB |
captions.font_size | int | 72 | 24 to 160 |
captions.margin_bottom | int | 87 | Pixels from the bottom edge, 0 to 600 |
Set type to choose how scenes are shown. The scene JSON and all 9 templates are identical in both layouts.
split_infographic (default) | fullscreen_overlay | |
|---|---|---|
| Speaker | Bottom half (1080x960 crop, set by crop_y) | Fills the whole 1080x1920 frame |
| Scenes | Play in the top half, speaker stays visible below | Cover the whole frame between start and end |
| Between scenes | Empty panel | Speaker visible again, so leave gaps where you want them on screen |
| Captions | Under the speaker | Over the whole frame. Scene content keeps clear of the bottom caption band |
crop_y | Used | Ignored |
In both layouts the speaker's audio plays throughout and is loudness-normalised. Fullscreen scenes switch on and off with a hard cut, and each scene's own short fade runs against a white background. Fullscreen works best with a portrait 1080x1920 source; other aspect ratios are scaled and centre-cropped to fill the frame. Cards are centred on the white canvas, so they look sparser than in the half-screen layout.
| Field | Type | Default | Description |
|---|---|---|---|
start | float | required | Start in seconds (absolute video time) |
end | float | required | End in seconds, at least 2s after start |
template | string | required | One of the 9 templates below |
kicker | string | "" | Small uppercase label, max 38 chars |
title | string | "" | Headline, max 56 chars. Keep it short: about 22 characters per line, two lines |
accent | string | blue | blue, green, purple or orange |
illustrative | bool | false | Prints "ILLUSTRATIVE SHAPE · NO REAL DATA" |
at fields are absolute video times (not relative to the scene) and must fall inside the scene. Set each one to the moment the matching word is spoken so the animation lands on the voice.
icon_pairIcon tiles (1-3) with optional symbol, numbered badges and a stamp. Fields: icons (required, 1-3, each name + optional caption + accent), symbol, numbered, stamp (text, at), at (list of cue times, up to 4).
{
"start": 0,
"end": 5,
"template": "icon_pair",
"kicker": "THE PROMISE",
"title": "Safe pension for life?",
"accent": "blue",
"icons": [
{
"name": "shield-check",
"accent": "green"
}
],
"symbol": "?",
"at": [
0.6,
1.4
]
}
logo_flowBrand logo, arrow, then an icon. Fields: logo_domain (required, bare domain), to_icon, arrow (default true), at (up to 3 cue times). If no logo exists for the domain, an icon tile is used instead.
{
"start": 0,
"end": 6.5,
"template": "logo_flow",
"kicker": "UK MINISTRY OF DEFENCE",
"title": "Now hunting down retired soldiers",
"accent": "orange",
"logo_domain": "mod.uk",
"to_icon": "users",
"at": [
0.5,
1.6,
2.3
]
}
stat_countupBig number counting up with a progress bar. Fields: value (required), prefix, suffix, decimals (0-2), label, bar_max (must be >= value), icon, at.
{
"start": 0,
"end": 5.3,
"template": "stat_countup",
"kicker": "REPAYMENT DEMANDED",
"title": "Up to",
"accent": "orange",
"value": 100000,
"prefix": "£",
"bar_max": 100000,
"label": "TOP-END DEMAND",
"at": 0.7
}
hbar_compare2-4 horizontal bars on one scale. Fields: bars (required; each label, value_text, ratio 0-1, optional accent), optional hero (value, prefix, suffix, decimals), at. Set ratio to the real fraction of the largest value.
{
"start": 0,
"end": 4.5,
"template": "hbar_compare",
"kicker": "HE WAS TOLD HE OWES",
"accent": "orange",
"hero": {
"value": 16000,
"prefix": "£"
},
"bars": [
{
"label": "MICHAEL",
"value_text": "£16,000",
"ratio": 0.16,
"accent": "orange"
},
{
"label": "TOP-END DEMAND",
"value_text": "£100,000",
"ratio": 1.0
}
],
"at": 0.5
}
ring_percentDonut ring filling to a real percentage. Fields: percent (required, 0-100), label, stamp, at.
{
"start": 0,
"end": 6,
"template": "ring_percent",
"kicker": "THE SPLIT",
"title": "Who was at fault",
"accent": "green",
"percent": 46,
"label": "AT FAULT",
"stamp": {
"text": "PUNISHED ANYWAY",
"at": 4.0
},
"at": 1.0
}
rowsChecklist that resolves row by row on the spoken word. Fields: items (required, 2-5; each text, at, state = keep or reject).
{
"start": 0,
"end": 15,
"template": "rows",
"kicker": "TREASURY RULES",
"title": "Every overpayment is collected",
"accent": "blue",
"items": [
{
"text": "TREASURY RULES STATE",
"at": 0.3
},
{
"text": "EVERY SINGLE OVERPAYMENT",
"at": 2.2
},
{
"text": "MUST BE COLLECTED BY LAW",
"at": 4.8
},
{
"text": "REGARDLESS OF WHO WAS AT FAULT",
"at": 8.4
}
]
}
bar_columns3-12 columns dropping in, e.g. months. Fields: columns (required; each label up to 4 chars, value_text up to 8 chars), note, at.
{
"start": 0,
"end": 11,
"template": "bar_columns",
"kicker": "A BRUTAL CUT",
"title": "£414 less every month",
"accent": "orange",
"columns": [
{
"label": "J",
"value_text": "−£414"
},
{
"label": "F",
"value_text": "−£414"
},
{
"label": "M",
"value_text": "−£414"
},
{
"label": "A",
"value_text": "−£414"
},
{
"label": "M",
"value_text": "−£414"
},
{
"label": "J",
"value_text": "−£414"
},
{
"label": "J",
"value_text": "−£414"
},
{
"label": "A",
"value_text": "−£414"
},
{
"label": "S",
"value_text": "−£414"
},
{
"label": "O",
"value_text": "−£414"
},
{
"label": "N",
"value_text": "−£414"
},
{
"label": "D",
"value_text": "−£414"
}
],
"note": "≈ £4,968 A YEAR",
"at": 1.2
}
growing_barsRising bars showing a trend shape. Fields: count (4-10), left_label, right_label, at. Not real data, so set illustrative: true to print the tag.
{
"start": 0,
"end": 6,
"template": "growing_bars",
"kicker": "QUIETLY OVERPAYING",
"title": "Thousands of retired troops, for years",
"accent": "orange",
"count": 8,
"left_label": "YEAR 1",
"right_label": "OVERPAYMENTS ADD UP →",
"illustrative": true,
"at": 1.0
}
progress_trackOne bar filling or draining. Fields: label (required), direction (fill or drain), text, at. Set illustrative: true if the level is not real data.
{
"start": 0,
"end": 9.9,
"template": "progress_track",
"kicker": "UNTIL THE STATE STEPS IN",
"title": "Life savings vanish",
"accent": "orange",
"label": "LIFE SAVINGS",
"direction": "drain",
"text": "DRAINING…",
"illustrative": true,
"at": 1.0
}
A bad payload is rejected with 422 before any render starts, and the response names the failing field.
at) must fall inside their scene.shield-check.logo_domain must be a bare domain such as mod.uk.type must be split_infographic or fullscreen_overlay.Smallest valid request. Good for testing credentials and the polling loop.
Request Body
{
"video_url": "https://cdn.example.com/talking-head.mp4",
"type": "split_infographic",
"scenes": [
{
"start": 0,
"end": 5.3,
"template": "stat_countup",
"kicker": "REPAYMENT DEMANDED",
"title": "Up to",
"accent": "orange",
"value": 100000,
"prefix": "£",
"bar_max": 100000,
"label": "TOP-END DEMAND",
"at": 0.7
}
]
}
Add captions.srt_url to burn captions under the speaker. Generate the SRT with POST /api/v1/ffmpeg/video/caption and burn_in: false.
Request Body
{
"video_url": "https://cdn.example.com/talking-head.mp4",
"type": "split_infographic",
"captions": {
"srt_url": "https://ffmpeg.eranol.com/files/captions.srt"
},
"scenes": [
{
"start": 0.0,
"end": 5.0,
"template": "icon_pair",
"kicker": "THE PROMISE",
"title": "Safe pension for life?",
"accent": "blue",
"icons": [
{
"name": "shield-check",
"accent": "green"
}
],
"symbol": "?",
"at": [
0.6,
1.4
]
},
{
"start": 5.0,
"end": 10.3,
"template": "stat_countup",
"kicker": "REPAYMENT DEMANDED",
"title": "Up to",
"accent": "orange",
"value": 100000,
"prefix": "£",
"bar_max": 100000,
"label": "TOP-END DEMAND",
"at": 5.7
},
{
"start": 10.3,
"end": 16.3,
"template": "ring_percent",
"kicker": "THE SPLIT",
"title": "Who was at fault",
"accent": "green",
"percent": 46,
"label": "AT FAULT",
"stamp": {
"text": "PUNISHED ANYWAY",
"at": 14.3
},
"at": 11.3
}
]
}
Change caption colours and size, and move the speaker crop down when the face sits low in the source.
Request Body
{
"video_url": "https://cdn.example.com/talking-head.mp4",
"type": "split_infographic",
"captions": {
"srt_url": "https://ffmpeg.eranol.com/files/captions.srt",
"color": "#1db57a",
"outline_color": "#14171c",
"font_size": 64,
"margin_bottom": 120
},
"crop_y": 420,
"scenes": [
{
"start": 0.0,
"end": 6.5,
"template": "logo_flow",
"kicker": "UK MINISTRY OF DEFENCE",
"title": "Now hunting down retired soldiers",
"accent": "orange",
"logo_domain": "mod.uk",
"to_icon": "users",
"at": [
0.5,
1.6,
2.3
]
},
{
"start": 6.5,
"end": 21.5,
"template": "rows",
"kicker": "TREASURY RULES",
"title": "Every overpayment is collected",
"accent": "blue",
"items": [
{
"text": "TREASURY RULES STATE",
"at": 6.8
},
{
"text": "EVERY SINGLE OVERPAYMENT",
"at": 8.7
},
{
"text": "MUST BE COLLECTED BY LAW",
"at": 11.3
},
{
"text": "REGARDLESS OF WHO WAS AT FAULT",
"at": 14.9
}
]
}
]
}
Pair hbar_compare with bar_columns to show one person's number against the maximum, then the recurring cost.
Request Body
{
"video_url": "https://cdn.example.com/talking-head.mp4",
"type": "split_infographic",
"scenes": [
{
"start": 0.0,
"end": 4.5,
"template": "hbar_compare",
"kicker": "HE WAS TOLD HE OWES",
"accent": "orange",
"hero": {
"value": 16000,
"prefix": "£"
},
"bars": [
{
"label": "MICHAEL",
"value_text": "£16,000",
"ratio": 0.16,
"accent": "orange"
},
{
"label": "TOP-END DEMAND",
"value_text": "£100,000",
"ratio": 1.0
}
],
"at": 0.5
},
{
"start": 4.5,
"end": 15.5,
"template": "bar_columns",
"kicker": "A BRUTAL CUT",
"title": "£414 less every month",
"accent": "orange",
"columns": [
{
"label": "J",
"value_text": "−£414"
},
{
"label": "F",
"value_text": "−£414"
},
{
"label": "M",
"value_text": "−£414"
},
{
"label": "A",
"value_text": "−£414"
},
{
"label": "M",
"value_text": "−£414"
},
{
"label": "J",
"value_text": "−£414"
},
{
"label": "J",
"value_text": "−£414"
},
{
"label": "A",
"value_text": "−£414"
},
{
"label": "S",
"value_text": "−£414"
},
{
"label": "O",
"value_text": "−£414"
},
{
"label": "N",
"value_text": "−£414"
},
{
"label": "D",
"value_text": "−£414"
}
],
"note": "≈ £4,968 A YEAR",
"at": 5.7
}
]
}
rows items can be keep (green tick) or reject (grey strike-through). Each at is when that row is spoken.
Request Body
{
"video_url": "https://cdn.example.com/talking-head.mp4",
"type": "split_infographic",
"scenes": [
{
"start": 0,
"end": 12,
"template": "rows",
"kicker": "WHAT TO CHECK",
"title": "Before you sign",
"accent": "purple",
"items": [
{
"text": "READ THE CONTRACT",
"at": 0.5
},
{
"text": "CHECK THE FEES",
"at": 3.0
},
{
"text": "VERBAL PROMISES",
"at": 6.0,
"state": "reject"
}
]
}
]
}
Use growing_bars and progress_track for a shape that is not real data, and set illustrative: true so the panel prints the disclaimer.
Request Body
{
"video_url": "https://cdn.example.com/talking-head.mp4",
"type": "split_infographic",
"scenes": [
{
"start": 0.0,
"end": 6.0,
"template": "growing_bars",
"kicker": "QUIETLY OVERPAYING",
"title": "Thousands of retired troops, for years",
"accent": "orange",
"count": 8,
"left_label": "YEAR 1",
"right_label": "OVERPAYMENTS ADD UP →",
"illustrative": true,
"at": 1.0
},
{
"start": 6.0,
"end": 15.9,
"template": "progress_track",
"kicker": "UNTIL THE STATE STEPS IN",
"title": "Life savings vanish",
"accent": "orange",
"label": "LIFE SAVINGS",
"direction": "drain",
"text": "DRAINING…",
"illustrative": true,
"at": 7.0
}
]
}
Set type to fullscreen_overlay and the speaker fills the frame. Each scene covers the whole frame between start and end, then the speaker shows again. Gaps between scenes (here 8 to 12s and 18 to 22s) stay on the speaker. crop_y is not needed.
Request Body
{
"video_url": "https://cdn.example.com/talking-head.mp4",
"type": "fullscreen_overlay",
"captions": {
"srt_url": "https://cdn.example.com/captions.srt",
"color": "#2a78d6",
"outline_color": "#ffffff",
"font_size": 72,
"margin_bottom": 87
},
"scenes": [
{
"start": 3.0,
"end": 8.0,
"template": "stat_countup",
"kicker": "AVERAGE PENSION",
"title": "Monthly payout",
"accent": "green",
"value": 1250,
"prefix": "£",
"bar_max": 2000,
"at": 3.6
},
{
"start": 12.0,
"end": 18.0,
"template": "rows",
"kicker": "CHECKLIST",
"title": "What you need",
"accent": "blue",
"items": [
{ "text": "Proof of ID", "at": 13.0 },
{ "text": "Service record", "at": 14.5 },
{ "text": "Old paperwork", "at": 16.0, "state": "reject" }
]
},
{
"start": 22.0,
"end": 27.0,
"template": "ring_percent",
"kicker": "SUCCESS RATE",
"title": "Claims approved",
"accent": "purple",
"percent": 87,
"label": "APPROVED",
"at": 22.6
}
]
}
The job flow is the same as for the half-screen layout. The completed result includes "type": "fullscreen_overlay" and "resolution": "1080x1920".
Building the scene list is the creative step, so the usual flow puts an AI node before the render call.
video_url.POST https://eranol.com/api/v1/ffmpeg/video/caption with {"url": "{{ $json.video_url }}", "burn_in": false, "max_words_per_line": 3}. Poll status and result until it returns srt_url.{"scenes": [...]} that matches this page. The AI returns data only, and the API validates it, so a bad answer fails with a 422 instead of rendering.POST https://eranol.com/api/v1/ffmpeg/video/animate with video_url, captions: {"srt_url": ...} and the AI's scenes. Save job_id.GET https://eranol.com/api/v1/ffmpeg/status/{job_id} in a loop (every 10 to 15 seconds) until status is completed. Branch on failed.GET https://eranol.com/api/v1/ffmpeg/result/{job_id} and read url for the finished video. Then publish it (YouTube, TikTok, Instagram, X) with the other Eranol endpoints.at is the start time of the spoken trigger word, taken from the transcript. Land each title within about 1.5s of the scene start.ring_percent must use the real percentage, never a full circle.growing_bars, a draining progress_track) must set illustrative: true.kicker and title short. Use Lucide icon names only.logo_flow for named organisations. If no logo is found, the scene falls back to an icon tile.| Symptom | Cause |
|---|---|
422 mentioning a scene index | Overlap, under 2 seconds, at outside the scene, unknown icon or field |
Job failed with "No scene starts before the end of the video" | Every scene starts after the video ends (fullscreen or split) |
Job failed about the crop | split_infographic only: crop_y + 960 is taller than the scaled source video. Lower crop_y |
| Scene missing from the output | The scene starts after the real video ends. It is dropped and counted in dropped_scenes |
10 credits per request (Standard Plan), regardless of the number of scenes.
| Code | Meaning |
|---|---|
401 | Missing or invalid API key |
402 | Insufficient credits |
422 | Validation error — check your request body |
429 | Rate limit exceeded — slow down requests |
500 | Internal server error — try again or contact support |