84 votes

Comment puis-je supprimer plusieurs segments d'une vidéo en utilisant FFmpeg ?

J'essaie de supprimer quelques sections d'une vidéo en utilisant FFmpeg.

Par exemple, imaginez que vous enregistriez une émission de télévision et que vous vouliez couper les publicités. C'est très simple avec un éditeur vidéo graphique ; il suffit de marquer le début et la fin de chaque clip à supprimer, puis de sélectionner "supprimer". J'essaie de faire la même chose en ligne de commande avec FFmpeg.

Je sais comment couper un seul segment à un nuevo vidéo comme ça :

ffmpeg -i input.avi -ss 00:00:20 -t 00:00:05 -map 0 -codec copy output.avi

Cela permet de couper un clip de cinq secondes et de l'enregistrer dans un nouveau fichier vidéo, mais comment 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 peut être représentée par ABCDEFG, j'aimerais en créer une nouvelle qui serait composée de ACDFG.

71voto

ptQa Points 1979

Eh bien, vous pouvez toujours utiliser le trim filtre pour cela. Voici un exemple, supposons que vous voulez couper trois segments au début et à la fin de la vidéo ainsi qu'au milieu :

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

Ce que j'ai fait ici ? J'ai coupé les 30 premières secondes, les 40-50 secondes et les 80 secondes jusqu'à la fin, puis je les ai combinées en un flux. out1 avec le concat filtre, laissant 30-40 sec (10 sec) et 50-80 sec (30 sec).

A propos des setpts : nous en avons besoin car le trim ne modifie pas le temps d'affichage de l'image, et lorsque nous coupons 10 secondes, le compteur du décodeur ne voit pas d'images pendant ces 10 secondes.

Si vous voulez avoir de l'audio aussi, 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

39voto

Johan Points 51

Je n'arrive jamais à faire fonctionner la solution de ptQa, surtout parce que je n'arrive jamais à comprendre ce que signifient les erreurs des filtres ou comment les corriger. Ma solution semble un peu plus maladroite parce qu'elle peut laisser derrière elle un désordre, mais si vous la lancez dans un script, le nettoyage peut être automatisé. J'aime aussi cette approche parce que si quelque chose ne va pas à l'étape 4, vous vous retrouvez avec les étapes 1-3 complétées, donc la récupération des erreurs est un peu plus efficace.

La stratégie de base consiste à utiliser -t y -ss pour obtenir des vidéos de chaque segment que vous voulez, puis réunir toutes les parties pour votre version finale.

Disons que vous avez 6 segments ABCDEF de 5 secondes chacun et que vous voulez A (0-5 secondes), C (10-15 secondes) et E (20-25 secondes), vous feriez ceci :

ffmpeg -i abcdef.tvshow -t 5 a.tvshow -ss 10 -t 5 c.tvshow -ss 20 -t 5 e.tvshow

o

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 donnera les fichiers a.tvshow, c.tvshow et e.tvshow. Le site -t indique la durée de chaque clip, donc si c dure 30 secondes, vous pouvez passer en 30 ou 0:00:30. Le site -ss indique jusqu'où il faut aller dans la vidéo source, c'est-à-dire toujours par rapport au début du fichier.

Ensuite, une fois que vous avez un tas de fichiers vidéo, je fais un fichier ace-files.txt comme ça :

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 le commandement :

ffmpeg -f concat -i ace-files.txt -c copy ace.tvshow

Cela concasse tous les fichiers dans abe-files.txt ensemble, en copiant leurs codecs audio et vidéo et en créant un fichier ace.tvshow qui ne devrait être que les sections a, c et e. Ensuite, n'oubliez pas de supprimer ace-files.txt , a.tvshow , c.tvshow y e.tvshow .

