Große .mov-Videodateien bei schwarzen Frames automatisch in kleinere Dateien aufteilen (Szenenwechsel)?

11

Ich habe 65 .mov-Videodateien mit den Namen tape_01.mov, tape_02.mov, ..., tape_65.mov. Jeder ist ungefähr 2,5 Stunden lang und nimmt viele Gigabyte ein. Es sind digitale Kopien von VHS-Kassetten (den "Heimvideos" meiner Familie).

Jede 2,5-stündige .mov-Videodatei enthält viele "Kapitel" (in dem Sinne, dass die Szene manchmal mit einem Überblenden auf einen schwarzen Bildschirm endet und dann eine neue Szene eingeblendet wird).

Mein Hauptziel ist es, die 65 großen Dateien nach Kapiteln in kleinere Dateien aufzuteilen. Und ich möchte, dass dies automatisch durch Erkennen der schwarzen Rahmen zwischen "Szenen" erfolgt.

Könnte ich ffmpeg (oder irgendetwas anderes) verwenden, um die 65 ursprünglichen Dateinamen zu übergeben und es automatisch über jedes Video iterieren zu lassen und es zu zerhacken, wobei ich die resultierenden Teile tape_01-ch_01.mov, tape_01-ch_02.mov, tape_01-ch_03.mov, benenne? usw? Und ich möchte, dass es ohne Neucodierung funktioniert (ich möchte, dass es ein einfaches verlustfreies Slice ist).

Wie kann ich das machen?

Hier sind die Schritte, die ich möchte:

  1. Durchlaufen Sie einen Ordner mit .mov-Dateien (mit dem Namen tape_01.mov, tape_02.mov, ..., tape_65.mov), um sie als mp4-Dateien (mit dem Namen tape_01.mp4, tape_02.mp4, ..., tape_65.mp4) zu exportieren. . Ich möchte, dass die Komprimierungseinstellungen für diesen Schritt einfach zu konfigurieren sind. Die ursprünglichen .mov-Dateien sollten nach dem Erstellen der .mp4-Dateien noch vorhanden sein.
  2. Durchlaufen Sie die MP4-Dateien: Generieren Sie für jede MP4-Datei eine Textdatei, die die Startzeit und Dauer der schwarzen Segmente zwischen "Szenen" angibt. Jeder mp4 kann 0 oder mehr dieser schwarzen Szenenumbrüche enthalten. Die Startzeit und -dauer sollte auf den Bruchteil einer Sekunde genau sein. HH: MM: SS-Format ist nicht präzise genug.
  3. Ich kann diese Textdateien dann manuell überprüfen, um festzustellen, ob sie Szenenänderungen (schwarze Segmente) korrekt identifizieren. Ich kann die Timings anpassen, wenn ich möchte.
  4. Ein anderes Skript liest jede mp4-Datei und ihre txt-Datei und teilt die mp4 (auf superschnelle und verlustfreie Weise) basierend auf den Zeilen der Textdatei (den dort angegebenen Timings) in so viele Teile wie nötig auf. Die Teilungen sollten in der Mitte jedes schwarzen Segments erfolgen. Hier ist eine Beispiel-MP4-Datei (mit aus Datenschutzgründen entferntem Audio), bei der Szenenänderungen in Schwarz überblendet werden. Die ursprünglichen MP4-Dateien sollten beim Erstellen der kleineren MP4-Dateien unberührt bleiben. Die kleineren mp4-Dateien sollten das Namensschema tape_01_001.mp4, tape_01_002.mp4, tape_01_003.mp4 usw. haben.
  5. Bonus! Es wäre fantastisch, wenn ein anderes Skript die kleinen mp4-Dateien durchlaufen und für jedes ein PNG-Bild generieren könnte, das ein Mosaik von Screenshots darstellt, nach denen diese Person sucht : Sinnvolle Miniaturansichten für ein Video mit FFmpeg

