Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Rightsize

Batch-optimize a folder of mixed footage into edit-ready, server-friendly HD MP4s.

Point it at 400 clips of assorted resolutions, ratios, codecs and frame rates. Get back one consistent set that scrubs properly in DaVinci Resolve and Premiere, previews in a browser, and is small enough to live on a shared drive.

Built at Starting Frame to put a 181 GB stock-footage library onto a 1 TB shared drive. It came out at 10.5 GB — 94% smaller — across 889 files, with no visible quality loss on the footage that mattered.


The rule

Never upscale. Fit the long edge inside 1920 and the short edge inside 1080 — whichever runs out first.

That second half is the part other tools get wrong.

Source Output
3840×2160 1920×1080 UHD, the easy case
4096×2160 1920×1012 DCI 4K — not 1080, and the free dimension is forced even
4096×3072 1440×1080 4:3 — the short edge binds first
5760×2160 1920×720 ultrawide
1080×1920 untouched vertical HD — a plain 1920×1080 box crushes this to 608×1080
2160×3840 1080×1920 vertical 4K — the box turns with the footage
1920×1080 untouched never upscales, never re-encodes for nothing

--cap 2160 | 1440 | 1080 | 720 | 540 moves the box. The rule is the same at every size.


Rules live in a file, not in the code

rules.json decides what happens to each file. Read top to bottom, first match wins. No AI, no network, no API key — just if <measured facts> then <action>.

{
  "name": "already a lean HD MP4",
  "when": { "ext": ["mp4"], "codec": ["h264"],
            "long_edge_lte": 1920, "short_edge_lte": 1080, "bitrate_mbps_lte": 14 },
  "do": "ignore",
  "why": "already the deliverable — don't spend a re-encode on it"
}

Conditions: alpha · hdr · ext · codec · long_edge_lte/gt · short_edge_lte/gt · bitrate_mbps_lte/gt · size_gb_lte/gt · duration_s_lte/gt · path_contains

Actions: ignore · compress · copy (repackage, no re-encode) · keep-alpha (ProRes 4444 .mov)

Per-rule encode overrides: quality · crf · maxrate_mbps · flat_areas · aq_strength · grain

A malformed rules file is rejected at startup, naming the offending rule and reason.

Different content, different settings

Live action and motion graphics fail differently — a gradient bands at 8-bit long before a face does.

{
  "name": "motion graphics / gradients",
  "when": { "path_contains": ["Backgrounds", "Textures", "Elements"] },
  "do": "compress",
  "quality": "high", "maxrate_mbps": 24,
  "flat_areas": true, "aq_strength": 1.3, "grain": 2.5
}

Measured on a midtone gradient encoded both ways: the longest run of identical pixels along a row went 794 px → 23 px. The grain does most of that work — it gives the encoder something to dither against.


Why the output actually plays

Small and smooth aren't in tension at 1080p. What breaks NLE playback is the encode parameters, not the codec.

Setting Value Why
GOP closed, keyframe every 1 s Scrubbing needs anchors. Open GOP is the biggest cause of sluggish seeking.
Rate control capped CRF (CRF 20 + a ceiling) Quality-driven, but a noisy clip can't blow up the drive.
Pixel format yuv420p 8-bit 10-bit 4:2:2 is what makes an NLE choke on camera footage.
B-frames 2, no pyramid Less reordering to unwind on a seek.
Colour Rec.709 written explicitly An untagged file makes Resolve guess.
Range limited (tv), converted not relabelled Mixed full/limited range in one library means blacks jump between clips.
Frame rate left alone Mixed 24/25/30/60 is fine. CFR is forced only for genuinely variable sources.
Audio AAC 192k, surround downmixed A batch tool that mangles audio is dead on arrival.
Container MP4 + faststart Streams without downloading the whole file.

Codec choice does more than shrink things: H.264 MP4 previews natively in most browsers, SharePoint and Teams. ProRes and MJPEG .mov don't. In the library above, 115 files couldn't be previewed at all before conversion.


What it won't touch

  • Real transparency. H.264/MP4 can't carry alpha. But an alpha-capable pixel format isn't proof of transparency — the alpha plane is sampled, and a fully opaque one converts as normal video. Set the rule to keep-alpha to downscale as ProRes 4444 .mov instead.
  • HDR / wide gamut, unless your ffmpeg has zimg or libplacebo. Without a correct tonemap, a naive convert comes out washed-out grey. Better to skip and say so.
  • Anything already within spec. A copy rule repackages to MP4 with -c copy — verified pixel-identical to the source.

Install

Needs Python 3.9+ and ffmpeg / ffprobe on your PATH.

brew install ffmpeg          # macOS
git clone https://github.com/startingframe/rightsize.git

Use

# see the plan first — encodes nothing, writes an HTML table of every decision
python3 rightsize.py ~/Footage -o ~/Footage-HD --dry-run

# do it
python3 rightsize.py ~/Footage -o ~/Footage-HD

python3 rightsize.py ~/Footage -o ~/out --cap 720 --quality small
python3 rightsize.py ~/Footage -o ~/out --mirror        # keep the folder tree
python3 rightsize.py ~/Footage -o ~/out --hw            # Apple VideoToolbox, much faster
python3 rightsize.py ~/Footage -o ~/out --rules my.json

Every run writes a dated HTML report showing what happened to each file, which rule fired, and why anything was skipped.

The other three

verify.py check the output against the source — box, dimensions, pixel format, colour tags, closed GOP, keyframe spacing, faststart, duration, and exact frame counts on a decoded sample
organize.py sort a flat output back into the source folder structure; --dedupe sets aside the same clip delivered twice
recover.py if you change the rules or the code mid-run, list exactly which files need another pass — pipe it straight back into rightsize.py --overwrite

Notes from the build

Things that cost real time, in case they save you some:

  • ffmpeg 8.1 drops -color_primaries / -color_trc through a filter chain. Only the matrix and range survive as output options. The tags have to come from a setparams filter.
  • setparams must run before anything that touches pixels. Some ProRes files carry frame-level primaries of reserved, an invalid enum that swscale refuses to convert — the encode dies with "Nothing was written into output file." Both scale and format are swscale, so putting setparams after either one only fixes half your files. ffprobe doesn't expose this at stream level, so it can't be detected up front.
  • An alpha-capable pixel format isn't proof of alpha. Sample the plane.
  • Duplicate filenames across folders will silently eat footage in any flat output. Three camera cards all hold C0001.MP4.
  • Never reconstruct identity from a formatted string when you still have the original. A dedupe keyed on stripping a trailing -<digits> collapsed every Clip-676847382 to Clip — because the names end in digits.

MIT licensed. rightsize shells out to ffmpeg rather than linking it, so it isn't a derivative work.

Made by Starting Frame — production tools for people making media.

About

Batch-optimize mixed footage into edit-ready, server-friendly HD MP4s. Never upscales; fits an orientation-aware box so vertical clips aren't crushed. Rules live in a JSON file, not the code.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages