A file's bitrate and its video stream's are each one audio stream short #175

Closed
opened 2026-08-21 18:46:36 +02:00 by Claude · 1 comment
Collaborator

One file, two servers, and the bitrates disagree by exactly one audio stream.

Read off the player statistics of the same 22.5 GiB Matroska on an upstream
instance and on ours:

container video stream audio stream
upstream 20.1 Mbps 16.3 Mbps 3.8 Mbps
ours 16.3 Mbps 12.5 Mbps 3.8 Mbps

The arithmetic says which path each server took. Upstream's container figure is
the one ffprobe reports for the format, and its video stream is what is left of
it — 20.1 − 3.8 = 16.3. Ours is the other way round: the video stream carries
12.5 from somewhere and the container is the sum of the streams,
12.5 + 3.8 = 16.3. So on this file our probe came away with no format
bitrate and a per-stream video bitrate
, and upstream's with the reverse.

Both of ours are therefore one audio stream short of upstream's, and both are
served: MediaSource.Bitrate is the row's TotalBitrate straight off
(crates/ephyra-http/src/map/media.rs), and the stream's is the row's. A client
picking a version by bitrate, and every ladder built off the source bitrate, is
choosing against a number that is 19% low.

crates/ephyra-ffmpeg/src/probe.rs::infer_video_bitrate and ::infer_total_bitrate
are ports of upstream's two, guard for guard, and neither is where this parts
company: both only ever fill a figure that is missing. What differs is what
arrived — the container's own bitrate, and whether the video stream had one
before the inference ran.

Before anything else

The two clients are not the same build. The pinned bundle
(WEB_REF=v12.0-rc5) draws the compact statistics — Target codecs, Video
attributes
, Progress / speed — and the instance compared against drew the
itemised one — Play method, Player dimensions, Video bitrate. Nothing is to
be read into the labels or the grouping; the numbers above are the same fields
under two names, and only they are comparable.

Which also means the upstream half of the table is not known to be the pin. The
first move is to put the same file in front of the reference instance and read
/Items/{id}/PlaybackInfo from both rather than two dashboards — a divergence
that only exists against a 10.x server is a different ticket, or none.

Where to start

crates/ephyra-ffmpeg/src/probe.rs reads format.bit_rate and each stream's,
and crates/ephyra-ffmpeg/src/probe/stream.rs reads what a Matroska BPS tag
says. Upstream's ProbeResultNormalizer reads all three too, and the order it
prefers them in is what decides both of these numbers. The file is a Matroska
with per-track BPS tags, which is the case where the three disagree.

How to know it is done

The same file probed by both servers answers one container bitrate and one video
stream bitrate. A corpus case over a Matroska carrying BPS tags, failing today.

House rules

CLAUDE.md is the short version and these are the parts that bite: tests run
under cargo nextest; upstream's source is the checkout in upstream/ at the
pin
; generated files are regenerated rather than edited; a comment says what
the code does, not how it got there.

A test that cannot fail is worse than none.

One file, two servers, and the bitrates disagree by exactly one audio stream. Read off the player statistics of the same 22.5 GiB Matroska on an upstream instance and on ours: | | container | video stream | audio stream | |---|---|---|---| | upstream | 20.1 Mbps | 16.3 Mbps | 3.8 Mbps | | ours | 16.3 Mbps | 12.5 Mbps | 3.8 Mbps | The arithmetic says which path each server took. Upstream's container figure is the one ffprobe reports for the format, and its video stream is what is left of it — `20.1 − 3.8 = 16.3`. Ours is the other way round: the video stream carries `12.5` from somewhere and the container is the sum of the streams, `12.5 + 3.8 = 16.3`. So on this file our probe came away with **no format bitrate and a per-stream video bitrate**, and upstream's with the reverse. Both of ours are therefore one audio stream short of upstream's, and both are served: `MediaSource.Bitrate` is the row's `TotalBitrate` straight off (`crates/ephyra-http/src/map/media.rs`), and the stream's is the row's. A client picking a version by bitrate, and every ladder built off the source bitrate, is choosing against a number that is 19% low. `crates/ephyra-ffmpeg/src/probe.rs::infer_video_bitrate` and `::infer_total_bitrate` are ports of upstream's two, guard for guard, and neither is where this parts company: both only ever fill a figure that is missing. What differs is what arrived — the container's own bitrate, and whether the video stream had one before the inference ran. ## Before anything else **The two clients are not the same build.** The pinned bundle (`WEB_REF=v12.0-rc5`) draws the compact statistics — *Target codecs*, *Video attributes*, *Progress / speed* — and the instance compared against drew the itemised one — *Play method*, *Player dimensions*, *Video bitrate*. Nothing is to be read into the labels or the grouping; the numbers above are the same fields under two names, and only they are comparable. Which also means the upstream half of the table is not known to be the pin. The first move is to put the same file in front of the reference instance and read `/Items/{id}/PlaybackInfo` from both rather than two dashboards — a divergence that only exists against a 10.x server is a different ticket, or none. ## Where to start `crates/ephyra-ffmpeg/src/probe.rs` reads `format.bit_rate` and each stream's, and `crates/ephyra-ffmpeg/src/probe/stream.rs` reads what a Matroska `BPS` tag says. Upstream's `ProbeResultNormalizer` reads all three too, and the order it prefers them in is what decides both of these numbers. The file is a Matroska with per-track `BPS` tags, which is the case where the three disagree. ## How to know it is done The same file probed by both servers answers one container bitrate and one video stream bitrate. A corpus case over a Matroska carrying `BPS` tags, failing today. ### House rules `CLAUDE.md` is the short version and these are the parts that bite: tests run under `cargo nextest`; upstream's source is the checkout in `upstream/` **at the pin**; generated files are regenerated rather than edited; a comment says what the code does, not how it got there. A test that cannot fail is worse than none.
Author
Collaborator

