4.FFmpeg自動エンコード+Googleフォトに無限保存編 - TS抜き環境構築

DTV記事一覧
1.予備知識、ハードウェア、ドライバ編
TS抜きとは何か、何が必要なのか、共通の項目。
2'.TVTest0.7.23+EDCB人柱版10.66編(非対応)
・(非推奨)取り敢えず視聴、録画が出来る環境を構築する。
2.TVTest0.9.0+xtne6f版EDCB編
・(推奨)安定して視聴、録画が出来る環境を構築する。
2018年4月~BSトラポン移動によるTVTest、EDCBのバイナリ変更点
3.xtne6f EDCB-Work-Plus-sでTwitter連携編
Twitter連携はRubyTwitter API Gemを使用する。
4.FFmpeg自動エンコード+Googleフォトに無限保存編
・バッチ又はPowerShellからFFmpegで綺麗にエンコードし、Backup and Syncでアップロードする。
・音ズレさせない正しい処理や、ファイルの自動削除機能等も備えているため、完全放置が可能。

滅茶苦茶ザックリ言うと、EDCBで録画した後、ffmpegで自動エンコードして、FHD10GBまで無料無制限のGoogleフォト(ドライブ)に無限に保存しちゃおうぜっていう話。

TSファイル、バッチやPowerShellffmpegGoogleフォトの厄介な仕様に対応させ、放置しても完全自動で動いてくれるようなスクリプトを書いた。

あと画質の悪いデジタル放送を出来る限り綺麗に、現実的な速さで、圧縮率の高いエンコードを行う為にかなり試行錯誤した。エンコード品質にお悩みの方はffmpegの引数だけでも見ていってくれると嬉しい。

録画後スクリプトの更新情報(詳細はgist参照)
2017/4
・先行探索固定品質を使う
2017/5
・改良
・文章の整理
・3つのバッチを2つに集約
・~~自動エンコバッチで特定の条件下で音ズレする問題について記述~~
・サービスIDを指定してエンコすることで特定条件下での音ズレを回避
・デュアルモノを処理する方法を詳しく
・番組情報を参照し条件分岐することで2つのバッチを1つに集約
・~~斧にうpした自動エンコバッチを更新~~
・2GB以下ではなく未満の場合Google Photos Backup用フォルダに移動させるように変更
・フォルダやffmpegオプションの登録を環境変数で集約
・自動エンコバッチでデジタル放送に合わせてYV12を使うように、24fps化するように変更、解説を分かりやすく?修正
ffmpegのDLリンクが不正確だったので修正
・エンコ時に字幕を埋め込む方法を記述
2017/6
ffmpegのppフィルタでかなり綺麗に
ffmpegの使い方について少し補足
・~~自動エンコでbwdifよりyadifの方が若干綺麗~~だったので戻した
・自動エンコでrefs等でかなり綺麗になりほぼ完成かな?
・QSVの動作条件を追記
・EDCBの機能を使ってtsやmp4を削除する方法を追記
・ループカウントによる無限ループ対策
・うp容量オーバー時とエラー時にツイートで報告するオマケ機能を追加
・無駄な条件分岐を削除し合理化
・24fps化を廃止
・解像度を1440x810→1280x720に変更
2017/7
・古いファイルの自動削除機能を付けてタスクスケジューラを不要にした
・古いファイルの自動削除機能が動いてなかったので修正
・global_qualityを29に変更
Shift_JIS以外の文字コードの場合動作出来ないことを追記
Googleフォトが長過ぎるファイル名に対応していないっぽいのでRecName_Macro.dllで適切に処理
Backup and Sync登場でスマホ版と同じく2GB制限が廃止されたので10GBまでうpできるように対応
2017/8
・リサイズにフィルタを使うように変更し品質を向上
・ファイルサイズの判別時の余計な処理を削減
コマンドプロンプトのタイトルバーにエンコ中のファイル名を表示する機能の追加
・~~roop~~->loop///
2017/9
・yadifからbwdifに変更
GoogleフォトがHEVCに対応したので記述(実装はしない)
2017/10
・ループ回数を10から50へ
・ffmpeg3.4への対応
・~~Googleフォトの仕様を鑑みて720p->810pに変更~~
2017/11
・_EDCBX_DIRECT_を使用
・PostRecEnd.batと録画後実行batの違いを明記
・見やすく
2018/1
・tsファイルサイズが20GBより大きい場合エンコ品質を下げて10GBに収める処理を追加
・ファイルの削除oldセクションのバグ修正orz
・一定数残して古い方から削除ではなく録画フォルダのサイズを一定に保ちながら古い方から削除する方法に変更
・ファイルの削除devバグ修正
Googleフォトにおいて一部のファイルのmoov atomが壊れてしまう問題について記述
・-fflags +discardcorrupt、-bsf:a aac_adtstoascを追加
解説を書き換え改善
2018/2
・デュアルモノ番組直後の番組の音声は再エンコが必要な(ことを忘れていた)ので-c:a copyを諦め再エンコする仕様に戻した(暫定)
・ログ出力機能追加
・エラー処理を整理
・削除機能周りの無駄を大幅に書き直した
・フォルダサイズを一定に保つファイルの削除サブルーチンに入ると1つの古いファイルでなく全てのファイルに対して削除が実行されてしまう不具合を修正
・事前準備、オプション設定の解説が雑だったので修正
・delがechoになったままで無限ループしてしまう不具合を修正
・削除機能をGB単位で指定出来るようにした
・ツイート機能にも環境変数を割り当てた
・コードの整理
・視聴予約時は実行しない
2018/3
ffmpegのオプションを修正し品質、圧縮率を向上した(pp=ac->hqdn3d、scale=1280:720~~:flags=lanczos+accurate_rnd~~、~~-look_ahead_depth 100~~、-look_ahead_downsampling off)
・-global_quality 26、-vf pp=dr,hqdn3d=3.000,scale=1280:720:flags=bicubic+accurate_rndへ変更
・hqdn3dフィルタの前にscaleすることで高速化(ppフィルタの順番は据え置き)
~~・無駄にcallしてたのをネストして纏めた~~
・バッチの解説を滅茶滅茶書いた
・最低限エンコしてうpしてくれればいいよって方向けに、ログ出力、ファイルの削除、tsファイルサイズ判別、ツイート警告機能が無いものを追加
・ログの自動削除機能、自動予約キーワードによる連番jpg出力機能を追加
・ツイート時にテキストファイルではなく環境変数を渡すようにした
・EpgTimerSrvをサービス登録しても正常に動作する為に環境変数Pathの使用を廃止
WindowsサービスでffmpegのQSVが使用できないことを追記
エンコードが開始しなかった場合のエラー処理を追加
・exist->definedに修正
・widthがメタデータの2箇所にありjpg出力が2度実行されちゃうバグを修正
Googleドライブの仕様について修正
PowerShell版PostRecStart追加と共に、この項を記事化した
・PostRecEnd.ps1の詳しい解説を追加
・PostRecEnd.ps1のミスを修正、若干処理の見直しを行った。
・jpg出力ルーチン内の変数を修正
・PostRecEnd.ps1でユーザ設定を1箇所で行えるように改良、使い方を追記
変数が間違っていたのを修正
・ps1でwhile文から無理矢理exitするのを辞め改善
2018/4
・誤字修正
ffmpegのオプションを改善、説明を追加、誤字修正
・-refs 9に修正(level=5.0->level=4.1)
2018/5
・ps1のmp4のファイルサイズによるQSVループ処理を、終了コードで行うことにより不具合を網羅した
・特殊な音声チャンネルに対応した
・一部Gistで管理するようにした
・ffmpeg4.0に対応(-init_hw_device qsv:hw削除)
・PostRecEnd.batも終了コードによるループに変更した
・色空間を明示する必要があることを知り引数を修正orz
・batのffmpeg引数を修正、ps1は全てのtsのエンコードに対応するようPID判別処理を追加した(ドロップログ必須)
環境変数pathの説明を修正
・"写真と動画をgoogleフォトにアップロード"のチェックが不要
・記事内容を大幅に整理
・ps1:-ltを-lsにしてしまっていたため起動できない問題を修正
・ps1に新しいffmpeg引数を反映
・ts.errがts.ts.errになっていたのを修正
・-LiteralPathが無いとpowershellが"["を処理出来ない(今回はPID判別)ので修正
・ts.errの削除がされていないのを修正
2018/6
・事前準備の項目に指定サービスのみのtsである必要があることを追記
・PID判別の漏れを修正
・PID判別はドロップログ式では不可能なことが判明したため、ffmpeg式を完成させた
・PID判別でVideoが複数あった場合余計な文字列が入ってしまう、また適切な判別が行われない不具合を修正
・警告ツイートやバルーンチップにエラー詳細が表示されるようにした
・PID判別の処理方法を変えることで、480iな裏番組にも対応させ、その場合にもインターミッション等も処理できるようにした(ffmpeg4.0では未だにデュアルモノをスプリットできないようだ)
ffmpegプロセスの優先度を高で実行することでQSVのエラーを減らすようにした
2018/7
・ps1の解説を更新した
・タスクトレイアイコンのヒントが表示されない時がある不具合を修正
・DiscordのWebhookで警告する機能追加
・720リサイズ+シャープフィルタでは1080より処理速度、品質共に劣るためリサイズを辞めた
・EpgDataCap_Bonの同時起動数をログや警告に加えた
2018/8
・PID判別の修正
・x265はじめました(やめました)
・プロセス優先度、プロセッサ親和性の項目をユーザ設定に加えた
・例外処理の整理
・ts、mp4の自動削除が無効の場合にエンコード失敗時に退避させない
・ts、mp4の自動削除の処理方法を改め高速化した
エイリアスと干渉した関数名を修正した
2018/9
・jpg出力でwaifu2x等出来るように
・(ローカル保存用途向け)ts、mp4の自動削除が無効の場合、フォルダが上限サイズを超過したらTwitter、Discordに警告するように
・(ローカル保存用途向け)mp4の10GB制限を設定可能な項目とした
・ユーザ設定の説明を更新

スクリプト

表を見ると分かるように、PostRecEnd.ps1を推します。設定が一箇所で出来るから1番導入が簡単だし、ps1以外にはPID判別が無いので偶にエンコード失敗しちゃうんだよね。

機能 PostRecEnd.batテスト構成 PostRecEnd.bat最小構成 PostRecEnd.batフル構成 PostRecEnd.ps1(推奨)
主な用途 録画後エンコードのテスト? 録画後エンコード、ローカルに保存 録画後エンコードGoogleフォトに保存
放置 X エンコードの放置:△
ストレージ放置:X
エンコードの放置:△
ストレージ放置:O
O
ログオンせずにEpgTimerSrv.exeをサービスで動かす
X:ユーザ環境変数使用、QSVを使用 X:QSVを使用
タスクトレイアイコン、バルーンチップ
マウスオーバー時にファイル名を表示、エンコード終了・失敗時にファイル名とts、mp4それぞれのファイルサイズを表示
X X X O
ユーザ設定
環境設定、機能のオンオフ、閾値の設定時にプログラム本体を直接弄る必要が無い
X X X O
ログ出力
不具合の原因を探しやすい
X X O O
ts、mp4ファイル等の削除
放っておいてもストレージが満杯にならない為に、フォルダサイズを一定(100GB等)に丸め込むように古いファイルから削除する
X X O:tsフォルダ最大サイズに合わせてts、ts.program.txt、ts.err、mp4を削除(設定が複雑、ファイルサイズ計算が不正確) O:tsフォルダ最大サイズに合わせてts、ts.program.txt、ts.err、mp4フォルダ最大サイズに合わせてmp4を削除
jpg出力
好みの番組を自動予約キーワードで判別し、連番jpgでも保存する
X X O:キーワードにスペース等の記号が含まれる場合に非対応 O
ffmpegでtsをmp4にエンコード
デュアルモノの判別
デュアルモノ音声の場合正しく分離する為に番組情報ファイルで音声引数を条件分岐
X:手動 O O O
PIDの判別
音声・映像チャンネルの切替でffmpegの出力が音ズレ・失敗しない為にffmpegで必要なPIDのみを取得
X:手動 X:ffmpegの引数である程度は判別 X:ffmpegの引数である程度は判別 O
QSV処理失敗の回復
FFmpegのQSVエラーによるエンコ失敗からの回復の為、ffmpegの終了コードが1の間50回までループ
X:手動 O O O
Googleフォトにアップロード
tsファイルサイズによる品質選択
mp4が出来るだけ10GB以内になるように大きなtsでは品質を落とす
X X O:ファイルサイズ計算が不正確 O
mp4ファイルサイズ判別
品質を落としても10GBに収まらなかったものはうp出来ない為、tsやmp4等が削除されないよう隔離
X X O:ファイルサイズ計算が不正確 O
ツイート警告機能
録画失敗、エンコード失敗等でうp出来なかった場合にツイートで警告(別記事参照)
X X O O
Discord警告機能
録画失敗、エンコード失敗等でうp出来なかった場合にDiscordのWebhookで警告
X X X O

ウォーターマークが無い番組への対応が難しそうなので、今のところCMカット機能は実装していない。
※実装はしていないが、ffmpegでロゴ消しする方法についてはここを参照すると良いcf.

PostRecEnd.batテスト構成

録画後エンコードのテストに使います。これだけでは自動処理としてマトモに動きません。
じゃあ後のやつはなんでこんなに長いの?エンコするだけでしょ?と思うかもしれませんが、安定した動作やログ取り、ファイルの削除の自動化を含めた機能があるためです。以下のような1行のffmpegコマンドでもエンコード自体は可能ですが、完全自動化は出来ません。

rem _EDCBX_DIRECT_
ffmpeg -y -hide_banner -nostats -fflags +discardcorrupt -i "%FilePath%" -c:a aac -vf yadif=0:-1:1,hqdn3d=4.0,scale=1280:720,unsharp=3:3:0.5:3:3:0.5:0 -global_quality 27 -c:v h264_qsv -preset:v veryslow -g 300 -bf 16 -refs 9 -b_strategy 1 -look_ahead 1 -look_ahead_downsampling off -pix_fmt nv12 -bsf:v h264_metadata=colour_primaries=1:transfer_characteristics=1:matrix_coefficients=1 -map 0:p:%SID10%:0 -map 0:p:%SID10%:1 -movflags +faststart "出力フォルダのパス\%FileName%.mp4"
pause

PostRecEnd.bat最小構成

@echo on
rem 180521
rem _EDCBX_DIRECT_
rem _EDCBX_DONTHIDE_
rem 視聴予約なら終了
if "%RecMode%" == "4" (
goto :eof
)
rem ファイル名をタイトルバーに表示
title %~nx0:%FileName%.ts
rem ====================環境変数設定====================
rem 一時的にmp4を吐き出すフォルダのパス
set "tmp_folder_path=C:\DTV\tmp"
rem backup and sync用フォルダのパス
set "bas_folder_path=C:\DTV\backupandsync"
rem エンコしたファイルが10GBより大きい、処理に25回失敗した場合にts、ts.program.txt、mp4を退避するフォルダのパス
set "err_folder_path=C:\Users\Shibanyan\Desktop"
rem ====================デュアルモノの判別====================
rem 番組情報の中に"デュアルモノ"という文字列があれば環境変数"audio_option"に"-filter_complex channelsplit"を加え、音声ビットレートを半分にする
findstr "デュアルモノ" "%FilePath%.program.txt"
if %errorlevel% equ 0 (
set audio_option=-c:a aac -b:a 128k -filter_complex channelsplit
) else if %errorlevel% equ 1 (
rem set audio_option=-c:a copy -bsf:a aac_adtstoasc
set audio_option=-c:a aac -b:a 256k
)
rem ====================エンコード====================
rem ループ処理用
set cnt=0
:encode
rem 録画の開始終了でビジーなので負荷を減らすためにちょっと待つ
timeout /t 10 /nobreak
rem エンコ(%quality%は使用不可)
ffmpeg -y -hide_banner -nostats -init_hw_device qsv:hw -fflags +discardcorrupt -i "%FilePath%" %audio_option% -vf yadif=0:-1:1,hqdn3d=4.0,scale=1280:720:flags=lanczos+accurate_rnd,unsharp=3:3:0.5:3:3:0.5:0 -global_quality 27 -c:v h264_qsv -preset:v veryslow -g 300 -bf 16 -refs 9 -b_strategy 1 -look_ahead 1 -look_ahead_downsampling off -pix_fmt nv12 -map 0:p:%SID10%:0 -map 0:p:%SID10%:1 -map 0:p:%SID10%:2 -sn -dn -ignore_unknown -movflags +faststart "%tmp_folder_path%\%FileName%.mp4"
rem 終了コードが1且つループカウントが25以下までの間、エンコードを試みる
if "%errorlevel%" equ "1" (
if "%cnt%" leq "25" (
set /a cnt+=1
goto :encode
) else (
goto :err
)
)
rem ====================mp4ファイルサイズ判別====================
rem エンコ後mp4のサイズを環境変数"mp4_size"に指定
for %%i in ("%tmp_folder_path%\%FileName%.mp4") do (
set mp4_size=%%~zi
)
rem 10GB=10737418240byte以下ならbackup and sync用フォルダ、より大きいなら10GB以上用フォルダへ(mp4が約40GBを超える場合は想定していない)
if %mp4_size:~0,-1% leq 1073741824 (
move "%tmp_folder_path%\%FileName%.mp4" "%bas_folder_path%"
) else if %mp4_size:~0,-1% gtr 1073741824 (
goto :err
)
rem 正常終了
exit
:err
rem ffmpegによるエラーのみ対応、シンタックスエラーやコマンドプロンプトの不具合はここに到達しないのでログを参照されたし
rem エンコしたファイルが10GBより大きい、処理に25回失敗した場合にts、ts.program.txt、mp4を退避する
move "%FilePath%" "%err_folder_path%"
move "%FilePath%.program.txt" "%err_folder_path%"
move "%tmp_folder_path%\%FileName%.mp4" "%err_folder_path%"
rem 異常終了
exit
view raw PostRecEnd.bat hosted with ❤ by GitHub

PostRecEnd.batフル構成

@echo on
rem 180521
rem _EDCBX_DIRECT_
rem _EDCBX_DONTHIDE_
rem 視聴予約なら終了
if "%RecMode%" == "4" (
goto :eof
)
rem ファイル名をタイトルバーに表示
title %~nx0:%FileName%.ts
rem ====================環境変数設定====================
rem ログフォルダのパス
set "log_path=C:\DTV\EncLog"
rem 連番jpgを出力するフォルダ用のディレクトリのパス
set "jpg_path=C:\Users\Shibanyan\Desktop\TVTest"
rem 一時的にmp4を吐き出すフォルダのパス
set "tmp_folder_path=C:\DTV\tmp"
rem backup and sync用フォルダのパス
set "bas_folder_path=C:\DTV\backupandsync"
rem エンコしたファイルが10GBより大きい、処理に25回失敗した場合にts、ts.program.txt、mp4を退避するフォルダのパス
set "err_folder_path=C:\Users\Shibanyan\Desktop"
rem tweet.rbのパス
set "tweet_rb_path=C:\DTV\EDCB\tweet.rb"
rem SSL証明書のパス
set "ssl_cert_file=C:\DTV\EDCB\cacert.pem"
rem ====================ログ====================
if not "%started%" == "1" (
set started=1
rem 自分自身を子バッチとして起動し直し、ログ出力
call "%~0" %* > "%log_path%\%FileName%.txt" 2>&1
exit
)
rem dirでログフォルダのtxtファイルをファイル名のみ日付順で表示し、新しい方から100個飛ばし、あぶれたファイルを削除する
for /f "skip=100 delims=" %%a in ('dir "%log_path%\*.txt" /b /o-d') do (
rem log削除
del "%log_path%\%%~na.txt"
)
rem ====================jpg出力====================
rem 環境変数Addkey(自動予約時のキーワード)に特定の文字が含まれている場合は連番jpgも出力
rem findstr ""でerrorlevel=2となる
echo "%AddKey%" | findstr "ポプテピピック フランキス BEATLESS エヴァーガーデン"
if %errorlevel% equ 0 (
rem 出力フォルダ作成
md "%jpg_path%\%FileName%"
rem 生TSの横が1920か1440か調べる
rem 変数ts_widthに格納することで、tsファイル特有のwidthがメタデータの2箇所にあり2つ出力されちゃう問題を解決
for /f "delims=" %%a in ('ffprobe -v quiet -i "%FilePath%" -show_entries stream^=width -of default^=noprint_wrappers^=1:nokey^=1') do (
set "ts_width=%%a"
)
if "%ts_width%" == "1440" (
rem 1440ならば1920x1080にリサイズしてjpg出力
ffmpeg -hide_banner -nostats -an -skip_frame nokey -i "%FilePath%" -vf yadif=0:-1:1,scale=1920:1080,hqdn3d=4.0 -f image2 -q:v 0 -vsync 0 "%jpg_path%\%FileName%\%%05d.jpg"
) else (
rem 1440でなければリサイズせずにjpg出力
ffmpeg -hide_banner -nostats -an -skip_frame nokey -i "%FilePath%" -vf yadif=0:-1:1,hqdn3d=4.0 -f image2 -q:v 0 -vsync 0 "%jpg_path%\%FileName%\%%05d.jpg"
)
)
rem ====================tsフォルダサイズを一定に保つファイルの削除====================
rem 録画フォルダの最大サイズを指定(1~20000の整数、単位:GB)
set folder_max=100
rem GBをbyteの下4桁切り捨て(32bit計算の為)た状態に直す
set /a folder_max=folder_max*107374
:filedeltop
rem tsのフォルダ内のファイルすべてのサイズをdirでソートし、findstrでフォルダサイズの行を抽出し、3番目のトークンを環境変数に格納
for /f "tokens=3 delims= " %%a in ('dir /-d /-c "%FolderPath%" ^| findstr "個のファイル"') do (
set folder_size=%%a
)
rem 録画フォルダのサイズがfolder_maxで指定したサイズより大きいならサブルーチンを読んでこのルーチンの最初に戻る
rem 環境変数folder_size、folder_maxがそれぞれ下4桁切り捨てのbyte単位で計算される
if %folder_size:~0,-4% gtr %folder_max% (
call :filedelsub
goto :filedeltop
)
rem ====================デュアルモノの判別====================
rem 番組情報の中に"デュアルモノ"という文字列があれば環境変数"audio_option"に"-filter_complex channelsplit"を加え、音声ビットレートを半分にする
findstr "デュアルモノ" "%FilePath%.program.txt"
if %errorlevel% equ 0 (
set audio_option=-c:a aac -b:a 128k -filter_complex channelsplit
) else if %errorlevel% equ 1 (
rem set audio_option=-c:a copy -bsf:a aac_adtstoasc
set audio_option=-c:a aac -b:a 256k
)
rem ====================tsファイルサイズ判別====================
rem TSファイルのサイズを環境変数"ts_size"に指定
for %%i in ("%FilePath%") do (
set ts_size=%%~zi
)
rem 20GB=21474836480byte以下ならquality 26、より大きいなら28
if %ts_size:~0,-1% leq 2147483648 (
set quality=26
) else if %ts_size:~0,-1% gtr 2147483648 (
set quality=28
)
rem ====================エンコード====================
rem ループ処理用
set cnt=0
:encode
rem 録画の開始終了でビジーなので負荷を減らすためにちょっと待つ
timeout /t 10 /nobreak
rem エンコ
ffmpeg -y -hide_banner -nostats -init_hw_device qsv:hw -fflags +discardcorrupt -i "%FilePath%" %audio_option% -vf yadif=0:-1:1,hqdn3d=4.0,scale=1280:720:flags=lanczos+accurate_rnd,unsharp=3:3:0.5:3:3:0.5:0 -global_quality %quality% -c:v h264_qsv -preset:v veryslow -g 300 -bf 16 -refs 9 -b_strategy 1 -look_ahead 1 -look_ahead_downsampling off -pix_fmt nv12 -map 0:p:%SID10%:0 -map 0:p:%SID10%:1 -map 0:p:%SID10%:2 -sn -dn -ignore_unknown -movflags +faststart "%tmp_folder_path%\%FileName%.mp4"
rem 終了コードが1且つループカウントが25以下までの間、エンコードを試みる
if "%errorlevel%" equ "1" (
if "%cnt%" leq "25" (
set /a cnt+=1
goto :encode
) else (
goto :err
)
)
rem ====================mp4ファイルサイズ判別====================
rem エンコ後mp4のサイズを環境変数"mp4_size"に指定
for %%i in ("%tmp_folder_path%\%FileName%.mp4") do (
set mp4_size=%%~zi
)
rem 10GB=10737418240byte以下ならbackup and sync用フォルダ、より大きいなら10GB以上用フォルダへ(mp4が約40GBを超える場合は想定していない)
if %mp4_size:~0,-1% leq 1073741824 (
move "%tmp_folder_path%\%FileName%.mp4" "%bas_folder_path%"
) else if %mp4_size:~0,-1% gtr 1073741824 (
goto :err
)
rem 正常終了
exit
rem ====================サブルーチン====================
:filedelsub
rem dirで録画フォルダのtsファイルをファイル名のみ新しいものから日付順で表示し、最後に環境変数del_nameに代入される一番古いtsファイル名を取得
rem 遅延環境変数を使うと!をなんとかする処理が増えるので、ラベルをcallすることにした
for /f "delims=" %%a in ('dir "%FolderPath%\*.ts" /b /o-d') do (
set "del_name=%%~na"
)
rem ts削除
del "%FolderPath%\%del_name%.ts"
rem 同名のts.program.txt削除
del "%FolderPath%\%del_name%.ts.program.txt"
rem 同名のmp4削除
del "%bas_folder_path%\%del_name%.mp4"
exit /b
:err
rem ffmpegによるエラーのみ対応、シンタックスエラーやコマンドプロンプトの不具合はここに到達しないのでログを参照されたし
rem エンコしたファイルが10GBより大きい、処理に25回失敗した場合にts、ts.program.txt、mp4を退避する
move "%FilePath%" "%err_folder_path%"
move "%FilePath%.program.txt" "%err_folder_path%"
move "%tmp_folder_path%\%FileName%.mp4" "%err_folder_path%"
rem ツイートで警告
set "tweet_content=ERROR:%FileName%.tsと関連ファイルを退避しました。ログを確認して下さい。"
ruby "%tweet_rb_path%"
rem 異常終了
exit
view raw PostRecEnd.bat hosted with ❤ by GitHub

