¿Cómo puedo eliminar múltiples segmentos de un video usando FFmpeg?

51

Estoy tratando de eliminar algunas secciones de un video usando FFmpeg.

Por ejemplo, imagina si grabaste un programa en televisión y quisiste recortar los comerciales. Esto es simple con un editor de video GUI; solo marca el comienzo y el final de cada clip que se va a eliminar y selecciona eliminar. Estoy tratando de hacer lo mismo desde la línea de comandos con FFmpeg.

Sé cómo cortar un solo segmento a un nuevo video así:

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

Esto corta un clip de cinco segundos y lo guarda como un nuevo archivo de video, pero ¿cómo puedo hacer lo contrario y guardar todo el video sin el clip especificado, y cómo puedo especificar que se eliminen varios clips?

Por ejemplo, si mi video pudiera estar representado por ABCDEFG, me gustaría crear uno nuevo que consistiría en ACDFG.

Matias
fuente
1
Eso no es lo que estoy preguntando, me gustaría tenerlo todo en un "episodio", pero esto tendría diferentes secciones del video original.
Matias
@Matias, si estuvieras preguntando cómo cortar algunos clips del video y dejar el resto como está, eso sería una cosa, pero quieres tomar algunos clips y combinarlos con clips de otros videos que hacen Esta no es una pregunta separada y única. Tiene que hacer lo que las otras preguntas le pidieron para obtener los segmentos separados, luego combinarlos.
Synetech
1
@Synetech gracias por sus respuestas. No quiero combinarlos con clips de otros videos. Solo quiero eliminar algunas partes de los videos. Por ejemplo, si mi video pudiera estar representado por ABCDEFG, me gustaría crear uno nuevo que consistiría en ACDFG.
Matias
@Synetech Ese no fui yo, fue Tog quien debió haber entendido mal.
Matias

Respuestas:

47

Bueno, todavía puedes usar el trimfiltro para eso. Aquí hay un ejemplo, supongamos que desea cortar segmentos 30-40 segundos (10 segundos) y 50-80 segundos (30 segundos):

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é hice aquí? Recorté primero 30 segundos, 40-50 segundos y 80 segundos para finalizar, y luego los combiné en la corriente out1con el concatfiltro.

Acerca de los ajustes: necesitamos esto porque el recorte no modifica el tiempo de visualización de la imagen, y cuando cortamos el contador del decodificador de 10 segundos no ve ningún marco para estos 10 segundos.

Si también desea tener audio, debe hacer lo mismo para las transmisiones de audio. Entonces el comando debería ser:

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
fuente
¡Muy buena! ¿Por qué necesitamos setpts? ¿Podría agregar la explicación?
Rajib
Ok, mira la respuesta actualizada.
ptQa
44
+1 Esto realmente debería seleccionarse como la respuesta. Es más adecuado para "segmentos múltiples" y elimina la necesidad de realizar múltiples comandos uno tras otro. (a menos que otra forma sea más eficiente en cuanto a velocidad)
Knossos
1
¿Cómo harías para mantener los datos de subtítulos cuando saltas marcos?
Andrew Scagnelli
1
Esto es muy inhumano.
golopot
15

Nunca puedo lograr que funcione la solución de ptQa, principalmente porque nunca puedo entender qué significan los errores de los filtros o cómo solucionarlos. Mi solución parece un poco más complicada porque puede dejar un desorden, pero si la está agregando a un script, la limpieza puede automatizarse. También me gusta este enfoque porque si algo sale mal en el paso 4, terminas con los pasos 1-3 completos, por lo que la recuperación de errores es un poco más eficiente.

La estrategia básica es usar -ty -ssobtener videos de cada segmento que desee, luego juntar todas las partes para su versión final.

Digamos que tiene 6 segmentos ABCDEF cada 5 segundos de duración y desea A (0-5 segundos), C (10-15 segundos) y E (20-25 segundos) haría esto:

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

