大きな.movビデオファイルを黒いフレームで自動的に小さなファイルに分割しますか(シーンの変更)?


11

tape_01.mov、tape_02.mov、...、tape_65.movという名前の65個の.movビデオファイルがあります。それぞれの長さは約2.5時間で、何ギガバイトも消費します。これらはVHSテープ(私の家族の「ホームムービー」)のデジタルコピーです。

各2.5時間の.movビデオファイルには、多くの「チャプター」が含まれています(シーンが黒い画面にフェードして終了し、新しいシーンがフェードインするという意味)。

私の主な目標は、65個の大きなファイルを章ごとに小さなファイルにスライスすることです。そして、「シーン」間の黒いフレームを検出することで、それが自動的に行われるようにしたいと思います。

ffmpeg(または何でも)を使用して65の元のファイル名を渡し、各ビデオを自動的に反復して切り取って、結果の部分にtape_01-ch_01.mov、tape_01-ch_02.mov、tape_01-ch_03.mov、等?そして、私はそれ再エンコードせずにそれをしたいです(私はそれを単純なロスレススライスにしたいです)。

これどうやってするの?

ここに私が欲しいステップがあります:

  1. .movファイル(tape_01.mov、tape_02.mov、...、tape_65.mov)のフォルダーを反復処理して、mp4ファイル(tape_01.mp4、tape_02.mp4、...、tape_65.mp4)としてエクスポートします。 。このステップでは、圧縮設定を簡単に構成できるようにしたいと考えています。元の.movファイルは、.mp4ファイルの作成後も存在しているはずです。
  2. mp4ファイルを反復処理します。各mp4ファイルについて、「シーン」間の黒いセグメントの開始時間と継続時間を指定するテキストファイルを生成します。各mp4は、これらの黒いシーンブレークを0以上持つことができます。開始時間と継続時間は、1秒未満の精度である必要があります。HH:MM:SS形式は正確ではありません。
  3. 次に、これらのテキストファイルを手動でダブルチェックして、シーンの変化(黒いセグメント)を正しく識別できるかどうかを確認します。必要に応じてタイミングを調整できます。
  4. 別のスクリプトが各mp4ファイルとそのtxtファイルを読み取り、テキストファイルの行(そこに示されているタイミング)に基づいて、mp4を必要な数の断片に(超高速かつ無損失で)分割します。分割は、各黒いセグメントの中央で行われる必要があります。以下は、フェードトゥブラックのシーンに変更されたサンプルのmp4ファイルです(プライバシー保護のためにオーディオが削除されています)。小さいmp4ファイルが作成されるため、元のmp4ファイルはそのままにしておく必要があります。小さいmp4ファイルには、tape_01_001.mp4、tape_01_002.mp4、tape_01_003.mp4などの命名スキームが必要です。
  5. ボーナス!次に、別のスクリプトが小さなmp4ファイルを反復処理し、それぞれに対して1つの.png画像を生成できれば、この人が試したようなスクリーンショットのモザイクになります。FFmpegを使用したビデオの意味のあるサムネイル

これらのスクリプトをMac、Ubuntu、およびWindows Git Bashで実行できるシェルスクリプトにしたいのですが。

回答:


11

以下は、長いビデオを黒いシーンで小さな章に分割する2つのPowerShellスクリプトです。

それらをDetect_black.ps1およびCut_black.ps1として保存します。Windows用のffmpegをダウンロードし、オプションセクションの下のffmpeg.exeとビデオフォルダーへのパスをスクリプトに伝えます。

ここに画像の説明を入力してください

どちらのスクリプトも既存のビデオファイルには触れず、そのまま残ります。
ただし、入力ビデオと同じ場所にいくつかの新しいファイルが作成されます

  • ビデオごとのログファイルと、使用された両方のffmpegコマンドのコンソール出力
  • 手動の微調整用の黒いシーンのすべてのタイムスタンプを含むビデオごとのCSVファイル
  • 以前に検出された黒いシーンの数に応じて、いくつかの新しいビデオ

