この記事は書きかけなので、後で大幅に追記する可能性が高いので注意
あと、この記事は解説が古いです
はじめに
この記事では、ijsがEDCBで使っているPowerShellスクリプト(PostRecEnd.ps1)についての概要を解説する。
このスクリプトでは、録画後のTSを自動エンコしてGoogleフォトに高画質モードで無限保存する。
スクリプト全体を見たい方は、以下の記事を参照してください。
ijs01140.hatenablog.com
EDCBから環境変数受け取るのに必要なやつ
必要な関数
関数は先に書かないと動作しない。今回はTweetする部分だけ関数化している。
この関数は、Tweetする文をtxtに保存してTweet用Rubyスクリプトに渡している。
PostRecEnd.ps1側の記述
function Tweet ($tw) {
Write-Output "${tw}" | Out-File -LiteralPath 'C:\tools\script\tweet_utf8.txt' -Encoding UTF8
ruby 'C:\tools\script\tweet_utf8.rb' 'C:\tools\script\tweet_utf8.txt'
Write-Output "ツイート完了`:${tw}"
}
#!/usr/bin/env ruby
require "twitter"
require "csv"
client = Twitter::REST::Client.new do |config|
config.consumer_key = '(ここにconsumer_keyを入力)'
config.consumer_secret = '(ここにconsumer_secretを入力)'
config.access_token = '(ここにaccess_tokenを入力)'
config.access_token_secret = '(ここにaccess_token_secretを入力)'
end
talk = CSV.read( ARGV[0] )
client.update( talk[rand(talk.length - 1)][0] )
ログ出力設定
これで、エラー時に原因を特定しやすくなる
Start-Transcript -LiteralPath "${env:FilePath}.log"
夜間電力を利用する(非推奨)
ただ単に夜間電力の時間帯になるまで待つというもの。処理が詰まる恐れがあるので非推奨。 ijsは現に使っていない。
$nowhour=Get-Date -Format 'H'
if ($($nowhour -ge 7) -and $($nowhour -lt 22)) {
$waittime = 22 - $(Get-Date -Format 'HH') * 3600 - $(Get-Date -Format 'mm') * 60 - $(Get-Date -Format 'ss')
start-sleep $waittime
}
必要な変数や環境変数をここで設定する。ffmpegのパスやGoogleフォトへのアップロード用のフォルダのパス等はここで設定。
$myHost = (Get-Host).UI.RawUI
$myHost.WindowTitle = "${env:FileName}.tsをエンコード中..."
$FFFolderPATH='C:\tools\ffmpeg'
$MP4FolderPATH='D:\temp\mp4'
$BASFolderPATH='D:\temp\gsync\tv'
$treatedTSPATH='D:\treatedTS'
$OtherFolderPATH='D:\temp\sizeover'
Set-Item env:SSL_CERT_FILE -Value 'C:\tools\script\cacert.pem'
エンコード時のビデオフィルタを設定
変数$VFILTER
にビデオフィルタを順番に足していく
インタレース解除フィルタを追加
ffmpegでのインタレース解除フィルタでよく使われるもの(ijsの偏見)として、bwdif
とyadif
が挙げられる。
yadif
のほうが処理速度的には優れているが、品質はbwdif
のほうが上である。パソコンの処理速度や求める品質を考慮して、どちらかお好きな方を。
FFmpeg Filters Documentation 10.12 bwdif
FFmpeg Filters Documentation 10.193 yadif
ijsの場合はbwdif
を使っている。
$VFILTER='bwdif=0:-1:1'
Write-Output 'インタレース解除を行います'
24fps化フィルタを追加
アニメや映画は、24fpsで制作されている場合が多い。しかし、テレビ放送は30fps(インタレース形式なので実質60fps)なので、フレームのダブリが生じてカクカク感が出てしまう。(鈍感なijsにはわからないけど)
そこで、アニメをエンコしている人たちなら誰しもが聞いたことのあるだろう、24fps化を行う。
・・・と言いたいところだが、アニメや映画でも24fps制作でない場合もあり、そこを自動判定するのはキツイものがある。
現にijsは即座に諦め、24fps制作であるとの情報を得られた某自称クソアニメだけを24fps化している。
ポプテピピックを検出する方法は簡単で、EDCBから出力される(TSファイル名).program.txtを読み込み、「ポプテピピック」という文字列が含まれるか判定するだけである。
$decimatechk = $(Get-Content -LiteralPath "${env:FilePath}.program.txt" | Select-String -SimpleMatch 'ポプテピピック' -quiet)
if ($decimatechk -eq $True) {
$VFILTER += ',decimate'
Write-Output 'ポプテピピックが検出されたので、24fps化します'
}
自動ロゴ消し
放送局をServiceNameから判定し、それぞれの放送局用に用意した局ロゴ画像を用いてロゴ消しする。
一応、ロゴ消した部分にフィルタ適用することも考えて、フィルタデータの入ったtxtを検知してビデオフィルタに自動で追加する機能もつけている。(が、まだ使っていない)
この部分は詳しく解説したいが、記事が見づらくなるのであえて概要に留める。詳しい解説については後日、単独で記事にまとめる予定。
$LogoNA=0
switch ($env:ServiceName) {
"NHKBS1" {$BROADCASTER='nhkbs1'}
"NHKBSプレミアム" {$BROADCASTER='nhkbsp'}
"BS日テレ" {$BROADCASTER='bsntv'}
"BS-TBS" {$BROADCASTER='bstbs'}
"BSジャパン" {$BROADCASTER='bsjapan'}
"BSフジ・181" {$BROADCASTER='bsfuji'}
"BS11イレブン" {$BROADCASTER='bs11'}
"NHK総合1・福岡" {$BROADCASTER='nhk'}
"NHKEテレ1福岡" {$BROADCASTER='etv'}
"FBS福岡放送1" {$BROADCASTER='fbs'}
"KBCテレビ" {$BROADCASTER='kbc'}
"TVQ九州放送1" {$BROADCASTER='tvq'}
"テレビ西日本1" {$BROADCASTER='tnc'}
default {$LogoNA = 1}
}
if ($LogoNA -eq 0) {
$VFILTER += ",removelogo=${BROADCASTER}.png"
Write-Output "放送局ロゴ消しに${BROADCASTER}.pngが使用されます"
if (Test-Path "C:\TV\logo\${BROADCASTER}.txt") {
$logofilter = $(Get-Content -LiteralPath "C:\TV\logo\${BROADCASTER}.txt")
$VFILTER += $logofilter
Write-Output "ロゴ修正フィルタ適用:${logofilter}"
}
} else {
Write-Output "ロゴ消しは、いたしません"
}
デブロック・デノイズフィルタを追加
せっかく自分でエンコするのだから、品質改善ができるならしたいでしょ?ijsはしたい。ということでこのフィルタ適用で!
あと、ロゴ消し後の不自然な部分を目立たなくなるよう矯正するという目的もある。
$VFILTER += ',pp=ac'
Write-Output 'デブロック・デノイズフィルタが適用されます'
リサイズフィルタを追加
ffproveに録画ファイルをぶちこんで、吐き出されてきたログに1920x1080が含まれるか判定し、含まれてなかったら720pにリサイズする。
正直、ここの部分はもっとスマートに書けるはずなのだが、面倒なのでさっさと諦めてわかりやすい方法を使う。
.NET Freamworkのオブジェクト取得する方式はスマートに書けるけどスマートに書けなくてめんどくさいからこれまたボツね
Start-Process -FilePath "${FFFolderPATH}\ffprobe.exe" -ArgumentList "`"${env:FilePath}`"" -RedirectStandardError 'D:\temp\temp.txt' -Wait
$fhdchk = $(Get-Content -LiteralPath 'D:\temp\temp.txt' | Select-String -SimpleMatch '1920x1080' -quiet)
if ($fhdchk -ne $True) {
$VFILTER += ',scale=1280:720:flags=lanczos+accurate_rnd'
Write-Output '解像度が1440x1080なので、720pにリサイズされます'
} else {
Write-Output '解像度が1920x1080なので、そのまま1080pでエンコします'
}
3次元デノイズフィルタを追加
これも上記のデブロック・デノイズフィルタと目的は同じで、品質改善が目的である。
このフィルタは処理速度向上を目的に、リサイズ後に適用する。
$VFILTER += ',hqdn3d=3.000'
Write-Output '3次元デノイズフィルタが適用されます'
ビデオフィルタは指定し終えたので、ビデオオプションを指定する。エンコード時のエンコーダやプリセットやCRF値はあえて変数化し、将来的に条件ごとに変更できるようにしている。
※ijsの使っているPCはQSVが使えないので、ソフトエンコを採用している※
$ENCPRESET = 'medium'
$ENCFORMAT = 'libx265'
$ENCCRF = 24
$VideoOption = "-vf $VFILTER -crf $ENCCRF -c:v $ENCFORMAT -preset:v $ENCPRESET -g 300 -bf 16 -refs 16 -b_strategy 1 -pix_fmt yuv420p"
オーディオオプションを設定
ビデオオプションを設定ときたら、次はオーディオオプションを設定するのだなと大半の人は察したはずだ。そう、もちろん設定する。
ffmpegを知っている方なら、音声形式ならエンコード前も後も変わらないから-c:a copy
でいいだろ!と思われるかもしれないが実際はそうはいかない。それをやってしまうと高確率で音ズレが訪れで阿鼻叫喚まっしぐらであるし、デュアルモノ音声の番組だと、片方のスピーカーからは日本語もう片方からは英語と、語学教材状態になってしまう。(また、厳密に言えば、同じAACでもMPEG-2とH.265では微妙に異なっているらしい)
それを防ぐためには、再エンコは免れない。どうせ音声の再エンコなんてすぐ終わるのだからそのくらい我慢しよう。
基本部分
エンコ後の音声形式をAACに指定する
$AudioOption = '-c:a aac'
デュアルモノ判定
テレビ放送で厄介なのが、デュアルモノ音声である。デュアルモノ音声とは、読んで時のごとく、言語ごとに片方ずつモノラル音声が割り当てられている2言語放送のことを言う。
この対策をしないと、先述した通り、片方のスピーカーからは日本語、もう片方からは英語が流れてくる、まさに語学教材状態になってしまう。
それを避けるために、デュアルモノ音声を判別する。
方法は、EDCBから出力される(TSファイル名).program.txtを読み込み、デュアルモノという文字列が含まれるか判定するというもの。含まれれば、それぞれの音声に128kbpsを割り当て、分離する。含まれていなければ、そのまま256kbpsにする。
$dualmonochk = $(Get-Content -LiteralPath "${env:FilePath}.program.txt" | Select-String -SimpleMatch 'デュアルモノ' -quiet)
if ($dualmonochk -eq $True) {
Write-Output 'デュアルモノ音声が検出されました'
$AudioOption += ' -b:a 128k -filter_complex channelsplit'
} else {
$AudioOption += ' -b:a 256k'
}
いよいよ、お待ちかねのエンコードである。長かった。
注意点
- ロゴ消し処理はカレントディレクトリにある画像しか使えないので、ロゴ消し用画像のあるフォルダをカレントディレクトリに設定する
- 開始してすぐにエンコに失敗するとファイルが出力されなかったりファイルが0Bだったりするので、10回まではループして復旧を試みる
- ffmpegはlogを全て標準エラー出力に出力するので、標準エラー出力をテキストに保存する
- Start-ProcessのRedirectStandardErrはファイルパスでなぜかワイルドカード判定するので、一旦無難なファイル名で出力して後ほどリネーム・移動してやる
Set-Location 'C:\TV\logo'
$CNT = 0
do {
$CNT = $CNT+1
if ($CNT -gt 10) {
Move-Item -LiteralPath "${env:FilePath}" "${OtherFolderPATH}" -force
Move-Item -LiteralPath "${MP4FolderPATH}\${env:FileName}.mp4" "${OtherFolderPATH}" -force
Tweet "${env:FileName}.tsのエンコードを10回トライしてもダメだったよぉ。10GB以上用フォルダに退避させておいたから早く助けてあげて!! #ijsrec_err"
Write-Output "${env:FileName}.tsのエンコードは異常終了しました。設定を見直しましょう。"
exit 1
}
Write-Output "エンコード${CNT}回目"
Start-Sleep -s 10
$FFOPTION="-analyzeduration 30M -probesize 100M -loglevel warning -y -fflags +discardcorrupt -i `"${env:FilePath}`" ${VideoOption} ${AudioOption} -map 0:p:${env:SID10}:0 -map 0:p:${env:SID10}:1 `"${MP4FolderPATH}\${env:FileName}.mp4`""
Write-Output "FFOPTION`:${FFOPTION}"
Start-Process "${FFFolderPATH}\ffmpeg.exe" -ArgumentList "${FFOPTION}" -RedirectStandardError 'D:\temp\postrecend_tmp.txt' -Wait
$MP4SIZE = $(Get-ChildItem -LiteralPath "${MP4FolderPATH}\${env:FileName}.mp4").Length
} while ($MP4SIZE -eq 0)
せっかくなら、処理速度を知りたいという気持ちからつけてみた機能。
ffmpegのlogから「encoded」の含まれる行を抜き出し、空白で区切り、目的の数値を取り出す。
$EncodeTimeLine = $(Get-Content -LiteralPath 'D:\temp\postrecend_tmp.txt' | Select-String -Pattern 'encoded' -SimpleMatch)
$EncodeTime = $(-split $EncodeTimeLine)[4]
$EncodeTimeTrim = $($EncodeTime.substring(0,${EncodeTime}.length-1))
$EncodeFps = $(-split $EncodeTimeLine)[5]
$EncodeFpsTrim = $($EncodeFps.substring(1,${EncodeFps}.length-1))
Tweet "${env:FileName}.mp4エンコード完了。エンコードに要した時間は${EncodeTimeTrim}秒(${EncodeFpsTrim}fps)です。"
ファイルのアップロード
いよいよ、最終段階である。Googleフォトに高画質モードでうpする。
※ここでは、Googleフォトへのアップロード用のフォルダを用意し、Backup and Syncの設定まで済ませた前提で進める
Googleフォトは10GB制限があるので、
10GB以内の場合、エンコ後mp4をアップロード用のフォルダに移動し、関連ファイルをまとめて処理済みフォルダに移動、
10GB超過の場合、当該録画関連ファイルをまとめて処理失敗フォルダに移動する。
if ($MP4SIZE -le 10GB) {
Write-Output 'エンコ後10GB以内に収まったよ!やったね!'
Move-Item -LiteralPath "${MP4FolderPATH}\${env:FileName}.mp4" "${BASFolderPATH}\mp4" -force
Move-Item -LiteralPath "${env:FilePath}" "${treatedTSPATH}" -force
Move-Item -LiteralPath "${env:FilePath}.program.txt" "${treatedTSPATH}" -force
Move-Item -LiteralPath "${env:FilePath}.err" "${treatedTSPATH}" -force
Move-Item -LiteralPath 'D:\temp\postrecend_tmp.txt' "${treatedTSPATH}\${env:FileName}.ffmpeg.txt" -force
Tweet "${env:FileName}.mp4アップロード準備完了。エンコーダは${ENCFORMAT}、ファイルサイズは約$([math]::round(${MP4SIZE}/1MB, 2))MBです。"
} else {
Write-Output '残念ながら、サイズオーバーです。エンコ設定を見直しましょう。'
Move-Item -LiteralPath "${env:FilePath}" "${OtherFolderPATH}" -force
Move-Item -LiteralPath "${MP4FolderPATH}\${env:FileName}.mp4" "${OtherFolderPATH}" -force
Move-Item -LiteralPath "${env:FilePath}.program.txt" "${OtherFolderPATH}" -force
Move-Item -LiteralPath "${env:FilePath}.err" "${OtherFolderPATH}" -force
Move-Item -LiteralPath 'D:\temp\postrecend_tmp.txt' "${treatedTSPATH}\${env:FileName}.ffmpeg.txt" -force
Tweet "${env:FileName}.mp4は残念ながら、サイズオーバーです。エンコ設定を見直しましょう。 #ijsrec_err"
}
Tweet "${env:FileName}.mp4の処理が終了しました"
Write-Output '処理終了おつかれさん'
exit
※必ず自己責任で使うこと※
ijs01140.hatenablog.com