web-dev-qa-db-ja.com

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

私は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再エンコード(単純なロスレススライスにしたい)。

これどうやってするの?

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

  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個以上含めることができます。開始時間と期間は、ほんの一瞬で正確である必要があります。 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ファイルを反復処理して、この人物が試したようなスクリーンショットのモザイクである.png画像をそれぞれ生成できるとしたら、すばらしいでしょう。 FFmpegを使用したビデオの意味のあるサムネイル

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

11
Ryan

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

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

enter image description here

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

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

enter image description here


実行する最初のスクリプト: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>であり、それらを除外したいためです。

すべての黒いシーンを検索し、開始タイムスタンプと黒いシーンの期間を<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



実行する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から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をご覧ください



参考文献



Todo

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

おまけは次のとおりです。この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の 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は私の意見では最良の選択です。他のすべてはコメント化されていますが、まだ試すことができます。 mpdecimateyadif、およびselect=not(mod(n\,XXX))を使用して、ぼやけたサムネイルのほとんどを削除することができました。うん!

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

enter image description here
拡大するにはクリックしてください

enter image description here
拡大するにはクリックしてください

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

3
nixda

両方のスクリプトに感謝します。これは、ビデオを分割するための優れた方法です。

それでも、後でビデオが正しい時刻を表示しないという問題があり、特定の時刻にジャンプできませんでした。 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
}

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

0
Andy Gebauer