Réparer un fichier MP4 corrompu

Il arrive parfois d'avoir des fichiers vidéo de conteneur MPEG4 illisibles. Cette astuce peut aider à les restaurer.

On va travailler sur un exemple réel qui m'est arrivé, on nommera le fichier à réparer broken.MP4 :

La lecture avec le player mpv échoue

1mpv broken.MP4
2[ffmpeg/demuxer] mov,mp4,m4a,3gp,3g2,mj2: moov atom not found
3[lavf] avformat_open_input() failed
4[ffmpeg/demuxer] mov,mp4,m4a,3gp,3g2,mj2: moov atom not found
5[lavf] avformat_open_input() failed
6Failed to recognize file format.
7Exiting... (Errors when loading file)

Le mediainfo n'arrive pas non plus à le parser, il semble indiquer que le fichier a été tronqué.

 1mediainfo broken.MP4
 2General
 3Complete name                            : broken.MP4
 4Format                                   : MPEG-4
 5Format profile                           : Base Media
 6Codec ID                                 : isom (mp41)
 7File size                                : 160 MiB
 8Conformance errors                       : 2
 9 mdat                                    : Yes
10  General compliance                     : Element size 192756737 is more than maximal permitted size 167772124 (offset 0x24)
11 MPEG-4                                  : Yes
12  General compliance                     : File size 167772160 is less than expected size 192756773 (offset 0x24)

L'outil untrunc peut dans certains cas réparer les fichiers de conteneur MPEG4, en se basant sur un fichier valide ayant les mêmes caractéristiques techniques (issu du même encodeur). On suppose qu'on a à disposition un fichier clean.MP4 qui est dans ce cas. L'astuce est de regénérer des entêtes correctes à partir du fichier sain. untrunc est installable via Docker ce qui évite une phase de compilation et le rend un peu plus portable. Le service Docker doit être actif sur la machine qui exécute ces commandes. Créons d'abord l'image Docker :

1git clone https://github.com/anthwlock/untrunc
2cd untrunc
3docker build -t untrunc .

Puis lançons la réparation proprement dite :

1docker run -v .:/mnt untrunc /mnt/clean.MP4 /mnt/broken.MP4

Le processus doit générer le fichier broken_fixed.MP4

On relance un mediainfo :

 1General
 2Complete name                            : broken_fixed.MP4
 3Format                                   : MPEG-4
 4Format profile                           : Base Media
 5Codec ID                                 : isom (mp41)
 6File size                                : 162 MiB
 7Duration                                 : 52 min 47 s
 8Overall bit rate mode                    : Variable
 9Overall bit rate                         : 428 kb/s
10Frame rate                               : 25.000 FPS
11Encoded date                             : 1970-01-01 00:00:00 UTC
12Tagged date                              : 1970-01-01 00:00:00 UTC
13Writing application                      : Lavf50.4.0
14Comment                                  : QuickTime 6.0 or greater
15
16Video
17ID                                       : 1
18Format                                   : AVC
19Format/Info                              : Advanced Video Codec
20Format profile                           : Main@L3
21Format settings                          : CABAC / 2 Ref Frames
22Format settings, CABAC                   : Yes
23Format settings, Reference frames        : 2 frames
24Format settings, GOP                     : M=3, N=120
25Codec ID                                 : avc1
26Codec ID/Info                            : Advanced Video Coding
27Duration                                 : 52 min 47 s
28Bit rate                                 : 360 kb/s
29Width                                    : 416 pixels
30Height                                   : 304 pixels
31Display aspect ratio                     : 4:3
32Frame rate mode                          : Constant
33Frame rate                               : 25.000 FPS
34Color space                              : YUV
35Chroma subsampling                       : 4:2:0
36Bit depth                                : 8 bits
37Scan type                                : Progressive
38Bits/(Pixel*Frame)                       : 0.114
39Stream size                              : 136 MiB (84%)
40Writing library                          : h264
41Encoded date                             : 1970-01-01 00:00:00 UTC
42Tagged date                              : 1970-01-01 00:00:00 UTC
43Codec configuration box                  : avcC
44
45Audio
46ID                                       : 2
47Format                                   : AAC LC
48Format/Info                              : Advanced Audio Codec Low Complexity
49Codec ID                                 : mp4a-40-2
50Duration                                 : 52 min 47 s
51Bit rate mode                            : Variable
52Bit rate                                 : 64.2 kb/s
53Channel(s)                               : 2 channels
54Channel layout                           : L R
55Sampling rate                            : 48.0 kHz
56Frame rate                               : 46.875 FPS (1024 SPF)
57Compression mode                         : Lossy
58Stream size                              : 24.2 MiB (15%)
59Encoded date                             : 1970-01-01 00:00:00 UTC
60Tagged date                              : 1970-01-01 00:00:00 UTC

