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)
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.
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 ✌️