PostRecEnd.ps1

#190101
#_EDCBX_HIDE_
#ファイル名をタイトルバーに表示
#(Get-Host).UI.RawUI.WindowTitle="$($MyInvocation.MyCommand.Name):${env:FileName}.ts"
#####################ユーザ設定####################################################################################################
<#
設定の書き方ルール(PowerShellの仕様)
X だめ
$Toggle= イコール残したままにしない!テストでXにされるよ!
O おk
$Toggle=$False 無効
$Toggle=$false 大文字小文字の区別はない
$Toggle=0 無効
$Toggle=$True 有効
$Toggle=1 有効
$Toggle = $true =や+=の前後にスペースがあっても良い(コーディング的にはこっちが推奨っぽい)
$Toggle 空欄
$Toggle=$Null null
#$Toggle コメント
X だめ
$Path=C:\DTV\EncLog エラー出る
$Path="C:\DTV\エンコード ログ" 処理できるかもしれないけど基本的にパスに半角や全角のスペースは非推奨
O おk
$Path='C:\DTV\EncLog' 変数が無ければリテラルでおk
$Path = "C:\DTV\EncLog" もちのろん
X だめ
$Arg='-quality $ArgQual' シングルクォートでは'$ArgQual'という文字列になってしまうので変数の中身が展開されないよ!
$Arg="-vf bwdif=0:-1:1$ArgScale" 変数名とコードが紛らわしいよ!
$Arg="-i "${FilePath}"" ダブルクオートの範囲が滅茶滅茶だよ><
$Arg='-i "${FilePath}"' 変数が展開されないよ!
O おk
$Arg="-quality $ArgQual" ダブルクォートでは変数の中身が展開される
$Arg="-vf bwdif=0:-1:1${ArgScale}" ${}を使おう
$Arg="-i `"${FilePath}`"" バッククオート`でエスケープしよう
X だめ
$logcnt_max="1000" こういうのはString(文字列)じゃないよ!int(数値)だよ!
O おk
$logcnt_max=1000
$logcnt_max=[int]"1000"
$Size=200GB
$Size="200GB" OKらしい(^^;;
$Size=0.2TB
#>
#ffmpeg.exe、ffprobe.exeがあるディレクトリ
$ffpath='C:\DTV\ffmpeg'
#--------------------ログ--------------------
#$False=無効、$True=有効
$log_toggle=$True
#ログ出力ディレクトリ
$log_path='C:\Rec\EncLog'
#ログを残す数
$logcnt_max=500
#--------------------tsの自動削除--------------------
#閾値を超過した場合、$False=容量警告、$True=tsを自動削除
$TsFolderRound=$True
#録画フォルダの上限
$ts_folder_max=100GB
#--------------------mp4の自動削除--------------------
#閾値を超過した場合、$False=容量警告、$True=mp4を自動削除
$Mp4FolderRound=$True
#mp4用フォルダの上限
$mp4_folder_max=50GB
#--------------------jpg出力--------------------
#$True=有効 $False=無効
$jpg_toggle=$True
#連番jpgを出力するフォルダ用のディレクトリ
$jpg_path='C:\Users\sbn\Desktop\TVTest'
#jpg出力したい自動予約キーワード(常にjpg出力する場合: $jpg_addkey='')
$jpg_addkey='インターミッション|やがて君になる|宇宙戦艦ヤマト|アリシゼーション|beatless|ハイスクール・フリート|灰と幻想のグリムガル|デート・ア・ライブ|ダンジョンに出会いを求める|荒野のコトブキ飛行隊|山田くんと7人の魔女|冴えない彼女の育てかた|キルラキル|サイコパス|PSYCHO|鬼滅の刃'
#自動予約キーワードに引っ掛かった場合に実行するコード 使用可能:$ArgScale(横が1440pxの場合のみ",scale=1920:1080"が格納される。画像にはSARとか無いので)
function ImageEncode {
#連番jpg出力する例
<#
#出力ディレクトリ
New-Item "${jpg_path}\${env:FileName}" -ItemType Directory
#プロセス実行
Invoke-Process -File "${ffpath}\ffmpeg.exe" -Arg "-y -hide_banner -nostats -an -skip_frame nokey -i `"${env:FilePath}`" -vf bwdif=0:-1:1,pp=ac,hqdn3d=2.0${ArgScale} -f image2 -q:v 0 -vsync 0 `"$jpg_path\$env:FileName\%05d.jpg`""
#>
#waifu2xをここで使用することは一応可能ですが、処理時間が非現実的です
#tsを保持用ディレクトリにコピーする例
Copy-Item -LiteralPath "${env:FilePath}" "F:\ts" -ErrorAction SilentlyContinue
}
#--------------------tsファイルサイズ判別--------------------
#映像の品質引数をtsファイルサイズによって適応的に変える($ArgQual)
#適応品質機能 $False=無効(エンコード引数内に記述)、$True=通常・低品質を閾値で切り替え
$tssize_toggle=$True
#閾値
$tssize_max=20GB #くらいがおすすめ
#通常品質(LA-ICQ:27,x265:25)
#$quality_normal='-init_qpI 20 -init_qpP 25 -init_qpB 26'
$quality_normal='-init_qpI 22 -init_qpP 24 -init_qpB 25'
#低品質(LA-ICQ:30,x265:27)
$quality_low='-init_qpI 22 -init_qpP 27 -init_qpB 28'
#--------------------デュアルモノの判別--------------------
#音声引数をデュアルモノか否かで変える($ArgAudio)
#デュアルモノ
$audio_dualmono='-c:a aac -b:a 128k -ac 1 -max_muxing_queue_size 400 -filter_complex channelsplit'
#通常
$audio_normal='-c:a aac -b:a 256k -ac 2 -max_muxing_queue_size 4000' #失敗しない、ただし再エンコ
#$audio_normal='-c:a copy' #失敗する上ExitCode=0
#$audio_normal='-c:a copy -bsf:a aac_adtstoasc' 失敗する上ExitCode=0
#--------------------PIDの判別--------------------
#必要なPIDを取得し-map引数に加える($ArgPid)
#--------------------エンコード--------------------
#mp4の一時出力ディレクトリ
$tmp_folder_path='C:\Rec\tmp'
#mp4保存(Backup and Sync、ローカル保存)用ディレクトリ
$mp4_folder_path='C:\Rec\mp4'
#例外ディレクトリ(ループしてもffmpegの処理に失敗、mp4が10GBより大きい場合 etc…にts、ts.program.txt、ts.err、mp4を退避)
$err_folder_path='C:\Rec\Err'
#mp4の10GBファイルサイズ上限 $True=有効 $False=無効
$googledrive=$True
#mp4用ffmpeg引数
<#
-File: 実行ファイルのパス
-Arg: 引数
$ArgAudio(エンコ失敗しない為に必須)
$ArgQual(エンコード引数内に記述し品質やビットレートを固定する場合不要)
$ArgPid(エンコ失敗しない為に必須)
-Priority: プロセス優先度 MSDNのProcess.PriorityClass参照 (Normal,Idle,High,RealTime,BelowNormal,AboveNormal) ※必須ではない
-Affinity: 使用する論理コアの指定 MSDNのProcess.ProcessorAffinity参照 コア5(10000)~12(100000000000)を使用=0000111111110000(2進)=4080(10進)=0xFF0(16進) ※必須ではない
NVEnc H.264 VBR MinQP
-Arg "-y -hide_banner -nostats -fflags +discardcorrupt -i `"${env:FilePath}`" ${ArgAudio} -vf bwdif=0:-1:1 -c:v h264_nvenc -preset:v slow -profile:v high -rc:v vbr_minqp -rc-lookahead 32 -spatial-aq 1 -aq-strength 1 -qmin:v 23 -qmax:v 25 -b:v 1500k -maxrate:v 3500k -pix_fmt yuv420p ${ArgPid} -movflags +faststart `"${tmp_folder_path}\${env:FileName}.mp4`""
QSV H.264 LA-ICQ
-Arg "-y -hide_banner -nostats -analyzeduration 30M -probesize 100M -fflags +discardcorrupt -ss 5 -i `"${env:FilePath}`" ${ArgAudio} -vf bwdif=0:-1:1,pp=ac,hqdn3d=2.0 -global_quality ${ArgQual} -c:v h264_qsv -preset:v veryslow -g 300 -bf 6 -refs 4 -b_strategy 1 -look_ahead 1 -look_ahead_depth 60 -pix_fmt nv12 -bsf:v h264_metadata=colour_primaries=1:transfer_characteristics=1:matrix_coefficients=1 ${ArgPid} -movflags +faststart `"${tmp_folder_path}\${env:FileName}.mp4`""
x265 fast
-Arg "-y -hide_banner -nostats -analyzeduration 30M -probesize 100M -fflags +discardcorrupt -i `"${env:FilePath}`" ${ArgAudio} -vf bwdif=0:-1:1,pp=ac -c:v libx265 -crf ${ArgQual} -preset:v fast -g 15 -bf 2 -refs 4 -pix_fmt yuv420p -bsf:v hevc_metadata=colour_primaries=1:transfer_characteristics=1:matrix_coefficients=1 ${ArgPid} -movflags +faststart `"${tmp_folder_path}\${env:FileName}.mp4`""
x265 fast bel9r inspire
-Arg "-y -hide_banner -nostats -analyzeduration 30M -probesize 100M -fflags +discardcorrupt -i `"${env:FilePath}`" ${ArgAudio} -vf bwdif=0:-1:1,pp=ac -c:v libx265 -preset:v fast -x265-params crf=${ArgQual}:rc-lookahead=40:psy-rd=0.3:keyint=15:no-open-gop:bframes=2:rect=1:amp=1:me=umh:subme=3:ref=3:rd=3 -pix_fmt yuv420p -bsf:v hevc_metadata=colour_primaries=1:transfer_characteristics=1:matrix_coefficients=1 ${ArgPid} -movflags +faststart `"${tmp_folder_path}\${env:FileName}.mp4`""
x264 placebo by bel9r
-Arg "-y -hide_banner -nostats -analyzeduration 30M -probesize 100M -fflags +discardcorrupt -i `"${env:FilePath}`" ${ArgAudio} -vf bwdif=0:-1:1,pp=ac -c:v libx264 -preset:v placebo -x264-params crf=${ArgQual}:rc-lookahead=60:qpmin=5:qpmax=40:qpstep=16:qcomp=0.85:mbtree=0:vbv-bufsize=31250:vbv-maxrate=25000:aq-strength=0.35:psy-rd=0.35:keyint=300:bframes=6:partitions=p8x8,b8x8,i8x8,i4x4:merange=64:ref=4:no-dct-decimate=1 -pix_fmt yuv420p -bsf:v h264_metadata=colour_primaries=1:transfer_characteristics=1:matrix_coefficients=1 ${ArgPid} -movflags +faststart `"${tmp_folder_path}\${env:FileName}.mp4`""
#>
function VideoEncode {
#hevc_nvenc constqp (qpI,P,Bはtsファイルサイズ判別を参照)
Invoke-Process -File "${ffpath}\ffmpeg.exe" -Arg "-y -hide_banner -nostats -analyzeduration 30M -probesize 100M -fflags +discardcorrupt -i `"${env:FilePath}`" $ArgAudio -vf bwdif=0:-1:1 -c:v hevc_nvenc -preset:v slow -profile:v main -rc:v constqp -rc-lookahead 32 $ArgQual -g 300 -bf 3 -refs 6 -pix_fmt yuv420p $ArgPid -movflags +faststart `"${tmp_folder_path}\${env:FileName}.mp4`"" -Priority 'BelowNormal' -Affinity '0xFFF'
}
#--------------------Post--------------------
#$False=Error時のみ、$True=常時 Twitter、DiscordにPost
$InfoPostToggle=$False
#Twitter機能 $False=無効、$True=有効
$tweet_toggle=$True
#ruby.exe
$ruby_path='C:\Ruby25-x64\bin\ruby.exe'
#tweet.rb
$tweet_rb_path='C:\DTV\EDCB\tweet.rb'
#SSL証明書(環境変数)
$env:ssl_cert_file='C:\DTV\EDCB\cacert.pem'
#Discord機能 $False=無効、$True=有効
$discord_toggle=$True
#webhook url
$hookUrl='https://discordapp.com/api/webhooks/XXXXXXXXXX'
#BalloonTip機能 $False=無効、$True=有効
$balloontip_toggle=$True
<#
エラーメッセージ一覧
・[EDCB] 録画失敗によりエンコード不可: tsファイルが無い(パスが渡されない)場合。録画失敗?
・[EDCB] PIDの判別不可: ストリームの解析が失敗以前に不可能。Drop過多orスクランブル解除失敗?
・[GoogleDrive] 10GB以上の為アップロードできません: GoogleDriveの仕様に合わせる。
・[h264_qsv] device failed (-17): QSVのエラー。ループして復帰を試みるも失敗した場合。
・[mpegts] コーデックパラメータが見つかりません: PID判別から渡されたPIDが適切でないorffmpegが非対応のストリーム。
・[aac] 非対応のチャンネルレイアウト: ffmpeg4.0~デュアルモノを少なくとも従来の引数では扱えなくなった。
・[-c:a aac] PIDの判別に失敗: -c:a aac時。指定サービスのみ(全サービスでない)録画になっていなければ必ず発生。また一通りのストリームに対応させた筈だけど起こるかもしれない。
・[-c:a copy] PIDの判別に失敗: -c:a copy時。上に同じ。
・[-c:a aac] PIDの判別に失敗? ExitCode:0: -c:a aac時。ffmpegの終了コードは0だが異常がある?場合。
・[-c:a copy] -c:a aacか-ss 1で治るやつ ExitCode:0: -c:a copy時。上に同じ。
・[FFmpeg] 無効な引数
・不明なエラー
#>
#########################################################################################################################
#====================Post関数====================
function Post
{
param
(
[bool]$exc,
[bool]$toggle,
[string]$content,
[string]$tipicon,
[string]$tiptitle
)
#例外。自動削除が有効の場合、ts、ts.program.txt、ts.err、mp4を退避
if ($exc)
{
if ($TsFolderRound)
{
Move-Item -LiteralPath "${env:FilePath}" "${err_folder_path}" -ErrorAction SilentlyContinue
Move-Item -LiteralPath "${env:FilePath}.program.txt" "${err_folder_path}" -ErrorAction SilentlyContinue
Move-Item -LiteralPath "${env:FilePath}.err" "${err_folder_path}" -ErrorAction SilentlyContinue
}
if ($Mp4FolderRound)
{
Move-Item -LiteralPath "${tmp_folder_path}\${env:FileName}.mp4" "${err_folder_path}" -ErrorAction SilentlyContinue
}
}
#Error時だけでなく、Info時もPostできるようにするトグル
if ($toggle)
{
#Twitter警告
if ($tweet_toggle)
{
$env:content = $content
&"$ruby_path" "$tweet_rb_path"
#Start-Process "${ruby_path}" "${tweet_rb_path}" -WindowStyle Hidden -Wait
}
#Discord警告
if ($discord_toggle)
{
$payload = [PSCustomObject]@{
content = $content
}
$payload = ($payload | ConvertTo-Json)
$payload = [System.Text.Encoding]::UTF8.GetBytes($payload)
Invoke-RestMethod -Uri $hookUrl -Method Post -Body $payload
}
}
#BalloonTip
if ($balloontip_toggle)
{
#特定のTipIconのみを使用可
#[System.Windows.Forms.ToolTipIcon] | Get-Member -Static -Type Property
$balloon.BalloonTipIcon = [System.Windows.Forms.ToolTipIcon]::$tipicon
#表示するタイトル
$balloon.BalloonTipTitle = $tiptitle
#表示するメッセージ
$balloon.BalloonTipText = $content
#balloontip_toggle=1なら5000ミリ秒バルーンチップ表示
$balloon.ShowBalloonTip(5000)
#5秒待って
Start-Sleep -Seconds 5
}
#タスクトレイアイコン非表示(異常終了時は実行されずトレイに亡霊が残る仕様)
$balloon.Visible = $False
}
#視聴予約なら終了
if ($env:RecMode -eq 4) {
return "視聴予約の為終了"
}
if ("${env:FilePath}" -eq $null) {
Post -Exc $True -Toggle $True -Content "Error:${env:Title}`n[EDCB] 録画失敗によりエンコード不可" -TipIcon 'Error' -TipTitle '録画失敗'
}
#====================Invoke-Process関数====================
#ffmpeg、&ffmpeg、.\ffmpeg:ffmpegが引数を正しく認識しない(ファイル名くらいなら-f mpegtsで行けるけどもういいです)
#Start-Process ffmpeg:-NoNewWindowはWrite-Host?-RedirectStandardOutput、Errorはファイルのみ、-PassThruはExitCodeは受け取れても.StandardOutput、Errorは受け取れない仕様
function Invoke-Process {
param
(
[string]$priority,
[int]$affinity,
[string]$file,
[string]$arg
)
Write-Output "Invoke-Process:$file $arg"
#設定
#ProcessStartInfoクラスをインスタンス化
$psi=New-Object System.Diagnostics.ProcessStartInfo
#アプリケーションファイル名
$psi.FileName = $file
#引数
$psi.Arguments = $arg
#標準エラー出力だけを同期出力(注意:$trueは1つだけにしないとデッドロックします)
$psi.UseShellExecute = $false
$psi.RedirectStandardInput = $false
$psi.RedirectStandardOutput = $false
$psi.RedirectStandardError = $true
$psi.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden
#実行
#Processクラスをインスタンス化
$p=New-Object System.Diagnostics.Process
#設定を読み込む
$p.StartInfo = $psi
#プロセス開始
$p.Start() > $Null
#プロセッサ親和性
if ($affinity)
{
#(Get-Process -Id $p.Id).ProcessorAffinity=[int]"$Affinity"
$p.ProcessorAffinity = [int]"$affinity"
}
#プロセス優先度
if ($priority)
{
$p.PriorityClass = $priority
}
#標準エラー出力をプロセス終了まで読む
$script:StdErr = $Null
while (!$p.HasExited)
{
$script:StdErr += "$($p.StandardError.ReadLine())`n"
}
#プロセスの標準エラー出力を変数に格納(注意:WaitForExitの前に書かないとデッドロックします)
#$script:StdErr=$p.StandardError.ReadToEnd()
#プロセス終了まで待機
#$p.WaitForExit()
#終了コードを変数に格納
$script:ExitCode = $p.ExitCode
#リソースを開放
$p.Close()
}
#====================NotifyIcon====================
#System.Windows.FormsクラスをPowerShellセッションに追加
Add-Type -AssemblyName System.Windows.Forms
#NotifyIconクラスをインスタンス化
$balloon=New-Object System.Windows.Forms.NotifyIcon
#powershellのアイコンを使用
$balloon.Icon=[System.Drawing.Icon]::ExtractAssociatedIcon('C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe')
#NotifyIcon.Textが64文字を超えると例外、String.Substringの開始値~終了値が文字数を超えると例外
switch (("$($MyInvocation.MyCommand.Name):${env:FileName}.ts").Length) {
{$_ -ge 64} {$TextLength="63"}
{$_ -lt 64} {$TextLength="$_"}
}
#タスクトレイアイコンのヒントにファイル名を表示
$balloon.Text=([string]($MyInvocation.MyCommand.Name) + ":${env:FileName}.ts").SubString(0,$TextLength)
#タスクトレイアイコン表示
$balloon.Visible=$True
#ログ有効時、NotifyIconクリックでログを既定のテキストエディタで開く
if ($log_toggle)
{
$balloon.add_Click({
if ($_.Button -eq [System.Windows.Forms.MouseButtons]::Left)
{
&"${log_path}\${env:FileName}.txt"
}
})
}
#====================ログ====================
#ログのソート例: (sls -path "$log_path\*.txt" 'faild' -SimpleMatch).Path
#log_toggle=$Trueならば実行
if ($log_toggle) {
#ログ取り開始
Start-Transcript -LiteralPath "${log_path}\${env:FileName}.txt"
#録画用アプリの起動数を取得
#$RecCount=(Get-Process -ErrorAction 0 "EpgDataCap_bon","TVTest").Count
#Write-Output "同時録画数:$RecCount"
#Get-ChildItemでログフォルダのtxtファイルを取得、更新日降順でソートし、logcnt_max個飛ばし、ForEach-ObjectでRemove-Itemループ
Get-ChildItem "${log_path}\*.txt" | Sort-Object LastWriteTime -Descending | Select-Object -Skip $logcnt_max | ForEach-Object {
Remove-Item -LiteralPath "$_"
Write-Output "ログ削除:$_"
}
}
#====================ts・mp4の自動削除====================
#フォルダの合計サイズを設定値以下に丸め込む関数
function FolderRound {
param
(
[bool]$toggle,
[string]$ext,
[string]$path,
[string]$round
)
if ($toggle)
{
#初期値
$delcnt=-1
#必ず1回は実行、フォルダ内の新しいファイルをSkipする数$iを増やしていって$maintsizeを$round以下に丸め込むループ
do {
$delcnt++
$maintsize=(Get-ChildItem "$path\*.$ext" | Sort-Object LastWriteTime -Descending | Select-Object -Skip $delcnt | Measure-Object -Sum Length).Sum
} while ($maintsize -gt $round)
#先程Skipしたファイルを実際に削除
Get-ChildItem "$path\*.$ext" | Sort-Object LastWriteTime | Select-Object -First $delcnt | ForEach-Object {
#tsかmp4を削除
Remove-Item -LiteralPath "$path\$($_.BaseName).$ext" -ErrorAction SilentlyContinue
$dellog="削除:$($_.BaseName).$ext"
#tsを削除中の場合、同名のts.program.txt、ts.errも削除
if ("$ext" -eq "ts") {
Remove-Item -LiteralPath "$path\$($_.BaseName).$ext.program.txt" -ErrorAction SilentlyContinue
Remove-Item -LiteralPath "$path\$($_.BaseName).$ext.err" -ErrorAction SilentlyContinue
$dellog+="、.program.txt、.err"
}
Write-Output $dellog
}
Write-Output "${ext}フォルダ:$([math]::round(${maintsize}/1GB,2))GB"
} elseif (!($toggle))
{
#超過時の警告
if ($((Get-ChildItem "$path" | Measure-Object -Sum Length).Sum) -gt $round) {
$err_detail="`n[FolderRound] ${ext}ディレクトリが${round}を超過"
}
}
}
#ts
FolderRound -Toggle $TsFolderRound -Ext "ts" -Path "$env:FolderPath" -Round $ts_folder_max
#mp4
FolderRound -Toggle $Mp4FolderRound -Ext "mp4" -Path "$mp4_folder_path" -Round $mp4_folder_max
#====================jpg出力====================
#jpg出力機能が有効(jpg_toggle=1)且つenv:Addkey(自動予約時のキーワード)にjpg_addkey(指定の文字)が含まれている場合は連番jpgも出力
if (($jpg_toggle) -And ("$env:Addkey" -match "$jpg_addkey")) {
Write-Output "jpg出力"
#生TSの横が1920か1440か調べる
$ts_width=[xml](&"${ffpath}\ffprobe.exe" -v quiet -i "${env:FilePath}" -show_entries stream=width -print_format xml 2>&1)
$ts_width=$ts_width.ffprobe.streams.stream.width
#SAR比(1440x1080しか想定してないけど)によるフィルタ設定、jpg出力
if ("$ts_width" -eq "1440") {
$ArgScale=',scale=1920:1080'
}
#jpg用ffmpeg引数を遅延展開
ImageEncode
}
#====================tsファイルサイズ判別====================
#tsファイルサイズを取得
$ts_size=(Get-ChildItem -LiteralPath "${env:FilePath}").Length
if ($tssize_toggle) {
#閾値$tssize_max以下なら通常品質$quality_normal、より大きいなら低品質$quality_low
switch ($ts_size) {
{$_ -le $tssize_max} {$ArgQual="$quality_normal"}
{$_ -gt $tssize_max} {$ArgQual="$quality_low"}
}
Write-Output "ArgQual:$ArgQual"
}
#====================デュアルモノの判別====================
#番組情報ファイルがありデュアルモノという文字列があればTrue、文字列がない場合はFalse、番組情報ファイルが無ければNull
if (Get-Content -LiteralPath "${env:FilePath}.program.txt" | Select-String -SimpleMatch 'デュアルモノ' -quiet) {
$ArgAudio=$audio_dualmono
} else {
$ArgAudio=$audio_normal
}
Write-Output "ArgAudio:$ArgAudio"
#====================PIDの判別====================
#PID引数の設定
#ffprobeでcodec_type,height,idをソート
$programs = [xml](&"$ffpath\ffprobe.exe" -v quiet -analyzeduration 30M -probesize 100M -i "${env:FilePath}" -show_entries stream=codec_type,height,id,channels -print_format xml 2>&1)
$programs.ffprobe.streams.stream
$programs.ffprobe.streams.stream | foreach {
#解像度の大きいVideoストリームを選ぶ
#xmlの要素はStringなのでintにする必要あり
if (($_.codec_type -eq "video") -And ($_.height -ne "0") -And ([int]($_.height) -gt [int]($otherheight)))
{
$otherheight = $_.height
$ArgPid = "-map i:$($_.id)"
}
}
#VideoのPIDの先頭(0x1..)と一致するAudioストリームを引数に追加
$programs.ffprobe.streams.stream | foreach {
if (($_.codec_type -eq "audio") -And ($_.channels -ne "0") -And ($($_.id).Substring(0,3) -eq $ArgPid.Substring(7,3)))
{
$ArgPid += " -map i:$($_.id)"
}
}
Write-Output "ArgPid:$ArgPid"
#====================エンコード====================
#カウントを0にリセット
$cnt=0
#終了コードが1且つループカウントが50未満までの間、エンコードを試みる
do {
$cnt++
#再試行時からクールタイムを追加
if ($cnt -ge 2) {
Start-Sleep -s 60
}
#エンコ mp4用ffmpeg引数を遅延展開
VideoEncode
#エンコ1回目と成功時(ExitCode:0)のログだけで十分
if (($cnt -le 1) -Or ($ExitCode -eq 0)) {
#プロセスの標準エラー出力をシェルの標準出力に出力
Write-Output $StdErr
#エンコ後のmp4のファイルサイズ
$mp4_size=$(Get-ChildItem -LiteralPath "${tmp_folder_path}\${env:FileName}.mp4").Length
}
} while (($ExitCode -eq 1) -And ($cnt -lt 50))
#最終的なエンコード回数、終了コード、ファイルサイズ
Write-Output "エンコード回数:$cnt"
Write-Output "ExitCode:$ExitCode"
$PostFileSize="`nts:$([math]::round(${ts_size}/1GB,2))GB mp4:$([math]::round(${mp4_size}/1MB,0))MB"
$PostFileSize
#====================Backup and Sync====================
#Invoke-Processから渡された$StdErrからスペースを消す
$StdErr=($StdErr -replace " ","")
#ffmpegの終了コード、mp4のファイルサイズによる条件分岐
if ($ExitCode -gt 0)
{
#$StdErrをソートしPost内容を決める
switch ($StdErr)
{
{$_ -match 'Errorduringencoding:devicefailed'} {$err_detail+="`n[h264_qsv] device failed (-17)"}
{$_ -match 'Couldnotfindcodecparameters'} {$err_detail+="`n[mpegts] PIDの判別に失敗"}
{$_ -match 'Unsupportedchannellayout'} {$err_detail+="`n[aac] 非対応のチャンネルレイアウト"}
{$_ -match 'Toomanypacketsbuffered'} {$err_detail+="`n[-c:a aac] max_muxing_queue_size"}
{$_ -match 'Inputpackettoosmall'} {$err_detail+="`n[-c:a copy] PIDの判別に失敗"}
{$_ -match 'Invalidargument'} {$err_detail+="`n[FFmpeg] 無効な引数"}
default {$err_detail+="`n不明なエラー"}
}
#Twitter、Discord、BalloonTip
Post -Exc $True -Toggle $True -Content "Error:${env:FileName}.ts${err_detail}${PostFileSize}" -TipIcon 'Error' -TipTitle 'エンコード失敗'
} elseif (($googledrive) -And ($mp4_size -gt 10GB))
{
#Post内容
$err_detail+="`n[GoogleDrive] 10GB以上の為アップロードできません"
#Twitter、Discord、BalloonTip
Post -Exc $True -Toggle $True -Content "Error:${env:FileName}.ts${err_detail}${PostFileSize}" -TipIcon 'Error' -TipTitle 'アップロード失敗'
} else
{
#mp4をmp4_folder_pathに投げる
Move-Item -LiteralPath "$tmp_folder_path\$env:FileName.mp4" "$mp4_folder_path"
#エラーメッセージが格納されていればTipIconをWarningに変える
if ($err_detail)
{
$TipIcon='Warning'
} else
{
$TipIcon='Info'
}
#Twitter、Discord、BalloonTip
Post -Exc $False -Toggle $InfoPostToggle -Content "${env:FileName}.ts${err_detail}${PostFileSize}" -TipIcon "$TipIcon" -TipTitle 'エンコード終了'
}
view raw PostRecEnd.ps1 hosted with ❤ by GitHub

事前準備

・特にQSVを使うとマザボチップセットがかなり熱くなるので、ファンやヒートシンクを増設したりグリスを塗り替えるなどしてしっかり冷やして下さい(cpu直結のストレージなら熱くならないかも?)。
・基本的にPCを起動しっぱなしにすることが前提。スリープや休止状態、サービスでは動作しませんがロック中は動作します。

EpgDataCap_Bonの設定

EpgDataCap_Bonで録画している場合はこちらを設定すること。
・現在のサービスのみ保存する:チェック

TVTestの設定

TVTest.exeで録画している場合はこちらを設定すること。
・全サービスを処理対象とする:チェックを外す

EDCBの設定

EDCBで以下の設定がされている環境で正常に動作します。

・録画情報保存フォルダを指定しない
スクリプト内で場所が$env:FilePath(録画フォルダ)になっているため。もし要望があれば設定項目に加えます。

・番組情報を出力する
デュアルモノ判別ルーチンで使用。D&Dで処理するバッチ等でもServiceIDを取得する為に使用。

・不要になったドロップログを出力する
(ps1のみ)PID判別ルーチンで使用。

・録画終了後のデフォルト動作:何もしない
Backup and Syncを動かすため。
エンコして終わりならスリープしても大丈夫。

・録画後動作の抑制条件なし
自動エンコを録画中も実行しないと詰まります。

・xtne6f版recname_macro.dllで半角リネーム
半角リネーム(ZtoH)して全角でffmpegがエラーを吐かないようにする。
$SubTitle2$を使う場合はHead文字数を使用し、意図しない長いサブタイトルがヒットし、長いファイル名になってうp出来なくなる等のエラーを避ける。
例:

$SDYY$$SDMM$$SDDD$_$ZtoH(Title)$$Head10~(ZtoH(SubTitle2))$.ts

・EpgTimerSrvをサービス登録しない
WindowsサービスからffmpegでQSVを使用しようと試みるとFailed to create Direct3D deviceエラーが出るので、EpgTimerSrvをサービス登録しない方法で環境構築cf.する必要がある。

Backup and Syncのインストール

https://photos.google.com/apps からBackup and SyncをDLしてインストール。
既に(別アカウント等で)使用している場合は、メニューから"新しいアカウントを追加"すると多重起動できる。
以前のアップローダを使っていた場合は勝手に置き換わる形になる)。
以下の設定をすることでローカルから削除してもGoogleフォトに残る旧Google Photos Uploaderのような挙動にできる。
・"Backup and Sync用フォルダ"のみを指定
・"高画質(無料、容量無制限)"を選択
・"写真と動画をgoogleフォトにアップロード":チェックしなくてもドライブにはうp(表示)されるので必要はありません(チェックすると旧Google Photos Uploaderと同じ挙動、当たり前だけど)。
・削除するアイテム:他の場所からアイテムを削除しない
・マイドライブをパソコンに同期:チェックを外す
・共有フォルダからアイテムを削除する際に警告を表示する:チェックを外す
・システム起動時にバックアップと同期を開く:チェック
※家族と共有する場合は、家族用Googleアカウントでログインするか、"Googleフォト"フォルダとフォルダ分け後のフォルダを家族との共有フォルダにすると良い。ただし他人とリンクを共有したらOUT。
※April 2018 Update等のWindowsの大きなアップデートの後、ログイン・設定が初期化されるので注意

ffmpegのインストール

1.https://www.ffmpeg.org/download.html
2.自分のOSに合ったものを選ぶ。今回はWindows Buildsを選択。
3.Versionは安定版、ArchitectureはOSのアーキテクチャに合わせ、LinkingはShared(dll)を選択すると良い。
・Version:開発版(20170605-4705edb等)/安定版(4.0等)(3.4.xのみ-init_hw_device qsv:hwが必要なので非推奨cf.)
・Architecture:64-bit/32-bit
・Linking:Static(exe版)/Shared(DLL版)/Dev(ソース)
4.Download FFmpegを押下してDLして解凍。
5.binフォルダの中身が必要ファイルとなる。管理者権限が必要ない、半角英数字の好みのディレクトリに配置(C:\DTV\ffmpegとか)。
6.ffmpeg環境変数Pathに設定する。
毎回"C:\bin\ffmpeg\ffmpeg.exe"のようにffmpegのフルパスを入力するのを省く為。
しない場合はバッチ内のffmpegffmpegのフルパスに置き換える必要がある。

使用方法

以下の手順を終えたら動くはずなので、とりあえず適当に録画してみて動作確認すること。

共通

上記スクリプトを保存

選んだスクリプトをコピーし、テキストエディタ等にペーストし、拡張子.bat又は.ps1で保存。文字コードはShift-JIS(ANSI)。
※好みの名前.batや、_EDCBX_DIRECT_を使用しない場合は環境変数の囲い文字を%から$に置き換える(例:%FilePath%->$FilePath$)。
※PostRecEnd.bat、PostRecEnd.ps1が両方存在する場合batが優先される。
※PostRecEnd.bat/ps1が存在する状態で録画後実行.batを登録した場合、同時に実行される。

ファイル名 好みの名前.bat PostHoge.bat/ps1
実行条件 個別に録画後実行batにパスを設定
※自動予約登録で既に予約一覧にあるものにもバッチのパスを適用させるには
1.自動予約登録ウィンドウ内の予約を一旦削除
2.batのパスを通す
3."自動予約登録条件を変更"を押下して再度予約一覧に追加
EpgTimerSrv.exeと同じディレクトリに設置
実行タイミング 録画終了時 PostAddReserve:予約追加
PostChgReserve:予約変更
PostRecStart:録画開始
PostRecEnd:録画終了
PostNotify:通知cf.
途中から録画、録画中にキャンセルした場合 EpgTimerSrv.iniのSETにErrEndBatRun=1を追加すれば実行cf. 実行
番組によって処理を変えるには 別のバッチを録画後実行bat登録する バッチ内の処理で条件分岐する
環境変数 $環境変数$ $環境変数$
EDCBX_DIRECTで%環境変数%|$env:環境変数
ffmpegの引数

・CPUの種類や世代によって使用できないオプションがあるので、環境に合わせる必要がある。
・引数次第で品質や圧縮率がかなり変わる。

nyanshiba.hatenablog.com

環境別の設定

・h264_qsv LA-ICQ
IntelのQSVに対応したHaswel以降のCPUであれば、LA-ICQ(先行探索固定品質)が使用できる。
グラボを付けている場合Windows8以降のOSでないと内蔵GPUを併用できず、QSVが使用できないので注意。
LA-ICQは少ないビットレートでも動きのあるシーンでもハードエンコの割にはよくこなしてくれるのが特徴だ。

… -global_quality %quality% -c:v h264_qsv -preset:v veryslow -look_ahead 1 -look_ahead_downsampling off -pix_fmt nv12 …

・h264_qsv ICQ
QSVが使用できるIvyBridgeのCPUの場合はLA-ICQが使えないのでICQ(固定品質)を使用すると良い。
LA-ICQと同じ品質を求める場合には値を小さくしてビットレートやファイルサイズを大きくする必要がある(27->25等)。

… -global_quality %quality% -c:v h264_qsv -preset:v veryslow -look_ahead 0 -pix_fmt nv12 …

・h264_qsv CQP
QSVが使用できるIvyBridgeより古いCPUの場合はCQP(固定量子化料)を使用すると良い。
多くビットレートを割り当てる場合はICQの代わりにCQPを使うのもアリだと思う。
LA-ICQICQと同じ品質を求める場合には値を小さくしてビットレートやファイルサイズを大きくする必要がある(27->24 等)。

… -c:v h264_qsv -q:v %quality% -lookahead 0 -pix_fmt nv12 …

・hevc_qsv ICQ
QSVが使用できるSkyLake以降のCPUであれば、QSVでHEVCを使用できる。
GoogleフォトがHEVCに対応したので、一応色々試してみて使えるオプションだけ残した。以下の例はICQ
私のSkyLake(i5-6500)ですらスペック不足でよくハングアップ(品質を落とせば一応動く)してタスクキルする羽目になる。
2倍くらいエンコ時間がかかるが、hwvc_qsv ICQ 1080p よりh264_qsv LA-ICQ 720pの方が綺麗なので現時点では使えない。

… -global_quality %quality% -c:v hevc_qsv -load_plugin hevc_hw -preset:v veryslow -g 300 -bf 16 -pix_fmt nv12 …

・h264_nvenc
NVEncが使用できる環境であること。CQP。

ffmpeg … -c:v mpeg2_cuvid -deint adaptive -i "%FilePath%" -c:v h264_nvenc … -preset slow -rc constqp -qmin 20 -qmax 26 -pix_fmt yuv420p …

・libx264
その他のCPUはx264でソフトウェアエンコードするのが良い。
veryfastが現実的なエンコ時間且つ圧縮率とのバランスも良いと思われる。
QSVより多くのオプションが使えるので適切なオプションと組み合わせれば高品質な結果が得られる筈だ。割愛。

… -c:v libx264 …-crf %quality% … -preset:v veryfast … -pix_fmt yuv420p …
エンコード品質・時間について

品質(圧縮率)と処理時間は基本的にトレードオフです。

処理時間の比較
-vf pp=fa<pp=ac<hqdn3d
-vf yadif=0:-1:1<bwdif=0:-1:1
-vf scale=1280:720<scale=1280:720:flags=lanczos<scale=1280:720:flags=lanczos+accurate_rnd
-preset:v veryfast<faster<fast<medium<slow<slower<veryslow

ビデオフィルタは前から適用される為、デインターレースは最初に行う。リサイズしたものをデノイズするか、デノイズしたものをリサイズするかでも品質や処理時間が大分変わってきます。

遅いけど綺麗な例:
… -vf yadif=0:-1:1,hqdn3d=4.0,scale=1280:720:flags=lanczos+accurate_rnd,unsharp=3:3:0.5:3:3:0.5:0 … -preset:v veryslow …

ちょっと速くなる代わりに品質がちょっと落ちる例:
… -vf yadif=0:-1:1,scale=1280:720,hqdn3d=4.0,unsharp=3:3:0.5:3:3:0.5:0 … -preset:v veryslow …

そこそこ速くなる代わりに品質がそこそこ落ちる例:
… -vf yadif=0:-1:1,scale=1280:720,pp=ac … -preset:v medium …

かなり速くなる代わりに品質がかなり落ちる例:
… -vf yadif=0:-1:1,scale=1280:720,pp=fa … -preset:v veryfast …

高品質&高速&低サイズなffmpeg引数になっている。
・yadifデインターレースを行います。同時にモスキートノイズを軽減する為にbwdifではなくyadifを使用し、高速化しています。
・リサイズ時にぼやけた輪郭をシャープにする処理により、実写、アニメ共にリサイズによる品質低下を極力抑えます。下手な1080pよりも綺麗です。
・デフォルトではh264_qsvのLA-ICQ(先行探索固定品質)を使用し、ハードウェアエンコードの速さを活かしつつ高品質高圧縮を実現しています。

※アニメの24fps化は行いません。処理負荷に対する効果が小さいのと、ジャンルでの判別では30fps制作アニメの除外が難しい為です。

bat

環境変数設定

管理者権限が必要ないディレクトリ(program files等以外)に、4つの空フォルダ(ログ、一時的にmp4を吐き出す、backup and sync用、エラー)を作成(名前は半角英数でお好み)し、環境変数設定ルーチンでそれぞれのパスに書き換える。
"環境変数使用時に補完されるので付けないで下さい(付けるならset "hoge=hoo"みたいに)。
ログフォルダのパスはログ出力機能が不要ならば指定しなくて良い。
連番jpgを出力するフォルダ用のディレクトリのパスは自動予約キーワードによる一部番組の連番jpg出力機能が不要であれば指定しなくて良い。
tweet.rb~のパス、SSL証明書のパスはツイート機能が不要ならば指定しなくて良い。

rem ====================環境変数設定====================
rem ログフォルダのパス
set "log_path=C:\DTV\EncLog"

rem 連番jpgを出力するフォルダ用のディレクトリのパス
set "jpg_path=C:\Users\Shibanyan\Desktop\TVTest"

rem 一時的にmp4を吐き出すフォルダのパス
set "tmp_folder_path=C:\DTV\MP4"
rem backup and sync用フォルダのパス
set "bas_folder_path=C:\DTV\backupandsync"
rem エンコしたファイルが10GBより大きい、処理に50回失敗した場合にts、ts.program.txt、mp4を退避するフォルダのパス
set "err_folder_path=C:\Users\Shibanyan\Desktop"

rem tweet.rb、tweet_postrecend.txtのあるディレクトリのパス
set "tweet_bin_path=C:\DTV\EDCB"
rem SSL証明書のパス
set "ssl_cert_file=C:\DTV\EDCB\cacert.pem"
ログ

ログ出力機能、以前のログを確認しエラーがあればツイートで警告する機能、ログ自動削除機能を使用しない場合は、ログ出力ルーチンを削除するだけで良い。

jpg出力

自動予約キーワードによる一部番組の連番jpg出力機能です。findstr "ポプテピピック フランキス BEATLESS"のようにスペースで区切って下さい。不要であればルーチンごと削除するか、findstr ""とする。

ファイルの削除機能の設定

※削除されたファイルは元に戻りません。正しく動作することを確認してから使用して下さい。

フォルダサイズを一定に保つファイルの削除

folder_max=に録画フォルダの最大合計サイズを指定。
こちらの機能がデフォルトです。録画フォルダの合計サイズが一定サイズ(100GB)より大きくなったら古い方からtsファイル、更に同じ名前のts.program.txt、mp4ファイルを一つずつ削除します。
TSファイルのサイズはまちまちなので、例えば映画や音楽番組等が連続した場合に"録画ファイルの数"を一定に保つようにするとフォルダサイズが膨れ上がりストレージが満杯で録画できない事態に陥る場合があるため、"録画フォルダのサイズ"を一定に保つこちらをオススメする。残されるファイル数は変動する。

ファイル数を一定に保つファイルの削除

del_count=に録画フォルダに一定数残すTSファイルの数を指定する。
tsファイルを一定数(デフォルト:20)残して、古い方からあぶれたTSファイル、更に同じ名前のts.program.txt、mp4ファイルを削除します。
こちらを使用する場合は「フォルダサイズを一定に保つファイルの削除」ルーチンに上書きして下さい。
EDCB側に同じような機能があるので存在意義なし。

rem ====================ファイル数を一定に保つファイルの削除====================
rem tsファイルを一定数残す数を指定
set del_count=20
rem dirで録画フォルダのtsファイルをファイル名のみ日付順で表示し、新しい方からdel_count個飛ばし、あぶれたファイルを削除する
for /f "skip=%del_count% delims=" %%a in ('dir "%FolderPath%\*.ts" /b /o-d') do (
    rem ts削除
    del "%FolderPath%\%%~na.ts"
    rem 同名のts.program.txt削除
    del "%FolderPath%\%%~na.ts.program.txt"
    rem 同名のmp4削除
    del "%bas_folder_path%\%%~na.mp4"
)
ファイル日時を一定に保つファイルの削除

del_day=に何日以上前のファイルを削除するかを指定する。
tsファイルを一定数(デフォルト:20)残して、古い方からあぶれたTSファイル、更に同じ名前のts.program.txt、mp4ファイルを削除します。
こちらを使用する場合は「フォルダサイズを一定に保つファイルの削除」ルーチンに上書きして下さい。
forfilesVista以降

rem ====================ファイル日時を一定に保つファイルの削除====================
rem 何日以上前のファイルを削除するか指定
set del_day=5
rem del_day日以上前のts、ts.program.txtを削除
forfiles /p "%FolderPath%" /d -%del_day% /m "*.*" /c "cmd /c del @file"
rem del_day日以上前のmp4を削除
forfiles /p "%bas_folder_path%" /d -%del_day% /m "*.mp4" /c "cmd /c del @file"
ツイート警告機能

Twitter連携についてはcf.。必要ならコメントremを外す。
こんな感じ

ps1

#####ユーザ設定#####の項目を自分の環境、用途に合わせるだけで動くようになっています。

#--------------------プロセス--------------------
#tsからmp4、jpg、waifu2x等の処理に使用される
#プロセス優先度 (Normal,Idle,High,RealTime,BelowNormal,AboveNormal) Process.PriorityClass参照
$Priority='BelowNormal'
#使用する論理コアの指定 コア5(10000)~12(100000000000)を使用=0000111111110000(2進)=4080(10進)=0x0FF0(16進) Process.ProcessorAffinity参照
$Affinity='0x0FF0'
#ffmpeg.exe、ffprobe.exeがあるディレクトリ
$ffpath='C:\DTV\ffmpeg'

tsをmp4にエンコする際、jpg出力する際、waifu2x等のプロセスを起動する際に使用するプロセスの優先度、コア指定等を行います。
プロセス優先度はProcess.PriorityClassを参照し設定すること。ただし、HighとRealTimeは非推奨。
プロセッサ親和性はProcess.ProcessorAffinity参照し設定すること。録画機兼用等で別プロセスが動いているような環境であれば、例えば12論理コアCPUの場合、5~12の8コアに制限するなどしておくと良い。

#--------------------ログ--------------------
#0=無効、1=有効
$log_toggle=1
#ログ出力ディレクトリ
$log_path='C:\DTV\EncLog'
#ログを残す数を指定
$logcnt_max=1000

ログ出力機能が不要な場合は0にして下さい(非推奨)。この場合、$log_toggleに関わらず、設定項目にtoggleが付いている場合は、例えば$log_pathや$logcnt_max等の詳細設定は残したままでも影響はありません。
$log_pathにtsファイルと同じ名前のログファイルが出力されるようになります。管理者権限が不要なディレクトリにフォルダを作成(こういったものにはスペースや全角文字が含まれないことを推奨)し、パスをシングルクォート'で囲って記述して下さい。
$logcnt_maxで指定した数だけログを残します。あぶれた場合は古いものから削除します。

#--------------------tsの自動削除--------------------
#0=無効(tsをローカルに残す)、1=有効(tsを自動削除)
$ts_del_toggle=1
#録画フォルダの上限 超過した場合、toggle=0:容量警告(Twitter、Discord) 1:削除
$ts_folder_max=200GB

tsファイル削除の機能が不要な場合は0にして下さい。ファイルを完全に削除する機能なので注意してください。
$foldersize_maxでTSファイルが入っている録画フォルダの上限サイズを指定します。
100GBと指定すると録画フォルダが100GBを超えた場合のみ、$ts_del_toggleが
・0の場合は、Discord、Twitterで警告します(警告機能の設定項目は後述、一括で無効にすることも可)。
・1の場合は、100GB以下になるまでTSファイル、同名のts.program.txt、ts.errを削除します。
1TBとかでも大丈夫です。8EBなんかも多分おkです。

#--------------------mp4の自動削除--------------------
#0=無効(mp4をローカルに残す)、1=有効(mp4を自動削除)
$mp4_del_toggle=1
#mp4用フォルダの上限 超過した場合、toggle=0:容量警告(Twitter、Discord) 1:削除
$mp4_folder_max=50GB

mp4ファイル削除の機能が不要な場合は0にして下さい。後は同様です。
$mp4_folder_max10GBよりも大きく、出来れば余裕を持たせてください。10GB近いサイズのmp4、又はその前後のmp4がアップロードされる前に削除される可能性があります。

#--------------------jpg出力--------------------
#0=無効、1=有効
$jpg_toggle=1
#連番jpgを出力するフォルダ用のディレクトリ
$jpg_path='C:\Users\sbn\Desktop\TVTest'
#jpg出力したい自動予約キーワード(全てjpg出力したい場合は$jpg_addkey=''のようにして下さい)
$jpg_addkey='とある魔術の禁書目録|アリシゼーション'
#自動予約キーワードに引っ掛かった場合に実行するコード 使用可能:$scale(横が1440pxの場合のみ",scale=1920:1080"が格納される、画像にはSARとか無いので)
function arg_jpg {
    #連番jpg出力の例
    New-Item "${jpg_path}\${env:FileName}" -ItemType Directory
    $script:file="${ffpath}\ffmpeg.exe"
    $script:arg="-y -hide_banner -nostats -an -skip_frame nokey -i `"${env:FilePath}`" -vf bwdif=0:-1:1,pp=ac,hqdn3d=2.0${scale} -f image2 -q:v 0 -vsync 0 `"$jpg_path\$env:FileName\%05d.png`""
    Invoke-Process
    #連番jpg出力したものをwaifu2xで上書きする例
    $script:file="C:\DTV\waifu2x-caffe\waifu2x-caffe-cui.exe"
    $script:arg="-m noise_scale -s 2 -n 3 -p cpu --model_dir models/upconv_7_photo -i `"$jpg_path\$env:FileName\*.png`" -o `"$jpg_path\$env:FileName\*.jpg`""
    Invoke-Process
    Remove-Item -LiteralPath "$jpg_path\$env:FileName\*.png" -ErrorAction SilentlyContinue
    #tsを保持用ディレクトリにコピーする例
    Copy-Item -LiteralPath "${env:FilePath}" "D:\tsfiles" -ErrorAction SilentlyContinue
}

$jpg_addkeyでjpg出力等をしたいお気に入りの番組の自動予約キーワードを指定します。複数指定時は|で区切り、全ての番組をjpg出力する場合は$jpg_addkey=''にします。この条件にマッチした場合のみ、以下の内容が実行される仕様です。

ユーザが柔軟に設定できるよう、function arg_jpgの中にコードを適宜書くような形にしました。arg_jpg関数内の不要なコードは<##>で囲ってコメントするか、削除して下さい。
New-Item "${jpg_path}\${env:FileName}" -ItemType Directoryによって、$jpg_path内にtsファイル名のフォルダが生成され、そこに連番jpgが出力されます。
外部プロセスを実行するには、$script:fileには実行ファイル、$script:argにはその引数を指定し、最後にInvoke-Processでプロセスを呼び出します。
Remove-Item -LiteralPath "$jpg_path\$env:FileName\*.png"でwaifu2xする前のpngを削除します。ffmpegとwaifu2xの出力が共にjpg、pngのように同じ場合は上書きされるので不要、というよりffmpegの出力がpng、waifu2xの出力がjpgの場合でなければ適切に動作しないので消すこと。
Copy-Item -LiteralPath "${env:FilePath}" "D:\tsfiles"でtsを"D:\tsfiles"にコピーします。

#--------------------tsファイルサイズ判別--------------------
#通常品質(LA-ICQ:29,x265:25)
$quality_normal=28
#0=無効(通常品質のみ使用)、1=有効(通常・低品質を閾値を元に切り替える)
$tssize_toggle=1
#閾値
$tssize_max=20GB
#低品質(LA-ICQ:31,x265:27)
$quality_low=30

通常は$quality_normalエンコードされます。
$tssize_toggle=1の場合のみ、tsファイルサイズが大きい場合は$quality_lowエンコードされます。これらの値はエンコード用の引数に使用される$qualityに格納されるので、お使いのx264やx265のcrf値、LA-ICQのglobal_quality…等に適宜合わせて下さい。
tsファイルサイズが大きいかどうかの閾値$tssize_maxで設定します。これは特に変えなくて良いでしょう。
10GB以内に収めてGoogleフォトにうpできるようにする不完全な処置(2passなんてしたくない)ですが、この機能のお陰で容量超過は極稀です。

#--------------------エンコード--------------------
#一時的にmp4を吐き出すディレクトリ
$tmp_folder_path='C:\DTV\tmp'
#mp4保存ディレクトリ(Backup and Sync、ローカル保存用)
$mp4_folder_path='C:\DTV\backupandsync'
#例外時にts、ts.program.txt、ts.err、mp4を退避するディレクトリ(ループしてもffmpegの処理に失敗、mp4が10GBより大きい場合 etc…)
$err_folder_path='C:\Users\sbn\Desktop'
#mp4のファイルサイズ上限(GoogleDriveの10GB制限用、ローカル保存時には不要なので20GB等にすると良い)
$mp4_max=10GB
#mp4用ffmpeg引数 使用可能:$audio_option(デュアルモノの判別)、$quality(tsファイルサイズ判別)、$pid_need(PID判別)
function arg_mp4 {
    $script:file="${ffpath}\ffmpeg.exe"
    #QSV H.264 LA-ICQ
    $script:arg="-y -hide_banner -nostats -analyzeduration 30M -probesize 100M -fflags +discardcorrupt -i `"${env:FilePath}`" ${audio_option} -vf bwdif=0:-1:1,pp=ac,hqdn3d=2.0 -global_quality ${quality} -c:v h264_qsv -preset:v veryslow -g 300 -bf 6 -refs 4 -b_strategy 1 -look_ahead 1 -look_ahead_depth 60 -pix_fmt nv12 -bsf:v h264_metadata=colour_primaries=1:transfer_characteristics=1:matrix_coefficients=1 ${pid_need} -movflags +faststart `"${tmp_folder_path}\${env:FileName}.mp4`""
    #x265 fast
    #$script:arg="-y -hide_banner -nostats -analyzeduration 30M -probesize 100M -fflags +discardcorrupt -i `"${env:FilePath}`" ${audio_option} -vf bwdif=0:-1:1,pp=ac -c:v libx265 -crf ${quality} -preset:v fast -g 15 -bf 2 -refs 4 -pix_fmt yuv420p -bsf:v hevc_metadata=colour_primaries=1:transfer_characteristics=1:matrix_coefficients=1 ${pid_need} -movflags +faststart `"${tmp_folder_path}\${env:FileName}.mp4`""
    #x265 bel9r
    #$script:arg="-y -hide_banner -nostats -analyzeduration 30M -probesize 100M -fflags +discardcorrupt -i `"${env:FilePath}`" ${audio_option} -vf bwdif=0:-1:1,pp=ac -c:v libx265 -preset:v fast -x265-params crf=${quality}:rc-lookahead=40:psy-rd=0.3:keyint=15:no-open-gop:bframes=2:rect=1:amp=1:me=umh:subme=3:ref=3:rd=3 -pix_fmt yuv420p -bsf:v hevc_metadata=colour_primaries=1:transfer_characteristics=1:matrix_coefficients=1 ${pid_need} -movflags +faststart `"${tmp_folder_path}\${env:FileName}.mp4`""
}

$tmp_folder_path $mp4_folder_path $err_folder_pathに相当するフォルダを作成し、パスを指定して下さい。
$mp4_max=10GBはGoogleDriveの為だが、ローカル保存用途の場合は値を大きくすると良い。
function arg_mp4$global:argにはffmpegのmp4出力用の引数を記述します。環境によって引数を変える必要があるため、注意すること。
${audio_option}には通常-c:a copy -bsf:a aac_adtstoasc、デュアルモノでは-c:a aac -b:a 128k -filter_complex channelsplitが格納されます。
${quality}には#-----tsファイルサイズ判別-----で指定した値が格納されます。
${pid_need}には#-----PID判別------map i:0x140 -map i:0x141のようにPIDを指定する引数が格納されます。

ffmpegの引数についてはキリがないので共通項を参照されたし。スクリプト内にも私が使っているものがいくつか載せてあるので参考にすると良い。
QSVが使用できるHaswell以降のCPU:デフォルトでおk
QSVが使用できるIvyBridgeのCPU:-look_ahead 1->-look_ahead 0
QSVが使用できるIvyBridgeより古いCPU:-global_quality ${quality}->-q:v ${quality}-look_ahead 1->-look_ahead 0

<#
エラーメッセージ一覧
・[EDCB] 録画失敗によりエンコード不可: tsファイルが無い(パスが渡されない)場合。録画失敗?
・[EDCB] PIDの判別不可: ストリームの解析が失敗以前に不可能。Drop過多orスクランブル解除失敗?
・[GoogleDrive] 10GB以上の為アップロードできません: GoogleDriveの仕様に合わせる。
・[h264_qsv] device failed (-17): QSVのエラー。ループして復帰を試みるも失敗した場合。
・[mpegts] コーデックパラメータが見つかりません: PID判別から渡されたPIDが適切でないorffmpegが非対応のストリーム。
・[aac] 非対応のチャンネルレイアウト: ffmpeg4.0~デュアルモノを少なくとも従来の引数では扱えなくなった。
・[-c:a aac] PIDの判別に失敗: -c:a aac時。指定サービスのみ(全サービスでない)録画になっていなければ必ず発生。また一通りのストリームに対応させた筈だけど起こるかもしれない。
・[-c:a copy] PIDの判別に失敗: -c:a copy時。上に同じ。
・不明なエラー: 
・[-c:a aac] PIDの判別に失敗? ExitCode:0: -c:a aac時。ffmpegの終了コードは0だが異常がある?場合。
・[-c:a copy] -c:a aacか-ss 1で治るやつ ExitCode:0: -c:a copy時。上に同じ。
#>

#--------------------ツイート警告--------------------
#0=無効、1=有効
$tweet_toggle=1
#ruby.exe
$ruby_path='C:\Ruby25-x64\bin\ruby.exe'
#tweet.rb
$tweet_rb_path='C:\DTV\EDCB\tweet.rb'
#SSL証明書(環境変数)
$env:ssl_cert_file='C:\DTV\EDCB\cacert.pem'

#--------------------Discord--------------------
#0=無効、1=有効
$discord_toggle=1
#webhook url
$hookUrl='https://discordapp.com/api/webhooks/XXXXXXXXXX'

エラーメッセージ一覧にあるものがツイートやDiscordで通知される。どちらか片方でも有効にしておくと良いと思う。もっとも、使われる機会はそうないだろうが。
Twitter連携についてはcf.
$hookUrlにdiscordのwebhook urlを指定するとそこでpost出来る。

#--------------------バルーンチップ表示--------------------
#0=無効、1=有効
$balloontip_toggle=1

バルーンチップ表示が要らなければ$balloontip_toggle=0にして下さい。

Googleフォト・ドライブでの録画ファイル管理

高画質モードcf.cf.では、
・無料、容量無制限
・10GBまで
・再エンコされる
※一応生TSも拡張子が.m2tsなら高画質モードでうp出来る。ただし実用性が低い上、Googleに対して迷惑でしかない(OneDriveの容量無制限廃止はDTV民が原因だとか?)のでお勧めしません。

Googleドライブでフォルダ分けする方法

https://drive.google.com の"パソコン"タブの中に"Backup and Sync用フォルダ"が現れる。
Backup and Sync以前のGoogleフォトのファイルをGoogleドライブで表示するには、歯車->設定->Googleフォトフォルダを作成するにチェックする。
前述の通り正しく設定してあればローカル側で削除してもクラウドにはしっかり残ってくれる。
左で必要な階層まで展開しディレクトリごとマイドライブにD&Dして(バックアップが引き続き行われるようPC側のアップローダから再設定して下さい)から検索バーで番組名検索してフォルダ分けする(マイドライブに移動してやらないとフォルダが重複してしまう)。
Shiftを押しながら↑↓でファイルの一括選択ができる。ファイル名検索でフォルダ分けする際、ファイルの詳細タブを見れば前回どこまでフォルダ分けしたか分かる。

※2017年9月30日現在、ブラウザ、iOSアプリ共に問題なく再生できることを確認しました。更に以前よりもレスポンスが良くなっています。2017年9月1日現在、2017年7月19日(数日前にBackup and Syncがリリース)以降にうpされた動画がGoogleドライブにて720p又は全ての解像度でストリーミング出来なくなっています。一応YouTubeの推奨設定でエンコしても変わりませんでした。
※2018年2月現在、必ず音声を再エンコードすることで解決しました。-c:v copyだとちょくちょくGoogleフォト側が再エンコに失敗するみたい?2018年1月6日現在、不規則に一部ファイルが「この動画は処理中です。しばらくしてからもう一度ご確認ください。」というエラーメッセージと共にGoogleドライブにて再生できず、そのうち大半はGoogleドライブ側でDLしたりGoogleフォトで探せば再生できるものの、また一部不規則にGoogleドライブ側でDLしたものはmoov atomやファイルの一部が壊れており、Googleフォト側に存在しないファイルがあった。

Googleフォトでアルバム分けする方法

https://photos.google.com にズラズラとファイルが表示される。
面倒なのでオススメしません。ただし、ドライブに不具合がある場合、フォト側では問題ない場合があるので使ってみて下さい。
検索機能は役に立たない。稀に一部のファイル名において数字やアルファベット等で上手く選別出来ることがある。
キーで前後に移動出来る。
(i)をクリックしないとファイル名が見られない。
?をクリックしてアルバムに追加すればおk。
連続でやっていると重くなるのでF6->Alt+Enterで新しいタブで開き直すと良い
どうやらアルバムの順番は中に入っている最新のファイルの日時によって勝手に並べ替えてくれるらしい(Googleドライブはデフォルトではフォルダ名順だが色々な条件でソート出来る)。
古いものから整理しないと、アルバム内で順番がめちゃめちゃになってしまうので注意。
古いものから遡って整理していけば、スクロールバーの同じ位置を長押ししてれば目的のフォルダに早く辿り着く。

スクリプトの解説

私の頭の直訳で日本語になってない所もあるのはご了承下さい。
更新に追い付いてないことが多いのもご了承ください。Twitterとかヲチすればここに載せきれてないことを、記事更新前に試行錯誤しながら呟いてたりします。質問や要望も受けてます。逆に質問もしたいw

PostRecEnd.bat

※フル構成版の解説です。ただし、最小構成版を網羅しています。

これを見返しながら書いてます。

[改訂新版]Windowsコマンドプロンプトポケットリファレンス

[改訂新版]Windowsコマンドプロンプトポケットリファレンス

大まかな流れ(PostRecEnd.batフル構成の場合)
1.TSファイルをEpgTimerSrvから入力
2.環境変数設定
3.以下を子バッチとして実行(ログを出力し、不具合の原因を探す際に役立てる)
    3-1.自動予約キーワードによるjpg出力
    3-2.フォルダサイズを一定に保つファイルの削除(放っておいてもストレージが満杯にならない為に):録画フォルダの合計サイズを調べる
        3-2-1.一定サイズ(100GB)より大きい:古い方から1つずつts、同名のts.program.txt、mp4ファイルを削除し4'へ
        3-2-2.一定サイズ(100GB)以下:4へ
    3-3.音声形式の判別(デュアルモノ音ズレ対策)
        3-3-1.デュアルモノ:左右を分離し2チャンネルに分ける、128kbps
        3-3-2.その他:256kbps
    3-4.tsファイルサイズ判別(うpできないような10GB以上のmp4が出来るだけ出力されない為に)
        3-4-1.20GB以下:品質26
        3-4-2.20GBより大きい:品質28
    3-5.エンコード(デインターレース->モスキートノイズ除去->280x720にリサイズ->高精度平滑化->h264_qsv LA-ICQ)
    3-6.mp4ファイルサイズ判別(正常にエンコできなかった場合はやり直して、うpできるサイズならうpし、できないならエラーとして関連ファイルを退避し警告する)
        3-3-1.0バイト
            3-3-1-1.0~49回目:6へ
            3-3-1-2.50~回目:8cへ
        3-3-2.10GB以下:Backup and Sync用フォルダへmove
        3-3-3.10GBより大きい、エラー:エラーフォルダへmove、Twitterで警告