ここに画像の説明を入力してください


実行する最初のスクリプト: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
}

それはどのように機能しますか

最初のスクリプトは、指定された拡張子に一致し、パターン*_???.*に一致しないすべてのビデオファイルを反復処理します。これは、新しいビデオチャプターに名前が付けられ<filename>_###.<ext>、それらを除外したいためです。

すべての黒いシーンを検索し、開始タイムスタンプと黒いシーンの継続時間を新しいCSVファイルに書き込みます <video_name>_cutpoints.txt

次のようにカットポイントも計算しますcutpoint = black_start + black_duration / 2。その後、ビデオはこれらのタイムスタンプでセグメント化されます。

サンプル動画のcutpoints.txtファイルは次のようになります。

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

実行後、必要に応じてカットポイントを手動で操作できます。スクリプトを再度実行すると、古いコンテンツはすべて上書きされます。手動で編集して他の場所に保存する場合は注意してください。

サンプルビデオの場合、黒いシーンを検出するためのffmpegコマンドは

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

スクリプトのオプションセクションで編集可能な3つの重要な番号があります

  • d=4 4秒より長い黒いシーンのみが検出されることを意味します
  • pic_th=0.98 画像を「黒」と見なすためのしきい値(パーセント)
  • pix=0.15ピクセルを「輝度」と見なすためのしきい値を設定します。古いVHSビデオがあるので、ビデオに完全に黒いシーンはありません。デフォルト値の10は機能せず、しきい値を少し上げる必要がありました

問題が発生した場合は、対応するログファイルを確認してください<video_name>__ffmpeg.log。次の行がない場合は、すべての黒いシーンが検出されるまで上記の数値を増やします。

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



実行する2番目のスクリプト: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        
}

それはどのように機能しますか

2番目のスクリプトは、最初のスクリプトと同じ方法ですべてのビデオファイルを反復処理します。それはビデオの対応からカットタイムスタンプのみを読み込みますcutpoints.txt

次に、チャプターファイルに適したファイル名をまとめ、ffmpegにビデオをセグメント化するように指示します。現在、ビデオは再エンコードなしでスライスされています(超高速でロスレス)。このため、ffmpegはkey_framesでしかカットできないため、カットポイントのタイムスタンプが1〜2秒不正確になる可能性があります。コピーして再エンコードしないため、key_framesを自分で挿入することはできません。

サンプルビデオのコマンドは次のようになります

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

問題が発生した場合は、対応するffmpeg.logを確認してください



参考文献



藤堂

  • CSV形式がカットポイントファイルとしてのテキストファイルよりも優れているかどうかOPに尋ね、Excelで少し簡単に編集できるようにする
    »実装済み
  • タイムスタンプを秒だけではなく[hh]:[mm]:[ss]、[milliseconds]にフォーマットする方法を実装する
    »実装済み
  • ffmpegコマンドを実装して、各章のmosaik pngファイルを作成
    »実装
  • 場合は精巧な-c copyOPのシナリオのために十分であるか、我々は完全に再エンコードする必要があります。
    ライアンはすでにそれに参加しているようです。

なんと完全な答えでしょう!それは有り難いです!残念ながら、私は今Macを使用しており、これをBashに変換する方法を最初に理解する必要があります。
ライアン、

これをPowerShellでまったく機能させることができませんでした。ログファイルは空白のままです。
ライアン

役立つ情報をアップロードできるか確認します。乞うご期待。関心をお寄せいただきありがとうございます。
ライアン

質問にサンプルmp4を追加しました。ご協力いただきありがとうございます!
ライアン14

プライバシー保護のため、本当のソースMOVはアップロードしません。私はそれを切り刻んでオーディオを削除したいと思います(mp4の場合と同様に)。とにかくそれは本当のオリジナルソースではないでしょう。それでも、アップロードするには大きすぎます。シーン分割のステップは、ディスク容量を確保するために、MOVファイルではなくmp4ファイルで発生する可能性があります。ありがとう!
ライアン

3

