0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ffmpegReEncode.ps1

Posted at

1. スクリプト概要

スクリプト名: ffmpegReEncode.ps1
このスクリプトは、対象の動画ファイルを再エンコードします。

2. 処理と目的

処理の流れ
 1. 再エンコード対象のファイルのフルパスを指定します
 2. エンコードに使用するコーデックを選択します
 3. ffprobeを使用して、指定された動画ファイルの情報を取得します
 4. ffmpegを使用して、動画の再エンコードを行います
 5. 手順3~4を手順1で指定したファイルの数だけ繰り返します

目的
 動画が正常に再生できない場合の対策の一つとして、コマンド一つで再エンコードを実行できると便利であるため、このスクリプトを作成しました。

3. 動作環境と要件

PowerShellのバージョン
7.0以上

OS
Windows10

必要なモジュール
ffprobe
ffmpeg

必要な権限
特になし

その他の設定
特になし

4. 使用方法

基本的な実行方法
スクリプトコードを拡張子ps1で保存してPowershellで実行してください。
ファイルを保存する際は、文字コードをUTF8 BOM付にしてください。
ffprobeおよびffmpegをインストールし、環境パスを通してください。

パラメータ
なし

使用例

  1. コマンドラインでpwsh ffmpegReEncode.ps1を実行します
  2. エンコード対象のパスの入力を求められるので、ファイルのフルパスを入力します
  3. 再エンコードに使用するコーデックを選択します

5. スクリプトコード


# ffmpeg実行時の文字化けを防ぐため、コンソールの文字コードをUTF-8に設定
chcp 65001

$host.UI.RawUI.WindowTitle = ([IO.Path]::GetFilenameWithoutExtension($PSCommandPath))
$ErrorActionPreference = 'Stop'

$commonTitle = "ffmpeg(再エンコード)"
# 初期ウィンドウタイトルを設定
$Host.UI.RawUI.WindowTitle = $commonTitle

# エンコード対象のファイルパスを格納するためのArrayListを初期化
$pathList = New-Object System.Collections.ArrayList

# ファイルパスの入力ループ
do {
  $inputText = Read-Host "エンコード対象のパス"
  $inputText = $inputText.Trim('"')

  # パスが入力されており、かつ、現在の入力が空の場合、ループを終了
  if( $pathList.Count -gt 0 -and $inputText -eq ''){
    break
  }

  # 入力されたパスが存在するかどうかをチェック
  if(Test-Path -literal $inputText){
    $null = $pathList.Add($inputText)
  } else {
    Write-Warning "無効なパスです"
  }
} while($true) # 無限ループ(空の入力で終了)

# 利用可能なビデオエンコーダーの一覧の例(コメントアウト)
# ffmpeg -encoders | Where-Object { $_ -match '^ V' }
# 特定のエンコーダー(例: h264_nvenc)の詳細情報を表示する例(コメントアウト)
# ffmpeg -hide_banner -h encoder=h264_nvenc
# 利用可能なオーディオエンコーダーの一覧の例(コメントアウト)
# ffmpeg -encoders | Where-Object { $_ -match '^ A' }

# エンコードコーデックの選択肢を定義
$codecList = @()
$codecList += New-Object PsCustomObject -Property @{'codec'='libx264';    'appendname'='h264'; 'name'='H264'}
$codecList += New-Object PsCustomObject -Property @{'codec'='h264_nvenc'; 'appendname'='h264'; 'name'='H264【GPU(GeForce)エンコード】'}
$codecList += New-Object PsCustomObject -Property @{'codec'='h264_amf';    'appendname'='h264'; 'name'='H264【GPU(Radeon)エンコード】'}
$codecList += New-Object PsCustomObject -Property @{'codec'='libx265';    'appendname'='h265'; 'name'='H265'}
$codecList += New-Object PsCustomObject -Property @{'codec'='hevc_nvenc'; 'appendname'='h265'; 'name'='H265【GPU(GeForce)エンコード】'}
$codecList += New-Object PsCustomObject -Property @{'codec'='hevc_amf';    'appendname'='h265'; 'name'='H265【GPU(Radeon)エンコード】'}

do {
  # Out-GridViewを使用してユーザーにコーデックを選択させる
  $selectCodec = $codecList | Out-GridView -title 'エンコードするコーデックを選択してください' -PassThru
  # コーデックが選択された場合、関連する情報を変数に格納
  if($selectCodec){
    $ffmpegCodec = $selectCodec.codec
    $codecName = $selectCodec.name
    $fileappendname = $selectCodec.appendname
  }
} while([String]::IsNUllOrEmpty($ffmpegCodec)) # コーデックが選択されるまでループを継続

# 出力ディレクトリを定義
$outputDir = "D:\"

echo "`n------------------------------------------------------------"
echo "-- ffmpeg再エンコード対象ファイル"

foreach( $filepath in $pathList ){
  echo "-- $($filepath)"
}

echo "-- コーデック    = ${ffmpegCodec}${codecName})"
echo "------------------------------------------------------------`n"

pause


