Placeholder Video APIs for Testing a Render Pipeline
The Editframe CLI finishes the TPL-30 payload end to end on its free tier, and seven other tools ran the same template with different trade-offs in speed, output size, and licence terms.
The shortest path from a video API for testing question to a working file is declarative, not a filter graph. The Editframe CLI takes a payload of layers and durations, and its free-tier local render of the TPL-30 template finished in 17.9 s, producing a 1.46 MB MP4 that runs 30.080 s. That run is vbc-2026-08-25-editframe-tpl30-001. Its payload, main.js, appears in full below rather than as a summary, because a paraphrase is not the code that ran.
A placeholder video API for testing does not need a paid account or a render queue to answer this question. Editframe's local mode starts a Vite dev server and a headless instance of the system's own Chrome. The free tier covers companies of three employees or fewer at no cost, and no cloud endpoint takes part anywhere in the run. The same TPL-30 payload, a title card, a captioned still image, and an outro, also ran against seven other tools: ffmpeg, MoviePy, MLT, Remotion, Revideo, Diffusion Studio Core, and Editly. Two of the seven did not return a benchmark-equivalent file. Editly failed before it could render anything. Diffusion Studio Core rendered correctly but stamped a vendor watermark onto the output.
Wall-clock speed is a separate question from setup effort, and the two do not point at the same tool. Among the six tools that produced a usable file, the fastest single-process render belongs to ffmpeg, not Editframe. That trade-off, and what each tool's number actually measures, is the subject of the two sections after the code below.
Versions and prerequisites
One version string sits behind every figure in this comparison, and swapping any of them changes what gets measured. The Editframe run behind 17.9 s used @editframe/cli 0.59.40 and @editframe/elements 0.59.40, bundled by Vite 8.2.2, on Node v25.6.1, driving system Google Chrome 152.0.7977.55. No API key and no account were involved. EF_NO_TELEMETRY=1 suppressed the CLI's telemetry request.
The version matters because Editframe's CLI exposes fps and scale as encode controls, plus a codec selector, and nothing else. It has no CRF flag and no preset flag; a pixel-format flag doesn't exist either. TPL-30 specifies crf 20 and preset medium; ffmpeg, MoviePy, and MLT can match those settings exactly, but Editframe cannot set them at all. Whatever bitrate the browser's own WebCodecs encoder picks is what lands in the file.
Editframe's dev media server caches image assets on first request. tools/editframe/.cache was emptied before this run, so 17.9 s includes that cold-cache cost rather than hiding it behind a warmed-up server. A repeat run against a populated cache would report a different figure. That figure has not been recorded here.
Each of the other seven tools brings its own prerequisite chain, and none of them are interchangeable. ffmpeg 8.0.1 on the measuring machine is a Homebrew build compiled without libfreetype, so its drawtext filter does not exist. The benchmark works around this the same way for every tool: it pre-renders the three on-screen text strings into transparent PNGs, and each tool composites the image instead of drawing type. MoviePy reports dist=2.2.1 and version 2.1.2 in the same install. It runs on Python 3.14.3, with numpy 2.5.1 and Pillow 11.3.0 alongside it, and its FFMPEG_BINARY variable points at the same system ffmpeg the bare ffmpeg run used, not the copy bundled inside imageio-ffmpeg. The two runs share an encoder build rather than two different ones.
MLT's melt CLI is version 7.40.0 against libavformat 63.1.101, driven from a generated XML timeline rather than a Python or JavaScript API. Revideo bundles its own ffmpeg 4.4, separate from and older than the 8.0.1 every other row encoded with, and @revideo/ffmpeg defaults to that bundled binary regardless of what is installed on the host. A script written against one of these version strings and pointed at a different one is not running the same test that produced these numbers.
The code that produced the output
The file behind 17.9 s is quoted below exactly as it ran. It is not paraphrased into pseudo-code, because a rewrite would not be the payload the run recorded.
/**
* BM-1 (TPL-30) as an Editframe composition.
*
* This is the payload, not the runner. tools/editframe/run.mjs drives
* `editframe render` over this directory, times it, and measures the result.
*
* Every layout number is read from ../bm1.json rather than typed here, for the
* same reason the ffmpeg, MoviePy and Remotion payloads read it: four
* transcriptions of one benchmark are only comparable if they cannot drift apart.
*
* The text is composited from the same pre-rasterised PNGs the other payloads use
* (bm1.json -> text_rendering), so no web font is loaded and no text shaping
* happens inside the measured render.
*
* Assets resolve through Vite's publicDir, which tools/editframe/vite.config.mjs
* points at ../_assets — the same directory every other payload reads. A bare
* filename is therefore served at the site root, exactly as Remotion's
* --public-dir=../_assets does.
*/
import '@editframe/elements';
import spec from '../bm1.json';
const V = spec.video;
const PAL = spec.palette;
const sceneById = (id) => spec.scenes.find((s) => s.id === id);
const title = sceneById('title');
const body = sceneById('body');
const outro = sceneById('outro');
/** "_assets/text-title.png" -> "/text-title.png" (served from publicDir). */
const asset = (p) => `/${String(p).replace(/^_assets\//, '')}`;
const seconds = (from, to) => `${to - from}s`;
/**
* An ef-image has no natural duration, so it defaults to 0 and never becomes
* active. Editframe calls this the editorial choice: a still has to be told how
* long it is on screen. Each image is given its own scene's length.
*/
const imageDuration = (scene) => seconds(scene.start_s, scene.end_s);
const FILL = 'position:absolute;left:0;top:0;width:100%;height:100%;';
const SCENE = `${FILL}`;
const rule = title.accent_rule;
const bar = body.bar;
document.getElementById('stage').innerHTML = `
<ef-timegroup
id="root"
mode="contain"
fps="${V.fps}"
style="position:relative;display:block;width:${V.width}px;height:${V.height}px;background:${PAL.background};overflow:hidden"
>
<ef-timegroup mode="sequence" style="${FILL}">
<!-- Scene 1 — title card -->
<ef-timegroup mode="fixed" duration="${seconds(title.start_s, title.end_s)}"
style="${SCENE}background:${title.background};">
<div style="position:absolute;left:${rule.x}px;top:${rule.y}px;width:${rule.w}px;height:${rule.h}px;background:${rule.color};"></div>
<ef-image src="${asset(title.text_png)}" duration="${imageDuration(title)}" style="${FILL}object-fit:contain;"></ef-image>
</ef-timegroup>
<!-- Scene 2 — still image, caption bar, caption -->
<ef-timegroup mode="fixed" duration="${seconds(body.start_s, body.end_s)}" style="${SCENE}">
<ef-image src="${asset(body.image)}" duration="${imageDuration(body)}" style="${FILL}object-fit:${body.image_fit};"></ef-image>
<div style="position:absolute;left:${bar.x}px;top:${bar.y}px;width:${bar.w}px;height:${bar.h}px;background:${bar.color};opacity:${bar.opacity};"></div>
<ef-image src="${asset(body.text_png)}" duration="${imageDuration(body)}" style="${FILL}object-fit:contain;"></ef-image>
</ef-timegroup>
<!-- Scene 3 — outro -->
<ef-timegroup mode="fixed" duration="${seconds(outro.start_s, outro.end_s)}"
style="${SCENE}background:${outro.background};">
<ef-image src="${asset(outro.text_png)}" duration="${imageDuration(outro)}" style="${FILL}object-fit:contain;"></ef-image>
</ef-timegroup>
</ef-timegroup>
<!-- The music bed is a sibling of the sequence, never a child of it: a
sequence would treat it as one more beat and double the runtime. -->
<ef-audio src="${asset(spec.audio.file)}" volume="1" duration="${V.duration_s}s" offset="${spec.audio.start_s}"></ef-audio>
</ef-timegroup>
`;
This script's text layers are each a pre-rasterised PNG from _assets, not a font loaded at render time. Keeping that constraint is what makes this run comparable to the ffmpeg and MoviePy runs, which share the same restriction for a different reason: a build with no libfreetype. The benchmark's caption bar sits at 1920×160, offset to y=820, at an opacity of 0.55, over the still image between the body scene's start and end marks, 5 s and 25 s. The title and outro scenes run from 0 s to 5 s and from 25 s to 30 s.
Nothing in this script issues a network call. It writes to a local output path, and the CLI's own dev server serves the static assets it composites; no account and no cloud render queue sit between the payload and the file it produces. Adapting this for a real pipeline means replacing the four PNG paths and the background-music WAV with production assets, then setting the codec flag along with fps and scale, explicitly on the command line, since this CLI does not read encode settings out of a shared benchmark definition the way the MLT and ffmpeg payloads read crf and preset out of bm1.json.
What the run produced
The eight recorded runs did not measure one thing at eight speeds. They measured eight different spans of work, and lining them up without saying so would misstate what each number is.
Sourcevideobycode verification runs(TPL-30, measured 2026-08-09 and 2026-08-25)
| Tool | Status | Wall time | Output size | Measured duration |
|---|---|---|---|---|
| Diffusion Studio Core | unsupported (watermark) | 6.4 s | 1.16 MB | 30.080 s |
| Editframe | ok | 17.9 s | 1.46 MB | 30.080 s |
| Editly | error (import failed) | 1.2 s | n/a | n/a |
| FFmpeg | ok | 7.5 s | 0.99 MB | 30.000 s |
| MLT (melt) | ok | 10.2 s | 1.02 MB | 30.016 s |
| MoviePy | ok | 86.6 s | 1.01 MB | 30.000 s |
| Remotion | ok | 18.2 s | 1.50 MB | 30.059 s |
| Revideo | ok | 21.8 s | 0.60 MB | 30.000 s |
Sourcevideobycode verification runs, TPL-30 benchmark, measured 2026-08-09 and 2026-08-25
ffmpeg's 7.5 s is the lifetime of a single process running one filter graph through one encode step before it exits. Editframe's 17.9 s, Remotion's 18.2 s, and Revideo's 21.8 s each start a Vite dev server first, then launch a full Chrome or Chromium instance and capture 900 frames through the browser, before encoding even begins. Comparing ffmpeg's number against those three as a ranking of raw renderer speed mixes a bare encode with a browser pipeline. The fairer comparison sits among three single-process tools reading the same PNG assets through a comparable encode step: ffmpeg, MLT's 10.2 s, and MoviePy's 86.6 s.
Output size tells a separate story from wall time. ffmpeg's file lands at 0.99 MB because the benchmark's crf 20 and preset medium settings pass through exactly; MoviePy and MLT, sharing that encoder configuration, land close to the same figure. Editframe, Remotion, and Revideo cannot take a CRF or preset argument at all, only a codec name and, for Revideo, a container format, so each browser encoder settles on its own bitrate. Revideo's file is the smallest of the eight at 0.60 MB. Its run notes record that the AAC track came in under the benchmark's 192 kbps target, because the exporter takes only a format argument and nothing finer; the actual bitrate it chose went uncaptured as a separate metric.
Completed runs land within a fraction of a second of the benchmark's nominal length rather than exactly on it. Editframe and Diffusion Studio Core both measured 30.080 s, and Remotion measured 30.059 s. Only ffmpeg, MoviePy, and Revideo hit the exact 30 s the benchmark specifies. None of these gaps matter for a placeholder clip used to smoke-test a pipeline. A downstream check that asserts an exact frame count against the nominal duration will fail against three of the eight files here, which is worth knowing before wiring one up.
This table's runs each cost $0 to produce, since every tool rendered locally with no consumption pricing attached to the render itself. What a tool costs in production is a different question, and the table below covers that one. Two rows, Editframe and Remotion, carry no self-hosted cost line at all, because both cap free use at three employees rather than charging per render.
| Tool | Licence | Self-hosted cost | Paid plan | Source |
|---|---|---|---|---|
| Diffusion Studio Core | MPL-2.0, free with watermark | not applicable | licence-key price unverified | GitHub README |
| Editframe | proprietary CLI, free ≤3 employees | not applicable | Team $49/mo, Cloud $99/mo | editframe.com/pricing |
| Editly | MIT | $0 | none published | GitHub LICENSE |
| FFmpeg | LGPL / GPL | $0 | none | ffmpeg.org/legal |
| MLT (melt) | LGPL | $0 | none | GitHub COPYING |
| MoviePy | MIT | $0 | none | GitHub |
| Remotion | free ≤3 employees | not applicable | not published | GitHub LICENSE |
| Revideo | MIT | $0 | hosted service unverified | GitHub LICENSE |
Sourcevendor pricing and licence pages; Editframe checked 2026-08-09, Diffusion Studio Core checked 2026-08-25
What breaks, and how to get past it
Two of the eight runs did not return a usable file at all, and three more that reported a clean exit can still produce wrong output under specific conditions the exit code never flags.
Editly never got to a render. Its 0.14.2 release pins two native Node dependencies, node-canvas and headless-gl, and the second loads unconditionally at module load, even for a spec, like this one, that uses no GL transition, because the library's own index.js pulls in a transitions module whose first line imports gl. gl 5.0.3 builds its bundled ANGLE from source and fails to compile against Node 25's V8 headers, with twenty recorded compile errors. Substituting the newest release, 8.1.6, through a pnpm override fails at the same point for the same reason, which rules out a version bump as the fix. The wall time recorded for this run, 1.2 s, measures a failed import, not a render, and does not belong in any speed comparison. Working around this means an older Node runtime that headless-gl still compiles against, or a fork that drops the GL dependency for specs with no transition. Any pipeline that shells out to Editly on Node 25 hits this same failure before a single frame renders, regardless of which spec it points at.
Diffusion Studio Core's watermark makes the free-tier output not the benchmark: the engine renders correctly and stamps every render with a visible mark, removable only through a paid one-time key. Treat any unlicensed render as a proof of concept, not a deliverable.
Diffusion Studio Core rendered the TPL-30 payload with no account and no network call during the run, and no process contacted a licence server; the watermark is what disqualifies the file anyway. The vendor's own README states the free terms directly: use is free at any volume as long as the "Made with Diffusion Studio" mark stays on the output. That is why this row is recorded as unsupported rather than ok, even though it finished in 6.4 s and produced a 1.16 MB file that otherwise plays back correctly. Teams testing against this tool need to budget for the licence key before treating any free-tier output as representative of production. That key's own price could not be checked past 2026-08-25, when the vendor's pricing URL redirected to its site root with no pricing text on the page. A comparison that reports this tool's wall time next to a licensed one's without flagging the watermark is comparing a proof of concept to a shippable file.
Three tools with an ok status carry a bug each that a status check alone will not catch. An Editframe ef-image element with no explicit duration attribute defaults to zero, so the element never draws, yet the CLI still reports all 900 frames captured and exits clean; the result is a black picture for however long that image was meant to appear. The fix is mechanical: assert that every ef-image in a generated payload carries an explicit duration before the render starts, rather than trusting the frame count. A duration-based sanity check on the finished file catches nothing here either, since the frame count still matches the spec.
MLT has no primitive for a filled rectangle, so both rectangles TPL-30 asks for, the accent rule and the caption bar, become full-frame colour producers cropped by a qtblend transition. qtblend preserves the source aspect ratio unless distort=1 is set; without that flag the accent rule renders as a small dash, and the caption bar renders at a fraction of its intended size, with no warning and a successful exit either way. A pipeline that trusts melt's zero exit code as a pass ships both shapes wrong with no log line to catch it.
Revideo resolves every audio asset's path in Node as outDir/../public/ joined with the element's src value, ignoring Vite's publicDir setting entirely. Point publicDir anywhere else and ffprobe fails to find the file; the exporter then substitutes a generated silent track, and the render still reports success with a full-length AAC stream containing nothing, a failure no ffprobe check catches after the fact. A CI job that only checks for a non-empty output file passes this render even though the soundtrack is silence.
Start from the ffmpeg payload, not a browser-based renderer. It is the only tool measured here with full control over crf and preset, plus pixel format, and it is the fastest of the three single-process encoders. Copy payloads/tpl30/ffmpeg.sh and point the four PNG paths and the WAV file at local assets, then run it once. The first thing to break is text: without libfreetype in the local ffmpeg build, drawtext does not exist, and the fix used throughout this comparison is pre-rasterising each string into a transparent PNG before the filter graph runs.
What we didn't test
TPL-30 is the only benchmark in these records, and every run above answers one question, does a render pipeline finish and produce a file, not the questions below.
- Only TPL-30 ran; no other benchmark definition exists in the records, so nothing here speaks to longer clips or different scene counts.
- All eight runs used h264; none tried a different codec.
- Each tool rendered once on one machine; batch throughput, concurrency, and queueing went unmeasured.
- No run burned captions into video.
- Extracting a thumbnail from a finished file wasn't tested either.
Questions people ask
How long did the TPL-30 render actually take, and on what machine?
Wall time depends on the tool: the Editframe CLI's local render finished in 17.9 s and the bare ffmpeg run finished in 7.5 s, both on the same machine used for every run in this comparison. Browser-based tools measure the whole invocation, including a Vite server start and a Chrome launch, not just the encode, so their numbers are not directly comparable to ffmpeg's single-process time.
Why are Diffusion Studio Core's prices shown as unverified?
Diffusion Studio Core's pricing page redirected to the site root with no pricing text when checked on 2026-08-25, so the one-time licence-key price that removes its watermark could not be read from the vendor's own page. No figure is asserted anywhere in this comparison because none could be sourced.
What does Diffusion Studio Core's free tier actually cover?
The free tier covers rendering at any volume with no account and no network call, and the run recorded here finished in 6.4 s with no licence key involved. It stops at the output file itself: every free render carries a visible watermark that only a paid one-time key removes.
References
Ref Editframe pricing (checked 2026-08-09)
Ref Diffusion Studio Core README (checked 2026-08-25)
Ref Editly LICENSE (checked 2026-08-25)
Ref FFmpeg legal (checked 2026-08-09)
Ref MLT Framework COPYING (checked 2026-08-25)
Ref MoviePy repository (checked 2026-08-09)
Ref Remotion LICENSE (checked 2026-08-09)
Ref Revideo LICENSE (checked 2026-08-25)
Related articles
Automating Video Editing With Python: MoviePy and FFmpeg
FFmpeg and MoviePy render the same 1920×1080 TPL-30 template; the code, exact versions, and measured render time and output size are given below.
How to Create Video With Code: A Working Pipeline End to End
Editframe, FFmpeg, MoviePy and Remotion render the same TPL-30 template; render time, output size and cost are compared with linked evidence, not vendor claims.
FFmpeg as a Video API: Encoding, Batch Jobs and Audio
FFmpeg's TPL-30 benchmark shows the exact script, versions and encode settings behind a scripted H.264 render, plus where drawtext and concat filters break.
Open-Source Video Editing APIs and SDKs, Installed and Tested
The test installed eight video editing APIs and SDKs, seven of them open source, and rendered each against one fixed benchmark, logging wall time and file size.
Generating Video From JSON: Schema, Payload and Render
Eight rendering tools ran the same JSON scene payload; this compares what rendered, what failed, and what each tool costs to run in production.
Generating Video in JavaScript: React Editor SDKs and npm
Editframe, Remotion, and Revideo rendered the same Node.js video benchmark; Diffusion Studio Core watermarked its output and Editly failed to import.