メインルーチン終了

上から順番になぞっていく。

@echo offコマンド内容を出力しなくなるが、ログファイルに出してるので関係ない。@echo onとして一応書き残してあるだけ。

rem 180309バージョンがこんがらがらないように私が勝手に更新日時を書いているだけ。remはコメントでその行に何を書いても実行されない。

rem _EDCBX_DIRECT_直接PostRecEnd.batを実行し、環境変数の括り文字を$ではなく%にしている。
rem _EDCBX_HIDE_コマンドプロンプトのウィンドウを表示しないで実行できる。自分がPCを弄っている時に動作していることがパッとわかるように有効にしていない。

拡張命令cf. 動作内容
_EDCBX_BATMARGIN_={bat実行条件(分)} このマージン以上録画予定がないときに実行(デフォルト:0)
_EDCBX_HIDE_ ウィンドウ非表示
_EDCBX_NORMAL_ ウィンドウを最小化しない
_EDCBX_DIRECT_ マクロを$置換$ではなく%環境変数%で渡して直接実行する
PowerShellスクリプトでは常に有効
・EpgTimerSrv.exeのあるフォルダに"EpgTimer_Bon_RecEnd.bat"を作らない
・EpgTimer.exeを経由する間接実行はしない
・カレントディレクトリはそのバッチのあるフォルダ
_EDCBX_FORMATTIME_ 日時についてのマクロ($SDYY$など)をISO8601形式の$StartTime$と$DurationSecond$に単純化する
PowerShellスクリプトでは常に有効

EpgTimerSrvから取得できるマクロ(環境変数)一覧の取得cf.

rem _EDCBX_DIRECT_
set
pause

EpgTimerから渡される環境変数RecModeが4=視聴なら処理が不要なので終了する。EpgTimerから受け渡されるマクロについてはcf.

rem 視聴予約なら終了
if "%RecMode%" == "4" (
    goto :eof
)

titleコマンドプロンプトのタイトルバーに表示する文字列を変更できる。
%0"自分のフルパス"、要するに例えば"C:\DTV\EDCB\PostRecEnd.bat"を表す。
%~0:C:\DTV\EDCB\PostRecEnd.bat
%~n0:PostRecEnd
%~x0:.bat
%~nx0:PostRecEnd.bat

rem ファイル名をタイトルバーに表示
title %~nx0:%FileName%.ts
環境変数設定

set環境変数をセットできる。最初に「%tmp_folder_path%"C:\DTV\MP4"だよ」と宣言しておけば、毎回フルパスを書かなくて済むので何度も使うパスや環境によって変更する必要がある部分は環境変数を使うと良い。

rem ====================環境変数設定====================
rem 一時的にmp4を吐き出すフォルダのパス
set "tmp_folder_path=C:\DTV\MP4"
rem backup and sync用フォルダのパス
set "bas_folder_path=C:\DTV\backupandsync"
ログ

なんかコマンド > log.txtで標準出力をテキストファイルにリダイレクト(ログを出力)できる。log.txtが無ければ自動で作成され、log.txtは上書きされる。
>>で追記。
2>標準エラー出力をリダイレクト。
2>&1で標準出力と標準エラー出力を同じ出力先にする。
if not "%sterted%" == "1"もし環境変数startedが1でなければ、
set started=1環境変数startedを1にして
call "%~0" %* > "%log_path%\log_%FileName%.txt" 2>&1 call呼ぶ"%~0"自分を%*諸々の環境変数を連れて。callした内容全てをログファイルに出力(ログ出力指定を一箇所にしたかった)。
exitcallした自分の処理(バッチ全体の処理)が終わったら終了

rem ====================ログ====================
if not "%started%" == "1" (
    set started=1
    rem 自分自身を子バッチとして起動し直し、ログ出力
    call "%~0" %* > "%log_path%\log_%FileName%.txt" 2>&1
    exit
)

dir"%log_path%\*.txt"ログフォルダ内のtxtファイルを/b名前順で/o-d新しい順にソート。
skip=100100個スキップし(新しい方から101個目~)、そこからforループ処理でファイル名が順番に%~naに格納されていき、delで削除。

rem dirでログフォルダのtxtファイルをファイル名のみ日付順で表示し、新しい方から100個飛ばし、あぶれたファイルを削除する
for /f "skip=100 delims=" %%a in ('dir "%log_path%\*.txt" /b /o-d') do (
    rem log削除
    del "%log_path%\%%~na.txt"
)
jpg出力

EpgTimerから渡される環境変数Addkey(自動予約時のキーワード)に特定の文字が含まれている場合は連番jpgも出力する。
echo "%AddKey%"環境変数を展開し|でパイプし(受渡)findstr "ポプテピピック フランキス BEATLESS"で文字列を探す(findstrのデフォルトの区切り文字はスペースです)。
環境変数errorlevelは直前のコマンドの終了コードを表す。0ならば1単語でも一致していたことになり、1なら不一致、2ならば異常終了となる。
findstr ""とすると検索文字列がありませんerrorlevel=2となる為、jpg出力機能が無効になる。
0ならjpg出力、1や2なら実行しない。
md "%jpg_path%\%FileName%"でフォルダ作成。
1440x1080のSAR4:3、DAR16:9の映像を切り出すと4:3の画像になってしまう(16:9に引き伸ばして"表示"させているだけ、コマ自体は4:3)。
そのため、ffprobeで(=^=としエスケープ)得たtsの横pxを元に判別し、1440ならば1920x1080にリサイズするようにしている。
あれおっかしいなぁ…

>ffprobe -hide_banner -v quiet -i "C:\Users\Shibanyan\Desktop\180119_ヴァイオレット・エヴァーガーデン 第2話「戻ってこない」.ts" -show_entries stream=width -of default=noprint_wrappers=1:nokey=1
1920
1920

と思ったら、録画したtsファイルでは、どうやらメタデータ内の2箇所に"width"の項目がある模様。

>ffprobe -hide_banner -v quiet -i "C:\Users\Shibanyan\Desktop\180119_ヴァイオレット・エヴァーガーデン 第2話「戻ってこない」.ts" -show_entries stream=width -print_format xml
<?xml version="1.0" encoding="UTF-8"?>
<ffprobe>
    <programs>
        <program >
            <streams>
                <stream width="1920"/>
                <stream />
                <stream />
            </streams>
        </program>
    </programs>

    <streams>
        <stream width="1920"/>
        <stream />
        <stream />
    </streams>
</ffprobe>

set "ts_width=%%a"よって、forループで変数に上書きすることで、widthを1つだけ取り出す。
%05dでファイル名は00001、00002、…のようになる。バッチ内では%%%としエスケープ。

rem ====================jpg出力====================
rem 環境変数Addkey(自動予約時のキーワード)に特定の文字が含まれている場合は連番jpgも出力
rem findstr ""でerrorlevel=2となる
echo "%AddKey%" | findstr "ポプテピピック フランキス BEATLESS エヴァーガーデン"
if %errorlevel% equ 0 (
    rem 出力フォルダ作成
    md "%jpg_path%\%FileName%"
    rem 生TSの横が1920か1440か調べる
    rem 変数ts_widthに格納することで、tsファイル特有のwidthがメタデータの2箇所にあり2つ出力されちゃう問題を解決
    for /f "delims=" %%a in ('ffprobe -v quiet -i "%FilePath%" -show_entries stream^=width -of default^=noprint_wrappers^=1:nokey^=1') do (
        set "ts_width=%%a"
    )
    if "%ts_width%" == "1440" (
        rem 1440ならば1920x1080にリサイズしてjpg出力
        "%ffmpeg_path%" -hide_banner -nostats -an -skip_frame nokey -i "%FilePath%" -vf yadif=0:-1:1,scale=1920:1080,hqdn3d=4.0 -f image2 -q:v 0 -vsync 0 "%jpg_path%\%FileName%\%%05d.jpg"
    ) else (
        rem 1440でなければリサイズせずにjpg出力
        "%ffmpeg_path%" -hide_banner -nostats -an -skip_frame nokey -i "%FilePath%" -vf yadif=0:-1:1,hqdn3d=4.0 -f image2 -q:v 0 -vsync 0 "%jpg_path%\%FileName%\%%05d.jpg"
    )
)
フォルダサイズを一定に保つファイルの削除

100GBは100x1024x1024x1024=107374182400Byteなんだけど、-21474836482147483647までの数値しか扱えない。

>set /a hoge=107374182400
無効な数字です。数値は 32 ビットで表記される数値です。

しょうがないから*107374で下4桁を切り捨てて計算する。

rem ====================フォルダサイズを一定に保つファイルの削除====================
rem 録画フォルダの最大サイズを指定(1~20000の整数、単位:GB)
set folder_max=100
rem GBをbyteの下4桁切り捨て(32bit計算の為)た状態に直す
set /a folder_max=folder_max*107374

:filedeltopこれはラベルといって、例えばgoto :filedeltopとすればそこへジャンプすることができる。

:hoge
if %foo% == foo (
    goto :hoge
)
exit
:hoge
if %foo% == foo (
    if %foo% == foo (
        if %foo% == foo (
            …
        )
        exit
    )
    exit
)
exit

call :filedeltopとすると、サブルーチンとして呼ぶことができる。

if %foo% == foo (
    call :hoge
)
exit

:hoge
echo foo
exit /b
if %foo% == foo (
    echo foo
)
exit

dir ファイル名ディレクトリ内のファイル等の情報を表示する。
%FolderPath%はEpgTimerから渡されるcf.

>dir "C:\DTV\ts"
 ドライブ C のボリューム ラベルがありません。
 ボリューム シリアル番号は B0D0-8490 です

 C:\DTV\ts のディレクトリ

~~

18/03/09 (金)  00:29                 0 180309_デスマーチからはじまる異世界狂想曲 第9話「デスマーチからはじまる情緒纒綿」.ts
18/03/09 (金)  00:29             1,646 180309_デスマーチからはじまる異世界狂想曲 第9話「デスマーチからはじまる情緒纒綿」.ts.program.txt
18/03/09 (金)  00:29     4,229,182,012 180309_ヴァイオレット・エヴァーガーデン 第9話「ヴァイオレット・エヴァーガーデン」.ts
18/03/08 (木)  23:59             1,548 180309_ヴァイオレット・エヴァーガーデン 第9話「ヴァイオレット・エヴァーガーデン」.ts.program.txt
              54 個のファイル      101,544,696,246 バイト
               2 個のディレクトリ  163,976,073,216 バイトの空き領域

/-dで名前の一覧表示をしない、/-cでファイルサイズのコンマ無し。

>dir /-d /-c "C:\DTV\ts"
 ドライブ C のボリューム ラベルがありません。
 ボリューム シリアル番号は B0D0-8490 です

 C:\DTV\ts のディレクトリ

~~

180309_デスマーチからはじまる異世界狂想曲 第9話「デスマーチからはじまる情緒纒綿」.ts
180309_デスマーチからはじまる異世界狂想曲 第9話「デスマーチからはじまる情緒纒綿」.ts.program.txt
180309_ヴァイオレット・エヴァーガーデン 第9話「ヴァイオレット・エヴァーガーデン」.ts
180309_ヴァイオレット・エヴァーガーデン 第9話「ヴァイオレット・エヴァーガーデン」.ts.program.txt

              54 個のファイル        101544696246 バイト
               2 個のディレクトリ    163384995840 バイトの空き領域

^| findstr "個のファイル" |パイプ(^エスケープ)でfindstrに渡し、個のファイルという文字列を探す。

>dir /-d /-c "C:\DTV\ts" | findstr "個のファイル"
              54 個のファイル        101544696246 バイト

for文にて、出力結果のdelims=区切り文字(スペース)でtokens=33番目のトークンを環境変数folder_sizeに格納する。
/f "tokens=1 delims= ":54
/f "tokens=2 delims= ":個のファイル
/f "tokens=3 delims= ":101544696246

rem tsのフォルダ内のファイルすべてのサイズをdirでソートし、findstrでフォルダサイズの行を抽出し、3番目のトークンを環境変数に格納
for /f "tokens=3 delims= " %%a in ('dir /-d /-c "%FolderPath%" ^| findstr "個のファイル"') do (
    set folder_size=%%a
)

ifもし%folder_size:~0,-4%先程得たフォルダのサイズの下4桁切り捨て(101544696246->10154469)がgtrより大きい%folder_max%(10737400)よりもな場合サブルーチンを実行。
dir "%FolderPath%\*.ts" /b /o-d %FolderPath%内の/bでファイル名だけ表示、/o-dで新しいファイルから順番に表示。
for文内でset "del_name=%%~na"でファイルの名前の部分だけ(%%%エスケープ)を新しいファイルから次々と環境変数del_nameに格納していくと、最終的に最後のファイル名=一番古いファイルの名前が格納される(tokensで最後のトークンを指定したかったのだけど無理ポ)。
%del_name%一番古いファイルの名前に合わせて.ts、.ts.program.txt、.mp4をdel削除する(遅延環境変数はファイル名の!エスケープするか削除するしかないので使用しない)。
goto :filedeltopでフォルダサイズ取得から再試行する。フォルダサイズがまだ大きいならもう一回行い、小さくなっていれば次に行く。

rem 録画フォルダのサイズがfolder_maxで指定したサイズより大きいならサブルーチンを読んでこのルーチンの最初に戻る
rem 環境変数folder_size、folder_maxがそれぞれ下4桁切り捨てのbyte単位で計算される
if %folder_size:~0,-4% gtr %folder_max% (
    call :filedelsub
    goto :filedeltop
)
:filedelsub
rem dirで録画フォルダのtsファイルをファイル名のみ新しいものから日付順で表示し、最後に環境変数del_nameに代入される一番古いtsファイル名を取得
for /f "delims=" %%a in ('dir "%FolderPath%\*.ts" /b /o-d') do (
    set "del_name=%%~na"
)
rem ts削除
del "%FolderPath%\%del_name%.ts"
rem 同名のts.program.txt削除
del "%FolderPath%\%del_name%.ts.program.txt"
rem 同名のmp4削除
del "%bas_folder_path%\%del_name%.mp4"
exit /b
音声形式の判別

findstrでts.program.txt内に"デュアルモノ"という文字列を探す。
環境変数errorlevel(直前のコマンドの終了コード)が0ならば、"デュアルモノ"という文字列があるので、左日本語右英語なので2chにスプリットして、音声ビットレートを適切にあわせる。
環境変数errorlevelが1ならば、"デュアルモノ"という文字列がないので、普通にエンコする。前後の番組にデュアルモノや特殊なチャンネルの音声の番組があった場合、-c:a copy -bsf:a aac_adtstoasc(MPEG-2/4 AAC ADTSをMPEG-4 ASCビットストリームに変換する必要あり)だと正常に処理できない場合があるので、再エンコする。

"[AVBSFContext @ 0000029810b1f260] PCE-based channel configuration without PCE as first syntax element is not implemented. Update your FFmpeg version to the newest one from Git. If the problem still occurs, it means that your file has a feature which has not been implemented. Error applying bitstream filters to an output packet for stream #0:1.
rem ====================音声形式の判別====================
rem 番組情報の中に"デュアルモノ"という文字列があれば環境変数"audio_option"に"-filter_complex channelsplit"を加え、音声ビットレートを半分にする
findstr "デュアルモノ" "%FilePath%.program.txt"
if %errorlevel% equ 0 (
    set audio_option=-c:a aac -b:a 128k -filter_complex channelsplit
) else if %errorlevel% equ 1 (
    rem set audio_option=-c:a copy -bsf:a aac_adtstoasc
    set audio_option=-c:a aac -b:a 256k
)
tsファイルサイズ判別

Backup and Syncの10GB制限を超過しない為に、録画ファイルサイズによる品質自動変更機能です。
コントラスト・動きが大きい番組、長い番組等でTSファイルが20GBより大きい場合は、自動で品質を落としてエンコする為、アップロード失敗がほぼ間違いなく起こりません(2017年末紅白はtsが26.5GB、品質27エンコで12.5GB、品質29エンコで9.5GB)。
EpgTimerから渡される%FilePath%tsファイルのサイズを取得%~z環境変数ts_sizeに格納する。
20GB=21474836480byteだが計算できるように下1桁切り捨て、同じように環境変数ts_sizeも下一桁切り捨てて以下leqとより大きいgtrで品質に差を付けて、Googleフォトの無制限モードでうpする時に10GB以内に出来るだけ収まってくれるよう試みる。

rem ====================tsファイルサイズ判別====================
rem TSファイルのサイズを環境変数"ts_size"に指定
for %%i in ("%FilePath%") do (
    set ts_size=%%~zi
)
rem 20GB=21474836480byte以下ならquality 26、より大きいなら28
if %ts_size:~0,-1% leq 2147483648 (
    set quality=27
) else if %ts_size:~0,-1% gtr 2147483648 (
    set quality=29
)
エンコード

set cnt=0はループカウント用に使うので予め指定しておく。goto :encodeして来たときには実行されないように先に書いてある。
timeout /t 10 /nobreak10秒待つ、操作を受け付けない。
EpgTimerから渡された%FilePath%、%SID10%、%FileName%と、バッチ内で格納された%audio_option%、%quality%、%tmp_folder_path%が展開される。
%tmp_folder_path%に%FileName%.mp4として出力される。
%SID10%複数サービスやチャンネルの中から必要なものだけをマッピングしてエンコすることで音ズレを回避する。必須。

Input #0, mpegts, from 'C:\DTV\ts\180308_・懊い繝九Γ繧ョ繝ォ繝会シ槭弱ム繝。繝励Μ ANIME CARAVAN縲冗ャャ9隧ア.ts':
  Duration: 00:30:00.35, start: 11688.074111, bitrate: 11967 kb/s
  Program 181
    Metadata:
      service_name    : ?BS?|ユク?181
      service_provider: ?BS?|ユク
    Stream #0:0[0x111]: Video: mpeg2video (Main) ([2][0][0][0] / 0x0002), yuv420p(tv, bt709, top first), 1440x1080 [SAR 4:3 DAR 16:9], 29.97 fps, 29.97 tbr, 90k tbn, 59.94 tbc
    Stream #0:1[0x112]: Audio: aac (LC) ([15][0][0][0] / 0x000F), 48000 Hz, stereo, fltp, 256 kb/s
    Stream #0:2[0x810]: Unknown: none ([13][0][0][0] / 0x000D)
    Stream #0:3[0x815]: Unknown: none ([13][0][0][0] / 0x000D)
  Program 182
    Metadata:
      service_name    : ?BS?|ユク?182
      service_provider: ?BS?|ユク
  Program 183
    Metadata:
      service_name    : ?BS?|ユク?183
      service_provider: ?BS?|ユク
  Program 188
    Metadata:
      service_name    : ?BS?|ユク?188
rem ====================エンコード====================
rem ループ処理用
set cnt=0
:encode
rem 録画の開始終了でビジーなので負荷を減らすために10秒待つ
timeout /t 10 /nobreak

rem エンコ
ffmpeg -y -hide_banner -nostats -fflags +discardcorrupt -i "%FilePath%" %audio_option% -vf yadif=0:-1:1,hqdn3d=4.0,scale=1280:720,unsharp=3:3:0.5:3:3:0.5:0 -global_quality %quality% -c:v h264_qsv -preset:v veryslow -g 300 -bf 16 -refs 9 -b_strategy 1 -look_ahead 1 -look_ahead_downsampling off -pix_fmt nv12 -bsf:v h264_metadata=colour_primaries=1:transfer_characteristics=1:matrix_coefficients=1 -map 0:p:%SID10%:0 -map 0:p:%SID10%:1 -movflags +faststart "%tmp_folder_path%\%FileName%.mp4"

QSVのデバイスエラーに対処しています。
・高負荷時、CPUスペックに対して背伸びしたオプションを使用した際等に、QSVが上手く利用出来ず0Byteのファイルだけが生成されてしまう既知の問題があります。極めて単純で、25回までループすることでほぼ間違いなく正常に処理されます。このような機能のないQSVを使用したスクリプトは、特定の条件下でエンコードに失敗し、自動処理が行えない可能性があります。
batでは、0Byteのmp4を検知してループしていますが、終了コードによるループを行っている為、例えば処理中にQSVが落ちてしまった場合にも対応している(私の環境では更に稀だが、念の為)。

mp4ファイルサイズ判別

tsファイルサイズ判別ルーチンと同様に、mp4のファイルサイズを環境変数mp4_sizeに格納。
if %mp4_size% equ 0もしmp4のファイルサイズが0Byteでエンコ失敗したなら、以下がループされる。
set /a cnt=cnt+1でカウントを1上げる。
if %cnt% geq 50もし%cnt%が50以上ならcall :errエラーサブルーチンを呼ぶ。
elseそれ以外ならgoto :encodeもっかいエンコしてみる。
else if %mp4_size:~0,-1% leq 1073741824 else ifもしmp4のファイルサイズが0Byte以外で、10GB以下ならbackup and sync用フォルダに移動。tmp_folder_pathに出力してbas_folder_pathに移動するんじゃなくて最初からbas_folder_pathに出力すればいいじゃんと思うかもしれないけど、直接出力するとエンコの途中でBackup and syncが動いてしまうので駄目。
else if %mp4_size:~0,-1% gtr 1073741824 else ifもしmp4のファイルサイズが0Byte以外で、10GB以下でないなら、エラーサブルーチンを呼ぶ。

rem ====================mp4ファイルサイズ判別====================
rem エンコ後ファイルのサイズを環境変数"mp4_size"に指定
for %%i in ("%tmp_folder_path%\%FileName%.mp4") do (
    set mp4_size=%%~zi
)
rem 稀にQSVのトランスコードに失敗すると"Error during encoding: device failed (-17)"が返されエンコに失敗してしまうので、ファイルサイズが0バイトなら失敗とみなしループさせ復旧を試みる
rem 10GB=10737418240byte以下ならbackup and sync用フォルダ、より大きいなら10GB以上用フォルダへ(10GB以上はうp出来ない)(mp4が約40GBを超える場合は想定していない)
if %mp4_size% equ 0 (
    rem 50回までループし、それでもダメなら諦めて無限ループを回避
    set /a cnt=cnt+1
    if %cnt% geq 50 (
        call :err
    ) else (
        goto :encode
    )
) else if %mp4_size:~0,-1% leq 1073741824 (
    rem エンコしたファイルが10GB以下ならbackup and sync用フォルダに移動
    move "%tmp_folder_path%\%FileName%.mp4" "%bas_folder_path%"
) else if %mp4_size:~0,-1% gtr 1073741824 (
    call :err
)
rem 終了
exit

call :errで呼ばれ実行しexit /bでサブルーチンを終了しメインルーチンに戻る。
move 移動したいファイルのパス 移動先ディレクトリ(ファイル)パスでts、ts.program.txt、mp4を退避する。
set "tweet_content=ERROR:%FileName%.tsと関連ファイルを退避しました。ログを確認して下さい。"環境変数tweet_contentにechoツイートしたい内容を格納。
ruby "%tweet_rb_path%"rubyに渡す

:err
rem ffmpegによるエラーのみ対応、シンタックスエラーやコマンドプロンプトの不具合はここに到達しないので注意
rem エンコしたファイルが10GBより大きい、処理に50回失敗した場合にts、ts.program.txt、mp4を退避する
move "%FilePath%" "%err_folder_path%"
move "%FilePath%.program.txt" "%err_folder_path%"
move "%tmp_folder_path%\%FileName%.mp4" "%err_folder_path%"
rem ツイートで警告
set "tweet_content=ERROR:%FileName%.tsと関連ファイルを退避しました。ログを確認して下さい。"
ruby "%tweet_rb_path%"
exit /b

PostRecEnd.ps1

既出の構文、処理が必要な理由等は端折ってます。

#180707
#視聴予約なら終了
if ($env:RecMode -eq 4) { exit }
#Powershellプロセスの優先度を高に
#(Get-Process -Id $pid).PriorityClass='High'

#180326バージョンのメモ。PowerShellではコメントは#(複数行は<# #>)
EpgTimerSrvから取得できるマクロ(環境変数)一覧の取得cf.

ls env:
Read-Host "Press enter to continue"

シェル変数ではなく外部プログラムからの環境変数のため、$env:RecMode
文字列の中に変数を書く場合等では、境界が曖昧になるので${env:RecMode}のように囲う。
タイトルバーにPostRecEnd.ps1:ファイル名.tsを表示させたい場合は、以下のようにコメントを外す。

(Get-Host).UI.RawUI.WindowTitle = $MyInvocation.MyCommand.Name + ":${env:FileName}.ts"
NotifyIcon
#====================NotifyIcon====================
#System.Windows.FormsクラスをPowerShellセッションに追加
Add-Type -AssemblyName System.Windows.Forms
#NotifyIconクラスをインスタンス化
$balloon=New-Object System.Windows.Forms.NotifyIcon
#powershellのアイコンを使用
$balloon.Icon=[System.Drawing.Icon]::ExtractAssociatedIcon('C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe')
#タスクトレイアイコンのヒントにファイル名を表示
$balloon.Text=$MyInvocation.MyCommand.Name + ":${env:FileName}.ts"
#タスクトレイアイコン表示
$balloon.Visible=$True
#ファイル名をタイトルバーに表示
#(Get-Host).UI.RawUI.WindowTitle=$MyInvocation.MyCommand.Name + ":${env:FileName}.ts"

これらはスクリプト開始直後に実行され、処理の間タスクトレイアイコンを表示している。
.NETのデフォルトで参照されてないSystem.Windows.Forms名前空間を使用するためにAdd-Typeする。
New-ObjectSystem.Windows.Forms.NotifyIconクラスをインスタンス化する。
IconプロパティにSystem.Drawing.Iconクラスの静的メソッドExtractAssociatedIcon('C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe')のアイコンを読み取る。

>#"と'の違い
>$hoge = 'fuga'
>Write-Host "$hoge"
fuga
>Write-Host '$hoge'
$hoge

Textプロパティはタスクトレイアイコンのヒント(マウスカーソルを合わせたときに出てくるやつ)に表示する内容を指定。
Visible $Trueでタスクトレイアイコン表示。

#====================BalloonTip関数====================
function BalloonTip {
    if ("${balloontip_toggle}" -eq "1") {
        #特定のTipIconのみを使用可
        #[System.Windows.Forms.ToolTipIcon] | Get-Member -Static -Type Property
        $balloon.BalloonTipIcon=[System.Windows.Forms.ToolTipIcon]::${ToolTipIcon}
        #表示するタイトル
        $balloon.BalloonTipTitle="エンコード${enc_result}"
        #表示するメッセージ
        $balloon.BalloonTipText="${env:FileName}`nts:$([math]::round(${ts_size}/1GB,2))GB mp4:$([math]::round(${mp4_size}/1MB,0))MB${err_detail}"
        #balloontip_toggle=1なら5000ミリ秒バルーンチップ表示
        $balloon.ShowBalloonTip(5000)
        #5秒待って
        Start-Sleep -Seconds 5
    }
    #タスクトレイアイコン非表示(異常終了時は実行されずトレイに亡霊が残る仕様)
    $balloon.Visible=$False
}

これらは処理の最後(失敗又は終了時)に関数として呼び出され、適切なバルーンチップを表示するとともに、タスクトレイアイコンを非表示にする。 #===ユーザ設定===のトグルのオンオフがif ("${balloontip_toggle}" -eq "1") {で機能する。
function 関数名 {処理内容}を先に書いておくと関数名を記述するだけで処理内容を実行できる。dosのラベルとexit /bcallするみたいに使えるけど、使う前に書く必要があることに注意。
System.Windows.Forms.NotifyIconBalloonTipIcon(バルーンチップに表示されるアイコン)プロパティにSystem.Windows.Forms.ToolTipIconクラスの静的メソッドにInfo Error等を指定。ここでは、条件によってアイコンを変える為に関数BallonTipを呼び出すと同時に変数$ToolTipIconに文字列を格納したものを使える状態にしている。
BalloonTipTitle(バルーンチップのタイトル)にも"エンコード失敗"のように補完できるようになっている。
BalloonTipText(バルーンチップの本文)には1行目にファイル名${env:FileName}を表示する。PowerShellでは`nで改行。
2行目にはts:$([math]::round(${ts_size}/1GB,2))GB mp4:$([math]::round(${mp4_size}/1MB,0))MBとし、変数$ts_size``$mp4_sizeに格納されたファイルサイズ(単位:Byte)をmathクラスの静的メソッドroundでそれぞれ/1GB``/1MBで割り算して、小数点以下,2``,0桁表示する。ts:1.23GB mp4:456MBのようになる。
ShowBalloonTipメソッドで5000ミリ秒バルーンチップを表示。
Start-Sleep -Seconds 5で5秒待ってVisible$False

Process

ハマったところ。

まず、普通にffmpegをオプションを沢山付けた状態で実行してみる。

>ffmpeg -y -hide_banner -nostats -fflags +discardcorrupt -i "${env:FilePath}" ${audio_option} -vf yadif=0:-1:1,pp= …
[NULL @ 000002b82a777840] Unable to find a suitable output format for 'yadif=0:-1:1,hqdn3d=4.0,scale=1280:720,unsharp=3:3:0.5:3:3:0.5:0'
yadif=0:-1:1,hqdn3d=4.0,scale=1280:720,unsharp=3:3:0.5:3:3:0.5:0: Invalid argument

適切な出力形式が見つからないとエラーが。

>ffmpeg -y -hide_banner -nostats -fflags +discardcorrupt -i "${env:FilePath}" -f mpegts ${audio_option} -vf yadif=0:-1:1,pp= …
Unknown encoder '-vf'

それじゃあ、

>&"${ffpath}\ffmpeg.exe" -y -hide_banner -nostats -fflags +discardcorrupt -i "${env:FilePath}" -f mpegts ${audio_option} -vf yadif=0:-1:1,pp= …
Unknown encoder '-vf'

もういい!Start-Process使うもん!

Start-Transcript -LiteralPath "${log_path}\${FileName}.txt"
$arg = "-y -hide_banner -nostats -fflags +discardcorrupt -i `"${FilePath}`" ${audio_option} -vf yadif=0:-1:1,pp= …"
$proc = Start-Process -FilePath "${ffmpeg_path}\ffmpeg.exe" -ArgumentList $arg -Wait -NoNewWindow

やった!できた!と思ったら、今度はStart-Transcriptffmpegの出力が書かれてない…

~~
**********************
トランスクリプトが開始されました。出力ファイル: C:\DTV\EncLog\180325_キリングバイツ#11.txt
ログ削除:180316_ラーメン大好き小泉さん 十一杯目「おいしいラーメン/大阪/コンビニ」.txt
録画フォルダ:104.6GB
削除:180322_魔法使いの嫁 第23話「Nothing seek, nothing find.」.ts、ts.program.txt、mp4
録画フォルダ:100.73GB
削除:180323_ヴァイオレット・エヴァーガーデン 第11話「もう、誰も死なせたくない」.ts、ts.program.txt、mp4
録画フォルダ:96.77GB
quality:26
audio_option:-c:a aac  -b:a 256k
>>>>>ここにffmpegの出力が欲しいのだが<<<<<
mp4:512MB
エンコード回数:1
エンコード終了:ts=2.42GB mp4=512MB
**********************
~~

おっ、どうやら-PassThruで実行するプロセスのオブジェクトを返す(System.Diagnostics.Process)ので、もしかして標準出力StandardOutput標準エラー出力StandardErrorを取得できるのでは?!

$arg = "-y -hide_banner -nostats -fflags +discardcorrupt -i `"${FilePath}`" ${audio_option} -vf yadif=0:-1:1,pp= …"
$proc = Start-Process -FilePath "${ffmpeg_path}\ffmpeg.exe" -ArgumentList $arg -Wait -WindowStyle Hidden -PassThru
Write-Output $proc.StandardError
> .\test.ps1
>

プログラマー諸君は、既にStart-Processから標準出力が正常に取得できないことに気づいていると思う。しかしシェルの不具合ではない。
繰り返す。これは不具合ではなく、Windows PowerShell本来の仕様である
というわけで結局.NET型で長々と書くことに、シェルの意味…

#====================Process====================
#ffmpeg、&ffmpeg、.\ffmpeg:ffmpegが引数を正しく認識しない(ファイル名くらいなら-f mpegtsで行けるけどもういいです)
#Start-Process ffmpeg:-NoNewWindowはWrite-Host?-RedirectStandardOutput、Errorはファイルのみ、-PassThruはExitCodeは受け取れても.StandardOutput、Errorは受け取れない仕様
function ffprocess {
    #設定
    #ProcessStartInfoクラスをインスタンス化
    $psi=New-Object System.Diagnostics.ProcessStartInfo
    #アプリケーションファイル名
    $psi.FileName="$file"
    #引数
    $psi.Arguments="$arg"
    #標準エラー出力だけを同期出力(注意:$trueは1つだけにしないとデッドロックします)
    $psi.UseShellExecute=$false
    $psi.RedirectStandardInput=$false
    $psi.RedirectStandardOutput=$false
    $psi.RedirectStandardError=$true
    $psi.WindowStyle=[System.Diagnostics.ProcessWindowStyle]::Hidden

    #実行
    #Processクラスをインスタンス化
    $p=New-Object System.Diagnostics.Process
    #設定を読み込む
    $p.StartInfo=$psi
    #プロセス開始
    $p.Start() | Out-Null
    #プロセス優先度を高に
    (Get-Process -Id $p.Id).PriorityClass='High'
    #Write-Output $p.Id
    #プロセスの標準エラー出力を変数に格納(注意:WaitForExitの前に書かないとデッドロックします)
    $global:StdErr=$p.StandardError.ReadToEnd()
    #プロセス終了まで待機
    $p.WaitForExit()
    #終了コードを変数に格納
    $global:ExitCode=$p.ExitCode
    #リソースを開放
    $p.Close()
}

これらはffmpeg、ffprobe使用時に$file $argとともに実行され、標準エラー出力を標準出力に出力する。
何度か使う機会があるので関数ffprobeとしておく。
New-ObjectSystem.Diagnostics.ProcessStartInfoクラスをインスタンス化。
FileNameプロパティにアプリケーションのファイル名を指定する。ここでは、変数に指定しているものも使用して${ffpath}\ffmpeg.exe${ffpath}\ffprobe.exeが展開される。
Argumentsには変数$argに格納された引数が展開される。
ffmpeg標準エラー出力を使用するので、RedirectStandardErrorのみ$trueにする。ほかは$falseにしないとデッドロックします。
WindowStyleでプロセス起動時のウィンドウをSystem.Diagnostics.ProcessWindowStyleHidden状態にする。

New-ObjectSystem.Diagnostics.Processクラスをインスタンス化。
StartInfoプロパティで、Startメソッドに渡す先程の設定$psiを読み込む。
Startメソッドで()とすることでStartInfoプロパティで指定されたプロセスリソースを起動。| Out-Nullで出力非表示。Get-Process -Id $p.IdでプロセスIDを取得し、PriorityClassHighに変更する。
$global:StdErr=$p.StandardError.ReadToEnd()グローバル変数StdErrにプロセスの標準エラー出力を格納する。これはPIDの判別、ログやエラーメッセージに使用する。
標準エラー出力StandardErrorStreamReader.ReadToEndメソッドで"同期"でWrite-Outputで標準出力に出力(注意:WaitForExitメソッドの前に書かないとデッドロックします)。
WaitForExitメソッドでプロセス終了まで待機する。
$global:ExitCode=$p.ExitCodeグローバル変数ExitCodeにプロセスの終了コードを格納する。これはエンコードが成功か失敗であるかの判別に使う。
Closeメソッドでプロセス終了。

ユーザ設定
#====================ユーザ設定====================
~~
#tweet.rb
$tweet_rb_path = 'C:\DTV\EDCB\tweet.rb'
#SSL証明書(環境変数)
$env:ssl_cert_file = 'C:\DTV\EDCB\cacert.pem'

変数$tweet_rb_pathはシェル内でしか使用せず、環境変数$env:ssl_cert_filerubyに渡された後使用される。

ログ
#====================ログ====================
#ログ取り開始
Start-Transcript -LiteralPath "${log_path}\${env:FileName}.txt"
#Get-ChildItemでログフォルダのtxtファイルを取得、更新日降順でソートし、100個飛ばし、Foreach-ObjectでRemove-Itemループ
Get-ChildItem "${log_path}\*.txt" | Sort-Object LastWriteTime -Descending | Select-Object -Skip 100 | Foreach-Object {
    Remove-Item -LiteralPath "$_"
    Write-Output "$_"
}

#====================ログ====================
#log_toggle=1ならば実行
if ("${log_toggle}" -eq "1") {
    #ログ取り開始
    Start-Transcript -LiteralPath "${log_path}\${env:FileName}.txt"
    #Get-ChildItemでログフォルダのtxtファイルを取得、更新日降順でソートし、logcnt_max個飛ばし、ForEach-ObjectでRemove-Itemループ
    Get-ChildItem "${log_path}\*.txt" | Sort-Object LastWriteTime -Descending | Select-Object -Skip ${logcnt_max} | ForEach-Object {
        Remove-Item -LiteralPath "$_"
        Write-Output "ログ削除:$_"
    }
}

ログ取得開始、古いログを削除を行う。
#===ユーザ設定===のトグルのオンオフがif ("${balloontip_toggle}" -eq "1") {で機能する。
Start-Transcript-LiteralPathリテラルのファイルパス"${log_path}\${env:FileName}.txt"にログ出力を開始。
Get-ChildItemでログフォルダ内のすべてのテキストファイル"${log_path}\*.txt"を取得、
Sort-Object LastWriteTime -Descendingで更新日降順でソート、
Select-Object -Skip ${logcnt_max}で設定された数飛ばして、
Foreach-Objectでループ処理。
リテラル文字列のパス-LiteralPathとしてファイルパス"$_"`Remove-Itemで削除。
Write-Outputで表示。

フォルダサイズを一定に保つファイルの削除

※tsとmp4で別れているが中身はほぼ同じです。

#====================tsフォルダサイズを一定に保つファイルの削除====================
#ts_del_toggle=1なら実行
if ("${ts_del_toggle}" -eq "1") {
    #録画フォルダの合計サイズを変数"ts_folder_size"に指定
    $ts_folder_size=$(Get-ChildItem "${env:FolderPath}" | Measure-Object -Sum Length).Sum
    Write-Output "録画フォルダ:$([math]::round(${ts_folder_size}/1GB,2))GB"
    #録画フォルダの合計サイズがts_folder_maxGBより大きいならファイルの削除
    while ($ts_folder_size -gt $ts_folder_max) {
        #録画フォルダ内の1番古いtsファイルのファイル名を取得
        #録画フォルダ内のtsファイルに対し、最終更新年月日でソートした1番最初にくるやつ、ファイル名(拡張子なし)を取得
        $ts_del_name=$(Get-ChildItem "${env:FolderPath}\*.ts" | Sort-Object LastWriteTime | Select-Object BaseName -First 1).BaseName
        #ts、同名のts.program.txt削除
        Remove-Item -LiteralPath "${env:FolderPath}\${ts_del_name}.ts"
        Remove-Item -LiteralPath "${env:FolderPath}\${ts_del_name}.ts.program.txt"
        #Remove-Item -LiteralPath "${env:FolderPath}\${ts_del_name}.ts.err"
        Write-Output "削除:${ts_del_name}.ts、ts.program.txt"
        #録画フォルダの合計サイズを取得
        $ts_folder_size=$(Get-ChildItem "${env:FolderPath}" | Measure-Object -Sum Length).Sum
        Write-Output "録画フォルダ:$([math]::round(${ts_folder_size}/1GB,2))GB"
    }
}

