FFmpeg JSON video composition shipped. Not in your build.

You read the week's FFmpeg news, ran ffmpeg -filters | grep libplacebo_scene, and got an empty line back.
FFmpeg JSON video composition is now a real filter. libplacebo_scene composites one or more inputs onto a canvas of fixed size and frame rate from a JSON scene graph, with per-input placement, timing and animation, and it was merged into git master on October 30, 2026. It exists only in master, and it needs a Vulkan-enabled build linked against libplacebo API 386 or newer, which no distribution packages yet. That is why the grep comes back silent on Debian, Ubuntu, Arch, Homebrew and every public Docker image.
What the filter does that an overlay chain cannot
libplacebo_scene takes the whole composition as data instead of as a graph you wire by hand. Per FFmpeg's filter documentation, scene, w, h and fps are required options, sar defaults to 0 meaning 1:1, and the /scene=file.json form loads the graph from disk using FFmpeg's read-option-from-file syntax. The number of inputs, where each one sits, when it starts and stops, and how it is fitted into its box all come from the JSON, not from the command line.
The scene format itself belongs to libplacebo. A pl_scene is a compositor configured from a JSON description: per-input placement and timing (start_pts, stop_pts, fit), each input's render parameters, and canvas-wide options, with animated parameters for movement and fades. libplacebo v7.387.0, released late October 2026, is the first version that ships the pl_compositor multi-input API and the pl_scene JSON API at all.
It is a separate filter rather than a mode of libplacebo for a blunt reason. The merge request says the scene graph invalidates most of the existing filter's options, including its scaling and fitting logic, so the two could not share one option set. That branch update was 21 commits covering shared per-output tagging, refactored Vulkan initialization and SAR handling, sponsored by nxtedition AB, and it builds on the earlier compositor work that gave vf_libplacebo multiple inputs in the first place.
The chain you have now, and the JSON that replaces it
Here is the composition most people actually run: two clips with a one-second crossfade, a logo in the top right, audio crossfaded to match. Normalize both clips first, because xfade needs identical size, pixel format, SAR and frame rate, and its offset is measured from the start of the first input, not from the join.
ffmpeg -i clip1.mp4 -i clip2.mp4 -i logo.png -filter_complex "\
[0:v]scale=1920:1080:force_original_aspect_ratio=decrease,\
pad=1920:1080:(ow-iw)/2:(oh-ih)/2,setsar=1,fps=25[v0];\
[1:v]scale=1920:1080:force_original_aspect_ratio=decrease,\
pad=1920:1080:(ow-iw)/2:(oh-ih)/2,setsar=1,fps=25[v1];\
[v0][v1]xfade=transition=fade:duration=1:offset=9[xf];\
[2:v]format=rgba,scale=240:-1[logo];\
[xf][logo]overlay=main_w-overlay_w-48:48[vout];\
[0:a][1:a]acrossfade=d=1[aout]" \
-map "[vout]" -map "[aout]" -c:v libx264 -crf 20 -c:a aac out.mp4
That is 10 lines of string concatenation for one cut and one logo. Add a third clip and you re-derive every offset by hand, because the output of each xfade is shorter than the sum of its inputs. format=rgba before scale is load-bearing too: without it the logo scales into a non-alpha pixel format and composites as a solid rectangle.
The scene version moves all of that into a file. Field names and defaults are versioned with libplacebo, so read the JSON reference for the commit you build against rather than copying any snippet, including this one. The shape is inputs with a position and a time window:
{
"inputs": [
{ "input": 0, "start_pts": 0.0, "stop_pts": 10.0 },
{ "input": 1, "start_pts": 9.0, "stop_pts": 19.0 },
{ "input": 2, "x": 1632, "y": 48, "w": 240 }
]
}
ffmpeg -i clip1.mp4 -i clip2.mp4 -i logo.png -filter_complex "\
[0:v][1:v][2:v]libplacebo_scene=/scene=scene.json:w=1920:h=1080:fps=25[vout]" \
-map "[vout]" -c:v libx264 -crf 20 out.mp4
The one-second overlap between input 0 and input 1 is the crossfade window, and an animated opacity parameter on the incoming input is what makes it a dissolve instead of a hard swap. fit controls how each input is scaled into its box, which is the job scale plus pad was doing above. Nothing recomputes when you add a fourth clip: you edit two numbers in a file your code already generates.
Five checks to run before you plan a migration
Run these on the machine that will actually do the rendering, not on your laptop, because the answers differ and only one of those boxes matters.
ffmpeg -filters | grep libplacebo_scene. Empty output is the whole answer. No FFmpeg release carries this filter, only git master.ffmpeg -hide_banner -buildconf | grep -E 'libplacebo|vulkan'. You want--enable-libplaceboand--enable-vulkan, and in practice--enable-libshadercand--enable-lcms2as well. libplacebo is supported in FFmpeg from 6.1 onward, and as this jrottenberg/ffmpeg issue documents, the common Docker FFmpeg images do not enable it and fail at pkg-config withoutlibplacebo-dev.pkg-config --modversion libplacebo. The middle number is the API version, so 7.349.0 is API 349, which is 37 short of the 386 this filter requires.vulkaninfo --summary. If it listsllvmpipeorlavapipe, you have CPU Vulkan, not a GPU. If it lists nothing, there is no ICD installed.- Ask where the job runs. On n8n Cloud, a distroless n8n image, a shared host or a Lambda, the answer to all four checks above is permanently no, and no amount of configure flags changes that.
The packaged versions are the short version of check 3:
| Where | libplacebo version | API 386+? |
|---|---|---|
| [Debian trixie](https://packages.debian.org/trixie/ffmpeg) (ffmpeg 7:7.1.5) | 7.349.0 | No |
| [Ubuntu 26.04](https://launchpad.net/ubuntu/+source/libplacebo) | 7.360.1-1 | No |
| Ubuntu 25.10 | 7.349.0-3 | No |
| [Arch extra](https://archlinux.org/packages/extra/x86_64/libplacebo/) | 7.360.1-7 | No |
| [libplacebo upstream](https://github.com/haasn/libplacebo/releases/tag/v7.387.0) | 7.387.0 | Yes |
The spread across every other distribution is in one place on repology, and nothing packaged is at 386 yet. This is the same shape as the gap covered in FFmpeg 9.1: what to re-test before you upgrade: the feature is upstream, your package manager is months behind it, and the only lever you have is which of those two you choose to own.
Even a correct build can fail on a cloud box
Compiling master with the right flags gets you the filter. It does not get you a working device. libplacebo renders through Vulkan, and a headless cloud instance or a container usually has no Vulkan ICD at all, or silently falls back to Mesa lavapipe, where jobs can exit 0 and still produce artifacts. A composition filter that returns a green frame with status success is worse than one that refuses to load.
Then there is where most of this blog's readers run their video step. n8n's official image went distroless in 2026, so there is no FFmpeg inside to grep and no apt to add one, which is the same wall described in n8n 3.0 disables community nodes: keep your FFmpeg step alive. Make and Zapier never had a shell. For those three platforms, libplacebo_scene is not a maybe-later, it is a no.
Your three honest options this week
Pick based on who owns the renderer, not on which approach is more elegant.
Build it yourself if you control the host and it has a real GPU. Clone master, configure with libplacebo, Vulkan, shaderc and lcms2, build libplacebo 7.387.0 or newer from source first because your distro will not give it to you, then budget for tracking two moving branches. That is reasonable for a render farm and unreasonable for one step in a content pipeline.
Wait for a release if the composition you have today works. Your existing overlay, xfade and filter_complex chains are untouched by this merge, exactly as they were by the 9.0 filter-graph changes covered in FFmpeg 9 didn't rewrite the filter graph. Nothing breaks by doing nothing.
Or send the same multi-input composition somewhere that already has the build. FFmpeg Micro takes several inputs and a filters array in one POST /v1/transcodes, which is the same filter graph as the chain above, run on managed FFmpeg with no binary for you to compile:
curl -X POST https://api.ffmpeg-micro.com/v1/transcodes \
-H "Authorization: Bearer $FFMPEG_MICRO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"inputs": [
{"url": "https://storage.example.com/clip1.mp4"},
{"url": "https://storage.example.com/clip2.mp4"},
{"url": "https://storage.example.com/logo.png"}
],
"filters": [
"[0:v]scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2,setsar=1,fps=25[v0]",
"[1:v]scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2,setsar=1,fps=25[v1]",
"[v0][v1]xfade=transition=fade:duration=1:offset=9[xf]",
"[2:v]format=rgba,scale=240:-1[logo]",
"[xf][logo]overlay=main_w-overlay_w-48:48"
],
"outputFormat": "mp4"
}'
{ "id": "tr_8f3c21", "status": "queued" }
Poll GET /v1/transcodes/tr_8f3c21 until the status is completed, then GET /v1/transcodes/tr_8f3c21/download for a signed MP4 URL. Status checks and downloads are free. Billing is on the duration of the input video, metered per second with no rounding up to the minute, and failed jobs are never billed, so a 19-second composition costs 19 seconds. The free tier is 200 tokens at sign-up with no card, about 33 minutes of video, which is enough to port a real chain and see the output.
The honest limit: this is an API, not a timeline. If what you want is a visual editor where a non-technical teammate drags clips around, a template-editor product is the right class of tool and an FFmpeg API is not. And if your composition is one static logo on one clip, overlay on whatever FFmpeg you already have is fine. The case for sending it out is multi-input assembly on a box that cannot run the filter.
FAQ about libplacebo_scene
Is libplacebo_scene in any FFmpeg release yet?
libplacebo_scene is in git master only, merged on October 30, 2026. No tagged FFmpeg release includes it, so installing FFmpeg 9.0 or 9.1 will not give you the filter.
What libplacebo version satisfies the API 386 requirement?
libplacebo 7.387.0 or newer satisfies the filter's API 386 requirement, because the middle number of a libplacebo version is its API version. Debian trixie's 7.349.0, Ubuntu 26.04's 7.360.1 and Arch's 7.360.1 are all below it.
Do I have to rewrite my existing overlay and xfade chains?
Existing overlay, xfade and filter_complex chains keep working unchanged, and libplacebo_scene is additive. You would migrate because generating JSON from code is easier than generating filter strings, not because anything deprecated your current graph.
Can I use libplacebo_scene from n8n, Make or Zapier?
You cannot use libplacebo_scene from n8n, Make or Zapier. All three run your workflow without a shell, and n8n's official Docker image is distroless with no FFmpeg inside and no package manager to install one, so there is nothing to compile against.
Will it work on a headless server or in Docker without a GPU?
A headless server or GPU-less container usually has no Vulkan driver, so libplacebo either refuses the device or falls back to Mesa lavapipe on the CPU, where renders can succeed and still show artifacts. Verify with vulkaninfo --summary on the exact host that renders before you plan around it.
If your composition is already a filter_complex string and the machine running it has no Vulkan device, the scene graph is next year's plan rather than this week's. The watermark walkthrough scripts the same multi-input pattern as the request above, upload through download, and Sign up free gets you the key it needs: no servers to run, no FFmpeg to install, callable from your code, from n8n, Make and Zapier, or from your AI agents, free tier included.
About Javid Jamae
Founder & CEO at FFmpeg Micro
Javid is a software engineer, author, and entrepreneur with over 25 years of professional software development experience across enterprise, startup, and consulting environments. He founded FFmpeg Micro to make video processing accessible to developers through a simple, automation-first REST API.
You might also like

Transcode every user uploaded video? Normalize to one house spec
Do you need to transcode user uploaded video? Probe with ffprobe first, normalize to one house spec, remux what already conforms, and re-encode only the rest.

What transcode means: transcode vs encode vs remux vs compress
Transcode means decoding compressed video and encoding it again. See how transcode differs from encode, remux, and compress, and when to skip it entirely.

Why your transcode job failed (and what to do in the poll loop)
A transcode failed almost always means bad input, not a broken service. The real causes, an ffprobe pre-check, and the poll loop that knows when to retry.
Ready to process videos at scale?
Start using FFmpeg Micro's simple API today. No infrastructure required.
Get Started Free