¿Divide automáticamente grandes archivos de video .mov en archivos más pequeños en cuadros negros (cambios de escena)?

11

Tengo 65 archivos de video .mov llamados tape_01.mov, tape_02.mov, ..., tape_65.mov. Cada una dura aproximadamente 2,5 horas y ocupa muchos gigabytes. Son copias digitales de lo que fueron cintas VHS (las "películas caseras" de mi familia).

Cada archivo de video .mov de 2.5 horas contiene muchos "capítulos" (en el sentido de que a veces la escena termina en un desvanecimiento a una pantalla en negro, y luego se desvanece una nueva escena).

Mi objetivo principal es tener los 65 archivos grandes divididos en archivos más pequeños por capítulo. Y quiero que se haga automáticamente mediante la detección de los cuadros negros entre "escenas".

¿Podría usar ffmpeg (o cualquier cosa) para pasar los 65 nombres de archivo originales y hacer que iteren automáticamente sobre cada video y lo corten, nombrando las piezas resultantes tape_01-ch_01.mov, tape_01-ch_02.mov, tape_01-ch_03.mov, etc? Y quiero que lo haga sin volver a codificar (quiero que sea un segmento simple sin pérdidas).

¿Cómo puedo hacer esto?

Aquí están los pasos que quiero:

  1. Itere sobre una carpeta de archivos .mov (llamada tape_01.mov, tape_02.mov, ..., tape_65.mov) para exportarlos como archivos mp4 (llamados tape_01.mp4, tape_02.mp4, ..., tape_65.mp4) . Quiero que la configuración de compresión sea fácil de configurar para este paso. Los archivos .mov originales aún deberían existir después de crear los archivos .mp4.
  2. Iterar sobre los archivos mp4: para cada archivo mp4, genere un archivo de texto que especifique la hora de inicio y la duración de los segmentos negros entre "escenas". Cada mp4 puede tener 0 o más de estos saltos de escena negra. El tiempo de inicio y la duración deben ser precisos a una fracción de segundo. HH: MM: el formato SS no es lo suficientemente preciso.
  3. Luego podré verificar manualmente estos archivos de texto para ver si identifican correctamente los cambios de escena (segmentos negros). Puedo ajustar los tiempos si quiero.
  4. Otro script leerá cada archivo mp4 y su archivo txt y dividirá (de una manera súper rápida y sin pérdidas) el mp4 en tantas partes como sea necesario según las líneas del archivo de texto (los tiempos indicados allí). Las divisiones deben ocurrir en el medio de cada segmento negro. Aquí hay un archivo mp4 de muestra (con audio eliminado para fines de privacidad) que tiene cambios de escena de negro a negro. Los archivos mp4 originales deben permanecer intactos a medida que se crean los archivos mp4 más pequeños. Los archivos mp4 más pequeños deben tener el esquema de nombres de tape_01_001.mp4, tape_01_002.mp4, tape_01_003.mp4, etc.
  5. ¡Prima! Sería increíble si otro script pudiera iterar sobre los pequeños archivos mp4 y generar una imagen .png para cada uno que sea un mosaico de capturas de pantalla como esta persona está intentando: Miniaturas significativas para un video usando FFmpeg

Me gustaría que estos scripts fueran scripts de shell que puedo ejecutar en Mac, Ubuntu y Windows Git Bash.

Ryan
fuente

Respuestas:

11

Aquí hay dos scripts de PowerShell para dividir videos largos en capítulos más pequeños por escenas negras.

Guárdelos como Detect_black.ps1 y Cut_black.ps1. Descarga ffmpeg para Windows y dile al script la ruta a tu ffmpeg.exe y a tu carpeta de video en la sección de opciones.

ingrese la descripción de la imagen aquí

Ambas secuencias de comandos no tocarán los archivos de video existentes, permanecen intactos.
Sin embargo, obtendrá un par de archivos nuevos en el mismo lugar donde están sus videos de entrada

  • Un archivo de registro por video con la salida de la consola para ambos comandos ffmpeg utilizados
  • Un archivo CSV por video con todas las marcas de tiempo de escenas negras para un ajuste manual
  • Un par de videos nuevos dependiendo de cuántas escenas negras se detecten previamente

