The render API returns a .mp4 URL, and the download comes back with HTTP 200. Can you mark the job done? In Deck the Halls Dance AI, two photos become a 15-second Christmas dance clip like this sample MP4. Before the Worker hands out a download link, it checks that the file has both video and audio, that each track runs for about 15 seconds, and that the full file made it into storage.
A valid-looking response can still carry a silent clip or end halfway through a box. Down the whole thing into an ArrayBuffer would make those checks straightforward, but memory would grow with every download. The Worker already has to read the response to save the video in Cloudflare R2, so it inspects the MP4 during that same pass.
An MP4 is organized into boxes. Each box has a type and a size. For this check the Worker cares about three top-level types: ftyp for file type information, moov for the movie metadata, and mdat for the actual media bytes. The parser buffers the moov payload, with an 8 MiB cap. It counts the mdat bytes and passes them through to storage.
Within moov, it walks the track metadata down to trak → mdia. The hdlr box identifies a video track (vide) or an audio track ( soun). The mdhd box provides the track’s duration and timescale. A track with a timescale of 48,000 and a duration of 720,000 units runs for 15 seconds. The app accepts a half-second of drift in either direction for each video and audio track. Checking each track catches cases like 15 seconds of video paired with one second of audio.
Network chunks can end anywhere, including in the middle of the size field. They can also carry the end of one box and the start of another. The parser holds up to 16 header bytes, including room for an extended size, and remembers how much payload of the active box remains. It can keep reading the other boxes when moov arrives after mdat. At end of stream, it checks that the stream ended on a valid boundary and that the required boxes and tracks were present.
The R2 multipart upload API lets the Worker upload parts before it calls complete. The Worker fills 5 MiB parts sequentially and counts incoming bytes against a 100 MiB file limit. It enforces that limit even if the response has no Content-Length.
This TypeScript sketch shows the ordering. writeParts handles buffering and part numbering, and flushParts uploads any bytes left in the buffer.
let bytesRead = 0
try {
while (true) {
const { done, value } = await reader.read()
if (done) break
bytesRead += value.byteLength
checkByteLimit(bytesRead)
inspector.push(value)
await writeParts(value)
}
checkExpectedLength(bytesRead)
inspector.finish()
await flushParts()
await upload.complete(parts)
} catch (error) {
await reader.cancel().catch(() => undefined)
await upload.abort().catch(() => undefined)
throw error
} finally {
reader.releaseLock()
}
Suppose the last few bytes disappear after several parts have already made it to R2. The final length or box-boundary check fails and the Worker aborts the upload. The download stays unavailable because the job row only gets an output key after the upload completes.
That job update also checks which attempt still owns the lease. Each attempt writes to a different object key, so a slow attempt that finishes after its lease expires can’t overwrite the newer attempt’s file. If R2 is temporarily unavailable, the job can retry the download from its stored source URL and reuse the existing render.
The test fixtures build tiny MP4-shaped byte arrays so they can change one field at a time. Useful cases include a missing audio track, a one-second audio track under a full-length video track, an oversized moov header, and a response shorter than its declared size. One fixture uses a payload over 5 MiB to check that the upload splits into two parts. For a rejected file, the important assertion is that complete was never called.
These are container checks. The Worker does not decode frames, listen for silence in a valid audio track, or check that the dancers land on the beat. Those would require media-level checks. For this download endpoint, the gate catches the structural failures it was designed for without buffering the full file or running a transcoder on every render.