web-dev-qa-db-fra.com

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

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.

43
Matias

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.

2
Matias

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
44
ptQa

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.

13
xbakesx

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 rejoint le groupe, 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 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
2
rocuinneagain