Dividi automaticamente file video .mov di grandi dimensioni in file più piccoli con cornici nere (cambi di scena)?


11

Ho 65 file video .mov chiamati tape_01.mov, tape_02.mov, ..., tape_65.mov. Ciascuno dura circa 2,5 ore e occupa molti gigabyte. Sono copie digitali di quelli che erano nastri VHS (i "film domestici" della mia famiglia).

Ogni file video .mov di 2,5 ore contiene molti "capitoli" (nel senso che a volte la scena termina in dissolvenza verso uno schermo nero, quindi una nuova scena sfuma).

Il mio obiettivo principale è quello di tagliare i 65 file di grandi dimensioni in file più piccoli per capitolo. E voglio che ciò avvenga automaticamente tramite il rilevamento delle cornici nere tra le "scene".

Potrei usare ffmpeg (o altro) per passare i 65 nomi di file originali e farlo scorrere automaticamente su ogni video e tagliarlo, denominando i pezzi risultanti tape_01-ch_01.mov, tape_01-ch_02.mov, tape_01-ch_03.mov, eccetera? E voglio che lo faccia senza ricodificare (voglio che sia una semplice fetta senza perdita di dati).

Come posso fare questo?

Ecco i passaggi che voglio:

  1. Scorrere su una cartella di file .mov (denominata tape_01.mov, tape_02.mov, ..., tape_65.mov) per esportarli come file mp4 (denominati tape_01.mp4, tape_02.mp4, ..., tape_65.mp4) . Voglio che le impostazioni di compressione siano facili da configurare per questo passaggio. I file .mov originali dovrebbero ancora esistere dopo la creazione dei file .mp4.
  2. Scorrere i file mp4: per ogni file mp4, genera un file di testo che specifica l'ora di inizio e la durata dei segmenti neri tra le "scene". Ogni mp4 può avere 0 o più di queste interruzioni di scena nere. L'ora e la durata di inizio devono essere precise a una frazione di secondo. HH: MM: il formato SS non è abbastanza preciso.
  3. Sarò quindi in grado di ricontrollare manualmente questi file di testo per vedere se identificano correttamente i cambi di scena (segmenti neri). Posso regolare i tempi se lo desidero.
  4. Un altro script leggerà ogni file mp4 e il suo file txt e suddividerà (in modo super veloce e senza perdita di dati) il mp4 in tutti i pezzi necessari in base alle righe del file di testo (i tempi indicati lì). Le divisioni dovrebbero avvenire nel mezzo di ogni segmento nero. Ecco un file mp4 di esempio (con audio rimosso per motivi di privacy) che presenta cambiamenti di scena da dissolvenza a nero. I file mp4 originali dovrebbero rimanere intatti quando vengono creati i file mp4 più piccoli. I file mp4 più piccoli dovrebbero avere lo schema di denominazione di tape_01_001.mp4, tape_01_002.mp4, tape_01_003.mp4, ecc.
  5. Bonus! Sarebbe fantastico se un altro script potesse quindi scorrere i piccoli file mp4 e generare un'immagine .png per ognuno che è un mosaico di schermate come questa persona sta cercando: Miniature significative per un video usando FFmpeg

Vorrei che questi script fossero script di shell che posso eseguire su Mac, Ubuntu e Windows Git Bash.

Risposte:


11

Ecco due script di PowerShell per dividere lunghi video in capitoli più piccoli per scene nere.

Salvali come Detect_black.ps1 e Cut_black.ps1. Scarica ffmpeg per Windows e indica allo script il percorso di ffmpeg.exe e della cartella video nella sezione delle opzioni.

inserisci qui la descrizione dell'immagine

Entrambi gli script non toccano i file video esistenti, rimangono intatti.
Tuttavia, otterrai un paio di nuovi file nello stesso punto in cui si trovano i tuoi video di input

  • Un file di log per video con l'output della console per entrambi i comandi ffmpeg utilizzati
  • Un file CSV per video con tutti i timestamp delle scene nere per la regolazione fine manuale
  • Un paio di nuovi video a seconda di quante scene nere sono state precedentemente rilevate

inserisci qui la descrizione dell'immagine


Primo script da eseguire: 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
}

Come funziona

