Comment puis-je supprimer plusieurs segments d'une vidéo à l'aide de FFmpeg?

51

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 dans 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 l’inverse 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.

Matias
la source
1
Ce n'est pas ce que je demande, je voudrais tout avoir dans un "épisode", mais cela aurait différentes sections de la vidéo originale.
Matias
@ Matias, si vous demandiez comment couper quelques clips de la vidéo et laisser le reste tel quel, ce serait une chose, mais vous souhaitez en prendre quelques clips et les combiner avec des clips d'autres vidéos, ce qui Ce n'est pas une question distincte et unique. Vous devez faire ce que les autres questions ont demandé pour obtenir les segments séparés, puis les combiner.
Synetech
1
@ Synetech merci pour vos réponses. Je ne veux pas les combiner avec des clips d'autres vidéos. Je veux juste supprimer certaines parties des vidéos. Par exemple, si ma vidéo pouvait être représentée par ABCDEFG, j'aimerais en créer une nouvelle composée d’ACDFG.
Matias
@ Synetech Ce n'est pas moi, c'est Tog qui doit avoir mal compris.
Matias

Réponses:

47

Eh bien, vous pouvez toujours utiliser le trimfiltre pour cela. 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é 30 premières secondes, 40-50 secondes et 80 secondes jusqu'à la fin, puis je les ai combinées en flux out1avec le concatfiltre.

À propos des points de repère: nous en avons besoin car trim ne modifie pas le temps d’affichage des images et, lorsque nous coupons le compteur de 10 secondes, le compteur du décodeur ne voit aucune image pendant ces 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
ptQa
la source
Elle est bonne! Pourquoi avons-nous besoin de setpts? Pourriez-vous ajouter l'explication?
Rajib
Ok, voir la réponse mise à jour.
ptqa
4
+1 Cela devrait vraiment être sélectionné comme réponse. Il convient mieux aux "segments multiples" et évite d'avoir à exécuter plusieurs commandes les unes après les autres. (sauf si une autre façon est plus efficace en
termes de
1
Comment feriez-vous pour conserver les données de sous-titres lorsque vous sautez des images?
Andrew Scagnelli
1
C'est très inhumain.
Golopot
15

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 également 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 plus efficacement.

La stratégie de base consiste à utiliser -tet -ssà obtenir des vidéos de chaque segment souhaité, puis à réunir toutes les parties de votre version finale.

Supposons que vous ayez 6 segments ABCDEF toutes les 5 secondes et que vous souhaitiez 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 -tdit combien de temps chaque clip est, donc si c est de 30 secondes, vous pourriez passer en 30 ou 0:00:30. L' -ssoption indique jusqu'où il faut aller dans la vidéo source, donc c'est toujours relatif au début du fichier.

Ensuite, une fois que vous avez plusieurs fichiers vidéo, je crée un fichier ace-files.txtcomme celui-ci:

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 concats tous les fichiers abe-files.txtensemble, en copiant leurs codecs audio et vidéo et crée un fichier ace.tvshowqui ne devrait contenir que les sections a, c et e. Alors souvenez - vous de supprimer ace-files.txt, a.tvshow, c.tvshowet e.tvshow.

Déni de responsabilité : Je ne sais pas à quel point cela est (in) efficace par rapport aux autres approches ffmpegmais en ce qui concerne mes objectifs, cela fonctionne mieux. J'espère que ça aide quelqu'un.

xbakesx
la source
4
celui-ci est très compréhensible pour les débutants comme moi, merci
juanpastas
4
Notez que si vous utilisez bash, vous pouvez éviter la création d'un fichier ( ace-files.txtdans votre exemple) avec:ffmpeg -f concat -i <(printf "file '%s'\n" $(pwd)/prefix_*.tvshow) -c copy output.tvshow
bufh
1
C'était vraiment utile, mais je devais supprimer les guillemets simples des noms de fichiers ou cela ne fonctionnait pas.
Tchaymore
Lors de la concaténation de vidéos, il existe une courte vidéo noire et un son silencieux entre elles, environ 25 ms. Des idées pour s'en débarrasser?
Antonio Bonifati 'Farmboy' le
1
Merci, @bufh, je me demandais comment faire ça! Juste une note qui puisse aider les autres: comme @pkSML, expliquez ci - dessous , vous devrez également ajouter des éléments -safe 0à la commande ffmpeg si vous utilisez des chemins de fichiers absolus pour que cela fonctionne. Vous pouvez également supprimer la $(pwd)/partie de la sous-commande bash.
waldyrious
5

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:

  • Prenez un ou plusieurs segments.
  • Vous pouvez combiner les segments dans un fichier résultant.
  • Après avoir adhéré, vous pouvez conserver ou supprimer les fichiers de pièce.
  • Vous pouvez conserver le fichier d'origine ou le remplacer par votre nouveau fichier.

Vidéo 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
rocuinneagain
la source
2
Ce script est génial! Juste une modification puisque vous utilisez des chemins absolus pour la concaténation: add safe=0, c. ffmpeg -f concat -safe 0 -i ...-à- d. Voir ffmpeg.org/ffmpeg-all.html#Options-36
pkSML le
2

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.

Matias
la source
1

Pour ceux qui ont du mal à suivre l'approche de ptQa, il existe un moyen légèrement plus simple de s'y prendre. Plutôt que de concaténer chaque étape, faites-les simplement à la fin.

Pour chaque entrée, définissez une paire A / V:

//Input1:
[0:v]trim=start=10:end=20,setpts=PTS-STARTPTS,format=yuv420p[0v];
[0:a]atrim=start=10:end=10,asetpts=PTS-STARTPTS[0a];
//Input2:
[0:v]trim=start=30:end=40,setpts=PTS-STARTPTS,format=yuv420p[1v];
[0:a]atrim=start=30:end=40,asetpts=PTS-STARTPTS[1a];
//Input3:
[0:v]trim=start=30:end=40,setpts=PTS-STARTPTS,format=yuv420p[2v];
[0:a]atrim=start=30:end=40,asetpts=PTS-STARTPTS[2a];

Définissez autant de paires que nécessaire, puis concatérez-les toutes en un seul passage, où n = nombre total d'entrées.

[0v][0a][1v][1a][2v][2a]concat=n=3:v=1:a=1[outv][outa] -map [outv] -map [outa] out.mp4

Cela peut facilement être construit en boucle.

Une commande complète qui utilise 2 entrées pourrait ressembler à ceci:

 -y -i in.mp4 -filter_complex 
[0:v]trim=start=10.0:end=15.0,setpts=PTS-STARTPTS,format=yuv420p[0v];
[0:a]atrim=start=10.0:end=15.0,asetpts=PTS-STARTPTS[0a];
[0:v]trim=start=65.0:end=70.0,setpts=PTS-STARTPTS,format=yuv420p[1v];
[0:a]atrim=start=65.0:end=70.0,asetpts=PTS-STARTPTS[1a];[0v][0a][1v]
[1a]concat=n=2:v=1:a=1[outv][outa] -map [outv] -map [outa] out.mp4
Shawnblais
la source