J'essaie de supprimer quelques sections d'une vidéo à l'aide de FFmpeg.
Par exemple, imaginez si vous avez enregistré une émission à la télévision et que vous vouliez supprimer les publicités. C'est simple avec un éditeur de vidéo graphique. il vous suffit de marquer le début et la fin de chaque clip à supprimer et de sélectionner Supprimer. J'essaie de faire la même chose à partir de la ligne de commande avec FFmpeg.
Je sais comment couper un seul segment en une nouvelle vidéo comme ceci:
ffmpeg -i input.avi -ss 00:00:20 -t 00:00:05 -map 0 -codec copy output.avi
Cela coupe un clip de cinq secondes et l'enregistre en tant que nouveau fichier vidéo, mais comment puis-je faire le contraire et enregistrer la vidéo entière sans le clip spécifié et comment puis-je spécifier plusieurs clips à supprimer?
Par exemple, si ma vidéo pouvait être représentée par ABCDEFG, j'aimerais en créer une nouvelle composée d’ACDFG.
Bien que la réponse fournie par ptQa semble fonctionner, j’ai développé une autre solution qui a fait ses preuves.
Ce que je fais est essentiellement de couper une vidéo pour chaque partie de la vidéo originale que je veux inclure dans mon résultat. Plus tard, je les concatène avec le Concat Demuxer expliqué ici .
La solution est la même que ce que j’ai essayé en premier et présenté des problèmes de synchronisation. Ce que j’ai ajouté, c’est la commande - Avoid_negative_ts 1 lors de la génération des différentes vidéos. Avec cette solution, les problèmes de synchronisation disparaissent.
Eh bien, vous pouvez toujours utiliser le filtre trim
. Voici un exemple, supposons que vous souhaitiez découper des segments de 30 à 40 secondes (10 secondes) et de 50 à 80 secondes (30 secondes):
ffmpeg -i in.ts -filter_complex \
"[0:v]trim=duration=30[a]; \
[0:v]trim=start=40:end=50,setpts=PTS-STARTPTS[b]; \
[a][b]concat[c]; \
[0:v]trim=start=80,setpts=PTS-STARTPTS[d]; \
[c][d]concat[out1]" -map [out1] out.ts
Qu'est-ce que j'ai fait ici? J'ai coupé les 30 premières secondes, 40-50 secondes et 80 secondes jusqu'à la fin, puis les ai combinées dans le flux out1
avec le filtre concat
.
À propos des points de repère: nous en avons besoin car trim ne modifie pas le temps d’affichage des images. Lorsque nous coupons le décodeur, le compteur du décodeur ne voit aucune image pendant 10 secondes.
Si vous voulez aussi avoir de l'audio, vous devez faire la même chose pour les flux audio. Donc, la commande devrait être:
ffmpeg -i utv.ts -filter_complex \
"[0:v]trim=duration=30[av];[0:a]atrim=duration=30[aa];\
[0:v]trim=start=40:end=50,setpts=PTS-STARTPTS[bv];\
[0:a]atrim=start=40:end=50,asetpts=PTS-STARTPTS[ba];\
[av][bv]concat[cv];[aa][ba]concat=v=0:a=1[ca];\
[0:v]trim=start=80,setpts=PTS-STARTPTS[dv];\
[0:a]atrim=start=80,asetpts=PTS-STARTPTS[da];\
[cv][dv]concat[outv];[ca][da]concat=v=0:a=1[outa]" -map [outv] -map [outa] out.ts
Je ne parviens jamais à faire fonctionner la solution ptQa, principalement parce que je ne peux jamais comprendre la signification des erreurs contenues dans les filtres ni la façon de les réparer. Ma solution semble un peu moins pratique car elle peut laisser des traces, mais si vous la mettez dans un script, le nettoyage peut être automatisé. J'aime aussi cette approche, car si quelque chose ne va pas à l'étape 4, vous obtenez les étapes 1 à 3 terminées, ce qui permet de remédier aux erreurs est un peu plus efficace.
La stratégie de base consiste à utiliser -t
et -ss
pour obtenir des vidéos de chaque segment de votre choix, puis regrouper toutes les parties de votre version finale.
Supposons que vous ayez 6 segments ABCDEF toutes les 5 secondes et que vous vouliez A (0-5 secondes), C (10-15 secondes) et E (20-25 secondes):
ffmpeg -i abcdef.tvshow -t 5 a.tvshow -ss 10 -t 5 c.tvshow -ss 20 -t 5 e.tvshow
ou
ffmpeg -i abcdef.tvshow -t 0:00:05 a.tvshow -ss 0:00:10 -t 0:00:05 c.tvshow -ss 0:00:20 -t 0:00:05 e.tvshow
Cela créera les fichiers a.tvshow, c.tvshow et e.tvshow. Le -t
indique la durée de chaque clip. Par conséquent, si c dure 30 secondes, vous pouvez le transmettre en 30 ou 0:00:30. L'option -ss
indique la distance à parcourir pour accéder à la vidéo source. Elle est donc toujours relative au début du fichier.
Ensuite, une fois que vous avez plein de fichiers vidéo, je crée un fichier ace-files.txt
comme ceci:
file 'a.tvshow'
file 'c.tvshow'
file 'e.tvshow'
Notez le "fichier" au début et le nom de fichier échappé après cela.
Puis la commande:
ffmpeg -f concat -i ace-files.txt -c copy ace.tvshow
Cela concatre tous les fichiers dans abe-files.txt
ensemble, en copiant leurs codecs audio et vidéo et crée un fichier ace.tvshow
qui ne devrait contenir que les sections a, c et e. Ensuite, n'oubliez pas de supprimer ace-files.txt
, a.tvshow
, c.tvshow
et e.tvshow
.
Disclaimer : Je ne sais pas à quel point cela est (in) efficace par rapport aux autres approches en termes de ffmpeg
mais, à mes fins, cela fonctionne mieux. J'espère que ça aide quelqu'un.
J'ai fait un script pour accélérer le montage de la télévision enregistrée. Le script vous demande les heures de début et de fin des segments que vous souhaitez conserver et les divise en fichiers. Il vous donne des options, vous pouvez:
Vidéo de celui-ci en action: ici
Laissez-moi savoir ce que vous pensez.
#!/bin/bash
/bin/date >>segmenter.log
function segment (){
while true; do
echo "Would you like to cut out a segment ?"
echo -e "1) Yes\n2) No\n3) Quit"
read CHOICE
if [ "$CHOICE" == "3" ]; then
exit
Elif [ "$CHOICE" == "2" ]; then
clear
break
Elif [ "$CHOICE" == "1" ]; then
clear
((segments++))
echo "What time does segment $segments start ?"
read SEGSTART
clear
echo -e "Segment $segments start set to $SEGSTART\n"
echo "What time does segment $segments end ?"
read SEGEND
clear
echo -e "Segment $segments end set to $SEGEND\n"
break
else
clear
echo -e "Bad option"
segment "$segments"
fi
done
if [ "$CHOICE" == "1" ]; then
echo "Cutting file $file video segment $segments starting at $SEGSTART and ending at $SEGEND"
ffmpeg -i "$file" -ss $SEGSTART -to $SEGEND -map 0:0 -map 0:1 -c:a copy -c:v copy "$filename-part$segments.$extension" >> segmenter.log 2>&1
clear
echo -e "Cut file $filename-part$segments.$extension starting at $SEGSTART and ending at $SEGEND\n"
segment "$segments"
fi
}
file="$1"
filename="${file%.*}"
extension="${file##*.}"
clear
segments=0
segment "$segments"
clear
if (("$segments"==1)); then
mv $filename"-part1."$extension "$filename-segmented.$extension"
Elif (("$segments">1)); then
echo "Would you like to join the segments into one file ?"
OPTIONS="Yes No Quit"
select opt in $OPTIONS; do
clear
if [ "$opt" == "Quit" ]; then
exit
Elif [ "$opt" == "Yes" ]; then
clear
echo "Joining segments"
ffmpeg -f concat -i <(for f in $filename"-part"*$extension; do echo "file '$(pwd)/$f'"; done) -c:a copy -c:v copy "$filename-segmented.$extension" >> segmenter.log 2>&1
clear
echo "Would you like to delete the part files ?"
select opt in $OPTIONS; do
clear
if [ "$opt" == "Quit" ]; then
exit
Elif [ "$opt" == "Yes" ]; then
for f in $filename"-part"*$extension; do rm $f; done
break
Elif [ "$opt" == "No" ]; then
break
else
clear
echo -e "Bad option\n"
fi
done
break
clear
Elif [ "$opt" == "No" ]; then
exit
else
clear
echo -e "Bad option\n"
fi
done
fi
echo "Would you like to replace the original file with the result of your changes ?"
OPTIONS="Yes No Quit"
select opt in $OPTIONS; do
clear
if [ "$opt" == "Quit" ]; then
exit
Elif [ "$opt" == "Yes" ]; then
rm $file
mv "$filename-segmented.$extension" $file
break
Elif [ "$opt" == "No" ]; then
break
else
clear
echo -e "Bad option\n"
fi
done