Ich möchte, dass diese Skripte Shell-Skripte sind, die ich in Mac, Ubuntu und Windows Git Bash ausführen kann.

Ryan
quelle

Antworten:

11

Hier sind zwei PowerShell-Skripte, mit denen Sie lange Videos nach schwarzen Szenen in kleinere Kapitel aufteilen können.

Speichern Sie sie als Detect_black.ps1 und Cut_black.ps1. Laden Sie ffmpeg für Windows herunter und teilen Sie dem Skript den Pfad zu Ihrer Datei ffmpeg.exe und Ihrem Videoordner im Optionsbereich mit.

Geben Sie hier die Bildbeschreibung ein

Beide Skripte berühren vorhandene Videodateien nicht, sie bleiben unberührt.
Sie erhalten jedoch einige neue Dateien an derselben Stelle, an der sich Ihre Eingabevideos befinden

  • Eine Protokolldatei pro Video mit der Konsolenausgabe für beide verwendeten ffmpeg-Befehle
  • Eine CSV-Datei pro Video mit allen Zeitstempeln schwarzer Szenen zur manuellen Feinabstimmung
  • Ein paar neue Videos, je nachdem, wie viele schwarze Szenen zuvor erkannt wurden

Geben Sie hier die Bildbeschreibung ein


Erstes Skript, das ausgeführt wird: 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
}

Wie funktioniert es

Das erste Skript durchläuft alle Videodateien, die einer bestimmten Erweiterung entsprechen und nicht mit dem Muster übereinstimmen *_???.*, da neue Videokapitel benannt wurden <filename>_###.<ext>und wir sie ausschließen möchten.

Es durchsucht alle schwarzen Szenen und schreibt den Startzeitstempel und die Dauer der schwarzen Szene in eine neue CSV-Datei mit dem Namen <video_name>_cutpoints.txt

Außerdem werden Schnittpunkte wie folgt berechnet : cutpoint = black_start + black_duration / 2. Später wird das Video zu diesen Zeitstempeln segmentiert.

Die Datei cutpoints.txt für Ihr Beispielvideo würde Folgendes anzeigen :

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

Nach einem Lauf können Sie die Schnittpunkte bei Bedarf manuell bearbeiten. Wenn Sie das Skript erneut ausführen, werden alle alten Inhalte überschrieben. Seien Sie vorsichtig beim manuellen Bearbeiten und speichern Sie Ihre Arbeit an einem anderen Ort.

Für das Beispielvideo lautet der Befehl ffmpeg zum Erkennen schwarzer Szenen

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

Es gibt 3 wichtige Nummern, die im Optionsbereich des Skripts bearbeitet werden können

  • d=4 bedeutet, dass nur schwarze Szenen erkannt werden, die länger als 4 Sekunden sind
  • pic_th=0.98 ist die Schwelle für die Betrachtung eines Bildes als "schwarz" (in Prozent)
  • pix=0.15Legt den Schwellenwert für die Betrachtung eines Pixels als "schwarz" (in Luminanz) fest. Da Sie alte VHS-Videos haben, haben Ihre Videos keine vollständig schwarzen Szenen. Der Standardwert 10 funktioniert nicht und ich musste den Schwellenwert leicht erhöhen

Wenn etwas schief geht, überprüfen Sie die entsprechende aufgerufene Protokolldatei <video_name>__ffmpeg.log. Wenn die folgenden Zeilen fehlen, erhöhen Sie die oben genannten Zahlen, bis Sie alle schwarzen Szenen erkennen:

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



Zweites Skript, das ausgeführt werden soll: 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        
}

Wie funktioniert es

Das zweite Skript durchläuft alle Videodateien auf dieselbe Weise wie das erste Skript. Es werden nur die ausgeschnittenen Zeitstempel aus dem entsprechenden cutpoints.txtVideo eingelesen .