foreach( $filepath in $pathList ){
  # 現在処理中のファイル名を含むウィンドウタイトルを設定
  $Host.UI.RawUI.WindowTitle = "${commonTitle}:""${filepath}""処理中..."
  
  # 出力ファイルのパスを生成 元のファイル名に選択されたコーデック名(appendname)を付加し、拡張子を.mp4とする
  $outputPath = Join-Path ([IO.Path]::GetDirectoryName($filepath)) ([IO.Path]::GetFileNameWithoutExtension($filepath) + "(${fileappendname}).mp4")
  $bitrate = 0

  # 入力ファイルの情報をffprobeで取得
  # -loglevel quiet: 出力ログを抑制
  # -show_streams: ストリーム情報を表示
  # -print_format json: 出力形式をJSONに設定
  $jsontext = & ffprobe -i $filepath -loglevel quiet -show_streams -print_format json
  
  # JSONテキストをPowerShellオブジェクトに変換
  $movieProp = ConvertFrom-Json -InputObject ($jsontext -join "`n")
  # 動画ストリーム情報を抽出
  $videostream = $movieProp.streams | Where-Object {$_.codec_type -eq 'video'}
  
  # 動画ストリームが存在する場合
  if( $videostream -ne $null){
    echo $videostream

    # ビットレートが直接取得できる場合
    if ($videostream.bit_rate -ne $null){
      $bitrate = $videostream.bit_rate
    }
    # ビットレートが直接取得できないが、デュレーションがある場合、ファイルサイズから計算
    elseif( $videostream.duration -ne $null) {
      $filesize = (Get-Item $filepath).Length # ファイルサイズをバイト単位で取得
      $bitrate = [int](($filesize * 8) / $videostream.duration) # ビットレートをビット/秒で計算
    }
    # 動画情報が取得できない場合、警告を表示
    else {
      Write-Warning "ファイル""${filepath}""の動画情報が取得できませんでした"
    }
  }



  # ビットレートが0より大きい場合のみエンコードを実行
  if ( $bitrate -gt 0 ){
    Write-Host """${filepath}""のエンコード開始" -BackgroundColor White -ForegroundColor Black

    # ffmpegコマンドを実行
    # -hide_banner: ffmpegの著作権情報などを非表示にする
    # -i: 入力ファイルを指定
    # -c:v: ビデオコーデックを指定
    # -b:v: ビデオビットレートを指定
    # -pix_fmt: ピクセルフォーマットをyuv420pに設定(幅広い互換性のため)
    # -movflags +faststart: Web再生に最適化する(メタデータをファイルの先頭に移動)
    # 2>&1 | %{Write-Host $_ -ForegroundColor Cyan -NoNewline}: ffmpegの標準出力と標準エラー出力をコンソールにリアルタイムで表示し、色をシアンにする
    & ffmpeg "-hide_banner" "-i" """$($filepath)""" "-c:v" "${ffmpegCodec}" "-b:v" "${bitrate}" "-pix_fmt" "yuv420p" "-movflags" "+faststart" $outputPath 2>&1 | ForEach-Object {Write-Host $_ -ForegroundColor Cyan -NoNewline}
    
    # 他のffmpegエンコードオプションの例(コメントアウト)
    # & ffmpeg "-hide_banner" "-i" """$($filepath)""" "-c:v" "${ffmpegCodec}" "-preset" "slow" "-pix_fmt" "yuv420p" "-movflags" "+faststart" $outputPath 2>&1 | ForEach-Object {Write-Host $_ -ForegroundColor Cyan -NoNewline}
    # & ffmpeg "-hide_banner" "-i" """$($filepath)""" "-c:v" "${ffmpegCodec}" "-g" "150" "-qcomp" "0.7" "-qmin" "10" "-qmax" "51" "-qdiff" "4" "-subq" "6" "-me_range" "16" "-i_qfactor" "0.714286" $outputPath 2>&1 | ForEach-Object {Write-Host $_ -ForegroundColor Cyan -NoNewline}
    # & ffmpeg "-hide_banner" "-i" """$($filepath)""" "-c:v" "${ffmpegCodec}" "-qcomp" "0.7" "-qmin" "10" "-qmax" "51" "-qdiff" "4" $outputPath 2>&1 | ForEach-Object {Write-Host $_ -ForegroundColor Cyan -NoNewline}

    # ffmpegの終了コードが0以外(エラー)の場合
    if($LASTEXITCODE -ne 0){
      Write-Warning "ffmpeg error '${LASTEXITCODE}'"
      pause
    }
  }
}

# 全ての処理が完了した後、ウィンドウタイトルを「FIN:スクリプト名」に変更
$Host.UI.RawUI.WindowTitle = "FIN:${commonTitle}"

# スクリプトがコンソールで実行されている場合、終了時に一時停止してユーザー入力を待つ
if($host.Name.IndexOf('Console') -ge 0) {pause}

6. 注意事項と既知の問題

制約事項
大量のファイルやサイズの大きいファイルを処理する場合、完了までに時間がかかる可能性があります。

既知のバグ
もしバグを発見された場合は、コメントでご報告ください。

トラブルシューティング
・ps1ファイルのエンコーディングには注意してください。
・ffprobe・ffmpegがインストールされて環境パスが正しく設定されているか注意してください。

7. 免責事項

本スクリプトにはいかなる保証もありません。使用は自己責任で行ってください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?