Le fichier est désormais valide !

Il était sensé faire 1h00, il ne fait plus que 52 min mais c'est toujours ça de sauvé, et ça confirme la thèse du fichier tronqué.

Si on essaye de le lire avec mpv, la lecture se fait correctement et sans erreur :

1mpv broken_fixed.MP4
2● Video  --vid=1  (h264 416x304 25 fps) [default]
3● Audio  --aid=1  (aac 2ch 48000 Hz 64 kbps) [default]
4AO: [coreaudio] 48000Hz stereo 2ch floatp
5VO: [gpu] 416x304 => 416x312 yuv420p
6AV: 00:00:01 / 00:52:47 (0%) A-V: -0.002
7Exiting... (Quit)

Capture d'écran du fichier réparé

Destruction contrôlée 🧨

Dans cette autre expérience on va :

  • Générer une mire vidéo de test de 30 sec, toujours de conteneur MP4
  • La tronquer
  • La réparer
  • Analyser l'impact du positionnement de l'entête atom moov

Génération de la mire 📺

1ffmpeg -f lavfi -i testsrc=size=640x360:rate=25 \
2-vf "drawtext=text='':x=(w-tw)/2:y=h-(2*lh):fontsize=40:fontcolor=white:box=1:boxcolor=black:boxborderw=4:timecode='00\:00\:00\:00':timecode_rate=25,format=yuv420p" \
3-t 30 \
4mire_1.mp4

Le fichier mire_1.mp4 généré fait 299672 octets.

Capture d'écran de la mire de test

Le hachoir 🪚

On le tronque disons au 2/3 :

1dd bs=1 count=200000 if=mire_1.mp4 of=mire_1_broken.mp4
 1mediainfo mire_1_broken.mp4
 2General
 3Complete name                            : mire_1_broken.mp4
 4Format                                   : MPEG-4
 5Format profile                           : Base Media
 6Codec ID                                 : isom (isom/iso2/avc1/mp41)
 7File size                                : 195 KiB
 8Conformance errors                       : 2
 9 mdat                                    : Yes
10  General compliance                     : Element size 289989 is more than maximal permitted size 199952 (offset 0x30)
11 MPEG-4                                  : Yes
12  General compliance                     : File size 200000 is less than expected size 290037 (offset 0x30)

Le fichier est bien devenu illisible avec mpv.

La réparation 🧰

On le répare en utilisant la version d'origine comme fichier sain :

 1docker run -v .:/mnt untrunc /mnt/mire_1.mp4 /mnt/mire_1_broken.mp4
 2Composition time offset atom found. Out of order samples possible.
 3Info: version 'v367-13cafed-dirty' using ffmpeg '4.4.2-0ubuntu0.22.04.1' Lavc58.134.100
 4Info: reading /mnt/mire_1.mp4
 5Info: parsing healthy moov atom ...
 6
 7Info: reading mdat from truncated file ...
 8Error: unable to find correct codec -> premature end (~96.66%)
 9       try '-s' to skip unknown sequences
10
11Info: Found 500 packets ( avc1: 500 avc1-keyframes: 2 )
12Info: Duration of avc1: 20s  (20000 ms)
13Info: saving /mnt/mire_1_broken.mp4_fixed.mp4
14
15185 warnings were hidden!