Als nächstes wird ein geeigneter Dateiname für Kapiteldateien zusammengestellt und ffmpeg angewiesen, das Video zu segmentieren. Derzeit werden die Videos ohne Neucodierung geschnitten (superschnell und verlustfrei). Aus diesem Grund kann es bei Schnittpunkt-Zeitstempeln zu Ungenauigkeiten von 1 bis 2 Sekunden kommen, da ffmpeg nur bei key_frames schneiden kann. Da wir nur kopieren und nicht neu codieren, können wir key_frames nicht alleine einfügen.

Der Befehl für das Beispielvideo wäre

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

Wenn etwas schief geht, schauen Sie sich das entsprechende ffmpeg.log an



Verweise



Machen

  • Fragen Sie OP, ob das CSV-Format besser ist als eine Textdatei als Schnittpunktdatei, damit Sie sie mit Excel etwas einfacher bearbeiten können.
    »Implementiert
  • Implementieren Sie eine Möglichkeit zum Formatieren von Zeitstempeln als [hh]: [mm]: [ss], [Millisekunden] und nicht nur als Sekunden
    »Implementiert
  • Implementieren Sie einen Befehl ffmpeg, um Mosaik-PNG-Dateien für jedes Kapitel zu erstellen.
    »Implementiert
  • Überlegen Sie, ob dies -c copyfür das OP-Szenario ausreicht oder ob wir es vollständig neu codieren müssen.
    Scheint, als wäre Ryan schon dabei .
Nixda
quelle
Was für eine gründliche Antwort! Ich schätze es! Leider bin ich gerade auf einem Mac und muss erst herausfinden, wie ich das in Bash übersetzen kann.
Ryan
Ich konnte dies in PowerShell überhaupt nicht zum Laufen bringen. Die Protokolldateien bleiben leer.
Ryan
Ich werde sehen, was ich hochladen kann, um hilfreich zu sein. Bleib dran. Danke für Ihr Interesse!
Ryan
Ich habe der Frage ein Beispiel mp4 hinzugefügt. Danke für Ihre Hilfe!
Ryan
Aus Datenschutzgründen werde ich den wahren Quell-MOV nicht hochladen. Ich möchte es hacken und Audio entfernen (wie ich es für mp4 getan habe). Es wäre also sowieso keine echte Originalquelle. Trotzdem wäre es viel zu groß zum Hochladen. Und der Schritt der Szenenaufteilung sollte wahrscheinlich ohnehin für MP4-Dateien anstelle von MOV-Dateien für Speicherplatzzwecke erfolgen. Vielen Dank!
Ryan
3

Hier ist der Bonus: Dieses 3. PowerShell-Skript generiert ein Mosaik-Miniaturbild

Sie erhalten ein Bild pro Kapitelvideo, die alle wie im folgenden Beispiel aussehen.

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

Die Hauptidee besteht darin, einen kontinuierlichen Bildstrom über das gesamte Video zu erhalten. Wir tun dies mit FFmpegs wählen Sie Option.

Zuerst rufen wir die Gesamtzahl der Frames mit einer ausgeklügelten Methode ab (z. B. 2000) und teilen sie durch unsere Standardanzahl der Miniaturansichten (z. B. 5 x 4 = 20). Wir wollen also seitdem alle 100 Frames ein Bild erzeugen2000 / 20 = 100

Der resultierende Befehl ffmpeg zum Generieren der Miniaturansicht könnte folgendermaßen aussehen

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

Im obigen Code sehen Sie 9 verschiedene -vfKombinationen bestehend aus

  • select=not(mod(n\,XXX)) Dabei ist XXX eine berechnete Framerate
  • thumbnail Dadurch werden die repräsentativsten Frames automatisch ausgewählt
  • select='gt(scene\,XXX)+ -vsync vfrwobei XXX eine Schwelle ist, mit der Sie spielen müssen
  • mpdecimate- Entfernen Sie nahezu doppelte Frames. Gut gegen schwarze Szenen
  • yadif- Trennen Sie das Eingabebild. Ich weiß nicht warum, aber es funktioniert