おまけは次のとおりです。この3番目のPowerShellスクリプトは、モザイクのサムネイル画像を生成します

以下の例のように見えるチャプタービデオごとに1つの画像を受け取ります。

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

主なアイデアは、ビデオ全体にわたって画像の連続ストリームを取得することです。これを行うには、ffmpegの選択オプションを使用します。

最初に、独創的な方法(例:2000)で合計フレーム数を取得し、デフォルトのサムネイル数(例:5 x 4 = 20)で割ります。したがって、100フレームごとに1つの画像を生成する必要があります。2000 / 20 = 100

サムネイルを生成する結果のffmpegコマンドは次のようになります

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

上記のコードでは、9つの異なる-vf組み合わせで構成されています。

  • select=not(mod(n\,XXX)) ここで、XXXは計算されたフレームレートです
  • thumbnail 最も代表的なフレームを自動的に選択します
  • select='gt(scene\,XXX)+ -vsync vfrXXXはあなたがプレイしなければならないしきい値です
  • mpdecimate-ほぼ重複するフレームを削除します。黒のシーンに対して良い
  • yadif-入力画像をインターレース解除します。理由はわかりませんが、うまくいきます

バージョン3は私の意見では最良の選択です。他のすべてはコメント化されていますが、まだ試すことができます。私が使用してぼやけたサムネイルの大部分を除去することができたmpdecimateyadifselect=not(mod(n\,XXX))。うん!

あなたのサンプルビデオのために、私はこれらのプレビューを取得します

ここに画像の説明を入力してください
拡大するにはクリックしてください

ここに画像の説明を入力してください
拡大するにはクリックしてください

それらのバージョンで作成されたすべてのサムネイルをアップロードました。完全な比較のためにそれらを見てください。


とてもかっこいい!これを見てみましょう。今日もselect='gt(scene\,0.4)'仕事に取り掛かろうとして時間を無駄にしました。そして、私もfps="fps=1/600"仕事に行くことができませんでした。ところで、superuser.com / questions / 725734について何か考えはありますか?ご協力ありがとうございました。家族がたくさんの古い思い出を発見できるように支援できることにとても興奮しています。
ライアン

一番下にリストした他の5つのエンコードバリアントを試しましたか?「シーン」オプションの実際の例を追加しました。FPSフィルターについては忘れてください。私はなんとかぼやけた親指のほとんどを取り除くことができました:)
nixda

0

両方のスクリプトに感謝します。ビデオを分割するための素晴らしい方法です。

それでも、ビデオが後で正しい時刻を表示しないという問題があり、特定の時間にジャンプできませんでした。1つの解決策は、Mp4Boxを使用してストリームを逆多重化してから多重化することでした。

私にとってもう1つの簡単な方法は、分割にmkvmergeを使用することでした。したがって、両方のスクリプトを変更する必要がありました。detect_black.ps1の場合、フィルターオプションに追加する必要があるのは「* .mkv」だけですが、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
}

機能は同じですが、これまでのビデオ時間に問題はありません。インスピレーションをありがとう。


-2

私が間違っていることを願っていますが、あなたの質問が何が可能であるとは思いません。ビデオの再エンコード中には可能かもしれませんが、「単純なロスレススライス」として、私には方法がわかりません。

多くのビデオ編集プログラムには自動分析機能または同等の機能があり、ビデオを黒いフレームで分割できる可能性がありますが、これにはビデオの再エンコードが必要になります。

幸運を!


それは絶対に可能です。問題なくロスレスでビデオをスライスできます(例:ffmpegのセグメントマクサー、ビデオのカットに関するffmpeg wikiも参照してください)。まともなビデオ編集プログラムなら、それができるはずです。唯一の問題は、シーンの変化を検出することです。
slhck 2013

1
実際にはできません。最大の問題はキーフレームかもしれません。すべてのクリップは、編集ツールでも再生時に正しくレンダリングできるように、キーフレームで開始する必要があります。黒いフレームがキーフレームで始まらない場合、ロスレスカットの使用中に互換性がなくなります。
JasonXA 2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.