Skip to content

[buzz-app] Fit uploaded videos to the relay's 4K envelope and name video rejections - #1129

Merged
baxen merged 2 commits into
mainfrom
murderbot/video-fit-relay-envelope
Oct 15, 2026
Merged

baxen merged 2 commits into
mainfrom
murderbot/video-fit-relay-envelope

Conversation

@baxen

@baxen baxen commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

Full-screen QuickTime screen recordings on a Retina Mac are captured at native pixel size (for example 3406×2236). Desktop video preparation already converts .mov to MP4 with ffmpeg, but it kept that size. The relay caps video at 2160 on the short edge and 3840 on the long edge (block/buzz crates/buzz-media/src/validation.rs), so it returned 422 "video resolution too high". The app discarded that text and showed "The server could not accept this file. Its format or metadata may not be supported."

Change

  • Fit the envelope. Both preparation hosts (native media_preparation.rs, dev browser-host/media-preparation.mjs) now scale videos down, never up, to fit 3840×2160 in either orientation. Scaled edges round to even values, so padding only touches odd-sized unscaled input. Videos already inside the limit are unchanged.
  • Name the rule. relayRejection() in attachment-limits.ts maps relay rejection text to its own code, for both the frontend reader and the dev broker. Resolution, duration (over 10 min) and codec rejections now get specific messages. Other rejections, including unsupported container (only non-video BMFF such as AVIF gets that far, since both hosts convert every recognized video brand), keep the generic one.

Validation

  • Ran the dev host's prepareMedia on baxen's real 3406×2236 QuickTime recording. Output: isom MP4, H.264, 3290×2160, top-level boxes ftyp, moov, free, mdat.
  • Checked the filter against other sizes: 3456×2234 → 3342×2160, portrait 2236×3406 → 2160×3290, 7680×4320 → 3840×2160, 5120×1440 → 3840×1080, 3840×2160 unchanged, 1081×721 → 1082×722 (same as before).
  • New real-ffmpeg tests in Rust and the browser host. New mapping tests for the frontend and broker.
  • Passed: cargo test --lib relay::media_preparation (13), full vitest run, and tsc --noEmit. On the full vitest run, read-state.test.ts failed once and then passed 4/4 when run alone. This PR doesn't touch it.
  • Not yet run: a native app upload to the live relay.

🤖 Generated with Claude Code

…deo rejections

Full-screen QuickTime recordings on Retina displays are captured at native
pixel size (e.g. 3406x2236). Video preparation converted them to MP4 but kept
that size, so the relay rejected them for exceeding the 2160 short-edge limit,
and the app showed only "format or metadata may not be supported".

Preparation now scales videos down (never up) to fit 3840x2160 in either
orientation before the existing odd-edge pad. Relay rejections for
resolution, duration, codec and container now get their own messages
instead of the generic one.

Co-authored-by: murderbot <3754f8729004d95654c46dbab3129e4ab9ef05cc2534e2a3fbfc155983bd637b@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Bradley Axen <[email protected]>
@baxen
baxen marked this pull request as ready for review October 14, 2026 21:45
@baxen
baxen requested a review from a team as a code owner October 14, 2026 21:45
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-14T21:49:09.244976Z 9bedcd8 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@kalvinnchau kalvinnchau 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.

🤖 One non-blocking cosmetic finding; no merge blockers identified.

Comment thread browser-host/media-preparation.mjs Outdated
// Fit the relay's 3840x2160 envelope in either orientation (Retina screen
// recordings exceed it), never upscale, then pad odd edges for yuv420p.
const VIDEO_FILTER =
"scale=w='trunc(iw*min(1,min(3840/max(iw,ih),2160/min(iw,ih))))':h='trunc(ih*min(1,min(3840/max(iw,ih),2160/min(iw,ih))))',pad=ceil(iw/2)*2:ceil(ih/2)*2";

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.

🤖 [P3] Round scaled dimensions to even values before padding

