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

FFmpeg 自動エンコード

滅茶苦茶ザックリ言うと、EDCBで録画した後、ffmpegで自動エンコードして、FHD10GBまで無料無制限のGoogleフォト(ドライブ)に無限に保存しちゃおうぜっていう話。
TSファイル、バッチやPowerShell、ffmpeg、Googleフォトの厄介な仕様に対応させ、放置しても完全自動で動いてくれるようなスクリプトを書いた。
あと画質の悪いデジタル放送を出来る限り綺麗に、現実的な速さで、圧縮率の高いエンコードを行う為にかなり試行錯誤した。エンコード品質にお悩みの方はffmpegの引数だけでも見ていってくれると嬉しい。

目次 見出しにジャンプ

更新履歴 見出しにジャンプ

録画後スクリプトの更新情報(詳細はgist参照)
2017-04-01
  • 先行探索固定品質を使う
  • 2017-05-01
  • 改良
  • 文章の整理
  • 3つのバッチを2つに集約
  • 自動エンコバッチで特定の条件下で音ズレする問題について記述
  • サービスIDを指定してエンコすることで特定条件下での音ズレを回避
  • デュアルモノを処理する方法を詳しく
  • 番組情報を参照し条件分岐することで2つのバッチを1つに集約
  • 斧にうpした自動エンコバッチを更新
  • 2GB以下ではなく未満の場合Google Photos Backup用フォルダに移動させるように変更
  • フォルダやffmpegオプションの登録を環境変数で集約
  • 自動エンコバッチでデジタル放送に合わせてYV12を使うように、24fps化するように変更、解説を分かりやすく?修正
  • ffmpegのDLリンクが不正確だったので修正
  • エンコ時に字幕を埋め込む方法を記述
  • 2017-06-01
  • ffmpegのppフィルタでかなり綺麗に
  • ffmpegの使い方について少し補足
  • 自動エンコでbwdifよりyadifの方が若干綺麗だったので戻した
  • 自動エンコでrefs等でかなり綺麗になりほぼ完成かな?
  • QSVの動作条件を追記
  • EDCBの機能を使ってtsやmp4を削除する方法を追記
  • ループカウントによる無限ループ対策
  • うp容量オーバー時とエラー時にツイートで報告するオマケ機能を追加
  • 無駄な条件分岐を削除し合理化
  • 24fps化を廃止
  • 解像度を1440x810→1280x720に変更
  • 2017-07-01
  • 古いファイルの自動削除機能を付けてタスクスケジューラを不要にした
  • 古いファイルの自動削除機能が動いてなかったので修正
  • global_qualityを29に変更
  • Shift_JIS以外の文字コードの場合動作出来ないことを追記
  • Googleフォトが長過ぎるファイル名に対応していないっぽいのでRecName_Macro.dllで適切に処理
  • Backup and Sync登場でスマホ版と同じく2GB制限が廃止されたので10GBまでうpできるように対応
  • 2017-08-01
  • リサイズにフィルタを使うように変更し品質を向上
  • ファイルサイズの判別時の余計な処理を削減
  • コマンドプロンプトのタイトルバーにエンコ中のファイル名を表示する機能の追加
  • roop->loop///
  • 2017-09-01
  • yadifからbwdifに変更
  • GoogleフォトがHEVCに対応したので記述(実装はしない)
  • 2017-10-01
  • ループ回数を10から50へ
  • ffmpeg3.4への対応
  • Googleフォトの仕様を鑑みて720p->810pに変更
  • 2017-11-01
  • _EDCBX_DIRECT_を使用
  • PostRecEnd.batと録画後実行batの違いを明記
  • 見やすく
  • 2018-01-01
  • tsファイルサイズが20GBより大きい場合エンコ品質を下げて10GBに収める処理を追加
  • ファイルの削除oldセクションのバグ修正orz
  • 一定数残して古い方から削除ではなく録画フォルダのサイズを一定に保ちながら古い方から削除する方法に変更
  • ファイルの削除devバグ修正
  • Googleフォトにおいて一部のファイルのmoov atomが壊れてしまう問題について記述
  • -fflags +discardcorrupt、-bsf:a aac_adtstoascを追加 解説を書き換え改善
  • 2018-02-01
  • デュアルモノ番組直後の番組の音声は再エンコが必要な(ことを忘れていた)ので-c:a copyを諦め再エンコする仕様に戻した(暫定)
  • ログ出力機能追加
  • エラー処理を整理
  • 削除機能周りの無駄を大幅に書き直した
  • フォルダサイズを一定に保つファイルの削除サブルーチンに入ると1つの古いファイルでなく全てのファイルに対して削除が実行されてしまう不具合を修正
  • 事前準備、オプション設定の解説が雑だったので修正
  • delがechoになったままで無限ループしてしまう不具合を修正
  • 削除機能をGB単位で指定出来るようにした
  • ツイート機能にも環境変数を割り当てた
  • コードの整理
  • 視聴予約時は実行しない
  • 2018-03-01
  • 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-04-01
  • 誤字修正
  • ffmpegのオプションを改善、説明を追加、誤字修正
  • -refs 9に修正(level=5.0->level=4.1)
  • 2018-05-01
  • 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-06-01
  • 事前準備の項目に指定サービスのみのtsである必要があることを追記
  • PID判別の漏れを修正
  • PID判別はドロップログ式では不可能なことが判明したため、ffmpeg式を完成させた
  • PID判別でVideoが複数あった場合余計な文字列が入ってしまう、また適切な判別が行われない不具合を修正
  • 警告ツイートやバルーンチップにエラー詳細が表示されるようにした
  • PID判別の処理方法を変えることで、480iな裏番組にも対応させ、その場合にもインターミッション等も処理できるようにした(ffmpeg4.0では未だにデュアルモノをスプリットできないようだ)
  • ffmpegプロセスの優先度を高で実行することでQSVのエラーを減らすようにした
  • 2018-07-01
  • ps1の解説を更新した
  • タスクトレイアイコンのヒントが表示されない時がある不具合を修正
  • DiscordのWebhookで警告する機能追加
  • 720リサイズ+シャープフィルタでは1080より処理速度、品質共に劣るためリサイズを辞めた
  • EpgDataCap_Bonの同時起動数をログや警告に加えた
  • 2018-08-01
  • PID判別の修正
  • x265はじめました(やめました)
  • プロセス優先度、プロセッサ親和性の項目をユーザ設定に加えた
  • 例外処理の整理
  • ts、mp4の自動削除が無効の場合にエンコード失敗時に退避させない
  • ts、mp4の自動削除の処理方法を改め高速化した
  • エイリアスと干渉した関数名を修正した
  • 2018-09-01
  • 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最小構成 見出しにジャンプ

    PostRecEnd.batフル構成 見出しにジャンプ

    PostRecEnd.ps1 見出しにジャンプ

    事前準備 見出しにジャンプ

    ・特に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のフルパスを入力するのを省く為。
    しない場合はバッチ内のffmpegをffmpegのフルパスに置き換える必要がある。

    使用方法 見出しにジャンプ

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

    共通 見出しにジャンプ

    上記スクリプトを保存 見出しにジャンプ

    選んだスクリプトをコピーし、テキストエディタ等にペーストし、拡張子.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_で%環境変数%

    ffmpegの引数 見出しにジャンプ

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

    /blog/ffmpeg

    環境別の設定 見出しにジャンプ

    ・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-ICQ、ICQと同じ品質を求める場合には値を小さくしてビットレートやファイルサイズを大きくする必要がある(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ファイルを削除します。
    こちらを使用する場合は「フォルダサイズを一定に保つファイルの削除」ルーチンに上書きして下さい。
    forfilesはVista以降

    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 見出しにジャンプ

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

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

    大まかな流れ(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-Transcriptにffmpegの出力が書かれてない…

    ~~
    **********************
    トランスクリプトが開始されました。出力ファイル: 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_fileはrubyに渡された後使用される。

    ログ 見出しにジャンプ

    #====================ログ====================
    #ログ取り開始
    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で言うと%~n、PowerShellはオブジェクトとして渡してるからちょっと違うけど)を取得、
    それを変数$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-ProcessでRubyのパス"${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エンコバッチを終了コードによるループに変更した

    /blog/ffmpeg

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

    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~回目:警告
    メインルーチン終了
    

    カットバッチ 見出しにジャンプ

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

    ImageMagickとffmpegが環境変数に設定されていることが前提。
    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.コマ数出力
    メインルーチン終了
    

    連結バッチ 見出しにジャンプ

    複数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解像度変更の影響を受けるので注意。

    ts抜き ロゴ消し

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

    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