Dividir automaticamente arquivos de vídeo .mov grandes em arquivos menores em quadros pretos (alterações de cena)?

11

Eu tenho 65 arquivos de vídeo .mov chamados tape_01.mov, tape_02.mov, ..., tape_65.mov. Cada uma 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 em que algumas vezes a cena termina em uma tela preta e, em seguida, uma nova cena desaparece).

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

Posso usar o ffmpeg (ou qualquer outra coisa) para passar os 65 nomes de arquivos originais e iterar automaticamente sobre cada vídeo e cortá-lo, nomeando as peças resultantes tape_01-ch_01.mov, tape_01-ch_02.mov, tape_01-ch_03.mov, etc? E quero fazê-lo sem recodificar (quero que seja uma fatia simples e sem perdas).

Como posso fazer isso?

Aqui estão os passos que eu quero:

  1. Itere sobre uma pasta de arquivos .mov (chamados tape_01.mov, tape_02.mov, ..., tape_65.mov) para exportá-los como arquivos mp4 (chamados tape_01.mp4, tape_02.mp4, ..., tape_65.mp4) . Quero que as configurações de compactação sejam fáceis de configurar para esta etapa. Os arquivos .mov originais ainda devem existir após a criação dos arquivos .mp4.
  2. Iterar sobre os arquivos mp4: para cada arquivo mp4, gere um arquivo de texto que especifique a hora de início e a duração dos segmentos pretos entre as "cenas". Cada mp4 pode ter 0 ou mais dessas quebras de cena em preto. O horário e a duração de início devem ser precisos em uma fração de segundo. O formato HH: MM: SS não é suficientemente preciso.
  3. Depois, poderei verificar 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 tantas partes quantas forem necessárias, com base nas linhas do arquivo de texto (os tempos indicados aqui). As divisões devem ocorrer no meio de cada segmento preto. Aqui está um exemplo de arquivo mp4 (com áudio removido para fins de privacidade) que possui alterações de cena de desbotamento para preto. Os arquivos mp4 originais devem permanecer intocados à medida que os arquivos mp4 menores são criados. Os arquivos mp4 menores devem ter o esquema de nomeação de tape_01_001.mp4, tape_01_002.mp4, tape_01_003.mp4, etc.
  5. Bônus! Seria incrível se outro script pudesse percorrer os pequenos arquivos mp4 e gerar uma imagem .png para cada um, que é 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 shell scripts que eu possa executar no Mac, Ubuntu e Windows Git Bash.

Ryan
fonte

Respostas:

11

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

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

insira a descrição da imagem aqui

Ambos os scripts não tocam nos arquivos de vídeo existentes, permanecem intocados.
No entanto, você receberá alguns novos arquivos no mesmo local em que seus vídeos de entrada estão

  • Um arquivo de log por vídeo com a saída do console para os dois comandos ffmpeg usados
  • Um arquivo CSV por vídeo com todos os carimbos de data / hora de cenas em preto para ajuste fino manual
  • Alguns vídeos novos, dependendo de quantas cenas em preto foram detectadas anteriormente

insira a descrição da imagem aqui


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 ao padrão *_???.*, pois os novos capítulos foram nomeados <filename>_###.<ext>e queremos excluí-los.

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

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

O arquivo cutpoints.txt para o seu exemplo de vídeo mostraria:

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 execução, você pode manipular os pontos de corte manualmente, se desejar. Se você executar o script novamente, todo o conteúdo antigo será substituído. Tenha cuidado ao editar manualmente e salve seu trabalho em outro local.

Para o vídeo de amostra, o comando ffmpeg para detectar cenas em preto é

$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 pretas com mais de 4 segundos são detectadas
  • pic_th=0.98 é o limite para considerar uma imagem como "preta" (em porcentagem)
  • pix=0.15define o limite para considerar um pixel como "preto" (em luminância). Como você possui vídeos VHS antigos, não há cenas completamente negras em seus vídeos. O valor padrão 10 não funcionará e eu tive que aumentar um pouco 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 em preto:

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



Segundo script a ser executado: 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 repete todos os arquivos de vídeo da mesma maneira que o primeiro script. Ele lê apenas os carimbos de data e hora cortados do correspondente cutpoints.txtde um vídeo.

Em seguida, reúne um nome de arquivo adequado para os arquivos dos capítulos e informa 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-2s nos registros de data e hora do ponto de corte, porque o ffmpeg só pode cortar em quadros-chave. Como apenas copiamos e não recodificamos, não podemos inserir quadros-chave por conta própria.

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, consulte o arquivo ffmpeg.log correspondente



Referências