ingrese la descripción de la imagen aquí


Primer script que se ejecuta: Detect_black.ps1

 ### Options __________________________________________________________________________________________________________
$ffmpeg = ".\ffmpeg.exe"            # Set path to your ffmpeg.exe; Build Version: git-45581ed (2014-02-16)
$folder = ".\Videos\*"              # Set path to your video folder; '\*' must be appended
$filter = @("*.mov","*.mp4")        # Set which file extensions should be processed
$dur = 4                            # Set the minimum detected black duration (in seconds)
$pic = 0.98                         # Set the threshold for considering a picture as "black" (in percent)
$pix = 0.15                         # Set the threshold for considering a pixel "black" (in luminance)

### Main Program ______________________________________________________________________________________________________

foreach ($video in dir $folder -include $filter -exclude "*_???.*" -r){

  ### Set path to logfile
  $logfile = "$($video.FullName)_ffmpeg.log"

  ### analyse each video with ffmpeg and search for black scenes
  & $ffmpeg -i $video -vf blackdetect=d=`"$dur`":pic_th=`"$pic`":pix_th=`"$pix`" -an -f null - 2> $logfile

  ### Use regex to extract timings from logfile
  $report = @()
  Select-String 'black_start:.*black_end:' $logfile | % { 
    $black          = "" | Select  start, end, cut

    # extract start time of black scene
    $start_s     = $_.line -match '(?<=black_start:)\S*(?= black_end:)'    | % {$matches[0]}
    $start_ts    = [timespan]::fromseconds($start_s)
    $black.start = "{0:HH:mm:ss.fff}" -f ([datetime]$start_ts.Ticks)

    # extract duration of black scene
    $end_s       = $_.line -match '(?<=black_end:)\S*(?= black_duration:)' | % {$matches[0]}
    $end_ts      = [timespan]::fromseconds($end_s)
    $black.end   = "{0:HH:mm:ss.fff}" -f ([datetime]$end_ts.Ticks)    

    # calculate cut point: black start time + black duration / 2
    $cut_s       = ([double]$start_s + [double]$end_s) / 2
    $cut_ts      = [timespan]::fromseconds($cut_s)
    $black.cut   = "{0:HH:mm:ss.fff}" -f ([datetime]$cut_ts.Ticks)

    $report += $black
  }

  ### Write start time, duration and the cut point for each black scene to a seperate CSV
  $report | Export-Csv -path "$($video.FullName)_cutpoints.csv" –NoTypeInformation
}

Como funciona

El primer script recorre todos los archivos de video que coinciden con una extensión especificada y no coinciden con el patrón *_???.*, ya que se nombraron nuevos capítulos de video <filename>_###.<ext>y queremos excluirlos.

Busca en todas las escenas negras y escribe la marca de tiempo de inicio y la duración de la escena negra en un nuevo archivo CSV llamado <video_name>_cutpoints.txt

También calcula puntos de corte como se muestra: cutpoint = black_start + black_duration / 2. Más tarde, el video se segmenta en estas marcas de tiempo.

El archivo cutpoints.txt para su video de muestra mostrará:

start          end            cut
00:03:56.908   00:04:02.247   00:03:59.578
00:08:02.525   00:08:10.233   00:08:06.379

Después de una carrera, puede manipular los puntos de corte manualmente si lo desea. Si ejecuta el script nuevamente, todo el contenido antiguo se sobrescribe. Tenga cuidado al editar manualmente y guardar su trabajo en otro lugar.

Para el video de muestra, el comando ffmpeg para detectar escenas negras es

$ffmpeg -i "Tape_10_3b.mp4" -vf blackdetect=d=4:pic_th=0.98:pix_th=0.15 -an -f null

Hay 3 números importantes que se pueden editar en la sección de opciones del script

  • d=4 significa que solo se detectan escenas negras de más de 4 segundos
  • pic_th=0.98 es el umbral para considerar una imagen como "negra" (en porcentaje)
  • pix=0.15establece el umbral para considerar un píxel como "negro" (en luminancia). Como tiene videos antiguos de VHS, no tiene escenas completamente negras en sus videos. El valor predeterminado 10 no funcionará y tuve que aumentar ligeramente el umbral

