mirror of
https://github.com/block/buzz.git
synced 2026-10-27 12:40:30 +00:00
fix(media): accept portrait video resolutions (#6058)
## Summary - apply the existing 2160×3840 video resolution envelope independent of orientation - accept portrait recordings whose short/long edges fit that envelope - retain rejection coverage for either edge exceeding its limit - make the relay error describe the orientation-independent limits ## Why The iOS simulator produces portrait H.264 recordings such as 1206×2622. The relay previously checked `width <= 3840 && height <= 2160`, so the equivalent portrait dimensions were rejected while landscape dimensions were accepted. ## Validation - `bin/just ci` - `cargo test -p buzz-media` (120 passed; one MinIO integration test ignored by its existing live-service guard) - `cargo clippy -p buzz-media --all-targets -- -D warnings` - exact blocked artifact `/Users/judeedwards/.buzz/.scratch/pr5874-mongo-evidence/native/head-20260816T050317Z/video-bitexact.mp4` validated through `validate_video_file`: 1206×2622 accepted Signed-off-by: Carl <5f365698229751c0461f57bb03a4e93134e6e936bd7039ebe7b737282a43c754@buzz.block.builderlab.xyz> Co-authored-by: Carl <5f365698229751c0461f57bb03a4e93134e6e936bd7039ebe7b737282a43c754@buzz.block.builderlab.xyz>
This commit is contained in:
@@ -72,8 +72,10 @@ pub enum MediaError {
|
||||
/// Video duration exceeds the 600-second limit.
|
||||
#[error("video too long: duration exceeds 600 seconds")]
|
||||
DurationTooLong,
|
||||
/// Video resolution exceeds 3840×2160.
|
||||
#[error("video resolution too high: maximum is 3840x2160")]
|
||||
/// Video resolution exceeds the 2160 short-edge / 3840 long-edge envelope.
|
||||
#[error(
|
||||
"video resolution too high: maximum is 2160 on the short edge and 3840 on the long edge"
|
||||
)]
|
||||
ResolutionTooHigh,
|
||||
/// MP4 moov atom appears after mdat — not fast-start.
|
||||
#[error("moov atom not at front of file (not fast-start)")]
|
||||
|
||||
@@ -294,7 +294,7 @@ pub fn validate_content(bytes: &[u8], config: &MediaConfig) -> Result<String, Me
|
||||
/// - Exactly one video track using `avc1` (H.264 only — rejects HEVC, VP9, AV1)
|
||||
/// - At most one audio track, using `mp4a` (AAC)
|
||||
/// - Duration ≤ 600 seconds (from mvhd timescale, not edit lists)
|
||||
/// - Resolution ≤ 3840×2160
|
||||
/// - Resolution: short edge ≤ 2160 and long edge ≤ 3840 (portrait or landscape)
|
||||
/// - moov atom precedes mdat (fast-start / web-optimised)
|
||||
///
|
||||
/// Returns [`VideoMeta`] on success.
|
||||
@@ -370,10 +370,14 @@ pub fn validate_video_file(path: &Path, config: &MediaConfig) -> Result<VideoMet
|
||||
return Err(MediaError::DurationTooLong);
|
||||
}
|
||||
|
||||
// Resolution check.
|
||||
// Resolution check. Apply the 2160×3840 envelope independent
|
||||
// of orientation so equivalent portrait and landscape videos
|
||||
// receive the same treatment.
|
||||
let width = track.width() as u32;
|
||||
let height = track.height() as u32;
|
||||
if width > 3840 || height > 2160 {
|
||||
let short_edge = width.min(height);
|
||||
let long_edge = width.max(height);
|
||||
if short_edge > 2160 || long_edge > 3840 {
|
||||
return Err(MediaError::ResolutionTooHigh);
|
||||
}
|
||||
|
||||
@@ -2547,6 +2551,43 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_video_accepts_portrait_resolution() {
|
||||
let mp4_bytes = build_mp4_bytes(true, b"avc1", 1_000, 2160, 3840, false);
|
||||
let tmp = tempfile::NamedTempFile::new().unwrap();
|
||||
std::fs::write(tmp.path(), &mp4_bytes).unwrap();
|
||||
|
||||
let meta = validate_video_file(tmp.path(), &test_config())
|
||||
.expect("portrait video within the 2160x3840 envelope should be accepted");
|
||||
assert_eq!((meta.width, meta.height), (2160, 3840));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_video_rejects_resolution_above_short_edge_limit() {
|
||||
let mp4_bytes = build_mp4_bytes(true, b"avc1", 1_000, 2161, 3840, false);
|
||||
let tmp = tempfile::NamedTempFile::new().unwrap();
|
||||
std::fs::write(tmp.path(), &mp4_bytes).unwrap();
|
||||
|
||||
let result = validate_video_file(tmp.path(), &test_config());
|
||||
assert!(
|
||||
matches!(result, Err(MediaError::ResolutionTooHigh)),
|
||||
"expected ResolutionTooHigh, got {result:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_video_rejects_resolution_above_long_edge_limit() {
|
||||
let mp4_bytes = build_mp4_bytes(true, b"avc1", 1_000, 2160, 3841, false);
|
||||
let tmp = tempfile::NamedTempFile::new().unwrap();
|
||||
std::fs::write(tmp.path(), &mp4_bytes).unwrap();
|
||||
|
||||
let result = validate_video_file(tmp.path(), &test_config());
|
||||
assert!(
|
||||
matches!(result, Err(MediaError::ResolutionTooHigh)),
|
||||
"expected ResolutionTooHigh, got {result:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_video_resolution_too_high() {
|
||||
let config = test_config();
|
||||
|
||||
Reference in New Issue
Block a user