Façam

  • Pergunte à OP se o formato CSV é melhor que um arquivo de texto como 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 data e hora como [hh]: [mm]: [ss], [milissegundos] em vez de apenas segundos
    »Implementado
  • Implemente um comando ffmpeg para criar arquivos png mosaik para cada capítulo
    »Implementado
  • Elabore se -c copyé suficiente para o cenário do OP ou se é necessário recodificar totalmente.
    Parece que Ryan já está nele .
nixda
fonte
Que resposta completa! Eu agradeço! Infelizmente, eu estou no Mac agora e primeiro preciso descobrir como traduzir isso para o Bash.
22713 Ryan
Não consegui fazer isso funcionar no PowerShell. Os arquivos de log permanecem em branco.
22914 Ryan
Vou ver o que posso enviar para ser útil. Fique ligado. Obrigado pelo seu interesse!
21714 Ryan
Eu adicionei uma amostra mp4 à pergunta. Obrigado pela ajuda!
21714 Ryan
Para fins de privacidade, não carregarei o MOV de origem verdadeiro. Eu gostaria de cortá-lo e remover o áudio (como fiz no mp4). Portanto, não seria uma fonte original verdadeira de qualquer maneira. Mesmo assim, seria muito grande para carregar. E a etapa de divisão de cenas provavelmente deve ocorrer em arquivos mp4, em vez de arquivos MOV, para fins de espaço em disco. Obrigado!
22714 Ryan
3

Aqui está o bônus: este terceiro script do PowerShell gera uma imagem em miniatura de mosaico

Você receberá uma imagem por vídeo do capítulo, semelhante ao 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 idéia principal é obter um fluxo contínuo de imagens em todo o vídeo. Fazemos isso com a opção de seleção do ffmpeg .

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

O comando ffmpeg resultante para gerar a miniatura pode parecer

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 -vfcombinações diferentes que consistem em

  • select=not(mod(n\,XXX)) onde XXX é uma taxa de quadros computada
  • thumbnail que seleciona os quadros mais representativos automaticamente
  • select='gt(scene\,XXX)+ -vsync vfronde XXX é um limite com o qual você precisa brincar
  • mpdecimate- Remova os quadros quase duplicados. Bom contra cenas negras
  • yadif- Desentrelaçar a imagem de entrada. Não sei por que, mas funciona

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 embaçadas usando mpdecimate, yadife select=not(mod(n\,XXX)). sim!

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

insira a descrição da imagem aqui
Clique para ampliar

insira a descrição da imagem aqui
Clique para ampliar

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

nixda
fonte
Muito legal! Vou dar uma olhada nisso. Eu perdi horas hoje também tentando chegar select='gt(scene\,0.4)'ao trabalho. E também não consegui fps="fps=1/600"trabalhar. A propósito, você tem alguma idéia sobre superuser.com/questions/725734 ? Muito obrigado por toda a sua ajuda. Estou super empolgado em ajudar minha família a descobrir toneladas de memórias antigas.
Ryan
Você já tentou essas outras 5 variantes de codificação listadas na parte inferior ? Eu adicionei um exemplo de trabalho para a opção "scene". Esqueça o filtro FPS. Eu consegui remover a maioria dos polegares borradas :)
nixda
0

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

Ainda assim, tive alguns problemas com os vídeos que não mostravam a hora correta posteriormente e não pude ir para uma hora específica. Uma solução foi desmontar e, em seguida, compactar os fluxos usando o Mp4Box.

Outra maneira mais fácil para mim era usar o mkvmerge para dividir. Portanto, os dois scripts tiveram que ser alterados. Para detect_black.ps1, apenas o "* .mkv" tinha que ser adicionado à opção de filtro, mas isso não é necessariamente se você iniciar 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 sem problemas com os tempos de vídeo até agora. Obrigado pela inspiração.

Andy Gebauer
fonte
-2

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

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

Boa sorte!

tbenz9
fonte
É absolutamente possível. Você pode cortar vídeos de uma maneira sem perdas, sem problemas (por exemplo, com o segmento muxer em ffmpeg , consulte também o wiki do ffmpeg sobre corte de vídeo ). Qualquer programa decente de edição de vídeo deve ser capaz de fazê-lo. O único problema é detectar alterações de cena.
slhck
1
Na verdade você não pode. Os maiores problemas podem ser quadros-chave. Todos os clipes precisam começar com um quadro-chave para serem renderizados corretamente na reprodução, mesmo nas ferramentas de edição. Se os quadros pretos não começarem com quadros-chave, haverá incompatibilidade ao usar cortes sem perdas.
JasonXA