Not a divergence: our numbers are the pinned upstream's, and the instance
compared against is not at the pin.

The anchor holds the row that server wrote for this file — Avatar: Fire and
Ash
, 32A00C15-D3F4-F320-902A-EBD0B4BD96B6:

Size 24,197,233,121
RunTimeTicks 118,407,040,000 → 11,840.704 s
TotalBitrate 20,133,140
video BitRate 16,348,509
audio BitRate 3,784,631

24,197,233,121 × 8 ÷ 11,840.704 = 16,348,509.76. The figure that server filed
as the video stream's bitrate is the bitrate of the whole file — what
ffprobe reports in format.bit_rate — and its TotalBitrate is that plus the
audio again, 16,348,509 + 3,784,631 = 20,133,140. A file 3 h 17 m long holding
22.5 GiB is not 20.1 Mbps; at that rate it would run 2 h 40 m. The audio is
counted twice.

That is exactly what the pin stopped doing. ProbeResultNormalizer.GetMediaStream
lets the container's figure stand in for a lone audio stream and no longer for a
video one:

// The bitrate info of FLAC audio is included in formatInfo.
// Don't do this for video streams: formatInfo.BitRate is the overall container
// bitrate (video + audio + subtitles + overhead), not the video bitrate.

and what is left of the container once the other streams are paid for is
estimated onto the video instead — 16,348,509 − 3,784,631 = 12,563,878.

Which is our pair, to the digit: container 16,348,509 → 16.3 Mbps, video
12,563,878 → 12.5 Mbps. crates/ephyra-ffmpeg/src/probe.rs::infer_video_bitrate
and probe/stream.rs::bit_rate are the port of that, and they answered the way
the pin does.

Closing. The half of the ticket that was real is #174.

Not a divergence: our numbers are the pinned upstream's, and the instance compared against is not at the pin. The anchor holds the row that server wrote for this file — *Avatar: Fire and Ash*, `32A00C15-D3F4-F320-902A-EBD0B4BD96B6`: | | | |---|---| | `Size` | 24,197,233,121 | | `RunTimeTicks` | 118,407,040,000 → 11,840.704 s | | `TotalBitrate` | 20,133,140 | | video `BitRate` | 16,348,509 | | audio `BitRate` | 3,784,631 | `24,197,233,121 × 8 ÷ 11,840.704 = 16,348,509.76`. The figure that server filed as the **video stream's** bitrate is the bitrate of the **whole file** — what ffprobe reports in `format.bit_rate` — and its `TotalBitrate` is that plus the audio again, `16,348,509 + 3,784,631 = 20,133,140`. A file 3 h 17 m long holding 22.5 GiB is not 20.1 Mbps; at that rate it would run 2 h 40 m. The audio is counted twice. That is exactly what the pin stopped doing. `ProbeResultNormalizer.GetMediaStream` lets the container's figure stand in for a lone audio stream and no longer for a video one: // The bitrate info of FLAC audio is included in formatInfo. // Don't do this for video streams: formatInfo.BitRate is the overall container // bitrate (video + audio + subtitles + overhead), not the video bitrate. and what is left of the container once the other streams are paid for is estimated onto the video instead — `16,348,509 − 3,784,631 = 12,563,878`. Which is our pair, to the digit: container 16,348,509 → *16.3 Mbps*, video 12,563,878 → *12.5 Mbps*. `crates/ephyra-ffmpeg/src/probe.rs::infer_video_bitrate` and `probe/stream.rs::bit_rate` are the port of that, and they answered the way the pin does. Closing. The half of the ticket that was real is #174.
Sign in to join this conversation.
No description provided.