Skip to content

fix(producer): export sizes an unsized phone video as it plays, not as stored - #7228

Merged
miguel-heygen merged 2 commits into
mainfrom
fix/video-display-size
Nov 5, 2026
Merged

miguel-heygen merged 2 commits into
mainfrom
fix/video-display-size

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Nov 5, 2026 •

Copy link
Copy Markdown
Collaborator

What

A <video> with no CSS size now renders at the size it plays at, sample aspect and rotation applied. Until now a phone's portrait clip (stored 1920x1080 with a -90 display matrix, as an iPhone records it) was laid out by export as a 1920x1080 box. In a 1080x1920 film that showed the picture small, letterboxed at the top right, while preview showed it filling the frame.

Why

Reported against 0.9.13: an iPhone HEVC .MOV in a portrait composition, <video class="clip"> with no width or height, exports wrong while preview is right. An explicit 1080x1920 size worked around it. Phone footage is common, and nothing tells the person why export differs from preview.

Related work

None open on this path.

Size

A lone fix: no open PR touches how export sizes a video, so nothing else can carry it.

How

Export gives each extracted <video> a poster of its natural size (applyVideoMetadataHints), so layout is right before the first injected frame. That size came from collectVideoMetadataHints, which passed ffprobe's stored width and height. Chrome and ffmpeg's autorotate both show the clip turned upright.

  • extractMediaMetadata now records the display matrix's turn as rotation (side data, else an older muxer's rotate tag).
  • displaySize(metadata) in ffprobe.ts turns stored size into the size Chrome lays the element out at: sample aspect applied to the width, then a quarter turn swaps the sides. The poster hint uses it.
  • width and height keep meaning the stored size. The BT.601/709 guess for untagged streams and background removal's own transposition read them that way.
  • buildVideoLayerInput keeps comparing the stored size with the canvas. Its frames come from ffmpeg, which turns the frame before widening the pixels, so a rotated anamorphic clip decodes to a different size than Chrome's. The stored-size check always scales a rotated clip to the canvas, which is correct (found in review).

Test plan

  • Unit tests added/updated

  • Manual testing performed

  • Documentation updated (if applicable)

  • Comments follow CONTRIBUTING.md "Comments": they say why, not what, and a bug fix says what the code must do and how to reproduce the bug

  • fails without the fix: collectVideoMetadataHints > sizes a phone's portrait clip as it plays, not as it is stored fails on main's renderOrchestrator.ts (hint 1920x1080), and the three a phone clip's display size tests in ffprobe.test.ts fail on main. All pass here.

  • renderOrchestrator.test.ts (298), the new ffprobe.test.ts cases and videoLayerInput.test.ts, 3 runs in a row on Linux. Engine and producer tsc are clean, and so are oxfmt and oxlint on the changed files.

  • ffprobe.test.ts has 4 failures on that Linux box with or without this change (the HDR PNG fixture tests and probeMediaProfile). They read a checked-in fixture that machine's copy lacks. CI has it.

  • Real render, the report's layout: a 1080x1920 composition, an unsized <video class="clip">, and an HEVC .MOV stored 1920x1080 with display rotation -90. Left: the clip's own upright frame. Middle: rendered by 0.9.13, small at the top right. Right: rendered from this branch, filling the frame.

  • Not covered: the reporter's own file, which isn't available, and an anamorphic clip in a real render (unit test only).

upright, 0.9.13, this branch

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: REQUEST CHANGES · 2 blocker · 0 important · 0 nits · head 2b210b4f

The poster-hint fix is right, and the fails without the fix: line names real tests. Two things block it:

  • The Comments check fails at this head. comment-ratchet.mjs reports the comment share rose in ffprobe.ts (+4 lines) and renderOrchestrator.ts (+1). Move those explanations into names or types, or cut them.
  • The layer path can regress for a rotated anamorphic clip (inline, videoFrameExtractor.ts:1247).
Scope

I read all 6 changed files and the consumers of buildVideoLayerInput (videoLayerPassthroughPlan.ts, and the streamingEncoder.ts overlay). Other readers of stored width/height (background-removal/pipeline.ts, init.ts) are unchanged, as the PR intends. CI was still running when I reviewed, except Comments, which had already failed. I tested the ffmpeg behaviour on 5.1 with a 320x180 clip, SAR 4:3 and rotation 90.

— Genesis lane B

});
const filters = chain(startTime);
if (metadata.width !== canvas.width || metadata.height !== canvas.height) {
const shown = displaySize(metadata);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blocker: displaySize gives Chrome's size, but this compares it with the size ffmpeg decodes. For a rotated clip with non-square pixels, those two sizes differ. ffmpeg's autorotate transposes before scale=trunc(iw*sar/2)*2:ih, and the transpose inverts the SAR. So the width scales the stored height by 1/SAR, which is not what displaySize assumes (stored width × SAR, then swap).

Repro: I made a 320x180 clip with SAR 4:3 and rotation 90, then ran it through this chain. It decodes to 134x320. displaySize returns 180x426. On a 180x426 canvas, the new check skips the scale, so [1:v] reaches the overlay in streamingEncoder.ts:478 at 134x320 instead of the canvas size. That is the same small-frame bug this PR fixes. On main, 320≠180, so it always scaled.

Fix: keep the stored-size check here (metadata.width/height), or compare against the size the chain really outputs (turn first, then trunc(h/sar/2)*2). Then fix the comment in displaySize and the widens anamorphic pixels before the turn test, which say the order matches the decode.

@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 267 (base branch 267), smooth 121 of those

The gate passes.
Smoothness is reported in the artifact, not gated. A case fails only if it fails 2 of 3 runs.

Quarantined, measured but not gated (8)

Unstable (1)

  • seqfastkeys-none-px-r0-root-z100: tracking 0.06, pressJump 0.05, drop 0, reload 0, render 0.02, renderKey -, undo false, teleport true / tracking 0.06, pressJump 0.05, drop 0, reload 0, render 0.02, renderKey -, undo true, teleport true / tracking 0.06, pressJump 0.05, drop 0, reload 0, render 0.02, renderKey -, undo true, teleport true

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: APPROVE — 2 resolved, 0 open, 0 new · 2b210b4f → e56e65d3

✅ The layer path is back to main's stored-size check, so a rotated clip with non-square pixels still gets scaled to the canvas. ✅ The new comment lines are gone, and Comments passes. CI passes too. regression and Windows render verification were still running when I approved.

— Genesis lane B

@miguel-heygen
miguel-heygen added this pull request to the merge queue Nov 5, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Nov 5, 2026
@miguel-heygen
miguel-heygen added this pull request to the merge queue Nov 5, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Nov 5, 2026
@miguel-heygen
miguel-heygen added this pull request to the merge queue Nov 5, 2026
Merged via the queue into main with commit 659ef56 Nov 5, 2026
90 of 91 checks passed
@miguel-heygen
miguel-heygen deleted the fix/video-display-size branch November 5, 2026 09:53
miguel-heygen added a commit that referenced this pull request Nov 5, 2026
miguel-heygen added a commit that referenced this pull request Nov 5, 2026
mitanshu450 pushed a commit to mitanshu450/hyperframes-video-generation that referenced this pull request Nov 5, 2026
* chore: release v0.9.16

* chore: format v0.9.16 release notes

* chore: add heygen-com#7228 and heygen-com#7231 to v0.9.16 notes

* chore: add heygen-com#7217 heygen-com#7224 heygen-com#7225 heygen-com#7226 heygen-com#7229 heygen-com#7233 to v0.9.16 notes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant