Windows 11 をクリーンインストールしたついでに、EDCB の録画後エンコード環境を見直した。bat ファイルで自動エンコードやろうとしてた時の文字化け問題、あれを PowerShell + HandBrakeCLI に乗り換えて解決した
0. やったことの流れ
- EDCB 0.10.70.0 (tkntrec-241220) へアップデート。
- 設定ファイル(
EDCB\Setting内.txt) の文字エンコーディングを UTF-8 BOM に統一。 - 録画後の処理を、バッチファイル (
.bat) から PowerShell スクリプト (.ps1) に変更。 - RecName_Macro.dll の設定をして、
.tsファイルのパスの長さを切り詰めておく。 - HandBrake 用のプリセット (
.json) を作った。*1 6.Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachineを管理者ターミナルから実行 - 作成した
autoencode.ps1を EDCB に登録して完了。
1. 文字化け問題と PowerShell への移行理由
- EDCB の録画ダイアログにある
録画終了後実行batのことをググってもパープレにきいてもバッチはSift JISでとあるが、情報が古い。 - 設定ファイルの中にある
.txtを UTF-8 BOM にしても、バッチが実行さる前に作成されるEpgTimer_Bon_RecEnd.batに記述されるパスは文字化けしてる。 - bat が UTF-8 で動かない理由:
cmd.exeは既定で コードページ 932 (Shift_JIS) でバッチを解釈。UTF‑8 で保存しても、EDCB からcmd /cで呼ばれる時点で 932 に戻るため日本語が??化する。chcp 65001を先頭に置いても処理途中で 932 に戻るため根本解決にならない。 - 「バッチ捨てて PS で処理させれば UTF-8 が扱えて文字化けを解決できる」 という結論。
2. PowerShell スクリプト (autoencode.ps1)
Gemini 2.5 Pro に書いてもらった。
# _EDCBX_HIDE_
#Requires -Version 5.0
<#
.SYNOPSIS
EDCB録画後処理スクリプト (HandBrakeによるエンコード)
.DESCRIPTION
EDCBの録画完了後に呼び出され、指定されたTSファイルをHandBrakeCLIでエンコードします。
EDCBからは環境変数経由でファイルパス($env:FilePath)や録画タグ($env:BatFileTag)などを受け取ります。
設定はスクリプト内の変数と、同ディレクトリにある HandBrake プリセット JSON ファイルで行います。
ログは指定されたディレクトリに出力されます。
EDCB拡張命令について:
- 先頭の `# _EDCBX_HIDE_` は、スクリプト実行時のコンソールウィンドウを非表示にするためのEDCB拡張命令です。
詳細は [@docs/edcb_post_rec_script_spec.md](mdc:docs/edcb_post_rec_script_spec.md) を参照してください。
- PowerShell スクリプトの場合、EDCB拡張命令の `_EDCBX_DIRECT_` (マクロの環境変数渡し) と
`_EDCBX_FORMATTIME_` (日付/時刻マクロのISO8601形式化) は常に有効として扱われます。
このスクリプトでは $env:FilePath を直接利用しています。
#>
param(
# CLIからのテスト実行用に、引数でファイルパスを受け取る
[Parameter(Mandatory=$false, Position=0)] # Mandatory=$false にしてEDCB実行時にもエラーにならないように
[string]$CliInputPath
)
# --- 設定 --- Start ---
# HandBrakeCLI.exe のフルパス
$handbrakeCliPath = "C:\TVRec\EDCB\HandBrakeCLI.exe"
# エンコード済みファイルの出力先ディレクトリ
$outputDir = "D:\Encoded"
# ログファイルの出力先ディレクトリ
$logDir = "C:\TVRec\EDCB\autoencode\Log"
# HandBrakeプリセットファイル名 (スクリプトと同じディレクトリにある前提)
$presetFile = "EncodePreset01.json"
# 使用するプリセット名 (JSONファイル内の "PresetName")
$presetName = "MovieDramaAnime"
# エラーファイルを移動するディレクトリ
$errorDir = "J:\TVFile\EncodeError"
# --- 設定 --- End ----
# --- メイン処理 --- Start ---
# 文字コードをUTF-8に設定 (スクリプトとログ出力のため)
$OutputEncoding = [System.Text.Encoding]::UTF8
[System.Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# ログファイルパスの生成
$timestamp = Get-Date -Format "yyyy-MM-dd_HH-mm-ss.ff"
$logFileName = "encode_$timestamp.log"
$logFilePath = Join-Path -Path $logDir -ChildPath $logFileName
# ログディレクトリ作成
if (-not (Test-Path -Path $logDir -PathType Container)) {
try {
New-Item -Path $logDir -ItemType Directory -ErrorAction Stop | Out-Null
} catch {
Write-Error "ログディレクトリの作成に失敗しました: $logDir $($_.Exception.Message)"
exit 1 # ディレクトリ作成失敗は致命的
}
}
# ログ出力関数
function Write-Log {
param(
[Parameter(Mandatory=$true)]
[string]$Message
)
$logTimestamp = Get-Date -Format "yyyy/MM/dd HH:mm:ss"
"$logTimestamp $Message" | Out-File -FilePath $logFilePath -Append -Encoding utf8
}
Write-Log "==== バッチファイル開始 ===="
# 入力ファイルの取得
$inputFile = $null
if (-not [string]::IsNullOrEmpty($CliInputPath)) {
# CLIから引数が渡された場合 (テスト実行)
$inputFile = $CliInputPath
Write-Log "入力ファイル (CLI引数): $inputFile"
} else {
# EDCBからの実行の場合 (環境変数)
$inputFile = $env:FilePath
if ([string]::IsNullOrEmpty($inputFile)) {
Write-Log "エラー: 入力ファイルパスが環境変数から取得できませんでした。"
exit 1
}
Write-Log "入力ファイル (環境変数): $inputFile"
}
# 録画タグのログ出力
if (-not [string]::IsNullOrEmpty($env:BatFileTag)) {
Write-Log "録画タグ (BatFileTag): $($env:BatFileTag)"
} else {
Write-Log "録画タグ (BatFileTag): (設定なし)"
}
# 入力ファイルの存在確認 (念のため)
if (-not (Test-Path -LiteralPath $inputFile -PathType Leaf)) {
Write-Log "エラー: 指定された入力ファイルが存在しません: $inputFile"
exit 1
}
# HandBrakeCLIの存在確認
if (-not (Test-Path -Path $handbrakeCliPath -PathType Leaf)) {
Write-Log "エラー: HandBrakeCLIが見つかりません: $handbrakeCliPath"
exit 1
}
# 出力ディレクトリ作成 (存在しない場合)
if (-not (Test-Path -Path $outputDir -PathType Container)) {
try {
New-Item -Path $outputDir -ItemType Directory -ErrorAction Stop | Out-Null
Write-Log "出力ディレクトリを作成しました: $outputDir"
} catch {
Write-Log "エラー: 出力ディレクトリの作成に失敗しました: $outputDir $($_.Exception.Message)"
exit 1
}
}
# 出力ファイル名の生成
$inputBaseName = [System.IO.Path]::GetFileNameWithoutExtension($inputFile)
$outputFileName = "$inputBaseName.mp4"
$outputFilePath = Join-Path -Path $outputDir -ChildPath $outputFileName
Write-Log "出力ファイル: $outputFilePath"
# プリセットファイルのフルパス生成
$presetFilePath = Join-Path -Path $PSScriptRoot -ChildPath $presetFile
if (-not (Test-Path -Path $presetFilePath -PathType Leaf)) {
Write-Log "エラー: HandBrakeプリセットファイルが見つかりません: $presetFilePath"
exit 1
}
# HandBrakeCLIの引数リストを作成
$arguments = @(
"-i", "`"$inputFile`"",
"-o", "`"$outputFilePath`"",
"--preset-import-file", "`"$presetFilePath`"",
"--preset", "`"$presetName`""
)
Write-Log "エンコード開始: $(Get-Date -Format 'yyyy/MM/dd HH:mm:ss')"
Write-Log "コマンド: `"$handbrakeCliPath`" $($arguments -join ' ')"
# HandBrakeCLIの実行とログリダイレクト
$processInfo = New-Object System.Diagnostics.ProcessStartInfo
$processInfo.FileName = $handbrakeCliPath
$processInfo.Arguments = $arguments -join ' '
$processInfo.RedirectStandardOutput = $true
$processInfo.RedirectStandardError = $true
$processInfo.UseShellExecute = $false
$processInfo.CreateNoWindow = $true # コンソールウィンドウを表示しない
$processInfo.StandardOutputEncoding = [System.Text.Encoding]::UTF8
$processInfo.StandardErrorEncoding = [System.Text.Encoding]::UTF8
$process = New-Object System.Diagnostics.Process
$process.StartInfo = $processInfo
# 標準出力と標準エラー出力をログファイルに追記するイベントハンドラ
$process.EnableRaisingEvents = $true
$stdOutEvent = Register-ObjectEvent -InputObject $process -EventName OutputDataReceived -Action {
if (-not [string]::IsNullOrEmpty($EventArgs.Data)) {
# HandBrakeの進捗表示(行頭が)はログに出さないようにする (任意)
if ($EventArgs.Data -notmatch '^\r') {
("$($EventArgs.Data)") | Out-File -FilePath $script:logFilePath -Append -Encoding utf8
}
}
} -SourceIdentifier StdOutHandler
$stdErrEvent = Register-ObjectEvent -InputObject $process -EventName ErrorDataReceived -Action {
if (-not [string]::IsNullOrEmpty($EventArgs.Data)) {
("ERROR: $($EventArgs.Data)") | Out-File -FilePath $script:logFilePath -Append -Encoding utf8
}
} -SourceIdentifier StdErrHandler
$process.Start() | Out-Null
$process.BeginOutputReadLine()
$process.BeginErrorReadLine()
$process.WaitForExit()
# イベントハンドラの解除
Unregister-Event -SourceIdentifier StdOutHandler
Unregister-Event -SourceIdentifier StdErrHandler
$stdOutEvent | Remove-Job
$stdErrEvent | Remove-Job
$exitCode = $process.ExitCode
if ($exitCode -eq 0) {
# HandBrake 正常終了
Write-Log "HandBrake 正常終了 (終了コード: 0)"
# 1. 出力ファイルの存在確認
if (Test-Path -LiteralPath $outputFilePath -PathType Leaf) {
Write-Log "出力ファイルが存在します: $outputFilePath"
# 2. 出力ファイルのサイズ確認 (例: 1MB以上)
$outputFileSize = $null
try {
$outputFileSize = (Get-Item -LiteralPath $outputFilePath -ErrorAction Stop).Length
} catch {
Write-Log "エラー: 出力ファイルの情報取得に失敗しました: $($_.Exception.Message)"
exit 1 # ファイル情報が取れないのは異常
}
if ($outputFileSize -gt 1MB) { # 1MB = 1048576 bytes
Write-Log "出力ファイルのサイズが妥当です ($([Math]::Round($outputFileSize / 1MB, 2)) MB)"
# --- ここまでのチェックをクリア ---
Write-Log "エンコード成功と判断しました。"
Write-Log "元のファイルを削除します: $inputFile"
try {
Remove-Item -LiteralPath $inputFile -Force -ErrorAction Stop
Write-Log "元のファイルを削除しました。"
# 正常終了
exit 0
} catch {
Write-Log "エラー: 元ファイルの削除に失敗しました: $($_.Exception.Message)"
exit 1 # 削除失敗はエラー扱い
}
} else {
Write-Log "エラー: 出力ファイルのサイズが小さすぎます ($outputFileSize バイト)。エンコード失敗と判断します。"
# 失敗したので、念のため生成された(かもしれない)小さい出力ファイルも削除する (任意)
try {
Remove-Item -LiteralPath $outputFilePath -Force -ErrorAction SilentlyContinue # エラーは無視
Write-Log "サイズの小さい出力ファイルを削除しました: $outputFilePath"
} catch {}
# 元ファイルをエラーフォルダへ移動
Move-ToErrorFolder $inputFile
exit 1
}
} else {
Write-Log "エラー: 出力ファイルが見つかりません。エンコード失敗と判断します。"
# 元ファイルをエラーフォルダへ移動
Move-ToErrorFolder $inputFile
exit 1
}
} else {
# HandBrake 異常終了
Write-Log "エラー: HandBrake が異常終了しました (終了コード: $exitCode)"
# 元ファイルをエラーフォルダへ移動
Move-ToErrorFolder $inputFile
exit 1
}
# --- 関数定義: エラーフォルダへの移動 ---
function Move-ToErrorFolder {
param(
[Parameter(Mandatory=$true)]
[string]$SourcePath
)
# エラーディレクトリ作成 (存在しない場合)
if (-not (Test-Path -LiteralPath $script:errorDir -PathType Container)) { # スクリプトスコープの変数を参照
try {
New-Item -Path $script:errorDir -ItemType Directory -ErrorAction Stop | Out-Null
Write-Log "エラーファイル移動先ディレクトリを作成しました: $script:errorDir"
} catch {
Write-Log "エラー: エラーファイル移動先ディレクトリの作成に失敗しました: $script:errorDir $($_.Exception.Message)"
# ディレクトリ作成失敗時は移動を試みない
return
}
}
$destinationPath = Join-Path -Path $script:errorDir -ChildPath ([System.IO.Path]::GetFileName($SourcePath))
Write-Log "元のファイルをエラーフォルダへ移動します: $destinationPath"
try {
Move-Item -LiteralPath $SourcePath -Destination $destinationPath -Force -ErrorAction Stop
Write-Log "元のファイルをエラーフォルダへ移動しました。"
} catch {
Write-Log "エラー: 元ファイルの移動に失敗しました: $($_.Exception.Message)"
}
}
# --- メイン処理 --- End ----
スクリプトのポイント
- EDCB連携:
# _EDCBX_HIDE_コンソールウィンドウを非表示にする。- 録画タグ (
$env:BatFileTag) はログに出す。現時点ではログに記録するだけ使い道はまた今度。
- HandBrakeCLI実行:
System.Diagnostics.Processを仕様して HandBrakeCLI を直接動かす。- HandBrake の出すメッセージ(標準出力・エラー出力)を横取りして、リアルタイムでログファイルに書き込んでる。これで進捗、エラー内容が追いやすい。
- エラーハンドリング:
- エンコードが終わったら、念のため出来上がったファイルがあるか、サイズが小さすぎないか見てる。ダメそうなら失敗扱い。
- 失敗したら、元の TS ファイルはエラー用フォルダ (
$errorDir) に移動させる (Move-ToErrorFolderって関数)。
- 設定の外部化:
- エンコード設定は外の JSON ファイル (
$presetFile,$presetName) で管理。 設定変える時にスクリプト変更がいらない - 使うツールのパスとか、出力先フォルダとかは、スクリプトの最初のほうで変数 (
$handbrakeCliPathとか) に記載。
- エンコード設定は外の JSON ファイル (
- ログ出力:
- スクリプト自体の動きは
Write-Logって関数でログに書出し - HandBrake の細かい出力は、ログに記録。
- スクリプト自体の動きは
3. EDCB への登録
- EpgTimer (または EpgTimerSrv設定ツール) を起動。
- 録画ダイヤログか、
設定> 動作設定 > 全般の録画プリセット設定を開く。 録画終了後実行batファイルの欄に、作成したautoencode.ps1のフルパス(例:C:\TVRec\EDCB\autoencode\autoencode.ps1)を入力する。- 引数は不要(スクリプトが環境変数からファイルパスを取得するため)。
これで、録画が終わった後この autoencode.ps1 が動いてエンコードが始まるはず。
4. 最後
- bat ファイルで苦しんでた Shift_JIS 由来の文字化け問題は、EDCBのバージョンアップと
EDCB/Settingファイルの文字エンコーディングをUTF-8 BOMに統一と、実行ファイルのPowerShellに変えたら解決した。 - エンコード設定を JSON ファイルで外出しにしたから、後々のメンテが楽になったはず。
5. 参考
EDCB+HandBrake でTSからMP4へ自動コンバートバッチ|はぐ
GitHub - tsukumijima/DTV-Builds: TS抜き(DTV)関連ソフトウェアのビルド済みアーカイブ
EDCB/Document/Readme_Mod.txt at work-plus-s · xtne6f/EDCB · GitHub
*1:今回の話とは関係ないんで解説はしない