Automatycznie dzielisz duże pliki wideo .mov na mniejsze pliki w czarnych ramkach (zmiany scen)?

11

Mam 65 plików wideo .mov o nazwie tape_01.mov, tape_02.mov, ..., tape_65.mov. Każda z nich trwa około 2,5 godziny i zajmuje wiele gigabajtów. Są to cyfrowe kopie taśm VHS („domowe filmy” mojej rodziny).

Każdy 2,5-godzinny plik wideo .mov zawiera wiele „rozdziałów” (w tym sensie, że czasami scena kończy się zanikaniem na czarnym ekranie, a następnie pojawia się nowa scena).

Moim głównym celem jest podzielenie 65 dużych plików na mniejsze pliki według rozdziałów. I chcę, aby było to zrobione automatycznie poprzez wykrywanie czarnych ramek między „scenami”.

Czy mogę użyć ffmpeg (lub cokolwiek innego), aby przekazać 65 oryginalnych nazw plików i pozwolić, aby automatycznie iterowało się po każdym filmie i pocięło go, nazywając powstałe fragmenty tape_01-ch_01.mov, tape_01-ch_02.mov, tape_01-ch_03.mov, itp? I chcę, żeby to zrobiło bez ponownego kodowania (chcę, żeby był to prosty bezstratny kawałek).

W jaki sposób mogę to zrobić?

Oto kroki, które chcę:

  1. Iteruj po folderze plików .mov (o nazwie tape_01.mov, tape_02.mov, ..., tape_65.mov), aby wyeksportować je jako pliki mp4 (o nazwie tape_01.mp4, tape_02.mp4, ..., tape_65.mp4) . Chcę, aby ustawienia kompresji były łatwe do skonfigurowania na tym etapie. Oryginalne pliki .mov powinny nadal istnieć po utworzeniu plików .mp4.
  2. Iteruj po plikach MP4: dla każdego pliku MP4 wygeneruj plik tekstowy, który określa czas rozpoczęcia i czas trwania czarnych segmentów między „scenami”. Każde MP4 może mieć 0 lub więcej z tych czarnych przerw scen. Czas rozpoczęcia i czas trwania powinny być precyzyjne z ułamkiem sekundy. GG: MM: Format SS nie jest wystarczająco precyzyjny.
  3. Będę wtedy mógł ręcznie sprawdzić te pliki tekstowe, aby sprawdzić, czy poprawnie identyfikują zmiany scen (czarne segmenty). Mogę dostosować czasy, jeśli chcę.
  4. Inny skrypt odczyta każdy plik mp4 i jego plik txt i podzieli (w super szybki i bezstratny sposób) mp4 na tyle części, ile to konieczne, w oparciu o linie pliku tekstowego (wskazane tam czasy). Podziały powinny odbywać się na środku każdego czarnego segmentu. Oto przykładowy plik mp4 (z usuniętym dźwiękiem w celu ochrony prywatności), który zmienia scenę blaknięcia na czarno. Oryginalne pliki MP4 powinny pozostać nietknięte, ponieważ tworzone są mniejsze pliki MP4. Mniejsze pliki MP4 powinny mieć schemat nazewnictwa tape_01_001.mp4, tape_01_002.mp4, tape_01_003.mp4 itp.
  5. Premia! Byłoby wspaniale, gdyby inny skrypt mógł następnie iterować małe pliki mp4 i wygenerować jeden obraz .png dla każdego, który jest mozaiką zrzutów ekranu, takich jak ta osoba próbuje: Znaczące miniatury dla filmu używającego FFmpeg

Chciałbym, aby te skrypty były skryptami powłoki, które mogę uruchamiać na komputerach Mac, Ubuntu i Windows Git Bash.

Ryan
źródło

Odpowiedzi:

11

Oto dwa skrypty PowerShell do dzielenia długich filmów na mniejsze rozdziały według czarnych scen.

Zapisz je jako Detect_black.ps1 i Cut_black.ps1. Pobierz ffmpeg dla Windows i podaj skryptowi ścieżkę do pliku ffmpeg.exe i folderu wideo w sekcji opcji.

wprowadź opis zdjęcia tutaj

Oba skrypty nie dotykają istniejących plików wideo, pozostają nietknięte.
Otrzymasz jednak kilka nowych plików w tym samym miejscu, w którym znajdują się Twoje filmy wejściowe

  • Plik dziennika na wideo z wyjściem konsoli dla obu używanych komend ffmpeg
  • Plik CSV na wideo ze wszystkimi znacznikami czasu czarnych scen do ręcznego dostrajania
  • Kilka nowych filmów w zależności od liczby wcześniej wykrytych czarnych scen

wprowadź opis zdjęcia tutaj


Pierwszy skrypt do uruchomienia: 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
}

Jak to działa