Version 3 ist meiner Meinung nach die beste Wahl. Alle anderen sind auskommentiert, aber Sie können sie trotzdem ausprobieren. Ich war in der Lage die meisten der verschwommenen Thumbnails entfernen verwenden mpdecimate, yadifund select=not(mod(n\,XXX)). Ja!

Für dein Beispielvideo bekomme ich diese Vorschau

Geben Sie hier die Bildbeschreibung ein
klicken um zu vergrößern

Geben Sie hier die Bildbeschreibung ein
klicken um zu vergrößern

Ich habe alle von diesen Versionen erstellten Miniaturansichten hochgeladen . Schauen Sie sie sich für einen vollständigen Vergleich an.

Nixda
quelle
Sehr cool! Ich werde mir das ansehen. Ich habe heute auch Stunden damit verschwendet, select='gt(scene\,0.4)'zur Arbeit zu kommen. Und ich konnte auch nicht fps="fps=1/600"zur Arbeit kommen. Haben Sie übrigens eine Idee zu superuser.com/questions/725734 ? Vielen Dank für all Ihre Hilfe. Ich freue mich sehr darauf, meiner Familie zu helfen, unzählige alte Erinnerungen zu entdecken.
Ryan
Haben Sie diese 5 anderen Codierungsvarianten ausprobiert, die ich unten aufgeführt habe ? Ich habe ein Arbeitsbeispiel für die Option "Szene" hinzugefügt. Vergessen Sie den FPS-Filter. Ich habe es geschafft, die meisten verschwommenen Daumen zu entfernen :)
Nixda
0

Schätzen Sie beide Skripte, eine großartige Möglichkeit, Videos zu teilen.

Trotzdem hatte ich einige Probleme mit Videos, die danach nicht die richtige Zeit zeigten, und ich konnte nicht zu einer bestimmten Zeit springen. Eine Lösung bestand darin, die Streams mit Mp4Box zu demuxen und dann zu muxen.

Eine andere, einfachere Möglichkeit für mich war, mkvmerge zum Teilen zu verwenden. Daher mussten beide Skripte geändert werden. Für detect_black.ps1 musste nur die "* .mkv" zur Filteroption hinzugefügt werden, aber das ist nicht unbedingt so, wenn Sie nur mit mp4-Dateien beginnen.

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

Die Funktion ist die gleiche, aber bisher keine Probleme mit den Videozeiten. Danke für die Inspiration.

Andy Gebauer
quelle
-2

Ich hoffe, ich liege falsch, aber ich denke nicht, dass Ihre Frage möglich ist. Es mag möglich sein, Videos neu zu codieren, aber als "einfaches verlustfreies Stück" kenne ich keinen Weg.

Viele Videobearbeitungsprogramme verfügen über eine automatische Analysefunktion oder eine ähnliche Funktion, mit der Sie Ihre Videos möglicherweise in schwarze Rahmen aufteilen können. Dies erfordert jedoch eine Neukodierung des Videos.

Viel Glück!

tbenz9
quelle
Es ist absolut möglich. Sie können Videos ohne Probleme verlustfrei schneiden (z. B. mit dem Segment-Muxer in ffmpeg , siehe auch das ffmpeg-Wiki zum Schneiden von Videos ). Jedes anständige Videobearbeitungsprogramm sollte dazu in der Lage sein. Das einzige Problem ist das Erkennen von Szenenänderungen.
Slhck
1
Eigentlich kannst du nicht. Die größten Probleme könnten Keyframes sein. Alle Clips müssen mit einem Keyframe beginnen, damit sie bei der Wiedergabe auch in Bearbeitungswerkzeugen ordnungsgemäß gerendert werden können. Wenn schwarze Frames nicht mit Keyframes beginnen, tritt bei der Verwendung verlustfreier Schnitte eine Inkompatibilität auf.
JasonXA