render: add --height and --crf; resolution-independent per-segment zoom - #158
Open
shoaib90 wants to merge 2 commits into
Open
render: add --height and --crf; resolution-independent per-segment zoom#158shoaib90 wants to merge 2 commits into
shoaib90 wants to merge 2 commits into
Conversation
The renderer encodes video twice — once per segment on extract, then
again to composite overlays and burn in subtitles. Both CRFs were
hardcoded and the scale was an unconditional `scale=1920:-2`, so there
was no way to deliver a 4K source at anything but 1080p, and no way to
raise the extract CRF that sets the quality ceiling.
Symptom: a 4K talking-head render looked visibly soft. Bits-per-pixel
turned out to be identical between source and output (0.253 vs 0.255) —
the loss was discarded pixels plus a second generation, not bitrate.
--height output height (default 1080, 720 with --draft); width follows
the source aspect
--crf CRF for the extract, i.e. the quality ceiling (default 16
final / 22 preview / 28 draft)
The composite encode is now derived as (crf - 2) instead of a hardcoded
18, so the second generation adds minimal further loss, and final
defaults to slow/16 rather than fast/20. Re-rendering one edit at
--height 2160 --crf 16 gave 0.451 bits/px with no downscale generation.
Also adds two EDL fields:
ranges[].zoom (+ zoom_x) per-segment push-in, to disguise jump cuts on
a static single-camera shot. Deliberately a number, not a filter
string: a hardcoded crop is only correct at one output height, and any
per-segment dimension mismatch breaks the -c copy concat. Relative
ffmpeg expressions do not fix this either — rounding to even
dimensions stops crop round-tripping to the exact original size
(1920 comes back as 1918). probe_scaled_dims() mirrors the scale
expression instead, so the crop is computed from the real post-scale
dimensions. Verified with 13 mixed 1.00x/1.06x/1.12x segments all
landing at exactly 3840x2160.
audio_filter a global audio chain applied per segment BEFORE the 30ms
fades, so the fades stay on the true segment edges (Hard Rule 3).
Intended for denoise/EQ on noisy location audio.
display_dims() factors out the rotation-aware probe that
is_portrait_source() was already doing inline.
shoaib90
pushed a commit
to shoaib90/video-use
that referenced
this pull request
Sep 7, 2026
Forked to shoaib90/video-use and opened three focused upstream PRs (browser-use#158 quality controls, browser-use#159 configurable subtitles, browser-use#160 Deepgram). Records the branch layout and, more usefully, why each PR branch was built by replaying changes onto clean main rather than cherry-picking out of local: local's commits bundle render.py work with kb/ updates, and two separate concerns touch build_final_composite and main(), so hunk-level splitting would have produced fragile branches. Also notes that transcribe_whisper.py is deliberately held back, since SKILL.md's anti-patterns name local Whisper explicitly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
All reported issues were addressed across 2 files
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
Four review findings, all valid. 1. The zoom path predicted ffmpeg's `scale=-2` width in Python and then hardcoded it as the scale-back target. I tested the arithmetic against ffmpeg across several non-16:9 aspects and it agreed in every valid case, but the concern holds for a reason the test could not reach: `-2` also honours sample aspect ratio, which arithmetic over coded dimensions ignores entirely, so an anamorphic source would diverge. Rather than reimplement ffmpeg's rounding, probe_scaled_dims() now *measures* it — one frame through the real scale filter, cached per (source, height) — and that measured value is used as an EXPLICIT scale target for every segment. Zoomed and unzoomed siblings therefore match by construction rather than by agreement, which also removes the subtler hazard: a zoomed segment has a cropped input aspect, so `-2` could legitimately hand it a width 2px off its siblings. Falls back to the previous arithmetic with a warning if the probe fails; correct for square-pixel sources, which is everything a camera produces. 2. --height accepted odd and non-positive values, surfacing as an ffmpeg failure partway through extraction. Now an argparse type that requires a positive even height (yuv420p needs even), failing before any work starts. 3. --crf 16.5 is valid for x264 but main() did int(args.crf) and raised ValueError after extraction had already run. CRF is now parsed as a float, range-checked to x264's 0-51, and the composite CRF derived numerically. Verified end to end: --crf 16.5 completes a full render. 4. SKILL.md's "Output spec" still told agents to pass --filter or hand-edit the extract command for non-1080p targets, contradicting the new --height guidance. Updated to point at --height. Also sorts the import block, which I had appended to out of order. Verified: 3 segments at mixed zoom 1.0/1.08/1.15 all land at identical dimensions; tests pass.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
render.pyencodes the video twice — once per segment inextract_segment, then again inbuild_final_compositeto composite overlays and burn in subtitles. The concat and the loudness pass are both-c copy, so those are lossless.Both CRFs were hardcoded (extract 20/22/28, composite a literal
18), and the scale was an unconditionalscale=1920:-2. So there was no way to deliver a 4K source at anything above 1080p, and no way to raise the extract CRF — which, being the first generation, is the quality ceiling the composite can only add loss on top of.I hit this on a 4K talking-head edit that came out visibly soft. The diagnostic that isolated it was bits per pixel, not bitrate:
Per-pixel quality was already matched — the loss was discarded pixels plus a second generation, not bitrate starvation. No flag could fix that.
Changes
--height— output height, default 1080 (720 with--draft). Width follows the source aspect.--height 2160delivers at the source resolution and skips the downscale generation entirely. Re-rendering the same EDL that way gave 0.451 bits/px.--crf— CRF for the extract, i.e. the ceiling. Defaults 16 final / 22 preview / 28 draft. The composite encode is now derived ascrf - 2rather than a hardcoded 18, so the second generation adds minimal further loss. Final also moves fromfast/20 toslow/16.ranges[].zoom(pluszoom_x, a 0–1 horizontal crop bias) — an optional per-segment push-in, for disguising jump cuts on a static single-camera shot.It is deliberately a number, not a filter string. A hardcoded
crop=1812:1018,scale=1920:1080is only correct at one output height, and any per-segment dimension mismatch breaks the-c copyconcat (Hard Rule 2). Relative ffmpeg expressions do not rescue it either: rounding to even dimensions stopscropround-tripping to the exact original size — 1920 comes back as 1918, while segments with no filter stay 1920, and the concat fails. Insteadprobe_scaled_dims()mirrors the scale expression, so the crop is computed from the real post-scale dimensions. Verified with 13 mixed 1.00×/1.06×/1.12× segments all landing at exactly 3840×2160.audio_filter— an optional global audio chain applied per segment before the 30ms fades, so the fades stay on the true segment edges (Hard Rule 3). Intended for denoise/EQ on noisy location audio; I needed it for car-interior road noise.display_dims()factors out the rotation-aware probe thatis_portrait_source()was already doing inline.Compatibility
All flags and EDL fields are optional. The only behaviour change without them is the final-mode default moving to
slow/CRF 16 — better output, slower encode. Happy to leave the oldfast/20 default and make the improvement strictly opt-in if you'd rather.tests/passes (16 tests, 15 subtests), including the existing fps and orientation suites.Note
Touches
build_final_composite's signature andmain(), so it overlaps slightly with #(the subtitle PR) if both land — whichever merges second needs a trivial rebase.Summary by cubic
Adds
--heightand--crftorender.pyplus per-segmentzoomand globalaudio_filterEDL fields. Previously every render was downscaled to 1080p with hardcoded CRFs; now 4K sources can be delivered at native resolution and the extract CRF, the quality ceiling, is configurable.New Features
--heightsets output height; default stays 1080 (720 with--draft) and width follows source aspect. It must be a positive even number since yuv420p requires it.--crfsets the extract CRF (defaults 16 final, 22 preview, 28 draft; fractional values like 16.5 are valid); the composite CRF is derived ascrf - 2instead of hardcoded 18.ranges[].zoomandzoom_xadd an optional per-segment push-in expressed as a number, not a filter string, so one EDL stays correct at any output height. The crop is computed from the source's measured post-scale dimensions, so zoomed and unzoomed segments always match and the lossless concat is preserved.audio_filterapplies a global audio chain per segment before the 30ms fades, keeping fades on true segment edges.Compatibility
slow/CRF 16 instead offast/20, which means better output but slower encodes.Written for commit 764f007. Summary will update on new commits.