Batch Green Screen Remover for Video: Queue Multiple MP4 Clips

A tested local queue for keying many clips to transparent WebM — measured timings, a resumable log, a per-file alpha check, and the one case where a single tolerance breaks the whole batch.

Short answer: put every MP4 in one folder, key each to an alpha WebM with one FFmpeg command, and run a small worker pool over the list. Skip files whose output already exists, write one log line per file, and check alpha_mode=1 on every result. Batch is a queue with naming, resume and per-file verification — not a wider tolerance slider.

What a batch job has to settle

A single clip is a filter question. Twenty clips is a bookkeeping question: which file failed, which one already succeeded, which output belongs to which input, and whether the transparent background is actually in the file. Get those four wrong and the keying maths can be perfect while the batch is still unusable.

This page is not a browser uploader and does not pretend to be one. The keyer on this site handles one still image at a time; no browser tab here decodes an MP4. What follows runs locally with FFmpeg, which is also why it can be checked line by line.

One command per clipSame filter and encoder settings for every file, so a bad result is a data problem rather than a different recipe.
One log line per fileOK, SKIP or FAIL plus the raw FFmpeg error, so a night's run can be read in the morning.
Resume by outputAn existing .webm is skipped. A crash costs the current clip, not the queue.
Verify the alphaalpha_mode=1 plus a decoded alpha frame; a file can exist and still have no transparency.

The command that runs once per clip

This is the tested command. It keys the green, converts to a pixel format with alpha, and encodes VP9 with the alpha plane switched on:

ffmpeg -y -v error -i input.mp4 ^
  -vf "chromakey=0x00b140:0.08:0.06,format=yuva420p" ^
  -c:v libvpx-vp9 -crf 32 -b:v 0 -cpu-used 5 -row-mt 1 ^
  output.webm

0x00b140 is the key colour, 0.08 is similarity and 0.06 is blend. Those are starting values, not a universal preset: the whole point of the next section is that this exact triplet behaves differently on a second clip. On Windows, ^ continues the line in cmd.exe; in PowerShell use a backtick or keep it on one line.

If your clips are 10-bit or have a different backdrop, sample the actual colour instead of pasting #00b140. The batch then needs one extra column — key colour per clip — or a pre-pass that writes those values into the queue.

The queue script (tested on PowerShell 5.1)

Save this as batch-key.ps1 beside two folders, in and out. It starts at most $Workers FFmpeg processes, skips any clip whose WebM already exists, logs OK, SKIP or FAIL, and checks alpha_mode=1 before calling a file OK. Run it with powershell -NoProfile -File .\batch-key.ps1 -Workers 2.

param([int]$Workers = 2, [string]$InDir = "in", [string]$OutDir = "out")

$ErrorActionPreference = "Continue"
New-Item -ItemType Directory -Force -Path $OutDir | Out-Null
$log = Join-Path $OutDir "batch-log.tsv"
$running = @()