Si algo sale mal, verifique el archivo de registro correspondiente llamado <video_name>__ffmpeg.log. Si faltan las siguientes líneas, aumente los números mencionados anteriormente hasta que detecte todas las escenas negras:

[blackdetect @ 0286ec80]
 black_start:236.908 black_end:242.247 black_duration:5.33877



Segundo script que se ejecuta: cut_black.ps1

### Options __________________________________________________________________________________________________________
$ffmpeg = ".\ffmpeg.exe"            # Set path to your ffmpeg.exe; Build Version: git-45581ed (2014-02-16)
$folder = ".\Videos\*"              # Set path to your video folder; '\*' must be appended
$filter = @("*.mov","*.mp4")        # Set which file extensions should be processed

### Main Program ______________________________________________________________________________________________________

foreach ($video in dir $folder -include $filter -exclude "*_???.*" -r){

  ### Set path to logfile
  $logfile = "$($video.FullName)_ffmpeg.log"

  ### Read in all cutpoints from *_cutpoints.csv; concat to string e.g "00:03:23.014,00:06:32.289,..."  
  $cuts = ( Import-Csv "$($video.FullName)_cutpoints.csv" | % {$_.cut} ) -join ","

  ### put together the correct new name, "%03d" is a generic number placeholder for ffmpeg
  $output = $video.directory.Fullname + "\" + $video.basename + "_%03d" + $video.extension

  ### use ffmpeg to split current video in parts according to their cut points
  & $ffmpeg -i $video -f segment -segment_times $cuts -c copy -map 0 $output 2> $logfile        
}

Como funciona

El segundo script itera sobre todos los archivos de video de la misma manera que lo hizo el primer script. Lee solo las marcas de tiempo cortadas del correspondiente cutpoints.txtde un video.

Luego, reúne un nombre de archivo adecuado para los archivos de capítulos y le dice a ffmpeg que segmente el video. Actualmente, los videos se cortan sin volver a codificar (superrápidos y sin pérdidas). Debido a esto, puede haber una imprecisión de 1-2 segundos con las marcas de tiempo del punto de corte porque ffmpeg solo puede cortar en fotogramas clave. Como solo copiamos y no volvemos a codificar, no podemos insertar fotogramas clave por nuestra cuenta.

El comando para el video de muestra sería

$ffmpeg -i "Tape_10_3b.mp4" -f segment -segment_times "00:03:59.578,00:08:06.379" -c copy -map 0 "Tape_10_3b_(%03d).mp4"

Si algo sale mal, eche un vistazo al ffmpeg.log correspondiente



Referencias



Que hacer

  • Pregunte a OP si el formato CSV es mejor que un archivo de texto como archivo de punto de corte, para que pueda editarlos con Excel un poco más fácil
    »Implementado
  • Implemente una forma de formatear marcas de tiempo como [hh]: [mm]: [ss], [milisegundos] en lugar de solo segundos
    »Implementado
  • Implemente un comando ffmpeg para crear archivos png mosaik para cada capítulo
    »Implementado
  • Elabore si -c copyes suficiente para el escenario de OP o si necesitamos volver a codificar por completo.
    Parece que Ryan ya está en eso .
nixda
fuente
¡Qué respuesta tan minuciosa! ¡Lo aprecio! Desafortunadamente, estoy en una Mac en este momento y primero tendré que descubrir cómo traducir esto a Bash.
Ryan
No he podido hacer que esto funcione en PowerShell en absoluto. Los archivos de registro permanecen en blanco.
Ryan
Veré lo que puedo subir para ser útil. Manténganse al tanto. ¡Gracias por tu interés!
Ryan
Agregué una muestra mp4 a la pregunta. ¡Gracias por tu ayuda!
Ryan
Por motivos de privacidad, no subiré el MOV de origen verdadero. Me gustaría cortarlo y eliminar el audio (como hice para mp4). Por lo tanto, no sería una verdadera fuente original de todos modos. Aun así, sería demasiado grande para cargar. Y el paso de división de escena probablemente debería ocurrir en archivos mp4 en lugar de archivos MOV para fines de espacio en disco de todos modos. ¡Gracias!
Ryan
3

