Entry

TSファイルの自動エンコードスクリプト

最近、TVをTSで録画した後に自動的にMP4形式にエンコードするようバッチ処理させたいと思い立ったので、色々調べてみた。
ちなみに自分の方針としては、CM等は削らない、画質はあまり問わない、設定にあまり手間をかけない、です。とにかく、サイズが小さく出来れば良いし、CM無しや良い画質が欲しけりゃBD買うよ、というスタンスなので。むしろ録画当時のCMとか残しておきたいよねー、って思ってる。

録画完了後にバッチを走らせるパターンが多いようだけど、録画直後にエンコーダを走らせると、その負荷が次の録画等の処理に影響してしまったり、複数のエンコーダ同時に走って、いつまで経ってもエンコードが終わらず面倒な事になるかもしれない…と思い、タスクスケジューラで指定時刻に順次エンコードさせることにした。
…が、そのような処理をさせる人をネットであまり見かけない。負荷による影響はあまり気にしなくても良いのかなぁ…。しかしまぁ、せっかくなので当初の予定通り、タスクスケジューラで実行させる事にした。

スクリプトは以下のような感じのを自前で作った。余所ではバッチファイルが多いけれど、私はあんまりバッチファイルを書けないもので…。細かいことやろうとすると色々調べる事になるので、結局WSHとか使った方が楽かな、と。何れにせよWindowsなら間違いなく動くので、別に良いかと。


var Fso      = new ActiveXObject("Scripting.FileSystemObject");
var WshShell = new ActiveXObject("WScript.Shell");

/// User ///////////////////////////////////////////////////////////////////////

var InputDirPath = "D:\Record";
var OutputDirPath  = InputDirPath;
var WorkDirPath    = Fso.BuildPath(OutputDirPath, "work");
var ProgramDirPath = WScript.ScriptFullName.replace(WScript.ScriptName, "");
var Threshold      = 1000000; // re-encode threshold size.

// Split
function splitTs(inputFilePath, outputDirPath) {
	var tsSplitterPath = Fso.BuildPath(ProgramDirPath, "TsSplitter.exe");
	var tsSplitterCommand = " -ECM -SD -1SEG -SEP2 ";
	
	WshShell.Run(tsSplitterPath + tsSplitterCommand + " -OUT \"" + outputDirPath + "\" \"" + inputFilePath + "\"", 0, true);
}

// Encode
function encodeTs(inputFilePath, outputFilePath) {
	var encoderPath = Fso.BuildPath(ProgramDirPath, "HandBrakeCLI.exe");
	var encoderCommand = " -f mp4 -e x264 --x264-preset=medium --x264-tune=animation --h264-profile=high -q 22 --cfr -a 1 -E fdk_aac -6 stereo -B 160 -R auto --gain 10 -w 1280 -l 720 --crop 0:0:0:0 --decomb ";
	
	WshShell.Run(encoderPath + " -i \"" + inputFilePath + "\" -o \"" + outputFilePath + "\" " + encoderCommand, 0, true);
}

///////////////////////////////////////////////////////////////////////////////

var InputExt = ".ts";
var OutputExt = ".mp4";

// Exec cscript.
if (Fso.GetFileName(WScript.FullName).toLowerCase() != "cscript.exe") {
	WshShell.Run("cscript " + WScript.ScriptFullName);
	WScript.Quit();
}

try {
	var workExists = Fso.FolderExists(WorkDirPath);
	if (!workExists) {
		Fso.CreateFolder(WorkDirPath);
	}
	
	var folder = Fso.GetFolder(InputDirPath);
	var enumerator = new Enumerator(folder.Files);
	for( enumerator.moveFirst(); !enumerator.atEnd(); enumerator.moveNext() ) {
		// Input File
		var inputPath = enumerator.item().Path;
		var inputName = enumerator.item().Name
		if (inputName.match(".*" + InputExt + "$") == null) continue; // not .ts file
		
		// Output File
		var outputPath = Fso.BuildPath(OutputDirPath, inputName.substr(0, inputName.length - InputExt.length) + OutputExt);
		if (Fso.FileExists(outputPath) && Fso.GetFile(outputPath).Size > Threshold) continue; // completed
		
		// Split
		splitTs(inputPath, WorkDirPath);
		
		// Get max size file
		var files = new Enumerator(Fso.GetFolder(WorkDirPath).Files);
		var maxSize = 0;
		var maxSizeFilePath = "";
		for(files.moveFirst(); !files.atEnd(); files.moveNext()) {
			if (inputName.match(".*" + InputExt + "$") == null) continue;
			if ( files.item().Size > maxSize) {
				maxSize = files.item().Size;
				maxSizeFilePath = files.item().Path;
			}
		}
		
		// Encode
		try {
			if (maxSize > 0) {
				WScript.Echo(maxSizeFilePath +  " begin.");
				encodeTs(maxSizeFilePath, outputPath)
				WScript.Echo(maxSizeFilePath +  " end.");
			}
		} catch (e) {
			WScript.Echo(maxSizeFilePath +  " error.");
		} finally {
			// Delete files
			for(files.moveFirst(); !files.atEnd(); files.moveNext()) {
				if (inputName.match(".*" + InputExt + "$") == null) continue;
				Fso.DeleteFile(files.item().Path);
			}
		}
	}
	
	if (!workExists) {
		Fso.DeleteFolder(WorkDirPath);
	}

} catch(e) {
	WScript.Echo(e.message);
} finally {
	WshShell = null;
	Fso = null;
}

