DEV Community

Cover image for Amazon's own BIF file stores a timestamp unit of 0xFFFFD8F0, and the obvious fix would break it
Edy Cu
Edy Cu

Posted on

Amazon's own BIF file stores a timestamp unit of 0xFFFFD8F0, and the obvious fix would break it

I wanted a seek bar that knows where the goals are, without a feed and without the scan decoding any video. (Disclosure: I directed Claude Code agents that wrote most of this code; I reviewed and decided.) A Vega player that shows BIF scrub previews, like Amazon's sample, already downloads a BIF file, one small JPEG about every ten seconds of the whole asset. So I treated that file as a contact sheet of the match, and wrote a detector that watches the score graphic across the thumbnails.

The detector works, on one development match. The more useful thing I found was in the BIF itself: Amazon's own hosted sample file has a timestamp header that the sample parser only survives by accident, and the "obvious" fix would have broken it.

The project is Flipbook, built for the Amazon Developer Hackathon (Fire TV track) on the Vega Virtual Device. It starts from a copy of Amazon's vega-video-sample. The 2:54 demo, recorded on the virtual device:

Landing page and pitch deck: flipbook.edycu.dev.

The header that says four billion milliseconds

A BIF is a 64-byte header, an index of (timestamp, offset) pairs, then JPEGs. The header stores a "timestamp multiplier": milliseconds per timestamp unit. I fetched the first 32 bytes of the file the sample streams, tv-webinar-hd.bif, with an HTTP Range request:

00000000: 8942 4946 0d0a 1a0a 0000 0000 ac01 0000  .BIF............
00000010: f0d8 ffff 0000 0000 0000 0000 0000 0000  ................
Enter fullscreen mode Exit fullscreen mode

428 frames (0x1AC), and a multiplier of 0xFFFFD8F0. Read as an unsigned 32-bit number that is 4,294,957,296. The sample's parser multiplies every timestamp by it, so each frame is "about 4.29 billion milliseconds" after the last. The player still shows sensible previews because the sample's lookup ignores the parsed times and assumes one frame per ten seconds.

Look at it as a signed 32-bit integer and it is −10,000: ten seconds, with the sign wrong. That is my inference. I cannot see how Amazon's file was made, and I am not claiming to know. But it matches the ten-second spacing the file actually has.

The catalogue entry for that title also says duration: 190, for a 71-minute video. The friction log has the details (entry 6).

Why the obvious fix is wrong

My plan for an upstream fix was to make the sample's thumbnail lookup binary-search the parsed positionMillis. That is how a BIF is supposed to work. On tv-webinar-hd.bif the parsed times are the 4.29-billion-millisecond values above, so a search by position cannot work, and the fix would break the one file Amazon's own sample ships. A fix that works on the spec and fails on the reference asset is not a fix you can send upstream, so the reader has to decide, per file, whether the header can be trusted.

Flipbook's reader (src/flipbook/core/bifIndex.ts) does that (the snippet is abridged from it). A multiplier is trusted only if it is between 1 ms and 60 s; otherwise the unit comes from the asset's duration, and failing that from the sample's own ten-second assumption:

if (rawMultiplier === 0) {
  unitMs = 1000; // Roku spec: 0 means 1000 ms
  intervalSource = 'header';
} else if (isSaneMultiplier(rawMultiplier)) {
  unitMs = rawMultiplier;
  intervalSource = 'header';
} else if (opts.durationMs && opts.durationMs > 0 && timestamps[frameCount - 1] > 0) {
  unitMs = opts.durationMs / timestamps[frameCount - 1];
  intervalSource = 'duration';
} else {
  unitMs = FALLBACK_UNIT_MS; // 10 s
  intervalSource = 'fallback-10s';
}
Enter fullscreen mode Exit fullscreen mode

The same function refuses a file with no 0xffffffff terminator entry. The sample logs a warning for that and then trusts the trailing offset, so the last frame is read from a corrupt range (entry 7).

The detector, in one paragraph

It is arithmetic, no ML. The viewer draws a box around the broadcaster's score graphic once. For each thumbnail Flipbook decodes the JPEG on the device with jpeg-js, takes a 64×5 luma grid of the box, and flags a change only if the new state holds for most of a three-frame window, so a replay or a flicker does not count. A first pass reads every eighth frame and bisects where something changed, to put the first ticks up quickly; a second pass reads all frames. Pressing fast-forward jumps to the marker minus 30 seconds, anchored on the last frame that still shows the old score, so you land before the play. By default the seek bar shows no ticks and the thumbnails are masked over the score box, so the result is not spoiled while you hunt.

What I measured, and what I did not

On a development match (a CC0 high-school soccer broadcast, 517 thumbnails), labelled by the detector's author (an AI agent working under my direction) — so not independent ground truth:

Changes found 2 of 2
False markers 0
Jumps that landed before the play 2 of 2 (28 s and 30 s early)
Frames read in pass 1 77 of 517 (14.9 %)
Score box vs. noise 2.64× at 320 px, 2.04× at 240×180 letterboxed

A plain frame differ finds both changes only at a threshold of 30 or lower (of the 8–80 I swept), and at 30, the best of those, it still fires 237 false markers. My first signature, a 16×4 mean, could not separate a goal from noise on that match, which is why there is a version 2.

On the Vega Virtual Device on an Apple M-series Mac, the same development match, calibrated with the D-pad and relaunched 19 times: pass 1 (77 of 517 frames) p50 2,771 ms, p95 3,422 ms; pass 2 p50 14,294 ms; the same two markers as the Node run, and the calibration box restored from storage after every relaunch. Earlier, the first detector version read 203 of 428 frames of Amazon's BIF in 4,085 ms, about 20 ms per decoded frame. I have no Fire TV hardware, so there is no real-device timing here, and the scan decoded no video bytes (the player still plays the video).

What I do not have is a held-out result. Two more matches are frozen behind a pre-registration file, and bench refuses to run on them until a filled-in ground-truth file that names its labeller is committed. The plan is that a person other than the detector's author does the labelling; the guard checks that the file is committed, filled in, for the right match and names its labeller, but it cannot check who that person is. Until those labels exist, I am not claiming a headline number, and the README says so.

Reproduce it

git clone https://github.com/edycutjong/flipbook && cd flipbook
npm ci --ignore-scripts && npm test   # 444 tests, 93 of them Flipbook's (skips the Shaka build only the device build needs)
npm run bench                    # the development-match numbers above, 20 runs
Enter fullscreen mode Exit fullscreen mode

What I would ask Amazon for

A frame-grab or pixel read-back API on the player surface, so apps are not limited to the frames inside a BIF; a BIF lookup that uses the file's own interval; and a corrected hosted tv-webinar-hd.bif. The full friction log is in the repo, 18 entries.

Top comments (0)