function Finish($job) {
  $ok = Test-Path $job.Out
  if ($ok) {
    $tag = (& ffprobe -v error -select_streams v:0 `
      -show_entries stream_tags=alpha_mode -of default=nw=1 $job.Out) -join " "
    if ($tag -notmatch "alpha_mode=1") { $ok = $false }
  }
  if ($ok) {
    "$(Get-Date -Format s)`tOK`t$($job.Name)`t$($job.Out)" | Add-Content $log
  } else {
    $why = if (Test-Path $job.Err) { [string](Get-Content $job.Err -Raw) } else { "no output" }
    "$(Get-Date -Format s)`tFAIL`t$($job.Name)`t$($why.Trim())" | Add-Content $log
  }
  $job.Done = $true
}

foreach ($f in (Get-ChildItem $InDir -Filter *.mp4 | Sort-Object Name)) {
  $out = Join-Path $OutDir ($f.BaseName + ".webm")
  if (Test-Path $out) {
    "$(Get-Date -Format s)`tSKIP`t$($f.Name)`t$out" | Add-Content $log
    continue
  }
  while (@($running | Where-Object { -not $_.Done }).Count -ge $Workers) {
    Start-Sleep -Milliseconds 150
    foreach ($j in @($running | Where-Object { $_.Proc.HasExited -and -not $_.Done })) { Finish $j }
  }
  $err = Join-Path $OutDir ($f.BaseName + ".err.txt")
  $args = @("-y","-v","error","-i",$f.FullName,
    "-vf","chromakey=0x00b140:0.08:0.06,format=yuva420p",
    "-c:v","libvpx-vp9","-crf","32","-b:v","0","-cpu-used","5","-row-mt","1",$out)
  $p = Start-Process ffmpeg -ArgumentList $args -NoNewWindow -PassThru -RedirectStandardError $err
  $running += [pscustomobject]@{ Name=$f.Name; Out=$out; Err=$err; Proc=$p; Done=$false }
}

while (@($running | Where-Object { -not $_.Done }).Count -gt 0) {
  Start-Sleep -Milliseconds 150
  foreach ($j in @($running | Where-Object { $_.Proc.HasExited -and -not $_.Done })) { Finish $j }
}

The script deliberately does not trust FFmpeg's exit code alone. In this PowerShell version a redirected child process can return an empty ExitCode, so the output file and the alpha tag are the evidence. That behaviour was observed during the test, not assumed.

Measured: four clips on a 2-vCPU machine

Test date 2026-10-23. Machine: AMD EPYC 9754 with 2 logical CPUs, FFmpeg 9.0.2, libvpx-vp9 at CRF 32, -cpu-used 5, -row-mt 1. Four synthetic MP4s with a solid green backdrop and a moving subject were keyed at similarity 0.08, blend 0.06. Time is the whole command: decode, key, encode.

ClipFramesKey + encodeOutputBackdrop alpha after key
A — 1280×720, 2 s603.81 s10.7 KB0
B — 1920×1080, 2 s608.47 s15.8 KB0
C — 1280×720, 3 s, darker green905.75 s16.1 KB12.3 — not keyed
D — 3840×2160, 1 s3017.71 s11.9 KB0

Backdrop alpha is the mean of a 20×20 pixel corner patch decoded from the alpha plane with libvpx-vp9, on a 0–255 scale. Zero means the corner is fully transparent. Clip C is the useful failure: the file exists, the command succeeded, and the background is still about 5% opaque.

What the queue overhead actually costs

RunFilesWall clock
Sum of the four individual encodes4 good35.74 s
Queue, one worker4 good + 1 corrupt36.46 s
Queue, two workers4 good + 1 corrupt35.60 s
Second run, all outputs present50.98 s

Two workers bought 0.86 s, about 2.4%, on a two-core box. That is not a reason to add workers — libvpx with row-mt already keeps both cores busy, so a second FFmpeg mostly waits. Set workers to the physical core count and measure; on a 2-core machine, 1 is almost as fast and uses half the memory. The resume run is the real win: under a second to confirm five files, because nothing was re-encoded.

The clip that proves one tolerance cannot cover a batch

Clip C was shot against a darker green, #00843d, while the batch keyed everything at #00b140. Same command, same numbers, different residue:

ClipSimilarityBackdrop alphaA real green object in frame
C — darker backdrop0.0812.3 (residue)—
C — darker backdrop0.120—
A — standard backdrop0.080251 (survives)
A — standard backdrop0.1400 (deleted)

So the obvious batch fix — raise similarity for everyone until C behaves — breaks A. At 0.14 the green box that belongs to the scene is keyed out along with the backdrop, because the key cannot tell a green prop from a green wall. The workable rule is one key per clip: sample each clip's backdrop, keep the colour and similarity in the job list, and let verification catch the clips where the sample drifted. If you must use one setting, accept that mixed footage will need a manual exception list, and do not call the batch clean until every corner reads 0.

This is also why a batch tool that asks you to set one tolerance for every file is a warning sign, not a feature. It is fine for a set shot under one light. It fails exactly when the footage is heterogeneous — which is when you wanted a batch in the first place.

Naming, resume and failure isolation

The script names the output from the input's base name. That is readable, and it has one sharp edge: day1/shot.mp4 and day2/shot.mp4 both become shot.webm. The second silently overwrites the first. If your input tree has folders, flatten with the relative path in the name — for example day1__shot.webm — or put one batch per folder and keep the output folder beside it.

Failure isolation was tested with a deliberately truncated MP4. It failed with moov atom not found, the other clips still finished, and the log kept the raw FFmpeg text. That matters more than the failure itself: a queue that stops at the first bad file turns one corrupt clip into a lost night.

To retry only what failed, read the log rather than the folder:

Select-String -Path out\batch-log.tsv -Pattern "`tFAIL`t" |
  ForEach-Object { ($_ -split "`t")[2] }

Move those files to a retry folder, point -InDir at it, and run again. Successful outputs are skipped, so a retry cannot duplicate work.

Verify the alpha, not just the file

Three checks, cheapest first:

  1. The container says alpha. ffprobe -v error -select_streams v:0 -show_entries stream_tags=alpha_mode -of default=nw=1 out.webm should print TAG:alpha_mode=1. A pix_fmt of yuv420p is normal for VP9 alpha and does not mean the alpha is missing.
  2. The alpha plane decodes. ffmpeg -c:v libvpx-vp9 -i out.webm -vf "alphaextract,format=gray" -frames:v 1 alpha.pgm. FFmpeg's native VP9 decoder cannot extract the plane; -c:v libvpx-vp9 can.
  3. The numbers are right. In that PGM, a backdrop corner should read 0 and the subject's centre 255. Clip C's corner read 12.3 on average — visible as a faint sheet over any new background.

Doing this per file is what makes the batch trustworthy. A folder full of .webm files proves only that FFmpeg wrote bytes.

When a batch is the wrong tool

Batch green screen remover FAQ

Can you remove a green screen from a video in a browser, or only from photos?

This site's browser tool removes green from one still image and exports a PNG; it cannot open an MP4. Nothing in a normal browser tab decodes an arbitrary video, keys every frame and writes a transparent container without either uploading the file or shipping a large WASM decoder. The batch recipe on this page is local FFmpeg for that reason. If a page offers “batch video in your browser” with no upload, check what it actually returns — a preview, a zip of frames, or a file that still has a black background.

Why did one clip keep a green background while the rest keyed cleanly?

Because the key colour and similarity were tuned on a different backdrop. In the measured run, a darker green (#00843d) keyed at #00b140 with 0.08 left a mean corner alpha of 12.3 out of 255 — the file looked finished but the background was still slightly opaque. Raising similarity to 0.12 cleared it. Raising the whole batch to 0.14 also cleared it, but deleted a real green object in another clip (alpha 251 to 0). Fix the clip, not the batch.

Do more workers make a batch of videos faster?

Only until the encoder already saturates the CPU. On the 2-vCPU test machine, two workers finished the same five files in 35.60 s versus 36.46 s with one — 2.4% faster. libvpx's row-mt was already using both cores, so the second process competed rather than added throughput. Start at the physical core count, measure one run, and add workers only if CPU sits below 90%.

How do I re-run only the failed clips?

Filter the TSV log for FAIL, copy those input names into a retry folder, and run the same script with -InDir retry. Existing outputs are skipped by name, so successful clips are not encoded twice. Keep the log: it is the only record of which failure was a corrupt source and which was a bad key setting.

Does the output WebM really have transparency?

Check TAG:alpha_mode=1 in ffprobe, then decode one frame with -c:v libvpx-vp9 and alphaextract. A WebM whose alpha was dropped still plays, usually on black, so playback alone proves nothing. In the tested files the backdrop corner decoded to 0 and the subject to 255; the one bad clip decoded to 12.3 at the corner, which is exactly the residue the eye sees as a green veil.

Next step

Key one clip first and inspect its alpha before queueing the rest — the single-clip workflow, including the container choices and the five-value similarity test, is in green screen remover for video. If the batch result still has a green rim on skin or hair, that is spill, and the despill page explains why another tolerance pass will not fix it. To key a single still frame in the browser, use the green screen remover.