Avis de non-responsabilité : Je n'ai aucune idée de l'efficacité (ou de l'inefficacité) de cette approche par rapport aux autres approches en termes de ffmpeg mais pour mes besoins, cela fonctionne mieux. J'espère que cela aidera quelqu'un.

21voto

jackncoke Points 613

Pour ceux qui ont du mal à suivre l'approche de la ptQa, il existe une façon un peu plus simple de procéder. Plutôt que de concaténer chaque étape du processus, il suffit de les faire toutes à 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=20,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 vous le souhaitez, puis concattez-les toutes en une seule fois, 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 :

ffmpeg -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

10voto

pulp_user Points 201

Edit : Cette approche désynchronisera notablement l'audio et la vidéo si le nombre de segments devient trop grand (>5-ish). Pour quelques segments, c'est parfait.

Cela va le faire :

 ffmpeg -i input.avi -vf "select='1-between(t,20,25)', setpts=N/FRAME_RATE/TB" -af "aselect='1-between(t,20,25)', asetpts=N/SR/TB" output.avi

Cette commande éliminera le segment de 20 secondes à 25 secondes et conservera tout le reste. Au lieu d'un exclusion vous pouvez aussi faire une inclusion une liste comme celle-ci :

ffmpeg -i input.avi -vf "select='lt(t,20)+between(t,790,810)+gt(t,1440)', setpts=N/FRAME_RATE/TB" -af "aselect='lt(t,20)+between(t,790,810)+gt(t,1440)', asetpts=N/SR/TB" output.avi

Cela permet de conserver 3 segments de la vidéo originale (0-20 secondes, 790-810 secondes, et 1440-fin secondes), et de supprimer tout le reste.

Les éléments clés (du deuxième exemple, car il est plus compliqué) :

  • -vf est l'option pour une expression de filtre vidéo. Nous utilisons le filtre select, qui prend une expression, l'évalue pour chaque image d'entrée, et conserve l'image si l'expression est égale à 1, ou la rejette si l'expression est égale à 0. (Il y a en fait un peu plus de nuances, mais ce n'est pas vraiment pertinent pour votre problème. Regardez aquí si vous voulez connaître l'histoire complète).

  • lt(t,20) est la première partie de l'expression du filtre de sélection. lt signifie "moins que" et t est l'horodatage de la trame en secondes qui est évaluée, donc cela donnera la valeur 1 pour toute trame avec t<20s

  • between(t,790,810) sera évalué à 1 pour toute image entre 790s et 810s

  • gt(t, 1440) (plus grand que) deviendra 1 pour toute image après 1440s

  • Nous ajoutons ensuite toutes les conditions, afin de les rendre logiques.

  • setpts=N/FRAME_RATE/TB est nécessaire pour nous fournir le t que nous utilisons dans l'expression logique. ffmpeg semble penser en échantillons/trames ici, et non en secondes, donc nous utilisons cette expression pour convertir en secondes.

  • -af est l'option pour une expression de filtre audio. Le filtre audio fonctionne de la même manière que le filtre vidéo, sauf que pour convertir en secondes, nous utilisons la fréquence d'échantillonnage. SR de l'audio, et non le FRAME_RATE de la vidéo.

Vous pouvez devenir fou avec l'expression de filtre et ajouter de nouveaux segments en ajoutant un between(t,...,...) et combinez les termes selon vos besoins. La liste d'exclusion que vous vouliez utilise simplement le fait que nous pouvons inverser notre expression de filtre en la soustrayant de 1, de sorte que tout ce qui est écarté sera maintenant conservé, et vice versa.

Assurez-vous que vous avez le même filtre de sélection pour l'audio et la vidéo, sinon ils seront désynchronisés !

Notez que vous ne pourrez pas utiliser la fonction -codec copy avec cette solution, puisque copy indique à ffmpeg de ne pas décoder la vidéo. Puisque le filtre vidéo est évalué pour les images décodées, nous doit décoder en premier.

7voto

hailstorm Points 61

J'ai fait un script pour accélérer le montage des enregistrements TV. Le script vous demande les heures de début et de fin des segments que vous voulez conserver et les répartit dans des fichiers. Il vous donne des options, vous pouvez :

  • Prenez un ou plusieurs segments.
  • Vous pouvez combiner les segments en un seul fichier résultant.
  • Après l'adhésion, vous pouvez conserver ou supprimer les fichiers partiels.
  • Vous pouvez conserver le fichier original ou le remplacer par votre nouveau fichier.

Faites-moi savoir ce que vous en 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

SistemesEz.com

SystemesEZ est une communauté de sysadmins où vous pouvez résoudre vos problèmes et vos doutes. Vous pouvez consulter les questions des autres sysadmins, poser vos propres questions ou résoudre celles des autres.

Powered by:

X