A 3456×2234 screen recording scales to an odd width of 3341, then the existing pad filter adds a black column to reach 3342×2160 (reproduced with FFmpeg 9.0.1). The portrait equivalent similarly adds a black row. This remains inside the relay envelope, but introduces a cosmetic border for an originally even-sized recording. Round the scaled edges to even values inside the scale expression, preserving padding for unscaled odd inputs. Apply the same adjustment to the mirrored filter in src-tauri/src/relay/media_preparation.rs:18.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

🤖 Fixed in 8338162. Scaled edges now round to even inside the scale expression, so padding only touches odd-sized unscaled input. 3456×2234 → 3342×2160 and 2234×3456 → 2160×3342, with no added column. Both real-ffmpeg tests now use 3456×2234.

@kalvinnchau kalvinnchau 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.

just a p3 nit

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Carl, an automated reviewer, commenting via Wes’s GitHub account.

Changes requested: one copy regression, the rest holds up. The scaling fix is right. One new rejection message shows up on the wrong files (inline).

Validation

  • I ran both new real-ffmpeg tests locally at 9bedcd8d (ffmpeg 9.0.1). Both produce 3290×2160. I also broke each assertion and confirmed it fails, so neither test is vacuous on a machine that has ffmpeg.
  • The relay's own validate_video_file (block/buzz 2f79374f4) accepts real outputs at 3290×2160, at 1920×3840 (rotated phone footage, autorotated) and at 3840×960 (2:1 SAR input). The 3406×2236 case writes a pasp box with SAR 14148:14147. That's harmless: the relay's metadata walk stops at stsd, and mp4 0.14 checks the coded avc1 width and height.
  • Gap, non-blocking: hosted CI has no ffmpeg, so both new tests skip there. The JavaScript 1/2 log shows skipping real conversion: ffmpeg not found. That matches the existing HEIC tests' deliberate convention, so this PR doesn't need to fix it. Until a required lane provisions ffmpeg, CI is not evidence for the filter.

Merge criteria: make the container copy accurate for the files that actually receive it, and update the M4A /zzzz fixture to match. The native live-relay upload listed as not yet run is still worth one try before merge.

Comment thread src/features/relay/attachments.ts Outdated
"Videos can be at most 10 minutes long. Trim this recording, then retry.",
codec:
"This video’s codec isn’t supported. Export it as an H.264 MP4, then retry.",
container:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 This copy appears for files that aren't QuickTime and aren't videos. The relay sends any ftyp file down its video path (buzz-relay/src/api/media.rs should_stream_as_video → looks_like_iso_bmff). If no brand is in MP4_BRANDS, it returns UnsupportedContainer (buzz-media/src/upload.rs:400). Meanwhile, both preparation hosts transcode every brand videoDemuxer recognizes, qt included, so a real .mov never reaches the relay raw.

The files that actually get container are non-video BMFF the app passes through raw. An AVIF photo, for example, has brands avif mif1 miaf MA1B. That user is now told "QuickTime (.mov)… Export it as an MP4". Before this PR they saw the accurate generic rejected message.

Fix: make this copy format-neutral (for example "This file’s format isn’t supported here."), or map container back to rejected. Also update leaves non-video BMFF %s to relay rejection in attachments.test.ts. It still mocks the broker as { code: "rejected" }, but the real relay plus broker now return container for those brands.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

🤖 Agreed, fixed in 8338162. container is gone, and unsupported container maps back to the generic rejected message, since only non-video BMFF like AVIF reaches it. The M4A /zzzz fixture's { code: "rejected" } is accurate again. The broker and frontend tests now assert unsupported container → rejected.

"This video is larger than 4K (3840×2160). Export it at a smaller size, then retry.",
duration:
"Videos can be at most 10 minutes long. Trim this recording, then retry.",
codec:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Nit, non-blocking: WrongCodec also fires for audio tracks. A metadata-free ALAC .m4a (brands M4A isom iso2) reaches validate_video_file and fails on the audio track. "This media’s codec isn’t supported…" would be accurate in every case.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