Eso hará que los archivos a.tvshow, c.tvshow y e.tvshow. El -tdice la duración de cada clip es, por lo que si c es de 30 segundos de duración que podría pasar en 30 o 0:00:30. La -ssopción dice qué tan lejos saltar al video fuente, por lo que siempre es relativo al inicio del archivo.

Luego, una vez que tienes un montón de archivos de video, hago un archivo ace-files.txtcomo este:

file 'a.tvshow'
file 'c.tvshow'
file 'e.tvshow'

Tenga en cuenta el "archivo" al principio y el nombre del archivo escapado después de eso.

Entonces el comando:

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

Eso concatena todos los archivos abe-files.txtjuntos, copiando sus códecs de audio y video y crea un archivo ace.tvshowque debería ser solo las secciones a, c y e. A continuación, sólo recuerde que debe eliminar ace-files.txt, a.tvshow, c.tvshowy e.tvshow.

Descargo de responsabilidad : no tengo idea de cuán (in) eficiente es esto en comparación con los otros enfoques en términos de ffmpegpero para mis propósitos funciona mejor. Espero que ayude a alguien.

xbakesx
fuente
44
este es muy comprensible para los novatos como yo, gracias
juanpastas
44
Tenga en cuenta que si está utilizando bash, puede evitar la creación de un archivo ( ace-files.txten su ejemplo) con:ffmpeg -f concat -i <(printf "file '%s'\n" $(pwd)/prefix_*.tvshow) -c copy output.tvshow
bufh
1
Esto fue realmente útil, pero tuve que eliminar las comillas simples de los nombres de los archivos o no funcionó.
tchaymore
Al concatenar videos, hay un video negro corto y audio silenciado entre ellos, aproximadamente 25 ms. ¿Alguna idea para deshacerse de eso?
Antonio Bonifati 'Farmboy'
1
¡Gracias, @bufh, me preguntaba cómo hacer eso! Solo una nota que puede ayudar a otros: como @pkSML explica a continuación , también deberá agregar -safe 0al comando ffmpeg si usa rutas de archivo absolutas, para que esto funcione. Alternativamente, puede eliminar la $(pwd)/parte del subcomando bash.
Waldyrious
5

Hice un guión para acelerar la edición de TV grabada. El script le pide los tiempos de inicio y finalización de los segmentos que desea conservar y los divide en archivos. Te da opciones, puedes:

  • Toma uno o varios segmentos.
  • Puede combinar los segmentos en un archivo resultante.
  • Después de unirse, puede conservar o eliminar los archivos de pieza.
  • Puede conservar el archivo original o reemplazarlo con su nuevo archivo.

Video de ella en acción: aquí

Déjame saber lo que piensas.

 #!/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
fuente
2
¡Este guión es genial! Solo una modificación ya que está utilizando rutas absolutas para la concatenación: agregar safe=0, es decir, ffmpeg -f concat -safe 0 -i ...ver ffmpeg.org/ffmpeg-all.html#Options-36
pkSML
2

Aunque la respuesta proporcionada por ptQa parece funcionar, he desarrollado otra solución que ha demostrado funcionar bien.

Esencialmente, lo que hago es cortar un video para cada parte del video original que quiero incluir en mi resultado. Más tarde, los concateno con el Concat Demuxer explicado aquí .

La solución es la misma que probé primero y presenté problemas de sincronización. Lo que he agregado es el comando -avoid_negative_ts 1 al generar los diferentes videos. Con esta solución, los problemas de sincronización desaparecen.

Matias
fuente
1

Para aquellos que tienen problemas para seguir el enfoque de ptQa, hay una forma un poco más simplificada de hacerlo. En lugar de concat cada paso del camino, solo hazlos todos al final.

Para cada entrada, defina un par de 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];

Defina tantos pares como necesite, luego conéctelos todos en una sola pasada, donde n = recuento total de entradas.

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

Esto se puede construir fácilmente en un bucle.

Un comando completo que usa 2 entradas podría verse así:

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