2011/03/30

ノートPCのAC電源復旧時にWoLでデスクトップPCを起動

停電時でもPCに稼働し続けて欲しければ、バッテリを搭載しているノートPCを使用するのが無難ですが、PCI接続の機器などを使う必要がある場合、デスクトップPCを選択せざるを得ません。
デスクトップPCでは、バッテリによる予備電源を提供するUPS(無停電電源装置)を利用しても、長時間に及ぶ停電を無停止で乗り切ることは出来ません。
また、USB接続などでPCと連動して停電時に自動で安全なシャットダウンを行う機能は多くのUPSに用意されていますが、電源復旧時にPCを自動で起動する機能は用意されていないことがあり、外出などで手動での電源投入が行なえない状況では、長時間シャットダウンされたままになってしまうことがあります。

どうしてもデスクトップPCの停止時間を最小限にとどめたければ、別途ノートPCを起動しておき、そこからデスクトップPCを自動起動させるとよいでしょう。

NILScriptでは「System.hasAC」で電源が供給されているかどうかを取得できる他、System.observe()で「powerStatusChange」イベントにコールバック関数を登録することで、電源の状態が変化したときに任意の処理を実行させられます。
また、LAN上にマジックパケットをブロードキャストして指定のコンピュータを起動させるWOL(WakeOnLAN)の機能も最近追加されました。
これらの機能を利用して以下のようなスクリプトを作成し、AC電源に接続したノートPC上で実行しておけば、AC電源が断たれた状態から復旧したときに自動で指定のPCを起動してくれるはずです。

var mac="00 01 02 03 04 05";//起動対象PCのMACアドレス
var dest="192.168.1.255"; //パケット送信先のブロードキャストアドレス

Main.createNotifyIcon();
var power=System.hasAC;

System.observe('powerStatusChange',function(){
 if(!power && System.hasAC){
  require('WOL').WOL.send(mac,dest);
 }
 power=System.hasAC;
});

なお、WOLでPCを遠隔起動するには、対象のPCがWOLに対応したLANカードでルーターに有線接続されていて、BIOSの設定でWOLが有効になっている必要があります。
また、停電でルーターが停止してしまう場合、powerStatusChangeイベントで電源復旧を検出した直後にWOL.send()を行ってもパケットが到達しない可能性があるため、適宜sleep()などを挿入して下さい。

2011/03/29

録画したテレビ番組のL字を除去する

大災害が発生したりすると、テレビ番組の画面の周囲にL字状の枠が追加されて被害情報などが表示されますが、録画した番組を後で視聴するときには邪魔でしかありません。
エンコード時にクロップして除去してしまえばよいのですが、CMなどでL時の有無が切り替わる部分ではフェードイン・アウトが行われるため、綺麗に除去するエンコード定義を手動で作成するのは非常に面倒です。
そこで、動画からL字の出現範囲を自動検出して、フェードイン・アウトの部分まで正確に除去するAviSynthスクリプトを生成するというNILScript用スクリプトを作成しました。

AviSynthは、動画の読み込みや加工処理を定義するスクリプト言語の処理系です。
AviSynthをインストールすると、VideoForWindows APIで拡張子「.avs」のスクリプトを動画として読み込めるようになります。
VirtualDubなどのソフトで読み込んで編集・エンコードする一般的な使い方ですが、NILScriptのVideoユニットもVFWを利用しているためAviSynthスクリプトを読み込み可能で、今回のスクリプトでも映像処理の大部分はAviSynthスクリプトで行っています。

NILScriptのVideoユニットは、このL字除去スクリプトを作成するために暫定的に実装されたものなので、限られた機能しか用意されていませんが、このスクリプトのようにAviSynthと組み合わせれば、様々な動画処理ツールを手軽に作成できるでしょう。


スクリプトと定義データ一式は以下のURLからダウンロードできます。
使用するには、別途NILScript本体とAviSynth、MPEG-2 VIDEO VFAPI Plug-Inが必要です。
スクリプトの内容や詳しい使用方法は、上記アーカイブ内のファイルを参照して下さい。

http://lukewarm.s151.xrea.com/rem.zip


L字の検出には、L字の左上辺りの映像が不変である部分のサンプル画像を用意しておき、動画の各フレーム画像との差を調べるという手法を用いています。
ほとんど全てのピクセルが一致した場合に完全なL字とみなし、それ以外はL字無しかフェードイン・アウト中のフレームとみなすことにしました。

スクリプトが実行されると、各フレームのL字の有無を示す小さな画像からなる動画を出力するAviSynthスクリプトを生成して、Videoユニットで読み込み、フレームを一定間隔で飛ばしながらL字の有無を調べます。
L字の有無が切り替わっている箇所が見つかったら、間のフレームを調べて正確な境目を探します。
ここでも、最初に範囲の中央を調べ、既に切り替わっていたら範囲の前半、切り替わっていなければ範囲の後半を再帰的に調べるという二分探索的な手法を採ることで高速化しています。
フェードイン・アウトのフレーム数は放送局ごとに一定なので、完全にL字である範囲がわかれば、フェードイン・アウトの範囲と、L字無しの範囲も確定します。
動画の末尾まで調べたら、元の動画を読み込んで各部分に対応するフィルタを適用して連結するというAviSynthスクリプトを構築して保存すれば処理完了です。