🤖 Taken in 8338162: "This file’s codec isn’t supported. Use H.264 video and AAC audio, then retry."

Round scaled edges to even values so padding never adds a black column to
an even recording (3456x2234 now scales to 3342x2160, not 3341+pad).
Drop the QuickTime-specific container message: prepared hosts convert every
recognized video brand, so only non-video BMFF such as AVIF reaches that
rejection and the generic message is accurate. Make the codec message cover
audio tracks too.

Co-authored-by: murderbot <3754f8729004d95654c46dbab3129e4ab9ef05cc2534e2a3fbfc155983bd637b@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Bradley Axen <[email protected]>
@baxen
baxen merged commit a6e9f89 into main Oct 15, 2026
32 checks passed
@baxen
baxen deleted the murderbot/video-fit-relay-envelope branch October 15, 2026 00:58
johnmatthewtennant added a commit that referenced this pull request Oct 15, 2026
…-drag

* origin/main: (34 commits)
  fix(channels): resolve template agents from ready inventory (#922)
  [buzz-app] Fit uploaded videos to the relay's 4K envelope and name video rejections (#1129)
  feat(github): reveal the PR comment a link points at (#947)
  fix(messages): stop WebKit freeze from muted composer video previews (#1143)
  test(browser): fix route-unroute and virtua-hold webkit flakes (#1149)
  test(browser): let dnd-kit see its first resize before text shrinks (#1150)
  fix(previews): keep a hovered preview through scroll anchoring (#1138)
  Unify owned agent profile panels (#1083)
  feat(agents): import old Buzz teams from the Teams tab (#985)
  Mark Me chats as personal in the session description for remote agents (#1058)
  feat(messages): optional way to open buzz message links in a thread tab (#1113)
  Distinguish thread originals and tighten conversation spacing (#929)
  fix(buzz-mcp): page channel reads past the relay limit (#1131)
  fix(inbox): show a session's top-level timeline as one conversation (#1094)
  feat: reorder sidebar groups by dragging their headers (#1118)
  fix(avatars): request the cached relay thumbnail at every avatar site (#1054)
  fix(messages): soften quote styling and tighten spacing (#1098)
  fix(emoji): shrink and raise inline custom emoji (#1097)
  fix: follow host theme in media review chrome (#1117)
  Highlight an already-open thread tab when reopening it (#1050)
  ...

Signed-off-by: John Tennant <[email protected]>
Co-authored-by: Claude Worker <f425b86c3b780d43f8d9cf1802429d04dd985bf92eac44fca9468b5ad3887c5e@buzz.block.builderlab.xyz>

# Conflicts:
#	src/features/channel-navigation/ChannelSidebarDnd.tsx
johnmatthewtennant added a commit that referenced this pull request Oct 15, 2026
* origin/main:
  fix(navigation): share one abort reason so Settings → Back stops leaking DOM on V8 (#1161)
  Unify sidebar resizing and polish Solo navigation (#1132)
  Organize Builderlab agent identity and runtime settings (#1153)
  fix(channels): keep consecutive tab closes in the closing pane (#1158)
  feat(goose): add the goose agent type (#1125)
  feat: edit the last message with Cmd+Up (#1042)
  fix(channels): make Enter press the prominent lifecycle button (#1152)
  feat(me): drag chats between projects and Chats (#1112)
  fix(search): find open channels past the first 500 in Command-K (#1128)
  Stop in-flight request mocks from failing finished browser tests (#987)
  fix(channels): resolve template agents from ready inventory (#922)
  [buzz-app] Fit uploaded videos to the relay's 4K envelope and name video rejections (#1129)
  feat(github): reveal the PR comment a link points at (#947)
  fix(messages): stop WebKit freeze from muted composer video previews (#1143)

Signed-off-by: John Tennant <[email protected]>

# Conflicts:
#	docs/sessions/README.md
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.

3 participants