Pierwszy skrypt iteruje wszystkie pliki wideo, które pasują do określonego rozszerzenia i nie pasują do wzorca *_???.*, ponieważ nazwano nowe rozdziały wideo <filename>_###.<ext>i chcemy je wykluczyć.

Przeszukuje wszystkie czarne sceny i zapisuje znacznik czasu rozpoczęcia i czas trwania czarnej sceny do nowego pliku CSV o nazwie <video_name>_cutpoints.txt

To również oblicza punkty cięcia, jak pokazano: cutpoint = black_start + black_duration / 2. Później wideo jest dzielone na segmenty według tych znaczników czasu.

Plik cutpoints.txt dla Twojego przykładowego wideo pokaże:

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

Po uruchomieniu można ręcznie manipulować punktami cięcia. Po ponownym uruchomieniu skryptu wszystkie stare treści zostaną zastąpione. Zachowaj ostrożność podczas ręcznej edycji i zapisz swoją pracę w innym miejscu.

Dla przykładowego wideo komenda ffmpeg do wykrywania czarnych scen to

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

Istnieją 3 ważne liczby, które można edytować w sekcji opcji skryptu

  • d=4 oznacza, że ​​wykrywane są tylko czarne sceny dłuższe niż 4 sekundy
  • pic_th=0.98 to próg uznawania obrazu za „czarny” (w procentach)
  • pix=0.15ustawia próg uznawania piksela za „czarny” (w luminancji). Ponieważ masz stare filmy VHS, nie masz w nich całkowicie czarnych scen. Domyślna wartość 10 nie zadziała i musiałem nieznacznie zwiększyć próg

Jeśli coś pójdzie nie tak, sprawdź odpowiedni plik dziennika o nazwie <video_name>__ffmpeg.log. Jeśli brakuje następujących linii, zwiększ liczby wymienione powyżej, aż wykryjesz wszystkie czarne sceny:

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



Drugi skrypt do uruchomienia: 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        
}

Jak to działa

Drugi skrypt iteruje wszystkie pliki wideo w taki sam sposób, jak pierwszy skrypt. Odczytuje tylko wycięte znaczniki czasu z odpowiedniego cutpoints.txtfilmu.

Następnie zestawia odpowiednią nazwę pliku dla plików rozdziałów i nakazuje ffmpeg segmentację wideo. Obecnie filmy są dzielone na części bez ponownego kodowania (superszybkie i bezstratne). Z tego powodu mogą występować niedokładności 1-2s ze znacznikami czasu punktu przecięcia, ponieważ ffmpeg może wycinać tylko w klatkach kluczowych. Ponieważ po prostu kopiujemy i nie kodujemy ponownie, nie możemy samodzielnie wstawiać klatek kluczowych.

Poleceniem dla przykładowego wideo byłoby

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

Jeśli coś pójdzie nie tak, spójrz na odpowiedni plik ffmpeg.log



Bibliografia



Do zrobienia

  • Zapytaj OP, czy format CSV jest lepszy niż plik tekstowy jako plik punktu cięcia, abyś mógł edytować je w programie Excel nieco łatwiej
    »Wdrożony
  • Zaimplementuj sposób formatowania znaczników czasu jako [hh]: [mm]: [ss], [milisekund] zamiast tylko sekund
    »Zaimplementowano
  • Zaimplementuj polecenie ffmpeg, aby utworzyć pliki mozaikowe png dla każdego rozdziału
    »Zaimplementowane
  • Sprawdź, czy -c copywystarczy scenariusz OP, czy też musimy w pełni ponownie zakodować.
    Wygląda na to, że Ryan już to robi .
nixda
źródło
Cóż za dokładna odpowiedź! Doceniam to! Niestety, jestem teraz na komputerze Mac i muszę najpierw dowiedzieć się, jak przetłumaczyć to na Bash.
Ryan
W ogóle nie udało mi się tego uruchomić w PowerShell. Pliki dziennika pozostają puste.
Ryan
Zobaczę, co mogę przesłać, aby być pomocnym. Bądźcie czujni. Dzięki za zainteresowanie!
Ryan
Do pytania dodałem próbkę mp4. Dzięki za pomoc!
Ryan
Dla celów prywatności nie będę przesyłał prawdziwego źródłowego MOV. Chciałbym go posiekać i usunąć audio (tak jak zrobiłem dla MP4). Więc i tak nie byłoby to prawdziwe oryginalne źródło. Mimo to przesyłanie go byłoby zbyt duże. I tak, w każdym razie, w przypadku plików na dysku, krok dzielący sceny powinien prawdopodobnie nastąpić na plikach MP4 zamiast na plikach MOV. Dzięki!
Ryan
3

Oto bonus: Ten trzeci skrypt PowerShell generuje obraz mozaiki miniatury

