Engineering notes
Eight test cases, every recovered frame byte‑exact against the original, green for hours. Then I pointed it at footage from an actual camera and it recovered a quarter of the file.
When a camera loses power mid‑recording, the video is all on the card but the index describing where each frame lives is never written. Rebuilding that index means walking a few gigabytes of raw bytes with nothing marking the boundaries and working out where each frame starts.
It is a satisfying problem because it is checkable. You take a known good file, truncate it, rebuild the index, and compare the result against the original's own tables. A frame only counts if it lands at the same byte offset and the same size the camera wrote. There is no partial credit and no judgement call.
By that measure the engine was perfect:
case video exact audio exact decode errs h264 1280x720 30fps cut 30% 90/90 100.0% 129/131 98.5% 5 h264 1280x720 30fps cut 50% 149/149 100.0% 212/214 99.1% 5 h264 1280x720 30fps cut 68% 203/203 100.0% 291/293 99.3% 5 h264 1280x720 30fps cut 90% 269/269 100.0% 386/388 99.5% 5 h264 1920x1080 24fps cut 55% 107/107 100.0% 190/192 99.0% 5 h264 640x480 60fps cut 45% 217/217 100.0% 157/157 100.0% 0 h264 no audio cut 60% 144/144 100.0% n/a 0 hevc 1280x720 30fps cut 60% 146/146 100.0% 205/208 98.6% 7
Different codecs, resolutions, frame rates and cut points. A frame decoded from a repaired file came out byte‑identical to the same frame in the original. I was fairly pleased with myself.
GoPro publishes genuine camera recordings in their gpmf-parser repository. Two minutes to download. The result:
Recovered 107 video frames (3.57s) <- 425 frames were recoverable [h264] non-existing PPS 1 referenced [h264] no frame!
Every claim resting on that green matrix was, for this entire class of file, worthless.
I had generated every fixture with ffmpeg, which writes the tidy case:
one video track, one audio track, clean interleave. A real camera writes
five tracks. Video, audio, timecode, telemetry, and a
camera‑specific one, all interleaved into the same block of bytes.
None of that existed in anything I had made, so none of it was tested. Two defects followed directly, and neither was visible without a real file.
Real video started at offset 740. My scanner confidently reported a frame at 406. The bytes in between are telemetry, and a chance pattern inside them parsed as a valid chain of length‑prefixed units. Everything after inherited the misalignment.
At offset 80970 sat something reading as NAL type 14, a prefix unit, length 32. The real frame began at 81000 with NAL type 9, an access unit delimiter. The spurious unit chained directly into the real frame, so it looked entirely valid on inspection.
The format permits several kinds of unit to open a frame. I was accepting all of them, because that is what the specification allows. But a given camera is consistent: GoPro opens every single frame with an access unit delimiter, ffmpeg opens with an SEI or the slice itself.
So instead of allowing everything legal, read the first unit type of 128 frames in
the reference file and accept only what that camera habitually uses. For GoPro the
answer is the single value [9]. That plus one more rule, that a recording
always opens on a keyframe so nothing before the first one counts, took it from 107
frames to all 425, exactly, with zero decoder errors.
Learn the producer's habits from a sample rather than allowing everything the spec permits. The same move solved two other problems in the same codebase: which byte prefix an encoder opens its audio frames with, and which terminator it closes them with. Three unrelated‑looking problems, one technique.
Audio recovery on an Apple‑muxed QuickTime file scored 0% and I was about to report it as broken. It was actually 92% correct. My comparison walked the two lists by index, and the repair had merged two 11‑byte priming frames at the start, so every later frame was two positions out of step while sitting at exactly the right byte offset.
An off‑by‑two at the head makes a working feature look totally broken, and the failure is silent and confident. Compare recovered artifacts by what they are, offset and size, not by where they appear in a list.
A later measurement returned NAL type counts of one and three, with impossible type numbers. The conclusion would have been interesting. The cause was that I was walking the media block from its start, which also contains audio and telemetry. Walking only the ranges the file's own index declares gave clean numbers immediately.
case video recovered exact audio decode HERO6 paired take 425/425 100% omitted 0 HERO6 paired, 85% 603/603 100% omitted 0 HERO5 self 456/456 100% omitted 0 HERO7 self 210/210 100% omitted 0 HERO8 self 210/210 100% omitted 0 Karma self 200/200 100% omitted 0 Fusion self 248/248 100% omitted 0 MAX self 175/175 100% omitted 0 QuickTime paired 420/420 100% 92% placed 130
Audio says omitted on every GoPro row, and that is deliberate. Telemetry bytes cannot be told apart from AAC frames confidently enough to place them, so the audio search produces plausible looking nonsense: 183 frames recovered, none of them at a real position. The engine now checks that recovered audio covers at least 90% of the recovered video duration and drops the track when it does not. A silent recovery is worth more than a loud wrong one.
Fixtures you generate test the generator, not the domain. My suite was thorough, byte‑exact, and measuring the wrong universe. Thoroughness inside the wrong universe reads exactly like rigour.
Ask of any suite: did I make these inputs, or did the world? The real artifacts were one search away the whole time.
This is from building Cinesalve, a Mac app that rebuilds the index of a truncated MP4 or MOV so it plays again. It shows you the recovered footage before you pay for anything. If you have a damaged clip from a camera not in that table, I would genuinely like it: cinesalve@proton.me (or the contact form).
The same engine described above, running in your browser. Nothing is uploaded: only the first and last megabyte are read. Add a working clip from the same camera and it counts the surviving frames exactly, which is the number the whole article is about.