このスクリプトは、スクリプトが置かれたフォルダ上にTSSplitter.exeとHandBrakeCLI.exeが必要。このパスを変更する場合は、「ProgramDirPath」の値を弄る。
スクリプトの処理の概要は以下の通り。

  1. WorkDirPathフォルダが無ければ作成。
  2. InputDirPathフォルダ内にあるTSファイルを走査。
  3. 出力ファイル(TSファイル名.mp4)が既に存在し、Thresholdよりサイズが大きい場合は次のTSファイルへ。
  4. TSファイルを TSSplitterで分割し、WorkDirPathフォルダに出力。
  5. WorkDirPathフォルダに分割されたTSファイルのうち、一番サイズの大きなTSファイルを取得。
  6. 一番サイズの大きなTSファイルをエンコードし、OutputDirPathフォルダに出力(TSファイル名.mp4)。
  7. WorkDirPathフォルダ内の分割TSファイルを削除。
  8. 3に戻り、次のTSファイルを処理。これをInputDirPathフォルダ内のTSファイル分繰り返す。
  9. WorkDirPathフォルダを作成した場合は、フォルダを削除。

基本的には、他のサンプルと同様にTSファイル分割→エンコードの流れ。本当はエンコードだけでも良かったのだけど、TsSplitter噛まさないと、MXの録画がエラーで止まるもので…。常時マルチ編成になった後はどうなったのかは知らないけれど。
ここでは分割時に一番大きなサイズのファイルを採用してエンコードしている。必ずしも目的のファイルが一番大きなサイズのファイルになる保証は無いけれど、まぁ9割9分ぐらい問題ないと思う。
Threshold判定は、何らかの事情でエンコードに失敗し、サイズの小さいゴミファイルが生成されてしまった場合に再エンコードさせるためのも。1000000という数字は適当。おまじないみたいな物だけど、必ずエラーが起こる場合は毎日エンコードを試みることになるので、無駄な処理が発生するかもしれない。
ちなみに、本スクリプトは必ずcscriptで起動させるようにしているけれど、これは単に途中経過を示すコンソール画面が欲しかったため。

splitTsメソッドとencodeTsメソッドの中のパラメータは、必要に応じて適当に弄る。気が向いたら別のエンコーダに差し替えるかもしれない。
補足としては、スクリプトではTsSplitterをExecメソッドで実行し、HandBrakeCLIをRunメソッドで実行している。TsSplitterは、実行させてもすぐに処理が戻ってきてしまうため、処理が完了するまでStatusプロパティを監視する必要がある。一方のHandBrakeCLIはStatusプロパティは利用できないが、Runメソッドで実行させると完了するまで処理が戻ってこない。そんなわけで、ExecとRunを使い分けてる。
すいません、大嘘ついてました。Runメソッドで結果がすぐ帰って来てたのは、第3引数にtrue指定し忘れてただけだった。Runでいいです。むしろExecはTsSplitterが正しく終了ステータスを返さない場合がある事を確認しているので、やめた方がいいです。(これはTsSplitterの問題では無く、Execメソッドが昔から抱える問題の模様)

あとは、これをタスクスケジューラに登録する。
自分はとりあえず、毎日5時に実行されるようにしている。この時間なら、まぁ録画は行われていないだろう、という判断。

そんな感じで、タスクスケジューラでエンコードのバッチ処理を行う方法でした。
スクリプトはそんなに真面目に検証してないので、もしかしたら間違いがあるかもしれないです。


Comments (0 件)

コメントを残す

メールアドレスが公開されることはありません。