Il primo script scorre attraverso tutti i file video che corrispondono a un'estensione specificata e non corrispondono al modello *_???.*, poiché sono stati nominati nuovi capitoli video <filename>_###.<ext>e vogliamo escluderli.

Cerca tutte le scene nere e scrive la data e l'ora di inizio e la durata della scena nera in un nuovo file CSV denominato <video_name>_cutpoints.txt

Si calcola anche punti di taglio come illustrato: cutpoint = black_start + black_duration / 2. Successivamente, il video viene segmentato in questi timestamp.

Il file cutpoints.txt per il tuo video di esempio mostrerebbe:

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

Dopo una corsa, è possibile manipolare manualmente i punti di taglio se lo si desidera. Se si esegue nuovamente lo script, tutto il vecchio contenuto viene sovrascritto. Fai attenzione quando modifichi manualmente e salva il tuo lavoro altrove.

Per il video di esempio è il comando ffmpeg per rilevare le scene nere

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

Ci sono 3 numeri importanti che sono modificabili nella sezione delle opzioni dello script

  • d=4 significa che vengono rilevate solo scene nere più lunghe di 4 secondi
  • pic_th=0.98 è la soglia per considerare un'immagine come "nera" (in percentuale)
  • pix=0.15imposta la soglia per considerare un pixel come "nero" (in luminanza). Dato che hai vecchi video VHS, non hai scene completamente nere nei tuoi video. Il valore predefinito 10 non funzionerà e ho dovuto aumentare leggermente la soglia

Se qualcosa va storto, controlla il file di registro corrispondente chiamato <video_name>__ffmpeg.log. Se mancano le seguenti righe, aumentare i numeri sopra menzionati fino a rilevare tutte le scene nere:

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



Secondo script da eseguire: 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        
}

Come funziona

Il secondo script scorre su tutti i file video allo stesso modo del primo script. Legge solo i timestamp tagliati dalla corrispondente cutpoints.txtdi un video.

Successivamente, mette insieme un nome file adatto per i file dei capitoli e dice a ffmpeg di segmentare il video. Attualmente i video vengono suddivisi senza ricodifica (superveloce e senza perdita di dati). Per questo motivo, potrebbe esserci un'inesattezza di 1-2 secondi con i timestamp del punto di taglio perché ffmpeg può tagliare solo in key_frames. Dato che copiamo e non ricodifichiamo, non possiamo inserire key_frames da soli.

Il comando per il video di esempio sarebbe

$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 qualcosa va storto, dai un'occhiata al corrispondente ffmpeg.log



Riferimenti



Fare

  • Chiedi a OP se il formato CSV è migliore di un file di testo come file di punti di taglio, quindi puoi modificarli con Excel un po 'più facilmente
    »Implementato
  • Implementa un modo per formattare i timestamp come [hh]: [mm]: [ss], [millisecondi] anziché solo secondi
    »Implementato
  • Implementa un comando ffmpeg per creare file png mosaik per ogni capitolo
    »Implementato
  • Elaborare se -c copyè sufficiente per lo scenario di OP o di abbiamo bisogno di ricodificare completamente.
    Sembra che Ryan ci sia già .

Che risposta completa! Lo apprezzo! Sfortunatamente, sono su un Mac in questo momento e dovrò prima capire come tradurre questo in Bash.
Ryan,

Non sono riuscito a farlo funzionare in PowerShell. I file di registro rimangono vuoti.
Ryan,

Vedrò cosa posso caricare per essere utile. Rimanete sintonizzati. Grazie per il tuo interesse!
Ryan,

Ho aggiunto un esempio di mp4 alla domanda. Grazie per l'aiuto!
Ryan,

Per motivi di privacy, non caricherò il vero sorgente MOV. Vorrei tagliarlo e rimuovere l'audio (come ho fatto per mp4). Quindi non sarebbe comunque una vera fonte originale. Anche così, sarebbe troppo grande per il caricamento. E la fase di suddivisione della scena dovrebbe probabilmente avvenire sui file mp4 anziché sui file MOV per scopi di spazio su disco comunque. Grazie!
Ryan,

3

Ecco il bonus: questo terzo script di PowerShell genera un'immagine in miniatura di mosaico

Riceverai un'immagine per capitolo video che assomigliano tutti all'esempio seguente.

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

