DEV Community

hao jia
hao jia

Posted on

A small ffprobe check for which videos not to re-encode, and what it printed on five public clips

Before I let a folder of mixed uploads go through a batch re-encode, I want to know which files will come out bigger. In a quick run I did with HandBrakeCLI and its Fast 1080p30 preset, a 2,030,217-byte clip became 5,853,449 bytes, and a 22,264,465-byte clip only dropped to 21,772,446. So I wrote a small check that reads a file with ffprobe and says skip or encode, and ran it on five public clips whose numbers anyone can reproduce.

The clips are five WebM files (VP8 and VP9) from Wikimedia Commons, not phone footage and not H.264 originals. Everything below is one run on an Apple M4 with ffprobe 9.0.2 and belongs to these five files only. Sizes are plain bytes.

The script

The check uses bits per pixel per frame, bpp from here on: video bitrate divided by width times height times frame rate. Absolute bitrate does not work as a threshold, because 1080p and 720p files at the same Mbps sit at very different bpp. The threshold is 0.10, and it comes from an earlier matrix, not from these five clips: 36 files made from four kinds of content, each pre-encoded at nine CRF values and then re-encoded once at CRF 23. Of the 24 files below 0.08 bpp, all 24 came out bigger, between 1.34x and 6.67x. At a cut of 0.10, 26 files fall under it; 24 of those grew, the other two saved only 4% and 9%, and none that would have saved 10% or more was skipped. Cutting at 0.08 skips the same 24 files, and 0.10 only adds those two small savers. I went with 0.10, which is a judgment call and not a derived constant.

import json, subprocess, sys
for p in sys.argv[1:]:
    cmd = ["ffprobe", "-select_streams", "v:0", "-show_entries",
           "stream=width,height,r_frame_rate,bit_rate:format=duration,size", "-of", "json", p]
    j = json.loads(subprocess.check_output(cmd, stderr=subprocess.DEVNULL))
    s, f = j["streams"][0], j["format"]
    n, d = map(int, s["r_frame_rate"].split("/"))
    br = s.get("bit_rate", "N/A")
    if br == "N/A": br = int(f["size"]) * 8 / float(f["duration"])  # WebM: no stream bitrate
    bpp = float(br) / (s["width"] * s["height"] * n / d)
    print("SKIP  " if bpp < 0.10 else "ENCODE", f"{p:18s} bpp={bpp:.4f}")
Enter fullscreen mode Exit fullscreen mode

Two things about how it runs. It only asks ffprobe for header information, so it can run over a whole folder before the queue starts, and nothing leaves the machine, which I care about when the files are internal recordings. And it never touches the files. What to do with the SKIP entries is a separate decision; mine is to copy them through unchanged.

What it printed

SKIP   s1-moose.webm      bpp=0.0332
ENCODE s2-disabled.webm   bpp=0.2880
ENCODE s3-amsterdam.webm  bpp=0.2431
ENCODE s4-ethiopian.webm  bpp=0.1153
ENCODE s5-zabadani.webm   bpp=0.3222
Enter fullscreen mode Exit fullscreen mode

On these WebM files the video stream has no bit rate of its own (ffprobe's default output prints bit_rate=N/A, and the JSON output simply omits the field), so for all five the script fell back to file size times 8 divided by duration. That number includes the audio track, which I come back to below.

Did the verdicts hold

To check, I re-encoded each clip once with ffmpeg -c:v libx264 -crf 23 -preset medium -an and compared sizes. Audio was dropped, so the ratios are video only.

Clip Script verdict Re-encoded size ÷ original video
s1 moose, 1080p SKIP 2.805
s2, 720p ENCODE 0.348
s3, 720p ENCODE 0.560
s4, 1080p ENCODE 0.831
s5, 360×240 ENCODE 0.664

The one flagged clip grew to 2.805 times its size. The other four shrank, by 16.9% to 65.2%. On these five the check was right every time, which is less impressive than it sounds: only one clip sat below the threshold, so the skip side was tested once.

Where the number is soft

The audio fallback overstates bpp. Using the video-only bitrate, the same five clips come out at 0.0317, 0.2793, 0.2391, 0.1134 and 0.2635. The fallback was higher by 1.7% to 4.7% on four clips and by 22.3% on s5, where the audio is a large share of a small file. No verdict flipped here, but a clip at 0.11 with a heavy audio track could land on the wrong side. s4 is the closest call at 0.1153, and it saved 16.9%.

The threshold also belongs to the target setting. It was tuned for x264 CRF 23, preset medium, H.264 to H.264. In the same matrix, a CRF 28 target pushed the break-even point to about 0.06. I read that as the threshold roughly tracking the bpp the target would produce anyway, but I did not test that explanation on its own. Other presets, H.265 and AV1 are untested.

Quality is the other blind spot. In that matrix a clip that grew 3.76x still scored an SSIM of 0.9180 against the master, down only from 0.9205, so a quality check after the encode would pass it.

Running it on your own files

Run it on a copy of your folder and compare against a real re-encode on a few files before trusting 0.10. If you would rather not write anything for a one-off batch, ImgIng https://imging.ai/ works in a desktop browser. Its video compression takes several files at once and lets you pick the quality tier. On these same five clips, its default smart tier kept four of them as originals, which is a different list from the script's single SKIP, so I treat the two as different tools with different settings and do not rank them.

Sample credits

Sample videos are from Wikimedia Commons, re-encoded and cut, so changed from the originals. s1: Moose by the road.webm, Forza1000, CC BY-SA 4.0. s2: A Demonstration of Disabled People June 2018 1.webm, DGW, CC BY-SA 3.0. s3: Worldwide Demonstration – March 2021 Amsterdam.webm, Karen Eliot, CC BY-SA 2.0. s4: Demonstration of Ethiopian residents in Israel, July 2019.webm, Israel Police, CC BY-SA 3.0. s5: Demonstration in Al-Zabadani, 23 March 2012.webm, Allfor Syria, CC BY 3.0.

Top comments (0)