Divide automaticamente grandes arquivos de vídeo .mov em arquivos menores em quadros pretos (mudanças de cena)?

10

Tenho 65 arquivos de vídeo .mov chamados tape_01.mov, tape_02.mov, ..., tape_65.mov. Cada um tem cerca de 2,5 horas de duração e ocupa muitos gigabytes. São cópias digitais do que eram fitas VHS ("filmes caseiros" da minha família).

Cada arquivo de vídeo .mov de 2,5 horas contém muitos "capítulos" (no sentido de que, às vezes, a cena termina em um desvanecimento para uma tela preta e, em seguida, uma nova cena aparece).

Meu objetivo principal é ter 65 arquivos grandes divididos em arquivos menores por capítulo. E eu quero que isso seja feito automaticamente através da detecção dos quadros pretos entre "cenas".

Poderia usar o ffmpeg (ou qualquer coisa) para passar os 65 nomes de arquivos originais e fazer com que iterasse automaticamente cada vídeo e cortá-lo, nomeando as partes resultantes tape_01-ch_01.mov, tape_01-ch_02.mov, tape_01-ch_03 .mov, etc? E eu quero fazer isso sem recodificar (eu quero que seja uma fatia simples e sem perdas).

Como posso fazer isso?

Aqui estão os passos que quero:

  1. Itere em uma pasta de arquivos .mov (denominados tape_01.mov, tape_02.mov, ..., tape_65.mov) para exportá-los como arquivos mp4 (denominados tape_01.mp4, tape_02.mp4, ..., tape_65 .mp4). Eu quero que as configurações de compactação sejam fáceis de configurar para esta etapa. Os arquivos .mov originais ainda devem existir depois que os arquivos .mp4 forem criados.
  2. Iterar os arquivos mp4: para cada arquivo mp4, gere um arquivo de texto que especifique o horário de início e a duração dos segmentos pretos entre "cenas". Cada mp4 pode ter 0 ou mais dessas quebras de cena pretas. O horário de início e a duração devem ser precisos para uma fração de segundo. HH: MM: O formato SS não é preciso o suficiente.
  3. Então, poderei verificar novamente manualmente esses arquivos de texto para ver se eles identificam corretamente as alterações de cena (segmentos pretos). Eu posso ajustar os horários se eu quiser.
  4. Outro script lerá cada arquivo mp4 e seu arquivo txt e dividirá (de maneira super rápida e sem perdas) o mp4 em quantas partes forem necessárias, com base nas linhas do arquivo de texto (as datas indicadas). As divisões devem acontecer no meio de cada segmento preto. Veja um exemplo de arquivo mp4 (com áudio removido para fins de privacidade) que tenha desaparecido para a cena muda de preto. Os arquivos mp4 originais devem permanecer intocados, pois os arquivos mp4 menores são criados. Os arquivos mp4 menores devem ter o esquema de nomenclatura de tape_01_001.mp4, tape_01_002.mp4, tape_01_003.mp4, etc.
  5. Bônus! Seria incrível se outro script pudesse então iterar sobre os pequenos arquivos mp4 e gerar uma imagem .png para cada um que seja um mosaico de capturas de tela como esta pessoa está tentando: Miniaturas significativas para um vídeo usando o FFmpeg

Gostaria que esses scripts fossem scripts de shell que eu possa executar no Mac, no Ubuntu e no Windows Git Bash.

    
por Ryan 24.12.2013 / 06:14

4 respostas

10

Aqui estão dois scripts do PowerShell para dividir vídeos longos em capítulos menores em cenas pretas.

Salve-os como Detect_black.ps1 e Cut_black.ps1. Faça o download do ffmpeg para Windows e diga ao script o caminho para o ffmpeg.exe e sua pasta de vídeo na seção de opções.

Ambososscriptsnãotocamnosarquivosdevídeoexistentes,elespermaneceminalterados.
Noentanto,vocêreceberáalgunsarquivosnovosnomesmolocalemqueseusvídeosdeentradasão

  • Umarquivodelogporvídeocomasaídadoconsoleparaosdoiscomandosffmpegusados
  • UmarquivoCSVporvídeocomtodososcarimbosdedataehoradecenasempretoparaajustemanual
  • Algunsnovosvídeos,dependendodequantascenasnegrasforamdetectadasanteriormente


Primeiro script a ser executado: 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

O primeiro script percorre todos os arquivos de vídeo que correspondem a uma extensão especificada e não corresponde ao padrão *_???.* , pois os novos capítulos de vídeo foram nomeados <filename>_###.<ext> e queremos excluí-los.

Ele pesquisa todas as cenas em preto e grava a data e hora de início e a duração da cena em preto em um novo arquivo CSV chamado <video_name>_cutpoints.txt

Ele também calcula os pontos de corte conforme mostrado: cutpoint = black_start + black_duration / 2 . Mais tarde, o vídeo é segmentado nesses timestamps.

O arquivo cutpoints.txt para o seu vídeo de amostra seria exibido:

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

Após uma corrida, você pode manipular manualmente os pontos de corte, se desejar. Se você executar o script novamente, todo o conteúdo antigo será substituído. Tenha cuidado ao editar manualmente e salvar seu trabalho em outro lugar.