2011/03/24

動画ファイルから画像を取得してサムネイル生成などを行う

動画ファイルから寸法などの情報や任意のフレームの画像を取得できる「Video」ユニットを暫定的に実装しました。

動画のフレームは、Imageユニットで定義されているImageオブジェクトとして取得され、リサイズなど様々な処理が可能です。

下記のスクリプトは、コマンドライン引数で指定した動画ファイルから等間隔にフレームを抜き出し、縮小して並べた画像をファイルに保存するという例です。
var file=cwd().file(Main.params[0]);
var scale=4;//縮小率
var cx=4, cy=4;//横、縦に並べる数
try{
    var vf=require('Video').VideoFile.open(file);
    var st=vf.openVideo();
    var dist=Math.floor(st.length/(cx*cy));
    var w=Math.floor(st.width/scale), h=Math.floor(st.height/scale);
    var img=require('Image').Image.create(w*cx,h*cy);
    for(var i=0;i<cy;i++){
        for(var j=0;j<cx;j++){
            try{
                var frame=st.getFrazme((i*cx+j)*dist);
                img.drawImage(frame,w*j,h*i,w,h);
            }finally{
                free(frame);
            }
        }
    }
    img.save(cwd().file(file.name+'.jpg'),{quality:80});
}finally{
    free(st,vf,img);
}
実行すると、左記のような画像ファイルが生成されます。


現在の所、VideoForWindowsのAPIを利用しているため、AVIなど限られた形式にしか対応していませんが、AviSynthをインストールして、DirectShowSource()などで動画を読み込むAVSスクリプトを作成してそれを読み込むようにすれば、MPEGなど他の形式にも対応させられるはずです。

2011/03/06

非アクティブのウィンドウにキー操作を送信

GUIプログラムの自動操作を行うとき、対象のウィンドウをいちいちアクティブにしていたのでは、他の作業の邪魔になってしまいます。
ウィンドウを非アクティブのままで操作したければ、キー操作時に送られるウィンドウメッセージを送信する「sendKeys()」を使うといいでしょう。(最近追加された機能なので、使用するには最新版にアップデートして下さい)

下記の例は、ある音楽プレイヤーソフトにPauseのショートカットキーを送信する例です。
require('Window').Window.find('{DA7CD0DE-1602-45e6-89A1-C2CA151E008E}').sendKeys('[x]');
「require('Window').Window.find('...')」で、指定のクラス名を持つウィンドウの内、一番手前のものを表すWindowオブジェクトが返されます。
そのsendKeys()メソッドを呼び出すことで、ウィンドウメッセージによる擬似的なキーストロークの送信を行います。
大抵のソフトでは、タスクトレイに格納されたりして非表示になっていても、ウィンドウ自体は非表示の状態で存在しているため、上記の要領で操作可能です。

ショートカットキーは、親ウィンドウではなく配下のコントロールに送信しなければ動作しない場合があります。
配下のコントロールを表すオブジェクトを取得するには、「control()」メソッドを使用します。
次の例は、「TVXEForm」というクラス名の一番手前のウィンドウの配下の「TEditorX」というクラス名のコントロールに「Ctrlキーを押しながらsキーを押して放しCtrlキーを放す」というキーストロークを送信するというものです。
require('Window').Window.find('TVXEForm').control('TEditorX').sendKeys('Ctrl+[s]');
controlの第2引数に数値を指定すれば、複数の同クラス名コントロールの内、任意のオフセットのものを取得できます。未指定時は0を指定したものとみなされ、最初の物が取得されます。

sendKeysで指定するキーストロークの定義には、NILScriptの「Hotstrokes」ユニットで使用されているストローク定義文字列の内、通常のキーボードのキーのみで構成された物が指定できます。
Hotstrokesでは様々なキーストロークを定義するためのルールがありますが、標準的なショートカットキーを送信するだけなら、修飾キー名(Shift/Ctrl/Alt)に「+」を付けたものに続いてキー名を「[]」で囲んで記述するという方式を覚えておけば十分でしょう。

なお、送信されるキーストロークの内、修飾キーの部分だけはウィンドウメッセージではなくシステム全体に影響する方式で擬似的なキーストローク生成が行われます。
これは、多くのソフトのショートカットキー認識では、修飾キーの状態をシステムに問い合わせて取得しており、ウィンドウメッセージでCtrlの押し下げを送信しても無視されてしまうためです。
このため、修飾キーを伴うショートカットキーを送信すると、他のソフトに意図しない操作が認識されてしまう可能性があります。
できれば実行したい動作を「F1」のような修飾キーを伴わないショートカットキーに割り当てておき、それを送信すると良いでしょう。