if ("${ts_del_toggle}" -eq "1") {で機能のオンオフ。
$folder_maxに録画フォルダ$env:FolderPathの最大サイズを指定。100GBのように単位も付けられる。
Get-ChildItem "${env:FolderPath}"Measure-Objectにパイプし-Sum Lengthで合計サイズを測り、変数$folder_sizeに格納。
while (実行条件) {処理内容}実行条件を満たしている間処理内容を実行。$folder_size$folder_maxより大きい-gt間実行。
Get-ChildItem "${env:FolderPath}\*.ts"で録画フォルダ情報を取得し、
Sort-Object LastWriteTime更新日時でソートし、
Select-Object BaseName -First 1).BaseNameファイル名(dosで言うと%~nPowerShellはオブジェクトとして渡してるからちょっと違うけど)を取得、
それを変数$del_nameに格納している。
Remove-Itemでそれぞれファイル削除。
tsとts.program.txt、mp4でそれぞれユーザ設定通りに丸め込む。
再度$folder_size = $(Get-ChildIt…でファイル削除後の録画フォルダサイズを取得する。これはwhileの実行条件に反映されるため、条件から外れればループを抜ける。結果的に$folder_max以下に保つ処理ができる。

jpg出力
#====================jpg出力====================
#環境変数Addkey(自動予約時のキーワード)に特定の文字が含まれている場合は連番jpgも出力(使用しない場合はコメント)
$addkey_jpg = $("${env:Addkey}" | Select-String -SimpleMatch 'ポプテピピック','フランキス','BEATLESS','エヴァーガーデン' -quiet)
if ($addkey_jpg -eq $True) {
    #出力フォルダ作成
    New-Item "${jpg_path}\${env:FileName}" -ItemType Directory
    Write-output "jpg出力:${env:FileName}.ts"
    #生TSの横が1920か1440か調べる
    #xml形式で扱い、tsファイル特有のwidthがメタデータの2箇所にあり2つ出力されちゃう問題を解決
    $ts_width = [xml](&"${ffpath}\ffprobe.exe" -v quiet -i "${env:FilePath}" -show_entries stream=width -print_format xml 2>&1)
    $ts_width = $ts_width.ffprobe.streams.stream.width
    #SAR比(1440x1080しか想定してないけど)によるフィルタ設定、jpg出力
    if ("${ts_width}" -eq "1440") {
        $arg = "-y -hide_banner -nostats -an -skip_frame nokey -i `"${env:FilePath}`" -vf yadif=0:-1:1,scale=1920:1080,hqdn3d=4.0 -f image2 -q:v 0 -vsync 0 `"${jpg_path}\${env:FileName}\%05d.jpg`""
    } else {
        $arg = "-y -hide_banner -nostats -an -skip_frame nokey -i `"${env:FilePath}`" -vf yadif=0:-1:1,hqdn3d=4.0 -f image2 -q:v 0 -vsync 0 `"${jpg_path}\${env:FileName}\%05d.jpg`""
    }
    $file = "${ffpath}\ffmpeg.exe"
    ffprocess
}

自動予約登録時のキーワードenv:Addkeyに特定の文字が含まれているか調べSelect-String -SimpleMatch 'ポプテピピック','フランキス','BEATLESS','エヴァーガーデン'て、変数addkey_jpgに結果を格納。-quietで非表示。
if ($addkey_jpg -eq $True) { $addkey_jpgの結果が$True(一致が1つでもあった)の場合実行。
New-Item"${jpg_path}\${env:FileName}"jpg出力フォルダ内にファイル名の-ItemType Directoryディレクトリを作成。
録画したtsファイルでは、メタデータの2箇所に"width"の項目があるので、

>ffprobe -hide_banner -v quiet -i "C:\Users\Shibanyan\Desktop\180119_ヴァイオレット・エヴァーガーデン 第2話「戻ってこない」.ts" -show_entries stream=width -print_format xml
<?xml version="1.0" encoding="UTF-8"?>
<ffprobe>
    <programs>
        <program >
            <streams>
                <stream width="1920"/>
                <stream />
                <stream />
            </streams>
        </program>
    </programs>

    <streams>
        <stream width="1920"/>
        <stream />
        <stream />
    </streams>
</ffprobe>

ffprobeでtsファイル"${env:FilePath}"メタデータの横px-show_entries stream=widthのみ-print_format xmlxml形式で2>&1標準、標準エラー出力取得。[xml]型で変数$ts_widthに格納。
変数$ts_widthに変数$ts_width.ffprobe.streams.stream.widthの階層のみを格納。
これで1920だけを取得している。
変数$ts_width1440ならscale=1920:1080でリサイズする。
$arg$fileを指定してffprocess関数を呼んでjpg出力のエンコード$arg = "-hoge `"C:\foo`""のように、ダブルクォートはバッククォートでエスケープ。

PIDの判別
#====================PIDの判別====================
#前の番組の音声や映像のPIDを引数に入れないため
#-analyzeduration 30M -probesize 100Mで適切にストリームを読み込む
$StdErr=[string](&"${ffpath}\ffmpeg.exe" -hide_banner -nostats -analyzeduration 30M -probesize 100M -i "${env:FilePath}" 2>&1)
#スペース、CRを消す
$StdErr=($StdErr -replace " ","")
$StdErr=($StdErr -replace "`r","")
#if x480だけの場合はx480、else x1080だけ・x480とx1080がある場合はx1080
if (($StdErr -match 'x480') -And ($StdErr -notmatch 'x1080')) {
    $res_need='x480'
} else {
    $res_need='x1080'
}
#LFで分割して配列として格納
$StdErr=($StdErr -split "`n")
#配列を展開(映像)
foreach ($a in $StdErr) {
    #"Video:"and"${res_need}"が含まれ、'none'が含まれない行の場合実行
    if (($a -match "^(?=.*Video:)(?=.*${res_need})") -And ($a -notmatch 'none')) {
        #引数に追記
        $pid_need+=' -map i:0x'
        #PIDの部分だけ切り取り
        $pid_need+=($a -split '0x|]')[1]
    }
}
#0x1なら音声も0x1**を選ぶ
#0x2なら音声も0x2**を選ぶ
if ("$pid_need" -match '0x1') {
    $audio_need='0x1'
} elseif ("$pid_need" -match '0x2') {
    $audio_need='0x2'
}
#配列を展開(音声)
foreach ($a in $StdErr) {
    #"Audio:"and"${audio_need}"が含まれ、'0channels'が含まれない行の場合実行
    if (($a -match "^(?=.*Audio:)(?=.*${audio_need})") -And ($a -notmatch '0channels')) {
        #引数に追記
        $pid_need+=' -map i:0x'
        #PIDの部分だけ切り取り
        $pid_need+=($a -split '0x|]')[1]
    }
}
Write-Output "PID:${pid_need}"

デジタル放送のtsファイルには複数のストリームが含まれている。
指定サービスのみの録画でワンセグ等のストリームは削られているが、前の番組や裏番組のストリームが混ざっている場合が殆どの為、

エンコード
#====================エンコード====================
#ループ処理用
$cnt = 0
#プロセス引数
$file = "${ffpath}\ffmpeg.exe"
$arg = "-y -hide_banner -nostats -fflags +discardcorrupt -i `"${env:FilePath}`" ${audio_option} -vf yadif=0:-1:1,hqdn3d=4.0,scale=1280:720,unsharp=3:3:0.5:3:3:0.5:0 -global_quality ${quality} -c:v h264_qsv -preset:v veryslow -g 300 -bf 16 -refs 9 -b_strategy 1 -look_ahead 1 -look_ahead_downsampling off -pix_fmt nv12 -bsf:v h264_metadata=colour_primaries=1:transfer_characteristics=1:matrix_coefficients=1 -map 0:p:${env:SID10}:0 -map 0:p:${env:SID10}:1 -movflags +faststart `"${tmp_folder_path}\${env:FileName}.mp4`""
Write-Output "Arguments:$arg"

$cnt = 0ループカウント用。
$file $argを指定するのは1度でいい為、do-whileの前に書いてある。

#ファイルサイズが0バイトの間実行
do {
    #録画の開始終了でビジーなので負荷を減らすために10秒待つ
    Start-Sleep -s 10
    #エンコプロセス
    ffprocess

    #====================mp4ファイルサイズ判別====================
    #エンコ後mp4のサイズを変数"mp4_size"に指定
    $mp4_size = $(Get-ChildItem -LiteralPath "${tmp_folder_path}\${env:FileName}.mp4").Length
    Write-Output "mp4:$([math]::round(${mp4_size}/1MB,0))MB"
    #ループ処理用
    $cnt = $cnt+1
    Write-Output "エンコード回数:$cnt"
    #mp4のファイルサイズ、エンコード回数による条件分岐
    if (($cnt -gt 25) -Or ($mp4_size -gt 10GB)) {
        #mp4が存在しない、50回失敗、10GBより大きい場合、ts、ts.program.txt、mp4を退避する
        Move-Item -LiteralPath "${env:FilePath}" "${err_folder_path}"
        Move-Item -LiteralPath "${env:FilePath}.program.txt" "${err_folder_path}"
        Move-Item -LiteralPath "${tmp_folder_path}\${env:FileName}.mp4" "${err_folder_path}"
        Write-Output "エンコード失敗:ts=$([math]::round(${ts_size}/1GB,2))GB mp4=$([math]::round(${mp4_size}/1MB,0))MB"
        #ツイート
        $env:tweet_content = "ERROR:${env:FileName}.tsと関連ファイルを退避しました。ログを確認して下さい。"
        Start-Process "${ruby_path}" "${tweet_rb_path}" -WindowStyle Hidden -Wait
        #バルーンチップ
        $ToolTipIcon = 'Error'
        $enc_result = '失敗'
        BalloonTip
        #強制終了
        exit 1
    } elseif ((-Not($mp4_size -eq 0)) -And ($mp4_size -le 10GB)) {
        #mp4が0バイトでない且つ10GB以下ならmp4をbas_folder_pathに移動
        Move-Item -LiteralPath "${tmp_folder_path}\${env:FileName}.mp4" "${bas_folder_path}"
        Write-Output "エンコード終了:ts=$([math]::round(${ts_size}/1GB,2))GB mp4=$([math]::round(${mp4_size}/1MB,0))MB"
        #バルーンチップ
        $ToolTipIcon = 'Info'
        $enc_result = '終了'
        BalloonTip
    }
} while ($mp4_size -eq 0)

do {処理内容} while (ループ条件)で処理内容を行ってからループ条件ならループ、条件外なら終了。処理開始時点では$mp4_size0で、必ず一度は処理内容を実行するため、do-while文を使用している。
Start-Sleep -s 10で録画終了直後のビジー状態を避けてQSVのエラーが起こる確率を下げる。QSVのエラー時にループの効果を出すためでもある。
$file $argffprocessを呼んでエンコード標準エラー出力は終了時に出力される。
$cnt = $cnt+1でループカウントを1大きくする。何度もループすると1ずつ大きくなっていく。
if (($cnt -gt 25) -Or ($mp4_size -gt 10GB)) { 25回ループしたか-Or、mp4のファイルサイズが10GBより大きい場合実行。
Move-Item ファイルパス ディレクトリパスでファイルをそれぞれ移動。ファイルが存在しないとか録画に失敗したとかでも何でも失敗ならここに来る雑実装だけど、そんなに機会が無いから許して。
$env:tweet_content="ERROR:${env:FileName}.ts…"環境変数$env:tweet_contentにツイート内容を格納。
悪名高きStart-ProcessRubyのパス"${ruby_path}"とツイート用Rubyスクリプトのパス"${tweet_rb_path}"を起動。-WindowStyle Hiddenでコンソール非表示、-Waitで処理終了まで待つ。
$ToolTipIcon = 'Error' $enc_result = '失敗'で関数BalloonTipを読んでエンコード失敗のバルーンチップを表示。
終了コード1を付けてexit強制終了。
} elseif ((-Not($mp4_size -eq 0)) -And ($mp4_size -le 10GB)) {ifの条件以外でもしelseif(else ifではない)(-Not($mp4_size -eq 0)mp4のファイルサイズが0でない-And且つ($mp4_size -le 10GB)10GB以下なら実行。
Move-Itemでmp4を$bas_folder_pathに移動(うpが開始される)。
$ToolTipIcon = 'Info' $enc_result = '終了'で関数BalloonTipを呼び出し、エンコード終了バルーンチップを表示。

お!わ!り!読んでくれてありがとう!!

自動エンコバッチの応用

ffmpegを繰り返し使う方は環境変数Pathに設定しておくと良い。ffmpegは色々なアプリケーションに使われているので競合してしまわないよう注意。
どれも基本的にD&Dで処理する用に書いてあるので、自動エンコバッチに導入する際は%1%FilePath%に修正したり、rem _EDCBX_DIRECT_を追加する等して使用して下さい。
また、内部に使用されている処理等を自分の用途に合わせたバッチをつくる際の参考にしてください。

応用編の更新情報
2017/7
・溜まったtsの自動処理に役立ちそうなサンプルを置いといた
・役立ちそうなバッチサンプルを追加
・実装しないが、番組情報から24fps化の選択を行うサンプルを追加
2017/8
・夜間時間(東京電力)にエンコを実行して電気代を節約するサンプルを追加
ffmpegでtsを綺麗に切り出すサンプルを追加
ffmpeg+ImageMagickでtsからgifを作成するサンプルを追加
2017/9
・キーフレーム切り出しの方法とffplayの使い方を追加
2018/1
D&Dでエンコするバッチで、ファイルかフォルダかを自動判別
ffmpegで動画ファイルをD&Dでキーフレーム連番jpgにするバッチを追加
・tsをミリ秒単位でカットしてgif、mp4、avi等にエンコードするバッチを追加
2018/2
・tsをカットするバッチはカットしない選択とgif、mp4、aviに加えjpgを作れるようにした
・カットバッチのmp4サブルーチンで0バイトならループするように雑実装した
・ウォータマーク(局ロゴ)を消す方法について記述
・解説を若干丁寧に
・ServiceIDの判別のコードを整理
・カットバッチでTSファイル以外への対応、カットしない場合の秒数指定の取り止め、リサイズしない選択肢の追加、jpg出力時にリサイズ出来るようにし1440x1080且つリサイズ指定が無い場合のみ1920x1080にリサイズするようにした
・更新情報を分けた
・複数aviファイルがあるフォルダをD&Dで連結するバッチを追加
・カットバッチのビデオフィルタコンマ部分のバグ修正
・カットバッチの修正、mp4出力時closed gop選択機能追加
・カットバッチを動くように修正、9つまでのファイルの同時入力に対応した
・解説を追記
2018/4
・カットバッチのバグ修正でビデオフィルタはプリセットを登録する方法に変更、処理の簡略化
2018/5
D&Dエンコバッチを終了コードによるループに変更した

nyanshiba.hatenablog.com

後述のバッチ等にも多く使用されているので参考までに。

TSファイルを扱う際の基本

ffmpeg -i %1 -vf bwdif… -map 0:p:211:0 -map 0:p:211:1 "%~dpn1.mp4"
pause

・入力を%1、出力を%~dpn1.mp4に変更しただけ。
・ビデオフィルタ-vfは前から順番に適用される。デインターレースしてプログレッシブスキャンにしてから、リサイズ時影響が大きいモスキートノイズ(デジタル放送特有の輪郭部に現れるアレ)を除去してからリサイズし、リサイズ時ジャギーもろとも諸々の・ノイズをお掃除してあげる感じにしてあげると綺麗且つ効率良くフィルタ処理を行える(例:-vf 高品質デインターレースyadif=0:-1:1(モスキートノイズ除去pp=dr),1280x720にリサイズscale=1280:720,高精度平滑化hqdn3d)。
・EpgTimerSrvで渡されていた%SID10%を番組情報ファイルのServiceID(例えばBS11なら211、MXなら23608)に置き換える。番組情報ファイルが無い場合はffprobe 入力ファイルパスで必要なServiceID(Program XXX)を見つける。
・D&D自動エンコバッチに使用されているcf.下記コードを使用すると、%SID10%が使用できる。

rem ====================ServiceIDの判別====================
rem 番組情報ファイルからServiceIDのある行を抜き出し、区切り文字":""("で2番目のトークンを環境変数SID10に格納
rem ServiceID:211(0x00D3)から211だけを抜き出す
for /f "tokens=2 delims=:(" %%a in ('findstr ServiceID "%~1.program.txt"') do (
    set SID10=%%a
)
トリミングしてavi出力

-ss10秒目から50秒目まで(-t40秒間)をavi出力

ffmpeg -ss 10 -i %1 -t 40 -vf yadif=0:-1:1,hqdn3d=3.000 -c:v rawvideo -c:a pcm_s16le "%~dpn1_enc.avi"
1秒あたり5コマ分png出力
md "%~dpn1"
ffmpeg -an -i %1 -vf yadif=0:-1:1 -c:v png -r 5 "%~dpn1\%%05d.png"
キーフレームをpng出力
md "%~dpn1"
ffmpeg -an -skip_frame nokey -i %1 -vf yadif=0:-1:1 -vsync 0 "%~dpn1\%%05d.png"
キーフレームをjpg出力
md "%~dpn1"
ffmpeg -an -skip_frame nokey -i %1 -vf yadif=0:-1:1 -f image2 -q:v 0 -vsync 0 "%~dpn1\%%05d.jpg"
再生
ffplay -i %1 -vf yadif=0:-1:1,hqdn3d=3.000
ffmpegだけでgifを作る
md "%~dpn1"
ffmpeg -y -ss 1400 -i %1 -t 1420 -an -vf yadif=0:-1:1,scale=640x360 -c:v rawvideo -r 5 "%~dpn1\temp.avi"
ffmpeg -y -i "%~dpn1\temp.avi" -vf palettegen "%~dpn1\palette.png"
ffmpeg -y -i "%~dpn1\temp.avi" -i "%~dpn1\palette.png" -lavfi paletteuse "%~dpn1\%~n1.gif"

D&D自動エンコバッチ

基本的には↑のバッチから削除機能、うp機能を消してD&D用に%FilePath%から%1等に変更しただけ。
D&Dされたのが"tsファイル"なのか"tsが溜まってるフォルダ"なのかを拡張子で判別し動作する。ファイルかフォルダかの判別は%1の末尾3文字が.tsかどうかで行っている。
EDCBから渡されないServiceIDを番組情報ファイルから抜き出している為、番組情報ファイルがtsと同じディレクトリにあることが条件。

大まかな流れ
1.TSファイル又はTSファイルの入ったフォルダをD&Dで入力
    1a.TSファイル:2以降をサブルーチンとし、一回だけ行う
    1b.TSファイルの入ったフォルダ:2以降をサブルーチンとし、ファイル数分繰り返す
        2.音声形式の判別
            2a.デュアルモノ:左右を分離し2チャンネルに分ける
            2b.その他:再エンコなし
        3.ServiceIDの判別
        4.tsファイルサイズ判別
            4a.20GB以下:品質27
            4b.20GBより大きい:品質29
        5.エンコード
        6.mp4ファイルサイズ判別
            6a.0バイト
                6a-1.0~49回目:5へ
                6a-2.50~回目:警告
メインルーチン終了
@echo on
rem 180521
rem tsもフォルダ内のtsもD&Dでエンコするバッチ
rem ====================環境変数設定====================
rem 出力フォルダのパス
set "optout_folder_path=C:\Users\sbn\Desktop"
rem ====================ファイルかフォルダを判別====================
rem ファイル名の後ろ3文字が.tsならばファイル、それ以外ならばフォルダとみなす
set "FilePath=%~1"
if "%FilePath:~-3%" == ".ts" (
rem ファイル名をタイトルバーに表示
title ファイル:"%~nx1"
call :main "%~1"
) else (
rem ファイル名をタイトルバーに表示
title フォルダ:"%~nx1"
rem D&Dしたフォルダ内のTSファイルを順次処理するよう呼ぶ
for %%a in ("%~1\*.ts") do (
call :main "%%a"
)
)
set /p hoge=ウィンドウを閉じるには何かキーを押してください . . .
exit
:main
rem ====================デュアルモノの判別====================
rem 番組情報の中に"デュアルモノ"という文字列があれば環境変数"audio_option"に"-filter_complex channelsplit"を加え、音声ビットレートを半分にする
findstr "デュアルモノ" "%~1.program.txt"
if %errorlevel% equ 0 (
set "audio_option=-c:a aac -b:a 128k -filter_complex channelsplit"
) else if %errorlevel% equ 1 (
rem set "audio_option=-c:a copy -bsf:a aac_adtstoasc"
set "audio_option=-c:a aac -b:a 256k"
)
rem ====================ServiceIDの判別====================
rem 番組情報ファイルからServiceIDのある行を抜き出し、区切り文字":""("で2番目のトークンを環境変数SID10に格納
rem ServiceID:211(0x00D3)から211だけを抜き出す
for /f "tokens=2 delims=:(" %%a in ('findstr ServiceID "%~1.program.txt"') do (
set SID10=%%a
)
rem ServiceIDが取得できなかった場合
if not defined SID10 (
set /p SID10=ServiceID^(10進数^)を入力:
)
rem ====================tsファイルサイズ判別====================
rem TSファイルのサイズを環境変数"ts_size"に指定
for %%i in ("%~1") do (
set ts_size=%%~zi
)
rem 20GB=21474836480byte以下ならquality 26、より大きいなら28
if %ts_size:~0,-1% leq 2147483648 (
set quality=27
) else if %ts_size:~0,-1% gtr 2147483648 (
set quality=29
)
rem ====================エンコード====================
rem ループ処理用
set cnt=0
:encode
rem 録画の開始終了でビジーなので負荷を減らすためにちょっと待つ
timeout /t 10 /nobreak
rem エンコ
ffmpeg -y -hide_banner -fflags +discardcorrupt -i "%~1" %audio_option% -vf yadif=0:-1:1,hqdn3d=4.0,scale=1280:720:flags=lanczos+accurate_rnd,unsharp=3:3:0.5:3:3:0.5:0 -global_quality %quality% -c:v h264_qsv -preset:v veryslow -g 300 -bf 16 -refs 9 -b_strategy 1 -look_ahead 1 -look_ahead_downsampling off -pix_fmt nv12 -bsf:v h264_metadata=colour_primaries=1:transfer_characteristics=1:matrix_coefficients=1 -map 0:p:%SID10%:0 -map 0:p:%SID10%:1 -map 0:p:%SID10%:2 -sn -dn -ignore_unknown -movflags +faststart "%optout_folder_path%\%~n1_tsenc.mp4"
rem 終了コードが1且つループカウントが25以下までの間、エンコードを試みる
if "%errorlevel%" equ "1" (
if "%cnt%" leq "25" (
set /a cnt+=1
goto :encode
) else (
echo "%~1":25回ループしても終了コードが1でした
)
)
exit /b
view raw tsenc.bat hosted with ❤ by GitHub

カットバッチ

動画ファイルをミリ秒単位でカットしてgif、mp4、avi、連番jpgにエンコードするバッチ。
TSファイルに限らず動画であれば編集できる。最早動画編集ソフトなので、簡単な編集はこれで済ませている。
1度に9個までのファイルのD&Dに対応している。環境変数に登録すればWin+R->tscut ほげ.ts "ふ~.ts" fuga.tsみたいに便利です。
ビデオフィルタはプリセットを登録することで使えます(最大2147483647個まで)。
・gif:ImageMagickFFmpegを使用し、出来るだけ綺麗なgifを作成します。
・mp4:h264_qsv LA-ICQ出力です。品質を選択する機能があります。
・avi:rawvideo、pcm_s16le出力です。連番機能があるので同じソースを複数にカットし編集素材を作成するのに向いてます。
・jpg:ffmpegで直接jpgを切り出します。一定のコマ数かキーフレームを選ぶことができます。

ImageMagickffmpeg環境変数に設定されていることが前提。
https://www.imagemagick.org/script/download.php#windows から"ImageMagick-xxx-Q16-(あなたのOSのアーキテクチャ)-(staticかdllお好みでどうぞ).exe"を(HTTPかFTPお好みでどうぞ)でDLしてインストール。

大まかな流れ
1.TSファイルをD&Dで入力(9個まで)
2.ファイル一覧を出力
3.それぞれのファイルでサブルーチンを呼ぶ
    3-1.出力フォルダ作成
    3-2.カット時刻の計算方式(hms、ミリ秒、カットしない)
        3-2-1.hms表記のプレイヤで秒単位のカット
        3-2-2.ffplayでミリ秒単位のカット
    3-3.ビデオフィルタ選択
        3-3-1.インターレース
            3-3-1-1.解像度入力
        3-3-2.プログレッシブ
            3-3-2-1.解像度入力
    3-4.出力形式選択
        3-4-1.ImageMagickとffmpegでgif出力
        3-4-2.ffmpegでmp4出力
        3-4-3.ffmpegでavi出力
        3-4-4.ffmpegでjpg出力
            3-4-4-1.キーフレーム出力
            3-4-4-2.コマ数出力
メインルーチン終了
@echo off
rem 180809
rem 最早編集ソフトと化したTSファイルをD&Dでミリ秒単位でカットしてgif、mp4、avi、連番jpgにエンコードするバッチ
rem D&Dした複数ファイルを表示
set cnt=0
setlocal enabledelayedexpansion
for %%a in ( %1 %2 %3 %4 %5 %6 %7 %8 %9 ) do (
if not %%a == "" (
set /a cnt+=1
echo !cnt!.%%a
)
)
setlocal disabledelayedexpansion
echo.
rem 複数ファイルD&Dでエンコ処理を呼ぶ
for %%a in ( %1 %2 %3 %4 %5 %6 %7 %8 %9 ) do (
if not %%a == "" (
title %~nx0:%%a
echo 処理対象:%%a
echo.
call :encode %%a
echo 処理終了:%%a
echo.
)
)
echo すべての処理終了
pause
exit
:encode
rem ==========出力フォルダを作成==========
rem D&DしたTSファイルのあるディレクトリにその名前のフォルダを作成
md "%~dpn1"
echo 出力ディレクトリ:"%~dpn1"
echo.
rem ==========カット時刻の計算方式==========
set /p hms_or_ffplay=カット時刻を入力する方式を選択[hms:hms表記、ff:ffplay等の秒数表記、Enter:カットしない]:
echo.
if "%hms_or_ffplay%" == "hms" (
call :cutcal_hms %1
) else if "%hms_or_ffplay%" == "ff" (
call :cutcal_ffplay %1
) else if "%hms_or_ffplay%" == "" (
rem カット変数クリア
set SS=
set T=
)
rem ==========ビデオフィルタ==========
rem vfプリセット
set "vf0= "
set "vf1=-vf bwdif=0:-1:1,pp=ac"
set "vf2=-vf bwdif=0:-1:1,hqdn3d"
set "vf3=-vf bwdif=0:-1:1,pp=ac,unsharp=3:3:2:3:3:2:0,scale=1280:720"
set "vf4=-vf bwdif=0:-1:1,hqdn3d,unsharp=3:3:2:3:3:2:0,scale=1280:720"
set "vf5=-vf bwdif=0:-1:1,pp=ac,unsharp=3:3:1:3:3:1:0,scale=1920:1080"
set "vf6=-vf bwdif=0:-1:1,pp=ac,unsharp=3:3:1:3:3:1:0"
set "vf7=-vf pp=ac"
set "vf8=-vf pp=ac,scale=1280:720"
set "vf9=-vf pp=ac,scale=960:540"
set "vf10=-vf pp=ac,scale=720:480"
rem set "vf8=-vf pp7=16:0,scale=1280:720:sws_dither=none"
rem set "vf9=-vf pp7=16:0,scale=960:540:sws_dither=none"
rem set "vf10=-vf pp7=16:0,scale=720:480:sws_dither=none"
set "vf11=-vf scale=1280:720"
set "vf12=-vf framestep=50"
set "vf13=-vf framestep=100"
rem set "vf13=-vf select=eq^(pict_type^\,I^),framestep=50"
rem 疑似foreachループで変数を展開して引数を表示
rem ループ用
set i=0
setlocal enabledelayedexpansion
:foreach
rem callで変数vfを遅延展開、!で巻き込まれないようにする
call set vfecho=%%vf!i!%%
rem 変数vfvarが定義されている場合のみ実行、gotoループでその間のみ実行
if defined vfecho (
echo vf!i!:!vfecho!
set /a i+=1
goto :foreach
)
rem 聞く
set /p i=ビデオフィルタ選択:vf
rem callで変数内の変数を遅延展開
call set vf=%%vf!i!%%
setlocal disabledelayedexpansion
echo vf:%vf%
echo.
rem ==========出力形式選択==========
echo ffplay:再生
echo gif:ImageMagick、FFmpeg
echo mp4:h264_qsv LA-ICQ。品質、GOP長選択機能。
echo avi:rawvideo、pcm_s16le出力。連番機能。
echo jpg:コマ数かキーフレーム選択。
echo png:コマ数かキーフレーム選択。
set /p gif_or_mp4=出力形式を選択[gif、mp4、avi、jpg、png]:
echo.
call :%gif_or_mp4% %1
exit /b
rem ==========カット時刻の計算サブルーチン==========
rem ----------hms表記のプレイヤ(秒単位)----------
:cutcal_hms
set /p SS=カット開始時刻を入力[例:0時間1分23秒=000123]:
set /p TT=カット終了時刻を入力:
rem 秒になおす計算
set /a SS=(%SS:~0,2%*3600+%SS:~2,2%*60+%SS:~4,2%)
set /a TT=(%TT:~0,2%*3600+%TT:~2,2%*60+%TT:~4,2%)
rem カット開始時刻と終了時刻の差を計算
set /a T=(%TT%-%SS%)
rem カット開始秒と終了までの秒数を表示し、再入力するか聞く
set ask_again=
set /p ask_again=カット開始秒は%SS%、カット終了までの秒数は%T%ですが、よろしいですか[Enter/n]:
echo.
if not "%ask_again%" == "" (
goto :cutcal_hms
)
rem ffmpegの引数を追加
set "SS=-ss %SS%"
set "T=-t %T%"
exit /b
rem ----------ffplay等の秒数表記のプレイヤ(ミリ秒単位)----------
rem tsファイルのptsズレに対応
:cutcal_ffplay
rem カット開始時刻と終了時刻をffplayに表示されているミリ秒数で入力
set /p SS=カット開始時刻を入力[例:0時間1分23秒4=000123.400s="000123400"ms]:
set /p TT=カット終了時刻を入力:
rem カット開始時刻と終了時刻の差を計算
set /a T=(%TT%-%SS%)
rem ffprobeの-show_entriesスイッチを使ってformat=start_timeを出力し環境変数start_timeに設定
for /f "delims=" %%a in ('ffprobe -v quiet -i %1 -show_entries format^=start_time -of default^=noprint_wrappers^=1:nokey^=1') do (
set "start_time=%%a"
)
rem ffplayの表示から入力したカット開始時間-ffprobeで取得したstart_time(tsファイル等は開始時間がズレている場合があるため)(小数点を抜きミリ秒単位で計算)
rem 39533.817722
set /a SS=(%SS%-%start_time:~0,-7%%start_time:~-6,-3%)
rem ミリ秒を秒に戻し桁揃え
set SS=%SS:~0,-3%.%SS:~-3%
set T=%T:~0,-3%.%T:~-3%
rem カット開始秒と終了までの秒数を表示し、再入力するか聞く
set ask_again=
set /p ask_again=カット開始秒は%SS%、カット終了までの秒数は%T%ですが、よろしいですか[Enter/n]:
echo.
if not "%ask_again%" == "" (
goto :cutcal_ffplay
)
rem ffmpegの引数を追加
set "SS=-ss %SS%"
set "T=-t %T%"
exit /b
rem ==========出力形式選択サブルーチン==========
rem ----------ffplay----------
:ffplay
ffplay %SS% -i %1 %T% %vf%
exit /b
rem ----------gif----------
:gif
rem フレームレートを入力
set /p framerate=フレームレートを入力[5、10、24、30等]:
rem ffmpegでカット、インタレ解除、ノイズ除去、リサイズ、avi出力
ffmpeg -y -hide_banner %SS% -i %1 %T% -an %vf% -c:v rawvideo "%~dpn1\temp.avi"
rem ffmpegでaviからpngを切り出し
ffmpeg -y -hide_banner -i "%~dpn1\temp.avi" -c:v png -r %framerate% "%~dpn1\%%05d.png"
rem imagemagickで複数のpngをgifに変換
magick convert "%~dpn1\*.png" -layers Optimize "%~dpn1\%~n1.gif"
exit /b
rem ----------mp4----------
:mp4
rem 品質を入力
set /p quality=crf又はglobal_qualityの値[LA-ICQ:27,x265:25]:
rem 入力された数値を元に音声オプションを選択し環境変数へ格納
set /p audio_option=音声オプションを選択[128,256,320,copy,an]:
if "%audio_option%" == "128" (
set audio_option=-c:a aac -b:a 128k
) else if "%audio_option%" == "256" (
set audio_option=-c:a aac -b:a 256k
) else if "%audio_option%" == "320" (
set audio_option=-c:a aac -b:a 320k
) else if "%audio_option%" == "copy" (
set audio_option=-c:a copy -bsf:a aac_adtstoasc
) else if "%audio_option%" == "an" (
set audio_option=-an
)
rem 入力された数値を元にGOPオプションを選択し環境変数へ格納
set /p gop_option=GOPオプションを選択[p:保存用、s:ストリーミング用(closed gop)]:
if "%gop_option%" == "p" (
set gop=30
set bf=16
) else if "%gop_option%" == "s" (
set gop=15
set bf=2
)
rem PID
ffmpeg -hide_banner -i %1
set /p pid_need=必要があればPIDを入力[-map i:0x140 -map i:0x141]:
rem ffmpegでカット、インタレ解除、ノイズ除去、リサイズ、mp4出力
set /p encoder_option=エンコーダを選択[qsv,x265]:
:loop
if "%encoder_option%" == "qsv" (
ffmpeg -y -hide_banner -analyzeduration 30M -probesize 100M -fflags +discardcorrupt %SS% -i %1 %T% %audio_option% %vf% -global_quality %quality% -c:v h264_qsv -preset:v veryslow -g %gop% -bf %bf% -refs 4 -b_strategy 1 -look_ahead 1 -look_ahead_depth 60 -pix_fmt nv12 -bsf:v h264_metadata=colour_primaries=1:transfer_characteristics=1:matrix_coefficients=1 %pid_need% -movflags +faststart "%~dpn1\%~n1.mp4"
) else if "%encoder_option%" == "x265" (
ffmpeg -y -hide_banner -analyzeduration 30M -probesize 100M -fflags +discardcorrupt %SS% -i %1 %T% %audio_option% %vf% -c:v libx265 -preset:v fast -x265-params crf=%quality%:rc-lookahead=40:psy-rd=0.3:keyint=%gop%:no-open-gop:bframes=%bf%:rect=1:amp=1:me=umh:subme=3:ref=3:rd=3 -pix_fmt yuv420p -bsf:v hevc_metadata=colour_primaries=1:transfer_characteristics=1:matrix_coefficients=1 %pid_need% -movflags +faststart "%~dpn1\%~n1.mp4"
)
rem 稀にQSVのトランスコードに失敗すると"Error during encoding: device failed (-17)"が返されエンコに失敗してしまうので、ファイルサイズが0バイトなら失敗とみなしループさせて復旧を試みる
rem 終了コードが1且つループカウントが50以下までの間、エンコードを試みる
if "%errorlevel%" equ "1" (
if "%cnt%" leq "50" (
set /a cnt+=1
timeout /t 10
goto :encode
) else (
goto :err
)
)
exit /b
rem ----------avi----------
:avi
rem 同じ名前のファイルがあれば連番付け(編集向け機能)
set cnt=1
if not exist "%~dpn1\%~n1_%cnt%.avi" (
call :avienc %1
) else if exist "%~dpn1\%~n1_%cnt%.avi" (
:loop
set /a cnt+=1
if not exist "%~dpn1\%~n1_%cnt%.avi" (
call :avienc %1
) else if exist "%~dpn1\%~n1_%cnt%.avi" (
goto :loop
)
)
exit /b
:avienc
ffmpeg -hide_banner %SS% -i %1 %T% %vf% -c:v rawvideo -c:a pcm_s16le "%~dpn1\%~n1_%cnt%.avi"
exit /b
rem ----------jpg----------
:jpg
rem キーフレームかコマ数出力か
set /p key_or_rate=キーフレームかコマ数出力か選択[k:キーフレーム、r:コマ数]:
if "%key_or_rate%" == "k" (
ffmpeg -hide_banner -an -skip_frame nokey %SS% -i %1 %T% %vf% -f image2 -q:v 0 -vsync 0 "%~dpn1\%%05d.jpg"
) else if "%key_or_rate%" == "r" (
ffmpeg -hide_banner -an %SS% -i %1 %T% %vf% -f image2 -q:v 0 -r 0.5 "%~dpn1\%%05d.jpg"
)
exit /b
rem ----------png----------
:png
rem キーフレームかコマ数出力か
set /p key_or_rate=キーフレームかコマ数出力か選択[k:キーフレーム、r:コマ数]:
if "%key_or_rate%" == "k" (
ffmpeg -hide_banner -an -skip_frame nokey %SS% -i %1 %T% %vf% -vsync 0 "%~dpn1\%%05d.png"
) else if "%key_or_rate%" == "r" (
ffmpeg -hide_banner -an %SS% -i %1 %T% %vf% -c:v png -r 0.5 "%~dpn1\%%05d.png"
)
exit /b
view raw tscut.bat hosted with ❤ by GitHub

連結バッチ

複数aviファイルがあるフォルダをD&Dで連結するバッチ。上のカットバッチと組み合わせて使うと良い。

@echo on
rem カレントディレクトリをD&Dしたフォルダに
cd /d %1

rem フォルダ内のaviを順番に呼んで
for %%a in ( *.avi ) do (
    rem txtにファイルの名前のみ出力
    @echo file %%~nxa >> input.txt
)

rem 結合
ffmpeg -f concat -safe 0 -i input.txt -global_quality 27 -c:v h264_qsv -preset:v veryslow -g 300 -bf 16 -refs 4 -b_strategy 1 -look_ahead 1 -look_ahead_downsampling off -pix_fmt nv12 -bsf:v h264_metadata=colour_primaries=1:transfer_characteristics=1:matrix_coefficients=1 -c:a aac -b:a 256k "%~1\enc.mp4"

pause
exit

ffplayにtsやmp4を関連付ける

ffplayは軽くて綺麗で対応する動画ファイルが多いのでオススメ。録画中のtsファイルをダブルクリックすれば追っかけ再生もできるよ。

設定

1.mp4等をプログラムから開く→その他のアプリ→ffplay.exeで関連付ける(ffplay "%1")
ってのが基本なんだが,録画したtsファイルはインターレース解除する必要があるので適切な引数(ffplay -i "%1" -vf yadif=0:-1:1)を指定しなきゃいけない。
2.コマンドプロンプトを管理者で起動し以下コマンドを実行(CLASSES_ROOTにtsファイル実行時の挙動を設定,CURRENT_USERの"プログラムから開く"リストに引数付きffplayを追加)
".ts"ってのはtsファイルだよってwindowsに教え込む(別に"tsfile"じゃなくてもいいよ)
assoc .ts=tsfile
実行結果がこんな感じならおk
.ts=tsfile
さっきのtsファイルはffplayにほげほげな引数で実行するんだよと教え込む
ftype tsfile="C:\ffplay.exeのパス" -i "%1" -vf "yadif=0:-1:1"
-vf "yadif=0:-1:1,hqdn3d=3.000"にして補正処理を有効にしても良い
実行結果がこんな感じならおk
tsfile="C:\DTV\ffmpeg\ffplay.exe" -i "%1" -vf "yadif=0:-1:1"
3.レジストリエディタでHKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.tsキー(CURRENT_USERのtsファイル実行時の挙動の設定)を削除(CLASSES_ROOTの設定を反映)
4.tsをプログラムから開く→ffplay(新規とか書いてあるかも?)を選択

使い方
キー 動作
q,ESC 終了
f 全画面
p,SPC 一時停止
m ミュート
9,0、/,* 音量調節
a 音声チャネル切り替え
v 映像チャンネル切り替え
t 字幕チャンネル切り替え
c プログラム切り替え
w 映像と音声波形切り替え
s コマ送り(暗黙的に一時停止)
left/right 10秒戻る/進む
down/up 1分戻る/進む
page down/page up チャプター戻る/進む,無い場合は10分戻る/進む
right mouse click 画面の幅を動画の時間とし右クリックした位置の割合までシーク
left mouse double-click 全画面
Seek to 81% ( 0:24:12) of total duration ( 0:30:00)       B f=0/0
1453.65 A-V: -0.002 fd=   1 aq=   36KB vq=  411KB sq=    0B f=0/0

1453秒目を表示(PTS)という意味(カット編集に使える)。ただ録画したts等はタイムスタンプがおかしい場合が多いので、以下のバッチを関連付けすると開始がズレたPTSを修正しながら再生してくれる(無理矢理感あるが)。

ffmpeg -loglevel quiet -i %1 -c copy -mpegts_copyts 1 -f mpegts - | ffplay - -vf yadif=0:-1:1

※私の環境ではffmpeg3.3.4、3.4においてSDL advised audio format 33056 is not supported!エラーにより音が出ないトラブルが発生した。3.3.3、3.4.1では発生しない。

番組情報から24fps化するか選択

危なっかしい、24fpsでないアニメがある、処理に時間がかかるので没になったやつ。
番組情報ファイルのジャンル :と書いてある行の次の行にアニメ又は映画という文字列があれば24fps化する。

ジャンル : ←まずこの行を見つけて
映画 - 邦画←んでここの行をみて判断する
映画 - アニメ
映画 - その他
rem 番組情報の中に"ジャンル"という文字列がある行に行番号を付けて取得
for /f "delims=" %%a in ('findstr /n "ジャンル" "%~1.program.txt"') do (
    set genre=%%a
)
rem 行番号の部分だけ取り出す
set genre=%genre:~0,-8%
rem 行番号を1大きくする(次の行に求める内容があるため)
set /a need_num=%genre%+1
rem その行に書いてある内容を抜く
for /f "delims=" %%a in ('findstr /n /r "." "%1" ^| findstr /r "^%need_num%:"') do (
    rem その行にて"アニメ"又は"映画"という文字列の有無でフィルタオプションを選択
    echo %%a ^| findstr "アニメ 映画"
    if %ERRORLEVEL% equ 0 (
        set vfilter=-yadif=0:-1:1,decimate
    ) else if %ERRORLEVEL% equ 1 (
        set vfilter=-yadif=0:-1:1
    )
)
rem エンコード
ffmpeg -i %1 -vf %vfilter%,hqdn3d …

夜間時間(東京電力)にエンコして電気代を節約?

先頭に追加すると動作します。ただのネタなので効果の程は知りません。詰まって時間内に処理しきれない場合は使わないで下さい。

rem dos標準の環境変数timeの先頭2文字が7以上且つ23未満の場合300秒待ってループ
:electric
if %time:~0,2% geq 7 (
    if %time:~0,2% lss 23 (
        timeout /t 300
        goto :electric
    )
)
rem ここから処理

字幕をmp4に付ける(ソフトサブ)

一度自動エンコに組み込んでみたが、そもそもgoogleドライブが対応していなかった。
以前EpgDataCap_Bon.exeで録画したtsではできるけどEdcbPlugin+TVTest.exeで録画したものでは字幕データが無いよって言われちゃう…
1.https://github.com/iGlitch/Caption2Ass からMPEG-TS字幕データ抽出ツールを入手
2.以下コマンドでsrt形式で字幕を抽出

"C:\hoge\Caption2AssC_x64.exe" -format srt %1

3.以下コマンドでエンコしながら結合

… -i %1 -f srt -i "C:\hogehoge.srt" … -map 0:p:hoge:0 -map 0:p:hoge:1 -map 1:0 -c:s mov_text "C:\hoge\%~n1.mp4"

ウォーターマーク(局ロゴ)を消す

黒背景のロゴ画像をもとにロゴを消す。CMカット等にも応用できるかもしれない。
チャンネルによって1920x1080のものと1440x1080のものがある。BS解像度変更の影響を受けるので注意。

1.以下の内容をバッチファイルとして保存する。
TSファイルD&Dでほぼほぼ真っ黒なシーンを探し、PNG出力し、2値化してffmpegで使えるロゴ解析用画像を作成するところを面倒なので自動化した。
邪道なコードだが殆ど使う機会がないので割愛。ffprobeの-f lavfiでも出来るかも?

@echo on
rem TSファイルをD&Dで動作する
rem txtやpngはTSファイルと同じディレクトリに作成された、TSファイルと同じ名前のフォルダに生成される
rem 出力フォルダ作成
md "%~dp1\%~n1"
rem 99%黒いキーフレームの時間をlogo.txtに出力
ffmpeg -hide_banner -nostats -skip_frame nokey -i %1 -an -vf field=0,blackframe=amount=99 -f null - 2> "%~dp1\%~n1\logo.txt"
rem logo.txt内をfidstrでソート
findstr "Parsed_blackframe_1" "%~dp1\%~n1\logo.txt" > "%~dp1\%~n1\logo2.txt"
rem はじめに行カウントを1にしておく
set cnt=1
rem 複数行ある場合は2行目からはここにループする
:loop
rem 環境変数ssをクリア
set ss=
rem テキストから1行ずつ読み出し、読み出した行から区切り文字を":"、" "とし、12番目のトークンを環境変数ssに格納
rem [Parsed_blackframe_1 @ 000002996eecee80] frame:596 pblack:99 pts:53795742 t:298.865233 type:I last_keyframe:596
for /f "tokens=12 delims=: " %%a in ('findstr /n /r "." "%~dp1\%~n1\logo2.txt" ^| findstr /r "^%cnt%:"') do (
set ss=%%a
)
rem 環境変数ssが設定されていれば実行、されていなければテキストファイルの終了とみなす
if defined ss (
rem 環境変数ssに格納された時間をもとにグレースケールpngを出力
ffmpeg -v fatal -y -ss %ss% -i %1 -vf bwdif=0:-1:1,format=gray -vframes 1 "%~dp1\%~n1\logo_%cnt%.png"
rem 行カウントを1上げてループ
set /a cnt=cnt+1
goto :loop
)
rem ffmpegで画像の2値化&輪郭グラデ、元画像削除
rem 5000~10000
for /r "%~dp1\%~n1" %%a in ( *.png ) do (
call :binari "%%a"
)
rem 環境変数ssが設定されていなければテキストファイルの終了とみなし終了
pause
exit
rem ffmpegで画像の2値化&輪郭グラデサブルーチン
:binari
rem magick convert -threshold 15000 -type GrayScale %1 "%~dpn1_b.png"
ffmpeg -i %1 -vf lutrgb=r=between(val\,11\,255)*val:g=between(val\,11\,255)*val:b=between(val\,11\,255)*val,colorchannelmixer=1.25:1.25:1.25:0:1.25:1.25:1.25:0:1.25:1.25:1.25:0,format=gray "%~dpn1_b.png"
rem 元画像削除
del %1
exit /b
view raw makelogo.bat hosted with ❤ by GitHub

2.TSファイルロゴ消し
logo.pngは各局のロゴを指定。カレントディレクトリに置く必要がある。
録画後自動エンコバッチに組み込む際は、それぞれのロゴ画像のファイル名をChset5.txtの局名に合わせて(コピペ)removelogo=%ServiceName%.pngみたいにすると良いと思う。
ビデオフィルタ-vfは前から順番に適用される為、インタレ解除yadif=0:-1:1の後にロゴ消しremovelogo=logo.pngを行い、高精度平滑化等のノイズ除去hqdn3dを行う必要がある。

cd /d "logo.pngのディレクトリ"
ffmpeg -i "TSファイルのフルパス" -vf yadif=0:-1:1,removelogo=logo.png,hqdn3d=4.0,scale=1280:720,unsharp=3:3:0.5:3:3:0.5:0
DTV記事一覧
1.予備知識、ハードウェア、ドライバ編
TS抜きとは何か、何が必要なのか、共通の項目。
2'.TVTest0.7.23+EDCB人柱版10.66編(非対応)
・(非推奨)取り敢えず視聴、録画が出来る環境を構築する。
2.TVTest0.9.0+xtne6f版EDCB編
・(推奨)安定して視聴、録画が出来る環境を構築する。
2018年4月~BSトラポン移動によるTVTest、EDCBのバイナリ変更点
3.xtne6f EDCB-Work-Plus-sでTwitter連携編
Twitter連携はRubyTwitter API Gemを使用する。
4.FFmpeg自動エンコード+Googleフォトに無限保存編
・バッチ又はPowerShellからFFmpegで綺麗にエンコードし、Backup and Syncでアップロードする。
・音ズレさせない正しい処理や、ファイルの自動削除機能等も備えているため、完全放置が可能。
  • nanashi

    ①PostRecEnd.ps1 が実行できない→実行ポリシーの変更
    ②EpgTimerで録画終了後batとして使用
    エンコード処理中、ダイアログが表示されない。
    エンコード終了後(?)、ファイル名無しのバッチが実行され停止してしまう→タスクマネージャでpowershellを終了する→次のエンコードバッチが起動する

  • nanashi

    注:「エンコード終了」はログテキストに“Windows PowerShell トランスクリプト終了”があったため

  • nanashi

    PostRecEnd.ps1
    (解決)止まってしまう件は、エンコードオプションを弄ったため、ffmpeg.exeのバージョンが開発版だったためのどちらか。
     なぜかバッチファイルを指定しない番組も自動エンコードされるようになった。
    しかし、ダイアログが表示されない。

    PowerShellのポリシーの変更もSet-ExecutionPolicyコマンドを叩く事が正しいのか。
    スクリプト導入環境編を期待します。

  • もっと読む
コメントを書く