Skip to content

fix(media-server): Cap Infra posters up to 1920 on the long side - #4209

Merged
richiemcilroy merged 2 commits into
mainfrom
fix/infra-ladder-bitrate-poster
Nov 13, 2026
Merged

richiemcilroy merged 2 commits into
mainfrom
fix/infra-ladder-bitrate-poster

Conversation

@richiemcilroy

@richiemcilroy richiemcilroy commented Nov 13, 2026 •

Copy link
Copy Markdown
Member

Cap Infra posters were capped at 1280x720 because both Infra encode paths used the regular thumbnail defaults. They are now produced at up to 1920 on the long side, keeping the aspect ratio and never upscaling. Regular Cap thumbnails are unchanged.

What changed

  • src/lib/infra-poster.ts: posterThumbnailOptions(infra) returns a 1920x1920 box for an Infra job and {} for everything else.
  • src/routes/video.ts: two call sites now pass posterThumbnailOptions(options.infra) instead of {}:
    • the /video/ladder poster, taken from the top rendition;
    • the /video/process thumbnail.
  • /video/process matters because each Infra encode part writes its own poster, and the video keeps whichever part finishes first. Without it, an MP4 part that finished first would still leave a 1280x720 poster.
  • Only Infra's encode request sends infra: true. Regular Cap, the Loom importer, mux, edit and /video/thumbnail either don't send it or don't reach this code, so they keep {}. DEFAULT_THUMBNAIL_OPTIONS is untouched.

Resulting sizes: a 16:9 top rendition at 1080p or above gives 1920x1080, a 9:16 one gives 1080x1920, and anything smaller than the box keeps its own size.

Measurements

Poster generation through generateThumbnail, on the top rendition as the ladder uses it. Run on an L4 test machine with the production image's FFmpeg 7.1.5, median of 9 runs:

Source Before After Time before Time after
4K text, 2160p rendition 1280x720, 91 KB 1920x1080, 153 KB 352 ms 348 ms
4K moving pattern 1280x720, 33 KB 1920x1080, 56 KB 304 ms 305 ms
1080p text, 1080p rendition 1280x720, 85 KB 1920x1080, 139 KB 142 ms 149 ms
1080p moving, 1080p rendition 1280x720, 35 KB 1920x1080, 59 KB 168 ms 175 ms

On an M-series Mac with FFmpeg 8: 4K text took 114 to 124 ms before and 113 to 151 ms after, and 1080p text took 49 ms before and 54 to 56 ms after.

Ready time: the poster renders while the renditions upload (and while the MP4 uploads in /video/process), so it isn't on the critical path. The worst measured cost is +7 ms on a 1080p source. Whole /video/ladder jobs with a local upload sink, 15 interleaved runs each on the Mac: 4K median 7327 ms before vs 7556 ms after, and 1080p 1466 ms before vs 1394 ms after. Round-to-round drift was larger than either difference.

Tests

  • New src/__tests__/lib/infra-poster.integration.test.ts, added to both Dockerfile and Dockerfile.standalone, so it runs on the image's own FFmpeg. It spies on generateThumbnail at the real call sites:
    • a regular /video/process job passes exactly {} and uploads a 1280x720 poster from a 2560x1440 source;
    • an Infra /video/process job passes the 1920 box and uploads 1920x1080;
    • an Infra /video/ladder at 1440p uploads 1920x1080;
    • a 1080x1920 portrait ladder uploads 1080x1920, so it is never upscaled.
  • Against main's route file, the three Infra cases fail and the regular case passes. It passes on both main and this branch, which shows regular Cap is unchanged.
  • The new test also passes inside the production image (Debian FFmpeg 7.1.5), 5 of 5.
  • Existing suites pass: hls-ladder-route 7, routes/video 38, media-routes-real-world 45, hls-ladder 68, infra-capacity 9.

Not in this PR: ladder bitrate order

Under constant quality, a lower rung can come out at a higher bitrate than the rung above it. This is deferred to a two-pass design after launch. The hosted player already ranks renditions by height, not BANDWIDTH, so the inversion wastes some bandwidth but never picks the wrong quality.

Root cause. Text content costs more bits when it is downscaled. A pristine 1080p text render encodes at 594k (libx264 CRF 23), while its 720p downscale costs about 2000k with any scaler (lanczos 2020k, bicubic 1841k, area 1831k). Natural content goes the other way. So the order depends on content and can't be predicted before encoding.

Repro with the current ladder args on the production image's FFmpeg 7.1.5 + NVENC (L4), 15 s synthetic clips, average kbps:

Source 2160p 1440p 1080p 720p 360p
1080p scrolling-text screen recording 726 1553 625
4K scrolling text 1857 3718 796 2229 619
moving test pattern 4507 2498 708
noise 4577 2550 704
static bars 37 26 20

libx264 shows the same: on the 1080p text clip, 1080p comes out at 735k and 720p at 2022k.

Options measured:

  • Re-encode only the inverted rungs, capped below the rung above: a strict guarantee, and 0% extra on natural content. On text content it costs 2.4 to 2.9 s against a 3.06 s 4K ladder, so up to about +90% encode time.
  • Cap lower rungs from the source's bits per pixel (single pass, 0% time): it keeps the order on low-bitrate uploads, but starves the lower rungs (1080p text clip: 720p 2022k to 145k, 360p 638k to 38k). It also leaves high-bitrate screen captures inverted, because their caps never bind.

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge; no actionable issues were found.

Summary

Infra posters now fit within 1920×1920 in both encode paths. Regular thumbnails keep their existing defaults.

  • Cap Infra posters can use up to 1920 pixels on their long side.

Acknowledgment: richiemcilroy explicitly deferred ladder bitrate ordering to a two-pass design after launch. This PR does not change that behavior.

Reviews (1) · Last reviewed commit: "test(media-server): run the Infra poster..." · Reviewed by Greptile

@vercel
vercel Bot temporarily deployed to Preview – cap-status November 13, 2026 13:57 Inactive
@vercel

vercel Bot commented Nov 13, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
cap-status Skipped Skipped Nov 13, 2026 1:57pm UTC

@sandchest-com

sandchest-com Bot commented Nov 13, 2026

Copy link
Copy Markdown

Sandchest

Sandchest reviewed 55129d5: nothing exploitable found in this change.

Sandchest reviews every change for vulnerabilities an attacker could use · 1 credit · Review details

@richiemcilroy

Copy link
Copy Markdown
Member Author

@greptileai

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Capy found no issues.

View 1 other finding in Capy.

Open in Capy

@richiemcilroy
richiemcilroy merged commit 0d3c302 into main Nov 13, 2026
46 checks passed

This branch was previously deployed

1 inactive deployment
Preview – cap-status — 55129d5a Deployed Nov 13, 2026 by vercel[bot]
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