私は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、等?そして、私はそれを実行したいwithout再エンコード(単純なロスレススライスにしたい)。
これどうやってするの?
ここに私が欲しいステップがあります:
これらのスクリプトを、Mac、Ubuntu、およびWindows GitBashで実行できるシェルスクリプトにしたいと思います。
以下は、長いビデオを黒いシーンで小さな章に分割する2つのPowerShellスクリプトです。
それらをDetect_black.ps1およびCut_black.ps1として保存します。 ffmpeg for Windows をダウンロードし、オプションセクションの下にあるffmpeg.exeとビデオフォルダーへのパスをスクリプトに伝えます。
どちらのスクリプトも既存のビデオファイルには触れず、そのまま残ります。
ただし、入力動画と同じ場所にいくつかの新しいファイルがあります
### 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>
であり、それらを除外したいためです。
すべての黒いシーンを検索し、開始タイムスタンプと黒いシーンの期間を<video_name>_cutpoints.txt
という名前の新しいCSVファイルに書き込みます。
次のようにカットポイントも計算します: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
### 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
からcutタイムスタンプのみを読み取ります。
次に、チャプターファイルに適したファイル名をまとめ、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をご覧ください
-c copy
がOPのシナリオに十分であるか、または完全に再エンコードする必要があるかを詳しく説明します。以下の例のように、チャプタービデオごとに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の select オプションで行います。
最初に、独創的な方法(例:2000)で合計フレーム数を取得し、デフォルトのサムネイル数(例:5 x 4 = 20)で割ります。したがって、_2000 / 20 = 100
_以降、100フレームごとに1つの画像を生成する必要があります。
サムネイルを生成するための結果の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 vfr
_ここで、XXXは操作する必要のあるしきい値です。mpdecimate
-重複に近いフレームを削除します。黒のシーンに対して良いyadif
-入力画像のインターレース解除。理由はわかりませんが、機能しますバージョン3は私の意見では最良の選択です。他のすべてはコメント化されていますが、まだ試すことができます。 mpdecimate
、yadif
、およびselect=not(mod(n\,XXX))
を使用して、ぼやけたサムネイルのほとんどを削除することができました。うん!
あなたのために サンプルビデオ 私はこれらのプレビューを取得します
私はアップロードしました すべてのサムネイル それらのバージョンによって作成されました。完全な比較のためにそれらを見てください。
両方のスクリプトに感謝します。これは、ビデオを分割するための優れた方法です。
それでも、後でビデオが正しい時刻を表示しないという問題があり、特定の時刻にジャンプできませんでした。 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
}
機能は同じですが、これまでのビデオ時間に問題はありません。インスピレーションをありがとう。