---
title: "Repair a corrupted or truncated MP4 in C#"
url: https://recursiveai.org/onemedia/cookbook/repair-corrupted-mp4/
description: "Repair a corrupted or truncated MP4 in C# with OneMedia (a preview): rebuild a cut-off recording whose moov is missing from its media data and a reference file, or a broken MKV index, into a new file with a report. Checked with ffprobe and Chrome against the original."
version: "0.7.0-preview.1"
---

# Repair a corrupted or truncated MP4 in C#

`Media.Repair(input, output, reference)` writes a repaired copy of a damaged video and returns a `RepairReport`: what was rebuilt, dropped or guessed, with a confidence, and what each track kept. It rebuilds the indexes of damaged MKV/WebM, AVI, MP4/MOV, fragmented MP4 and MPEG-TS files, and rebuilds an MP4, MOV or 3GP recording that was cut off before its movie box (`moov`) was written. The input is never modified, and the media bytes are copied, not re-encoded. Repair is a preview in 0.7.0-preview.1.

> **Repair is a preview, and it does not get back what isn’t in the file.** A cut-off recording comes back only up to where the file ends. In the 0.6.0-preview.1 changelog’s words:
>
> - “**Repair is a preview.** It was checked on generated files, published samples and 10 GoPro clips, not on a corpus of device recordings”.
> - “**Reconstruction of truncated MP4/MOV/3GP recordings has limits:** PCM audio, and video without in-stream parameter sets, need a reference file from the same device; 5.1 AAC is dropped; without a reference the AAC sample rate is a guess and the track is reported at Low confidence; AAC through `Media.Repair` needs the audio package’s `AacReconstructionProbe.Repair` (the `onemedia` tool does this for you).”
> - “**Index repair limits:** a TS file cut at 90 % keeps one cut frame (seen in two sweep cases); M2TS has no oracle sweep because ffmpeg 7.0.2 crashes on those files; ASF, FLV and Ogg are not repaired.”

The damaged file here is made from the Sintel trailer (© copyright Blender Foundation, durian.blender.org, CC BY 3.0): the MP4 is cut at 60 % of its length, as a camera that lost power would leave it. Its `moov` was at the end, so the cut takes it away, and no player opens the file. The reference is the trailer’s first five seconds, standing in for another recording from the same camera with the same settings.

```shell
dotnet new console -o RepairMp4 && cd RepairMp4
dotnet add package RecursiveAI.OneMedia.Audio --version 0.7.0-preview.1
curl -L -o sintel.mp4 https://download.blender.org/durian/trailer/sintel_trailer-480p.mp4
head -c 2623423 sintel.mp4 > sintel-cut.mp4          # 60 % of 4,372,373 bytes: moov is gone
ffmpeg -v error -i sintel.mp4 -t 5 -c copy reference.mp4
```

`RecursiveAI.OneMedia.Audio` brings the core package and the AAC frame probe that rebuilding an AAC track needs, so the program calls `AacReconstructionProbe.Repair`, which is `Media.Repair` with that probe. Put this in `Program.cs`:

```csharp
using RecursiveAI.OneMedia.Audio;
using RecursiveAI.OneMedia.Repair;

string input = args.Length > 0 ? args[0] : "sintel-cut.mp4";
string output = args.Length > 1 ? args[1] : "sintel-repaired.mp4";
string? reference = args.Length > 2 ? args[2] : null;

// Writes a new file and never touches the input. A healthy input is copied as it is (NothingToRepair).
RepairReport report = AacReconstructionProbe.Repair(input, output, reference);

Console.WriteLine($"{report.Status}: {report.Format}, confidence {report.Confidence}, {report.DroppedBytes:N0} bytes dropped");
foreach (RepairItem item in report.Items)
    Console.WriteLine($"  {item.Action} {item.Structure}: {item.Detail}");
foreach (RepairTrackReport track in report.Tracks)
    Console.WriteLine($"  track {track.Track} {track.Codec}: {track.Packets} packets, {track.DroppedPackets} dropped [{track.Confidence}]");
foreach (string warning in report.Warnings)
    Console.WriteLine($"  note: {warning}");
```

First without a reference:

```shell
dotnet run
```

```text
Unrepairable: isobmff, confidence None, 0 bytes dropped
  note: The movie box (moov) is missing or cut off and could not be rebuilt from the media data: The rebuilt tracks cannot be written to an ISO file: Track 0: a video track needs a width and height between 1 and 65535.
  note: A reference file from the same device (onemedia repair <in> <out> --reference <file>) may let the recording be rebuilt.
```

`Unrepairable`, and no output file. The trailer’s H.264 parameter sets (SPS and PPS) were stored only in the lost `moov`, as most encoders that write MP4 do, so the video can’t be set up from the stream. Now with the reference:

```shell
dotnet run -- sintel-cut.mp4 sintel-repaired.mp4 reference.mp4
```

```text
Repaired: isobmff, confidence High, 7,926 bytes dropped
  Rebuilt moov: The movie box was rebuilt from the media data: 2 track(s), codec setup taken from the reference file.
  Dropped mdat: A sample cut off by the end of the file was left out.
  track 0 h264: 648 packets, 1 dropped [High]
  track 1 aac: 1262 packets, 0 dropped [High]
  note: track 0: Pictures are reordered; presentation is delayed by 2 frame(s) after decoding (an edit list hides the delay).
  note: The media data box declares more bytes than the file holds (the recording was cut); everything up to the end of the file was scanned.
  note: The output keeps the input's brands (isom).
```

`Repaired` at `High` confidence: a new `moov` built from the media data with the codec setup from the reference, 648 video and 1,262 audio packets, and the one picture the cut went through left out. Everything after the cut is gone: the repaired file runs 27 seconds of the original 52.

## Check it against the original

ffprobe on the original and the repaired file:

```shell
for f in sintel.mp4 sintel-repaired.mp4; do
  ffprobe -v error -count_packets -show_entries stream=codec_name,nb_read_packets,duration:format=duration -of compact $f
done
```

```text
stream|codec_name=h264|duration=52.208333|nb_read_packets=1253
stream|codec_name=aac|duration=51.946667|nb_read_packets=2435
format|duration=52.208333
stream|codec_name=h264|duration=27.000000|nb_read_packets=648
stream|codec_name=aac|duration=26.922667|nb_read_packets=1262
format|duration=27.000000
```

What came back is the first 27.0 s: 648 of 1,253 video frames and 1,262 of 2,435 AAC frames. Lost: the last 25.2 s, 605 video frames and 1,173 audio frames, the part of the recording that was in the cut-off bytes. Each recovered packet is the original’s, with the same bytes, keyframe flag and time:

```shell
for s in v:0 a:0; do
  ffprobe -v error -select_streams $s -show_entries packet=pts_time,flags,size -show_data_hash MD5 -of csv sintel-repaired.mp4 > repaired.csv
  ffprobe -v error -select_streams $s -show_entries packet=pts_time,flags,size -show_data_hash MD5 -of csv sintel.mp4 \
    | head -n "$(wc -l < repaired.csv)" | cmp -s - repaired.csv && echo "$s: $(wc -l < repaired.csv) packets equal to the original's"
done
ffmpeg -v error -i sintel-repaired.mp4 -f null - && echo "decoded with no errors"
```

```text
v:0: 648 packets equal to the original's
a:0: 1262 packets equal to the original's
decoded with no errors
```

In headless Chrome 149 the cut file fails to open (“DEMUXER_ERROR_COULD_NOT_OPEN”); the repaired one plays at 854 × 480 with sound, its duration 27 s, as does the original at 52.2 s.

## A broken MKV index

The same call rebuilds the indexes of MKV/WebM, AVI, MP4, fragmented MP4 and MPEG-TS files. Here `test5.mkv`, from the Matroska project’s test files (cut from Elephants Dream, CC BY: © copyright 2006, Blender Foundation / Netherlands Media Art Institute / www.elephantsdream.org), gets its SeekHead and Cues, the index a player seeks with, overwritten with zeros:

```shell
curl -L -o test5.mkv https://raw.githubusercontent.com/ietf-wg-cellar/matroska-test-files/master/test_files/test5.mkv
cp test5.mkv test5-broken.mkv
dd if=/dev/zero of=test5-broken.mkv bs=1 seek=57 count=64 conv=notrunc status=none          # SeekHead
dd if=/dev/zero of=test5-broken.mkv bs=1 seek=31761782 count=757 conv=notrunc status=none   # Cues
dotnet run -- test5-broken.mkv test5-repaired.mkv
```

```text
Repaired: matroska, confidence High, 0 bytes dropped
  Rebuilt SeekHead: SeekHead entries pointed at the wrong positions or past the end of the file; rebuilt.
  Rebuilt Cues: 46 cue points built from the keyframes of the first video track; the old Cues were invalid.
  track 1 V_MPEG4/ISO/AVC: 1104 packets, 0 dropped [High]
  track 2 A_AAC: 2156 packets, 0 dropped [High]
  track 3 S_TEXT/UTF8: 5 packets, 0 dropped [High]
  track 4 S_TEXT/UTF8: 6 packets, 0 dropped [High]
  track 5 S_TEXT/UTF8: 6 packets, 0 dropped [High]
  track 6 S_TEXT/UTF8: 6 packets, 0 dropped [High]
  track 7 S_TEXT/UTF8: 6 packets, 0 dropped [High]
  track 8 S_TEXT/UTF8: 6 packets, 0 dropped [High]
  track 9 S_TEXT/UTF8: 6 packets, 0 dropped [High]
  track 10 A_AAC: 991 packets, 0 dropped [High]
  track 11 S_TEXT/UTF8: 6 packets, 0 dropped [High]
```

Both indexes are rebuilt, nothing is dropped, and every track is kept, listed by track number. The counts are frames, as ffprobe counts them: ffprobe reads 1,104, 2,156 and 991 packets for the video and the two AAC tracks of the repaired file. ffprobe reports “0x00 at pos 57 (0x39) invalid as first byte of an EBML number” on the broken file and nothing on the repaired one, and every packet of every stream is the original’s:

```shell
for f in test5.mkv test5-repaired.mkv; do
  ffprobe -v error -show_packets -show_data_hash MD5 -show_entries packet=stream_index,pts,flags,data_hash -of csv $f | md5sum
done
```

```text
619ac186f803beb6960847e6097d40a8  -
619ac186f803beb6960847e6097d40a8  -
```

Chrome 149 played and seeked the broken file as well: it reads the clusters in order and doesn’t need the index on a file this small. Other players and tools that seek by the index are where the damage shows.

## From the command line

`onemedia repair <in> <out> [--reference <file>]` does the same, AAC included, prints the report and exits 0 when repaired or nothing needed repair, 1 when only partly repaired and 2 when nothing could be recovered.

## Limits

- **A preview.** In the README’s words, repair was “**Checked on generated files, published samples and 10 GoPro clips, not on a corpus of device recordings.**” and “Real device recordings may have layouts the framing rules do not cover.”
- **Truncated recordings.** From the README: “**PCM audio, and video without in-stream parameter sets, need a reference file from the same device.** **5.1 AAC is dropped** (the frame probe does not take more than two channels). **Without a reference the AAC sample rate is a guess (44.1 kHz first) and the track is reported at Low confidence**; audio times may then be off. **AAC through the happy path needs the `.Audio` entry `AacReconstructionProbe.Repair` or the CLI**; `Media.Repair` in the core package has no AAC probe. Irregular frame durations of the original cannot be told from the stream. Fragmented files and an `mdat` split into several boxes are walked only up to the first `mdat`.”
- **Index repair.** From the README: “**A TS file cut at 90 % keeps one cut frame** (observed in two sweep cases …). **M2TS (192-byte packets) has no oracle sweep**: ffmpeg 7.0.2 crashes on those files. ASF, FLV and Ogg are not repaired”.
- **What is lost stays lost.** Media data that isn’t in the file can’t come back, and a sample the end of the file cuts through is dropped; the report lists each dropped byte range.
- **A new file.** The output must not exist yet; on `Unrepairable` no output is left. Rules per format: [Index repair](https://recursiveai.org/onemedia/docs/repair/) and [Truncated MP4 reconstruction](https://recursiveai.org/onemedia/docs/reconstruction/).
