Couper une vidéo

Parfois on a besoin de récupérer une partie d'une vidéo (pour récupérer une chanson d'un concert ou l'opening d'un épisode, ...).

tl;dr pour couper une vidéo il faut faire cette commande:

$ ffmpeg -i <vidéo> -ss <timestamp de début> -t <durée à extraire de la vidéo> -map 0:v -map 0:a -c:v libx264 -preset slow -crf 19 -c:a copy out.mkv

En faisant varier le -crf 19 en fonction de la vidéo et de la qualité recherchée.

Explications

Techniquement couper une vidéo est un peu compliqué car la plupart des images qui composent une vidéo ne sont pas encodées directement dans le fichier mais définies comme des modifications d'autres images de la vidéo. Les seules images qui sont présentes dans la vidéo sont appelées les I frames, sur le principes elles sont souvent similaires à des images JPEG.

Donc pour couper une vidéo à une image donnée il faut soit que l'image soit une I frame, soit il faut garder les images I auquels cette image fait référence, soit il faut réencoder la vidéo.

On pourrait faire une copie de la vidéo avec ffmpeg mais dans le cas où on ne coupe pas la vidéo sur une I frame la vidéo pourrait être mal lue dans certains logiciels (comme Aegisub par exemple :/).

$ ffmpeg -ss <timestamp de début> -i <vidéo> -t <durée à extraire de la vidéo> -map 0:v -map 0:a -c copy out.mkv

ffmpeg semble prendre la première I frame avant la timestamp donnée et décale le début de la vidéo dans le container. Il faut donc comprendre que dans le fichier il y a un bout de la vidéo qui ne devrait pas être joué. Certains logiciels (particulièrement vieux) ne prennent pas en compte cette information et afficher les premières images de la vidéo et donc décaler l'audio et la vidéo.

Bref ça marchera probablement un jour de couper une vidéo de cette façon mais pour l'instant c'est embêtant.

Aussi à noter que ce n'est pas un problème pour couper la fin de la vidéo (théoriquement un logiciel con pourrait lire quand même les frames après la fin de la vidéo mais ça ne désynchronisera pas l'audio). Donc si c'est uniquement pour couper la fin de la vidéo on peut utiliser cette commande:

$ ffmpeg -i <vidéo> -t <durée à extraire de la vidéo> -map 0:v -map 0:a -c copy out.mkv

Couper une vidéo sans tout réencoder et sans tout casser

Ok donc c'est bien tout ça mais c'est un peu chiant de devoir tout réencoder pour si peu. Il y a donc une façon de faire qui nécessite uniquement de réencoder le début de la vidéo. Globalement la seule partie de la vidéo qu'il faut réencoder est celle entre la première I frame avant la frame où on coupe la vidéo et la première I frame après la frame où on coupe la vidéo. Donc c'est ce qu'on va faire ;)

Pour ça je vais utiliser mkvtoolnix car ça simplifie beaucoup de choses pour couper la vidéo.

mkvmerge --split timestamps:XX:XX:XX.XXX -o split.mkv <la vidéo à couper>

XX:XX:XX.XXX dois être le temps auquel on voudra couper la vidéo. D'après ce que je vois la timestamp doit forcément être dans ce format débile mais ça marche

En sortie mkvmerge devrait créer 2 fichiers: split-001.mkv et split-002.mkv split-001.mkv aura la vidéo jusqu'à la première I frame après la timestamp donnée en paramètre et split-002.mkv aura le reste de la vidéo.

Ensuite on réencode le début de la vidéo.

$ ffmpeg -i split-001.mkv -ss XX:XX:XX.XXX -map 0:v -map 0:a -c:v libx264 -crf 19 -preset slow -c:a copy split_reenc.mkv

Et après on concatène les 2 vidéos. Il faut créer un fichier qu'on va donner à ffmpeg:

file '<chemin/vers/split_reenc.mkv>'
file '<chemin/vers/split-002.mkv>'

Puis on concatène les deux vidéos, en assumant que le fichier créé au dessus s'appelle concat.txt:

ffmpeg -f concat -safe 0 -i concat.txt -map 0:v -map 0:a -c copy -t <durée à extraire de la vidéo> out.mkv

Ça donnera une vidéo coupée qui fonctionnera même dans les logiciels les plus débiles.

Il est possible de faire tout ça avec ce script bash: proper_split

Pour l'appeler:

$ ./proper_split <vidéo à couper> XX:XX:XX.XXX <durée à extraire de la vidéo>

XX:XX:XX.XXX étant encore la timestamp du début de la partie de la vidéo qu'on veut extraire dans le format de la commande mkvmerge

Le script devrait créer un fichier ayant le nom de la vidéo d'origine suivi de '_cut' avec la vidéo coupée.