L'idea principale è quella di ottenere un flusso continuo di immagini sul video completo. Lo facciamo con l' opzione select di ffmpeg .

Innanzitutto, recuperiamo il conteggio totale dei fotogrammi con un metodo ingegnoso (ad es. 2000) e lo dividiamo per il conteggio delle miniature predefinito (ad es. 5 x 4 = 20). Quindi vogliamo generare un'immagine ogni 100 fotogrammi da allora2000 / 20 = 100

Il comando ffmpeg risultante per generare la miniatura potrebbe apparire

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

Nel codice sopra, vedi 9 diverse -vfcombinazioni costituite da

  • select=not(mod(n\,XXX)) dove XXX è un framerate calcolato
  • thumbnail che seleziona automaticamente i frame più rappresentativi
  • select='gt(scene\,XXX)+ -vsync vfrdove XXX è una soglia con cui devi giocare
  • mpdecimate- Rimuovere i frame quasi duplicati. Buono contro le scene nere
  • yadif- Deinterlaccia l'immagine di input. Non so perché, ma funziona

La versione 3 è la scelta migliore secondo me. Tutti gli altri sono commentati, ma puoi ancora provarli. Sono stato in grado di rimuovere la maggior parte delle miniature sfocate utilizzando mpdecimate, yadife select=not(mod(n\,XXX)). Si!

Per il tuo video di esempio ottengo queste anteprime

inserisci qui la descrizione dell'immagine
clicca per ingrandire

inserisci qui la descrizione dell'immagine
clicca per ingrandire

Ho caricato tutte le anteprime create da quelle versioni. Dai un'occhiata a loro per un confronto completo.


Molto bello! Darò un'occhiata a questo Anche oggi ho perso ore a cercare di mettermi select='gt(scene\,0.4)'al lavoro. E non sono nemmeno riuscito fps="fps=1/600"a lavorare. A proposito, hai qualche idea su superuser.com/questions/725734 ? Grazie mille per tutto il vostro aiuto. Sono super entusiasta di aiutare la mia famiglia a scoprire tonnellate di vecchi ricordi.
Ryan

Hai provato quelle altre 5 varianti di codifica che ho elencato in fondo ? Ho aggiunto un esempio funzionante per l'opzione "scena". Dimentica il filtro FPS. Sono riuscito a rimuovere la maggior parte dei pollici sfocati :)
nixda

0

Apprezzo entrambi gli script, ottimo modo per dividere i video.

Tuttavia, ho avuto dei problemi con i video che non mostravano l'ora corretta in seguito e non sono riuscito a passare a un orario specifico. Una soluzione consisteva nel demux e quindi nel mixare i flussi usando Mp4Box.

Un altro modo più semplice per me era usare mkvmerge per dividere. Pertanto, entrambi gli script dovevano essere modificati. Per detect_black.ps1, solo l'opzione "* .mkv" doveva essere aggiunta all'opzione filtro, ma ciò non è necessariamente se si inizia solo con i file 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 funzione è la stessa, ma finora nessun problema con i video. Grazie per l'ispirazione.


-2

Spero di sbagliarmi, ma non credo che la tua richiesta sia possibile. Potrebbe essere possibile durante la ricodifica del video, ma come "semplice fetta senza perdita di dati" non so in alcun modo.

Molti programmi di editing video avranno una funzione di analisi automatica o qualcosa di equivalente che potrebbe essere in grado di suddividere i tuoi video sui frame neri, ma ciò richiederà la ricodifica del video.

In bocca al lupo!


È assolutamente possibile. Puoi tagliare i video in modo senza perdita di dati (ad es. Con il segmento muxer in ffmpeg , vedi anche il wiki di ffmpeg sul taglio dei video ). Qualsiasi programma di editing video decente dovrebbe essere in grado di farlo. L'unico problema è il rilevamento dei cambi di scena.
slhck,

1
In realtà non puoi. I maggiori problemi potrebbero essere i fotogrammi chiave. Tutte le clip devono iniziare con un fotogramma chiave per poter essere visualizzate correttamente alla riproduzione anche negli strumenti di modifica. Se i fotogrammi neri non iniziano con i fotogrammi chiave, allora ci sarà incompatibilità durante l'utilizzo di tagli senza perdita.
JasonXA,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.