PowerShellとffmpegを使ってビットレートの大きいMP4を一括エンコードして元ファイルを置き換える

  • 投稿日:
  • by
  • カテゴリ:

HDDの容量を節約するために、特定のフォルダー配下にあるMP4の中から条件に合致する大き目の動画をffmpegで再エンコードしてオリジナルファイルを置き換えるPowerShellスクリプトを作成した。

条件としては、

  • 720P(1280x720)よりも解像度が大きい
  • 720Pであっても、ビットレートが1200Kbpsよりも大きい

のいずれか。
開始フォルダーのパス、ffmpegのパス、エンコードのパラメーター(解像度 scale=1280:720、出力ビットレート 1100k、GeForceを使うh264_nvenc)を書き換えれば応用が利くはず。なお、実行にあたっては、Get-MediaInfoというPowerShellモジュールをinstall-moduleする必要がある。(install-module -name get-mediainfoする)
最終的に元ファイルを削除して一時ファイルをリネームするので、実行にあたっては要所要所をコメントアウトしてISEなどで実行してみてから使うように。このスクリプトを実行したことで何があっても当方は一切責任を負いません。
なお、実際に実行してみてわかったのは、ファイル名に角括弧[]のような特殊文字が入っているとremove-itemがエラー無く失敗するため(内部的には特殊文字を含む当該パスは見つからないので削除対象が無いということだろうけど)、それに続く一時ファイルの元ファイル名へのリネームが失敗する。そこはパラメータとして-LiteralPathを指定することで対処。ただし、これだけは全部のエンコードが終わってからこの記事を書くときに変更したので実動作は不明。


# 指定したパスにあるファイルを再帰的に処理するスクリプト
param (
  [string]$path = "D:\MP4"
)

# 再帰的にMP4ファイルを取得
$files = Get-ChildItem -Path $path -Recurse -File -Include *.mp4

# 各ファイルに対して処理を実行
foreach ($file in $files) {
    Write-Output "Processing file: $($file.FullName)"

    # 720Pより大きいか、720Pだとしてもビットレートが1200k以上であればエンコード    
    if (
        ((Get-MediaInfo -Path $file.FullName).Width -gt 1280) -or
        (((Get-MediaInfo -Path $file.FullName).Width -eq 1280) -and (Get-MediaInfo -Path $file.FullName).Bitrate -gt 1200)) {

        # 一時ファイル名を生成
        $tempFileName = [System.IO.Path]::GetRandomFileName()

        # 一時ファイルの拡張子を変更
        $tempFileMp4 = [System.IO.Path]::ChangeExtension($tempFileName, ".mp4")

        # ファイルのフルパスからフォルダー名を取得
        $folder = Split-Path $file.FullName -Parent
        # 一時ファイルのフルパスを生成
        $tempFilePath = Join-Path -Path $folder -ChildPath $tempFileMp4

        # 一時ファイルのパスを表示
        Write-Output "一時ファイルが作成されました: $tempFilePath"

        # ffmpegでエンコード
        #Write-Output "E:\ffmpeg\bin\ffmpeg.exe -i $file -vf scale=1280:720 -c:v h264_nvenc -b:v 1100k -c:a copy $tempFilePath"
        E:\ffmpeg\bin\ffmpeg.exe -i $file -vf scale=1280:720 -c:v h264_nvenc -b:v 1100k -c:a copy $tempFilePath

      # 元ファイルを削除してから一時ファイルを元ファイルの名前にリネームする
      Remove-Item -LiteralPath $file.FullName -Force
        Rename-Item -Path $tempFilePath -NewName $file.FullName
    }
}

角括弧のような特殊文字がファイル名に入っているとdelでもremove-itemでもエラー無く失敗するので、エンコードするスクリプトを実行する前に元ファイル名から特殊文字を取り除いてリネームした。(以下の処理を前のスクリプトに反映させたのがremove-itemの-LiteralPathの部分)。
一括リネームする処理が以下で、開始フォルダーを指定して、その配下にあるファイル名から"[任意文字列]"の部分を取り除いてリネームする。合わせて、リネームした結果、ファイル名の拡張子を除いた部分の先頭と末尾にスペースがあれば削除する。
-LiteralPathのパラメータがキモで、[任意の文字列]を含んだファイル名をその文字列のまま解釈するように指定するだけ。もちろん、この文字列を取り除いた結果として既に同じファイルが存在する場合もリネームが失敗するので、そこは各自考える必要あり。


# 再帰的にファイルを取得
$files = Get-ChildItem -Path "D:\MP4" -Recurse -File -Include *.mp4

# 各ファイルに対して処理を実行
foreach ($file in $files) {
    $newName = ($file.basename -replace '\[.*?\]', '').Trim()
    $newPath = Join-Path -Path $file.DirectoryName -ChildPath ($newName + ($file).extension)
    Rename-Item -LiteralPath $file.FullName -NewName $newPath
    Write-Output "Renamed file: $($file.FullName) to $newPath"
}