Para o vídeo de exemplo, o comando ffmpeg para detectar cenas pretas é

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

Existem 3 números importantes que são editáveis na seção de opções do script

  • d=4 significa que apenas cenas negras com mais de 4 segundos são detectadas
  • pic_th=0.98 é o limite para considerar uma imagem como "preto" (em porcentagem)
  • pix=0.15 define o limite para considerar um pixel como "preto" (em luminância). Como você tem vídeos VHS antigos, não há cenas completamente pretas em seus vídeos. O valor padrão 10 não funcionará e eu tive que aumentar ligeiramente o limite

Se algo der errado, verifique o arquivo de log correspondente chamado <video_name>__ffmpeg.log . Se as seguintes linhas estiverem faltando, aumente os números mencionados acima até detectar todas as cenas pretas:

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



Segundo script para executar: 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

O segundo script interage sobre todos os arquivos de vídeo da mesma maneira que o primeiro script fez. Ele lê somente os timestamps cut do cutpoints.txt correspondente de um vídeo.

Em seguida, ele reúne um nome de arquivo adequado para arquivos de capítulo e diz ao ffmpeg para segmentar o vídeo. Atualmente, os vídeos são cortados sem recodificação (super rápida e sem perdas). Devido a isso, pode haver uma imprecisão de 1 a 2 segundos com carimbos de data e hora de ponto de corte, pois o ffmpeg só pode cortar em key_frames. Como apenas copiamos e não recodificamos, não podemos inserir key_frames sozinhos.

O comando para o vídeo de amostra seria

$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"

Se algo der errado, dê uma olhada no ffmpeg.log correspondente



Referências



Todo

  • Pergunte ao OP se o formato CSV é melhor que um arquivo de texto como um arquivo de ponto de corte, para que você possa editá-los com o Excel um pouco mais fácil
    »Implementado
  • Implemente uma maneira de formatar carimbos de hora como [hh]: [mm]: [ss], [milissegundos] em vez de apenas segundos
    »Implementado
  • Implementar um comando ffmpeg para criar arquivos mosaik png para cada capítulo
    »Implementado
  • Elabore se -c copy for suficiente para o cenário do OP ou se precisarmos recodificar totalmente.
    Parece que Ryan já está nele .
por 25.12.2013 / 21:36
3

Este é o bônus: este terceiro script do PowerShell gera uma imagem em miniatura em mosaico

Você receberá uma imagem por vídeo de capítulo, todas parecidas com o exemplo abaixo.

### 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
}

A ideia principal é obter um fluxo contínuo de imagens sobre o vídeo completo. Fazemos isso com a opção selecione do ffmpeg.

Primeiro, recuperamos a contagem total de quadros com um método engenhoso (por exemplo, 2000) e os dividimos por meio da contagem de miniaturas padrão (por exemplo, 5 x 4 = 20). Então, queremos gerar uma imagem a cada 100 quadros desde 2000 / 20 = 100

O comando ffmpeg resultante para gerar a miniatura pode se parecer com

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

No código acima, você vê 9 diferentes -vf combinações que consistem em

  • select=not(mod(n\,XXX)) em que XXX é uma taxa de quadros calculada
  • thumbnail , que seleciona automaticamente os quadros mais representativos
  • select='gt(scene\,XXX) + -vsync vfr em que XXX é um limite que você precisa jogar com
  • mpdecimate - Remove quadros quase duplicados. Bom contra cenas negras
  • yadif - Desentrelaça a imagem de entrada. Não sei porque, mas funciona

A versão 3 é a melhor escolha na minha opinião. Todos os outros são comentados, mas você ainda pode experimentá-los. Consegui remover a maioria das miniaturas borradas usando mpdecimate , yadif e select=not(mod(n\,XXX)) . Sim!

Para o seu exemplo de vídeo , recebo essas visualizações


Cliqueparaampliar


Cliqueparaampliar

Enviei todas as miniaturas criadas por essas versões. Dê uma olhada neles para uma comparação completa.

    
por 07.03.2014 / 23:01
0

Aprecie os dois scripts, ótima maneira de dividir vídeos.

Ainda assim, tive alguns problemas com vídeos que não mostravam o horário correto depois e não consegui saltar para um horário específico. Uma solução foi demux e depois mux os streams usando o Mp4Box.

Outra maneira mais fácil para mim foi usar o mkvmerge para dividir. Portanto, ambos os scripts tiveram que ser alterados. Para detect_black.ps1, apenas o "* .mkv" teve que ser adicionado à opção de filtro, mas isso não é necessariamente se você começar apenas com arquivos 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
}

A função é a mesma, mas não há problemas com tempos de vídeo até o momento. Obrigado pela inspiração.

    
por 10.05.2015 / 18:47
-2

Espero que esteja errado, mas não acho que sua pergunta seja possível. Pode ser possível recodificar o vídeo, mas como uma "fatia simples e sem perdas" que eu não conheço.

Muitos programas de edição de vídeo terão um recurso de análise automática ou algo equivalente que pode dividir seus vídeos nos quadros pretos, mas isso exigirá a recodificação do vídeo.

Boa sorte!

    
por 24.12.2013 / 21:57