Taille des fichiers :

1200000 mire_1_broken.mp4
2196088 mire_1_broken.mp4_fixed.mp4
3299672 mire_1.mp4

mire_1_broken.mp4_fixed.mp4 est bien à nouveau lisible par mpv, sa nouvelle durée annoncée est maintenant 20 sec.

Le atom moov header ⚛️

Pour ce dernier test, on va générer la même mire, mais on va mettre le header atom moov en début de fichier, car par défaut il est situé en fin de fichier. Pour celà il faut ajouter l'option -movflags faststart :

1ffmpeg -f lavfi -i testsrc=size=640x360:rate=25 \
2-vf "drawtext=text='':x=(w-tw)/2:y=h-(2*lh):fontsize=40:fontcolor=white:box=1:boxcolor=black:boxborderw=4:timecode='00\:00\:00\:00':timecode_rate=25,format=yuv420p" \
3-t 30 \
4-movflags faststart \
5mire_2.mp4

Notons que le fichier mire_2.mp4 fait exactement la même taille 299672 octets que mire_1.mp4.

On le tronque disons au 2/3 :

1dd bs=1 count=200000 if=mire_2.mp4 of=mire_2_broken.mp4

Et là surprise, le fichier se lance (et annonce une durée de 30 sec) :

 1mpv mire_2_broken.mp4
 2client removed during hook handling
 3● Video  --vid=1  (h264 640x360 25 fps) [default]
 4VO: [gpu] 640x360 yuv420p
 5[ffmpeg/demuxer] mov,mp4,m4a,3gp,3g2,mj2: Packet corrupt (stream = 0, dts = 249856).
 6[ffmpeg] NULL: Invalid NAL unit size (813 > 523).
 7[ffmpeg] NULL: missing picture in access unit with size 527
 8[ffmpeg/demuxer] mov,mp4,m4a,3gp,3g2,mj2: stream 0, offset 0x30e62: partial file
 9[ffmpeg/video] h264: Invalid NAL unit size (813 > 523).
10[ffmpeg/video] h264: Error splitting the input into NAL units.
11Error while decoding frame!
12V: 00:00:19 / 00:00:30 (65%)
13Exiting... (End of file)

Évidemment au bout de 20 sec, le fichier coupe avec quelques warnings.

mediainfo constate aussi qu'il y a un problème :

 1mediainfo mire_2_broken.mp4
 2General
 3Complete name                            : mire_2_broken.mp4
 4Format                                   : MPEG-4
 5Format profile                           : Base Media
 6Codec ID                                 : isom (isom/iso2/avc1/mp41)
 7File size                                : 195 KiB
 8Duration                                 : 30 s 0 ms
 9Overall bit rate                         : 53.3 kb/s
10Frame rate                               : 25.000 FPS
11Writing application                      : Lavf61.7.100
12Conformance errors                       : 3
13 mdat                                    : Yes
14  General compliance                     : Element size 289989 is more than maximal permitted size 190317 (offset 0x25D3) / Element size 817 is more than maximal permitted size 527 (conf, offset 0x30B31)
15 MPEG-4                                  : Yes
16  General compliance                     : File size 200000 is less than expected size 299672 (offset 0x25D3)

On a ainsi pu démontrer que la présence de ce moov atom est essentielle au bon décodage d'un fichier de conteneur MPEG4.

Pour plus de résilience, on peut mettre cette entête en début de fichier mais celà nécessite de procéder à une seconde passe sur l'ensemble du fichier à la fin de l'écriture de celui-ci.

Ce type de corruption de données peut se produire sur un support ancien qui a du mal à se lire mais également avec un enregistreur vidéo dont on arrache le câble d'alimentation en plein concert, ou bien un crash de drone, ou encore un téléchargement interrompu, bref beaucoup de cas de figure 😊

Voilà, en espérant que cette astuce puisse vous rendre service ✌️

Ressources

comments powered by Disqus