Otrzymasz jeden obraz na rozdział wideo, z których wszystkie wyglądają jak na poniższym przykładzie.

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

Główną ideą jest uzyskanie ciągłego strumienia obrazów w całym wideo. Robimy to z opcją wyboru ffmpeg .

Najpierw pobieramy całkowitą liczbę klatek za pomocą genialnej metody (np. 2000) i dzielimy ją przez naszą domyślną liczbę miniatur (np. 5 x 4 = 20). Od tego czasu chcemy generować jeden obraz na 100 klatek2000 / 20 = 100

Wynikowa komenda ffmpeg do wygenerowania miniatury mogłaby wyglądać

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

W powyższym kodzie widać 9 różnych -vfkombinacji składających się z

  • select=not(mod(n\,XXX)) gdzie XXX to obliczona liczba klatek na sekundę
  • thumbnail który automatycznie wybiera najbardziej reprezentatywne klatki
  • select='gt(scene\,XXX)+ -vsync vfrgdzie XXX to próg, z którym musisz się bawić
  • mpdecimate- Usuń prawie zduplikowane ramki. Dobry na czarne sceny
  • yadif- Usuń przeplot obrazu wejściowego. Nie wiem dlaczego, ale działa

Wersja 3 jest moim zdaniem najlepszym wyborem. Wszystkie pozostałe są komentowane, ale nadal możesz je wypróbować. Byłem w stanie usunąć większość rozmytych miniatur za pomocą mpdecimate, yadifi select=not(mod(n\,XXX)). Tak!

Dla twojego przykładowego wideo otrzymuję te zapowiedzi

wprowadź opis zdjęcia tutaj
Kliknij, aby powiększyć

wprowadź opis zdjęcia tutaj
Kliknij, aby powiększyć

Przesłałem wszystkie miniatury utworzone przez te wersje. Obejrzyj je, aby uzyskać pełne porównanie.

nixda
źródło
Bardzo fajny! Rzucę na to okiem. Również dzisiaj zmarnowałem godziny, próbując dostać się select='gt(scene\,0.4)'do pracy. Nie mogłem też zabrać się fps="fps=1/600"do pracy. Nawiasem mówiąc, czy masz pojęcie o superuser.com/questions/725734 ? Dziękuję bardzo za całą waszą pomoc. Bardzo się cieszę, że pomagam mojej rodzinie odkryć mnóstwo starych wspomnień.
Ryan
Próbowałeś już tych 5 innych wariantów kodowania, które wymieniłem na dole ? Dodałem działający przykład dla opcji „sceny”. Zapomnij o filtrze FPS. Udało mi się usunąć większość
rozmytych
0

Doceń oba skrypty, świetny sposób na dzielenie filmów.

Miałem jednak pewne problemy z tym, że filmy nie wyświetlały później właściwego czasu i nie byłem w stanie przejść do określonego czasu. Jednym z rozwiązań było demultipleksowanie, a następnie zmiksowanie strumieni przy użyciu Mp4Box.

Innym, łatwiejszym dla mnie sposobem było użycie mkvmerge do dzielenia. Dlatego oba skrypty musiały zostać zmienione. W przypadku wykrycia_black.ps1 tylko opcja „* .mkv” musiała zostać dodana do opcji filtru, ale niekoniecznie, jeśli zaczynasz tylko z plikami 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
}

Funkcja jest taka sama, ale jak dotąd nie ma problemów z czasem wideo. Dzięki za inspirację.

Andy Gebauer
źródło
-2

Mam nadzieję, że się mylę, ale nie sądzę, że to, o co pytasz, jest możliwe. Może być to możliwe podczas ponownego kodowania wideo, ale jako „prosty bezstratny wycinek” nie znam żadnego sposobu.

Wiele programów do edycji wideo będzie miało funkcję automatycznej analizy lub coś równoważnego, co może być w stanie podzielić twoje filmy na czarne ramki, ale będzie to wymagać ponownego kodowania wideo.

Powodzenia!

tbenz9
źródło
To absolutnie możliwe. Możesz pokroić wideo w bezstratny sposób bez problemów (np. Z multiplekserem segmentu w ffmpeg , zobacz także wiki ffmpeg na temat cięcia wideo ). Każdy przyzwoity program do edycji wideo powinien być w stanie to zrobić. Jedynym problemem jest wykrywanie zmian sceny.
slhck
1
Właściwie nie możesz. Największymi problemami mogą być klatki kluczowe. Wszystkie klipy muszą zaczynać się od klatki kluczowej, aby mogły być poprawnie renderowane podczas odtwarzania, nawet w narzędziach do edycji. Jeśli czarne ramki nie zaczynają się od klatek kluczowych, oznacza to niezgodność podczas korzystania z bezstratnych cięć.
JasonXA 18.04.15