Aquí está la ventaja: este tercer script de PowerShell genera una imagen en miniatura de mosaico

Recibirá una imagen por video de capítulo que se verá como en el siguiente ejemplo.

### Options __________________________________________________________________________________________________________
$ffmpeg = ".\ffmpeg.exe"       # Set path to your ffmpeg.exe; Build Version: git-45581ed (2014-02-16)
$folder = ".\Videos\*"         # Set path to your video folder; '\*' must be appended
$x = 5                         # Set how many images per x-axis
$y = 4                         # Set how many images per y-axis
$w = 384                       # Set thumbnail width in px (full image width: 384px x 5 = 1920px,)

### Main Program ______________________________________________________________________________________________________

foreach ($video in dir $folder -include "*_???.mp4" -r){

  ### get video length in frames, an ingenious method
  $log = & $ffmpeg -i $video -vcodec copy -an -f null $video 2>&1   
  $frames = $log | Select-String '(?<=frame=.*)\S+(?=.*fps=)' | % { $_.Matches } | % { $_.Value }  
  $frame = [Math]::floor($frames / ($x * $y))

  ### put together the correct new picture name
  $output = $video.directory.Fullname + "\" + $video.basename + ".jpg"

  ### use ffmpeg to create one mosaic png per video file
  ### Basic explanation for -vf options:  http://trac.ffmpeg.org/wiki/FilteringGuide
#1  & $ffmpeg -y -i $video -vf "select=not(mod(n\,`"$frame`")),scale=`"$w`":-1,tile=`"$x`"x`"$y`"" -frames:v 1 $output
#2  & $ffmpeg -y -i $video -vf "yadif,select=not(mod(n\,`"$frame`")),scale=`"$w`":-1,tile=`"$x`"x`"$y`"" -frames:v 1 $output 
    & $ffmpeg -y -i $video -vf "mpdecimate,yadif,select=not(mod(n\,`"$frame`")),scale=`"$w`":-1,tile=`"$x`"x`"$y`"" -frames:v 1 $output       

#4  & $ffmpeg -y -i $video -vf "select='gt(scene\,0.06)',scale=`"$w`":-1,tile=`"$x`"x`"$y`"" -frames:v 1 -vsync vfr $output  
#5  & $ffmpeg -y -i $video -vf "yadif,select='gt(scene\,0.06)',scale=`"$w`":-1,tile=`"$x`"x`"$y`"" -frames:v 1 -vsync vfr $output  
#6  & $ffmpeg -y -i $video -vf "mpdecimate,yadif,select='gt(scene\,0.06)',scale=`"$w`":-1,tile=`"$x`"x`"$y`"" -frames:v 1 -vsync vfr $output  

#7  & $ffmpeg -y -i $video -vf "thumbnail,scale=`"$w`":-1,tile=`"$x`"x`"$y`"" -frames:v 1 $output
#8  & $ffmpeg -y -i $video -vf "yadif,thumbnail,scale=`"$w`":-1,tile=`"$x`"x`"$y`"" -frames:v 1 $output
#9  & $ffmpeg -y -i $video -vf "mpdecimate,yadif,thumbnail,scale=`"$w`":-1,tile=`"$x`"x`"$y`"" -frames:v 1 $output
}

La idea principal es obtener un flujo continuo de imágenes a través del video completo. Hacemos esto con la opción de selección de ffmpeg .

Primero, recuperamos el recuento total de cuadros con un método ingenioso (por ejemplo, 2000) y lo dividimos a través de nuestro conteo de miniaturas predeterminado (por ejemplo, 5 x 4 = 20). Así que queremos generar una imagen cada 100 cuadros desde2000 / 20 = 100

El comando ffmpeg resultante para generar la miniatura podría verse así

ffmpeg -y -i input.mp4 -vf "mpdecimate,yadif,select=not(mod(n\,100)),scale=384:-1,tile=5x4" -frames:v 1 output.png

En el código anterior, ves 9 -vfcombinaciones diferentes que consisten en

  • select=not(mod(n\,XXX)) donde XXX es una velocidad de fotogramas calculada
  • thumbnail que selecciona los cuadros más representativos automáticamente
  • select='gt(scene\,XXX)+ -vsync vfrdonde XXX es un umbral con el que tienes que jugar
  • mpdecimate- Eliminar cuadros casi duplicados. Bueno contra escenas negras
  • yadif- Desentrelazar la imagen de entrada. No sé por qué, pero funciona.

La versión 3 es la mejor opción en mi opinión. Todos los demás están comentados, pero aún puedes probarlos. Pude eliminar la mayoría de las miniaturas borrosas usando mpdecimate, yadify select=not(mod(n\,XXX)). ¡Si!

Para su video de muestra , obtengo estas vistas previas

ingrese la descripción de la imagen aquí
Click para agrandar

ingrese la descripción de la imagen aquí
Click para agrandar

Subí todas las miniaturas creadas por esas versiones. Eche un vistazo a ellos para una comparación completa.

nixda
fuente
¡Muy genial! Echaré un vistazo a esto. Hoy también perdí horas tratando de llegar select='gt(scene\,0.4)'al trabajo. Y tampoco pude ir fps="fps=1/600"a trabajar. Por cierto, ¿tienes alguna idea sobre superuser.com/questions/725734 ? Muchas gracias por toda su ayuda. Estoy muy emocionado por ayudar a mi familia a descubrir toneladas de viejos recuerdos.
Ryan
¿Has probado esas otras 5 variantes de codificación que enumeré al final ? Agregué un ejemplo de trabajo para la opción "escena". Olvídate del filtro FPS.
Logré
0

Aprecio ambos guiones, excelente manera de dividir videos.

Aún así, tuve algunos problemas con los videos que no mostraban la hora correcta después y no pude saltar a una hora específica. Una solución fue demux y luego mux las transmisiones usando Mp4Box.

Otra forma más fácil para mí era usar mkvmerge para dividir. Por lo tanto, ambos scripts tuvieron que ser alterados. Para detect_black.ps1, solo se debía agregar "* .mkv" a la opción de filtro, pero eso no es necesariamente si comienza solo con archivos mp4.

### Options        
$mkvmerge = ".\mkvmerge.exe"        # Set path to mkvmerge.exe
$folder = ".\*"                     # Set path to your video folder; '\*' must be appended
$filter = @("*.mov", "*.mp4", "*.mkv")        # Set which file extensions should be processed

### Main Program 
foreach ($video in dir $folder -include $filter -exclude "*_???.*" -r){

  ### Set path to logfile
  $logfile = "$($video.FullName)_ffmpeg.log"

  ### Read in all cutpoints from *_cutpoints.csv; concat to string e.g "00:03:23.014,00:06:32.289,..."  
  $cuts = ( Import-Csv "$($video.FullName)_cutpoints.csv" | % {$_.cut} ) -join ","

  ### put together the correct new name, "%03d" is a generic number placeholder for mkvmerge
  $output = $video.directory.Fullname + "\" + $video.basename + "-%03d" + ".mkv"

  ### use mkvmerge to split current video in parts according to their cut points and convert to mkv
  & $mkvmerge -o $output --split timecodes:$cuts $video
}

La función es la misma, pero hasta ahora no hay problemas con los tiempos de video. Gracias por la inspiracion.

Andy Gebauer
fuente
-2

Espero estar equivocado, pero no creo que lo que pidas sea posible. Puede ser posible mientras se vuelve a codificar el video, pero como un "segmento simple sin pérdidas" no sé de ninguna manera.

Muchos programas de edición de video tendrán una función de análisis automático o algo equivalente que puede dividir sus videos en cuadros negros, pero esto requerirá una nueva codificación del video.

¡Buena suerte!

tbenz9
fuente
Es absolutamente posible Puede cortar el video sin pérdidas sin problemas (p. Ej., Con el segmento muxer en ffmpeg , consulte también la wiki de ffmpeg en video de corte ). Cualquier programa de edición de video decente debería poder hacerlo. El único problema es detectar cambios de escena.
slhck
1
En realidad no puedes. Los mayores problemas pueden ser fotogramas clave. Todos los clips deben comenzar con un fotograma clave para poder reproducirse correctamente incluso en las herramientas de edición. Si los cuadros negros no comienzan con fotogramas clave, habrá incompatibilidad al usar cortes sin pérdida.
JasonXA