タグ: WSH

  • 最近、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時に実行されるようにしている。この時間なら、まぁ録画は行われていないだろう、という判断。

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

  • CORESERVER.JP:コアサーバー

    レンタルサーバのCORESERVERのサーバにSSHでログインするためには、一度Webページの管理画面よりホスト登録をした後、接続を行う必要がある。登録しないと接続は全て弾かれる。
    これは、CORESERVERのセキュリティ管理上の仕様のようだけど、ハッキリ言って面倒くさい。SFTP(Secure File Transfer Protocol)接続するとしても、Subversionのレポジトリとして利用するとしても、SSHを利用する場合は必ず登録しなくてはならない。

    で、これを多少簡略化しようと思って作ったのが以下のWSHスクリプト。WSHスクリプトなので当然Windows専用。
    拡張子をjsにしたテキストファイルにスクリプトをコピー&ペーストした後、UserName, PassWord, HostNameを変更すれば使える。

    これは、Coreserver(XREAも?)のssh登録が面倒なので – WebProgを極めて居酒屋を開発するを参考にした。
    やってる事は、IEを開いてページを直接操作しているだけ。スクリプト中の「ダイアログ定数」の中身はあまり気にしないように。自前でテンプレート化しているだけなので。

    /*<ユーザー設定>***************************************************************/
    var UserName = "name";
    var PassWord = "pass";
    var HostName = "s16.coreserver.jp";
    /******************************************************************************/
    
    /*<ダイアログ定数>*************************************************************/
    // 表示ボタン
    var MessageBoxButtons = {
    "OK"               : 0,   // [OK]
    "OKCancel"         : 1,   // [OK][キャンセル]
    "AbortRetryIgnore" : 2,   // [中止][再試行][無視]
    "YesNoCancel"      : 3,   // [はい][いいえ][キャンセル]
    "YesNo"            : 4,   // [はい][いいえ]
    "RetryCancel"      : 5    // [再試行][キャンセル]
    }
    // 表示アイコン
    var MessageBoxIcon = {
    "None"             : 0,   // なし
    "Critical"         : 16,  // 警告メッセージアイコン [STOP]
    "Question"         : 32,  // 問い合わせメッセージアイコン [?]
    "Exclamation"      : 48,  // 注意メッセージアイコン [!]
    "Information"      : 64   // 情報メッセージアイコン [i]
    }
    // フォーカスボタン
    var MessageBoxDefaultButton = {
    "Button1"          : 0,   // 1番目のボタンにフォーカス
    "Button2"          : 256, // 2番目のボタンにフォーカス
    "Button3"          : 512  // 3番目のボタンにフォーカス
    }
    // モーダル
    var MessageBoxModal = {
    "Application"      : 0,   // アプリケーションモーダル
    "System"           : 4096 // システム モーダル
    }
    // 選択結果
    var DialogResult = {
    "OK"               : 1,   // [OK]
    "Cancel"           : 2,   // [キャンセル]
    "Abort"            : 3,   // [中止]
    "Retry"            : 4,   // [再試行]
    "Ignore"           : 5,   // [無視]
    "Yes"              : 6,   // [はい]
    "No"               : 7,   // [いいえ]
    "Over"             : -1   // 時間経過
    }
    /******************************************************************************/
    
    var Shell = new ActiveXObject("WScript.Shell");
    var IE    = new ActiveXObject("InternetExplorer.Application");
    
    try {
    IE.Visible = false;
    IE.Navigate("https://ss1.coressl.jp/" + HostName + "/jp/admin.cgi");
    while (IE.Busy) { WScript.Sleep(100) };
    IE.document.getElementsByName("id")[0].value = UserName;
    IE.document.getElementsByName("pass")[0].value = PassWord;
    IE.document.getElementsByName("explain")[0].click();
    while (IE.Busy) { WScript.Sleep(100) };
    IE.document.getElementsByName("telnet")[0].click();
    while (IE.Busy) { WScript.Sleep(100) };
    IE.document.getElementsByName("ssh2")[0].click();
    while (IE.Busy) { WScript.Sleep(100) };
    
    var result = Shell.Popup(
    "登録が完了しました。\n接続可能となるまで5分間待機中…",
    60*5,
    WScript.ScriptName);
    if (result == DialogResult.Over) {
    WScript.Echo("5分経過しました。\n接続を確認してください。");
    }
    } catch(e) {
    WScript.Echo(e.description);
    } finally {
    IE.Quit();
    }
    

    ちなみに、スクリプト実行後は5分ぐらい待たないと接続可能にならない。これは、CORESERVERの面倒な仕様のせい。何だかなぁ。

%d人のブロガーが「いいね」をつけました。