2012年05月03日

TS->MP4協調変換をJScriptでがんばるメモ

チューナーを導入したため、TSファイルが大量に出来てしまうようになりました
サーバ上にTSファイルを置き、自動でTSファイルをMP4変換させていますが、
サーバのスペックが貧弱な為、変換が追いつかないという状況に直面
そこで、別PCからも変換を助けてあげようかと模索中

下記JScriptは、外部PCから、サーバー上のディレクトリ(FTP)を検索し、TSファイルをダウンロードし、
ローカルでMP4に変換して元の場所に戻すというものです。
TS -> MP4変換にはffmpegを使用しています。
その他、basp21とffmpeg用のpresetが必要。
ffmpegは「FFmpeg rev.18607」を使わせてもらっています。
自前でコンパイルしたffmpeg.exeに変更。

TranscodeVideo.js

2012/05/05
失敗したファイルがあっても次のファイルを変換できるように改善
2012/05/04
TS -> MP4変換のみモード追加
エラーを例外処理に統一
TS -> MP4変換時の経過時間を少数2桁までに改善
他、文言修正



// FTP接続情報
g_FtpSvr = "ftp_svr";
g_FtpUser = "ftp_user";
g_FtpPass = "ftp_pass";

g_FtpSearchDir = "/usr/home/test/data/"; // TSファイルを検索する開始FTPディレクトリ
g_MaxTranscodeCount = 1; // 連続で変換するファイル数

g_ViewProgress = 1; // 1=処理中画面表示, 0=非表示
g_OLDFFMPEG = false; // ffmpeg.exe旧バージョンフラグ
eval( GetAllTextFile("Log.js") );

//-------------------------------------------------------------------
function GetAllTextFile( js_path )
{
var fso = null;
try {
fso = WScript.CreateObject("Scripting.FileSystemObject");
if( js_path.indexOf("\\") < 0 ) {
// 絶対パス指定で無い場合は、スクリプトパスとする
js_path = fso.GetParentFolderName(WScript.ScriptFullName) + "\\" + js_path;
}
var fs_txt = fso.OpenTextFile( js_path, 1);
try {
return fs_txt.ReadAll();
}
finally {
fs_txt.Close();
}
}
finally {
delete fso;
}
}

g_Log = new TLog( "File", "Info,Error");
var ret = main();
g_Log.Destructor();
delete g_Log;
WScript.Quit( ret );

//-----------------------------------------------------------------------------
//! 協調TS->MP4変換処理
/*!
TS->MP4変換のみ
TranscodeVideo.js [TSファイル名1] [[TSファイル名2]...[TSファイル名n]]

協調TS->MP4変換
TranscodeVideo.js (引数無し)
指定FTPディレクトリ(g_FtpSearchDir)からTSファイルを再帰的に検索し、
ローカルへダウンロードし、ffmpeg.exeによりTS->MP4変換を行い、
元のFTPディレクトリへ戻します
(正常終了した場合のみ元のTSファイルは削除します)

FTPディレクトリのファイル ローカルディレクトリのファイル
---------------------------------------------------------------------------
0.
"[元ファイル名.TS]"
1.ダウンロード中
"[元ファイル名.TS].PC名.tmp" -> "[元ファイル名.TS].PC名.tmp"
2.ダウンロード完了
"[元ファイル名.TS].PC名" "[元ファイル名.TS]"
3.TS->MP4変換中
"[元ファイル名.TS].PC名" "[元ファイル名.TS].src_tmp" (変換元 TS)

"[元ファイル名.TS].dst_tmp" (変換先 MP4)
4.TS->MP4変換完了
"[元ファイル名.TS].PC名" "[元ファイル名.MP4]"
5.アップロード中
"[元ファイル名.TS].PC名"
"[元ファイル名.MP4].tmp" <- "[元ファイル名.MP4].tmp"
6.アップロード完了 ↓
"[元ファイル名.TS].PC名" 削除
"[元ファイル名.MP4]"
7.全て完了
"[元ファイル名.MP4]"
"[元ファイル名.TS].PC名"

削除
n.途中で失敗した場合
"[元ファイル名.TS].PC名" 恐らく、
↓(戻す) "[元ファイル名.TS]"
"[元ファイル名.TS]" が、残っている
(このファイルを削除しないと同じファイルは再変換しない)
*/
function main()
{
var ConvertExtList = new Array(".TS"); // 変換対象の元ファイルの拡張子(大文字)

try {
var fso = null;
var net = null;
var lock_txt = null;
try {
fso = WScript.CreateObject("Scripting.FileSystemObject");
net = WScript.CreateObject("WScript.Network");

// 二重起動防止
try {
lock_txt = fso.OpenTextFile( fso.GetParentFolderName(WScript.ScriptFullName) + ".\\.lockfile", 8, true );
}
catch( e ) {
g_Log.LogOut( "Info", "二重起動防止" );
return 1;
}

{ // 引数TS -> MP4 変換のみモード
var argv = WScript.Arguments;
if( argv.length >= 1 ) {
g_Log.LogOut( "Info", "***** 変換のみ Begin..." );
for( var i=0; i<argv.length; i++ ) {
try {
g_Log.LogOut( "Info", (i+1).toString() + "/" + (argv.length).toString() + "..." );
transcode_video( argv(i) );
}
catch( e ) {
g_Log.LogOut( "Error", e );
}
}
g_Log.LogOut( "Info", "***** 変換のみ End" );
return 0;
}
}

// 協調TS->MP4変換モード
g_Log.LogOut( "Info", "***** Begin..." );
var ftp_list = new Array();
{ // 処理対象のファイル一覧を検索する
var ftp = WScript.CreateObject("basp21.FTP");
FtpConnect( ftp );
try {
FtpSearchDir( ftp, ConvertExtList, 100, g_FtpSearchDir, ftp_list ); // とりあえず、100ファイルで抜けるようにする
g_Log.LogOut( "Info", "未変換ファイル数 = " + ftp_list.length );
}
finally {
ftp.Close();
delete ftp;
}
}

var cnt = 0;
for( var i in ftp_list ) {
if( cnt >= g_MaxTranscodeCount ) {
break;
}

var ftp_dir = FtpGetParentFolderName(ftp_list[i]);
var ftp_fname = ftp_list[i];
var ftp_tmp_fname = ftp_list[i] + "." + net.ComputerName; // 処理中は、[ファイル名].[PC名]でFTPに残しておく
var local_dir = fso.GetParentFolderName(WScript.ScriptFullName) + "\\";
var local_fname = local_dir + FtpGetFileName(ftp_fname);
var local_tmp_fname = local_dir + FtpGetFileName(ftp_tmp_fname);
var ret_msg = "";

try {
FtpRenameFile( ftp_fname, ftp_tmp_fname );
}
catch( e ) {
g_Log.LogOut( "Info", "ファイルが既に存在しないかもしれません。次のファイルへスキップします。\r\n" + e );
continue;
}

try {
// 一度変換に失敗したファイルは再変換しない
if( fso.FileExists( local_fname ) == true ) {
throw "変換前のファイルが既に存在します(" + local_fname + ")\r\n"
+ "前回失敗した可能性があります\r\n再実行するには削除してください";
}

// download
FtpDownload( ftp_tmp_fname, local_dir, ".tmp" );

if( fso.FileExists( local_fname ) == true ) {
fso.DeleteFile( local_fname );
}
fso.MoveFile( local_tmp_fname, local_fname );

// TS -> MP4
local_fname = transcode_video( local_fname );

// upload
FtpUpload( local_fname, ftp_dir, ".tmp" );

// Uploadしたら消す
if( fso.FileExists( local_fname ) == true ) {
fso.DeleteFile( local_fname );
}

// 全て成功したら、元ファイルは消す
FtpDeleteFile( ftp_tmp_fname );

cnt++;
}
catch( e ) {
// 失敗した時だけ戻す
FtpRenameFile( ftp_tmp_fname, ftp_fname );
g_Log.LogOut( "Error", e );
}
}

g_Log.LogOut( "Info", "***** End" );
}
finally {
delete fso;
delete net;
}
}
catch( e ) {
g_Log.LogOut( "Error", e );
return 255;
}
return 0;
}

//-----------------------------------------------------------------------------
//! FTPファイル検索
/*! 指定開始FTPディレクトリから再帰的に検索し、変換対象のファイルパスのみを抽出し配列に保持する
@param[in] ftp FTP用
@param[in] ext_list 対象ファイルの拡張子一覧配列(".TS"など大文字)
@param[in] max_count リスト化最大数
@param[in] search_dir 検索開始FTPディレクトリ(末尾/)
@param[out] ftp_list 対象ファイルパス一覧(参照渡し)
*/
function FtpSearchDir( ftp, ext_list, max_count, search_dir, ftp_list )
{
if( ftp_list.length >= max_count ) {
return;
}
var r;
r = ftp.GetDir(search_dir,1);
if( IsArray(r) == true ) {
var dirs = r.toArray();
dirs.sort();
for( var i in dirs ) {
if( ftp_list.length >= max_count ) {
break;
}
FtpSearchDir( ftp, ext_list, max_count, search_dir + dirs[i] + "/", ftp_list );
}
}

r = ftp.GetDir(search_dir,0);
if( IsArray( r ) == true ) {
var files = r.toArray();
files.sort();
for( var i in files ) {
// 対象の拡張子のファイルのみ登録
if( ftp_list.length >= max_count ) {
break;
}
for( var k in ext_list ) {
if( FtpGetExtName(files[i]).toUpperCase() == ext_list[k] ) {
ftp_list[ ftp_list.length ] = search_dir + files[i];
break;
}
}
}
}
}

//-----------------------------------------------------------------------------
//! 配列チェック
/*!
*/
function IsArray( val )
{
var r = false;
try {
var arr = val.toArray();
r = true;
}
catch( e ) {
}
return r;
}

//-----------------------------------------------------------------------------
//! FTPファイルUpload
/*! FTPファイルUploadする
tmp_extオプションを付けると転送中はファイル名の末尾にtmp_extの文字を付けたファイル名で転送する
@param[in] src_full_fname local転送元ファイル名(フルパス ファイル名)
@param[in] dst_dir ftp転送先ディレクトリ(末尾/)
@param[in] tmp_ext(option) テンポラリ転送拡張子名
*/
function FtpUpload( src_full_fname, dst_dir, tmp_ext )
{
if( tmp_ext == undefined) tmp_ext = "";

var fso = null;
var ftp = null;
try {
fso = WScript.CreateObject("Scripting.FileSystemObject");
ftp = WScript.CreateObject("basp21.FTP");

var ret = ftp.Connect( g_FtpSvr, g_FtpUser, g_FtpPass );
if( ret != 0 ) {
throw "FtpUpload() ftp.Connect() Result=" + ret + "\r\n" + ftp.GetReply();
}
var tmp_full_fname = src_full_fname + tmp_ext;
var ftp_src_full_fname = dst_dir + fso.GetFileName(src_full_fname);
var ftp_tmp_full_fname = dst_dir + fso.GetFileName(src_full_fname) + tmp_ext;
var ret = 0;

g_Log.LogOut( "Info", "FtpUpload():" + src_full_fname + " -> " + dst_dir + " ..." );
if( tmp_ext != "" ) {
if( fso.FileExists( tmp_full_fname ) == true ) {
fso.DeleteFile( tmp_full_fname );
}
fso.MoveFile( src_full_fname, tmp_full_fname );
}
try {
if( tmp_ext != "" ) {
ret = ftp.PutFile( tmp_full_fname, dst_dir, 1 );
if( ret < 1 ) {
throw "FtpUpload() ftp.PutFile() Result=" + ret + "\r\n" + ftp.GetReply();
}
ret = ftp.RenameFile( ftp_tmp_full_fname, ftp_src_full_fname );
if( ret != 2 ) {
throw "FtpUpload() ftp.RenameFile() Result=" + ret + "\r\n" + ftp.GetReply();
}
}
else {
ret = ftp.PutFile( src_full_fname, dst_dir, 1 );
if( ret < 1 ) {
throw "FtpUpload() ftp.PutFile() Result=" + ret + "\r\n" + ftp.GetReply();
}
}
}
finally {
if( tmp_ext != "" ) {
if( fso.FileExists( src_full_fname ) == true ) {
fso.DeleteFile( src_full_fname );
}
fso.MoveFile( tmp_full_fname, src_full_fname );
}
}

g_Log.LogOut( "Info", "FtpUpload():" + src_full_fname + " -> " + dst_dir + " OK" );
ftp.Close();
}
finally {
delete fso;
delete ftp;
}
}

//-----------------------------------------------------------------------------
//! FTPファイルDownload
/*! FTPファイルDownloadする
tmp_extオプションを付けると転送中はファイル名の末尾にtmp_extの文字を付けたファイル名で転送する
@param[in] src_full_fname ftp転送元ファイル名(フルパス ファイル名)
@param[in] dst_dir local転送先ディレクトリ(末尾\\)
@param[in] tmp_ext(option) テンポラリ転送拡張子名
*/
function FtpDownload( src_full_fname, dst_dir, tmp_ext )
{
if( tmp_ext == undefined) tmp_ext = "";

var fso = null;
var ftp = null;
try {
fso = WScript.CreateObject("Scripting.FileSystemObject");
ftp = WScript.CreateObject("basp21.FTP");

var ret = ftp.Connect( g_FtpSvr, g_FtpUser, g_FtpPass );
if( ret != 0 ) {
throw "FtpDownload() ftp.Connect() Result=" + ret + "\r\n" + ftp.GetReply();
}
var tmp_full_fname = src_full_fname + tmp_ext;
var local_src_full_fname = dst_dir + FtpGetFileName(src_full_fname);
var local_tmp_full_fname = dst_dir + FtpGetFileName(src_full_fname) + tmp_ext;
var ret = 0;

g_Log.LogOut( "Info", "FtpDownload():" + src_full_fname + " -> " + dst_dir + " ..." );
if( tmp_ext != "" ) {
ret = ftp.RenameFile( src_full_fname, tmp_full_fname );
if( ret != 2 ) {
throw "FtpDownload() ftp.RenameFile() Result=" + ret + "\r\n" + ftp.GetReply();
}
}
try {
if( tmp_ext != "" ) {
ret = ftp.GetFile( tmp_full_fname, dst_dir, 1 );
if( ret < 1 ) {
throw "FtpDownload() ftp.GetFile() Result=" + ret + "\r\n" + ftp.GetReply();
}
if( fso.FileExists( local_src_full_fname ) == true ) {
fso.DeleteFile( local_src_full_fname );
}
fso.MoveFile( local_tmp_full_fname, local_src_full_fname );
}
else {
ret = ftp.GetFile( src_full_fname, dst_dir, 1 );
if( ret < 1 ) {
throw "FtpDownload() ftp.GetFile() Result=" + ret + "\r\n" + ftp.GetReply();
}
}
}
finally {
if( tmp_ext != "" ) {
ftp.RenameFile( tmp_full_fname, src_full_fname );
}
}

g_Log.LogOut( "Info", "FtpDownload():" + src_full_fname + " -> " + dst_dir + " OK" );
ftp.Close();
}
finally {
delete fso;
delete ftp;
}
}

//-----------------------------------------------------------------------------
//!
/*!
*/
function FtpConnect( ftp )
{
var ret = ftp.Connect(g_FtpSvr,g_FtpUser,g_FtpPass);
if( ret != 0 ) {
throw "FtpConnect() Result=" + ret + "\r\n" + ftp.GetReply();
}
}
//-----------------------------------------------------------------------------
//!
/*!
*/
function FtpRenameFile( src, dst )
{
var ftp = WScript.CreateObject("basp21.FTP");
FtpConnect( ftp );
try {
var ret = ftp.RenameFile( src, dst );
if( ret != 2 ) {
throw "FtpRenameFile() Result=" + ret + "\r\n" + "src=" + src + "," + "dst=" + dst + "\r\n" + ftp.GetReply();
}
}
finally {
ftp.Close();
delete ftp;
}
}
//-----------------------------------------------------------------------------
//!
/*!
*/
function FtpDeleteFile( src )
{
var ftp = WScript.CreateObject("basp21.FTP");
FtpConnect( ftp );
try {
var ret = ftp.DeleteFile( src );
if( ret <= 0 ) {
throw "FtpDeleteFile() Result=" + ret + "\r\n" + ftp.GetReply();
}
}
finally {
ftp.Close();
delete ftp;
}
}

//-----------------------------------------------------------------------------
//! "/aaa/bbb/ccc.ddd" -> "/aaa/bbb/"
/*!
*/
function FtpGetParentFolderName( full_fname )
{
var name = "";
var idx = 0;
if ( (idx = full_fname.lastIndexOf("/")) >= 0 ) {
name = full_fname.substring( 0, idx+1 );
}
return name;
}
//-----------------------------------------------------------------------------
//! "/aaa/bbb/ccc.ddd" -> "ccc.ddd"
/*!
*/
function FtpGetFileName( full_fname )
{
var name = "";
var idx = 0;
if ( (idx = full_fname.lastIndexOf("/")) >= 0 ) {
name = full_fname.substring( idx+1 );
}
return name;
}
//-----------------------------------------------------------------------------
//! "aaa.bbb" -> ".bbb"
/*!
*/
function FtpGetExtName( full_fname )
{
var name = "";
var idx = 0;
if ( (idx = full_fname.lastIndexOf(".")) >= 0 ) {
name = full_fname.substring( idx );
}
return name;
}

//-----------------------------------------------------------------------------
//! TS→MP4変換
/*! xxxx.ts -> xxxx.mp4へ変換する
成功した場合元のファイルは削除される
失敗した場合元のファイルはxxxx.ts.src_tmpとして残る(xxxx.tsにリネームすれば元のファイルになる)
@param[in] src 変換元(TS)ファイル名
@remarks スクリプトと同じディレクトリにffmpeg.exeを置いておくこと
*/
function transcode_video( src )
{
var shell = null;
var fso = null;
try {
shell = WScript.CreateObject("WScript.Shell");
fso = WScript.CreateObject("Scripting.FileSystemObject");

var CPU_CORES=2;
var base_path=fso.GetParentFolderName(WScript.ScriptFullName);
var preset_fname=base_path + "\\" + "libx264-hq-ts.ffpreset";
var preset_flgname = (g_OLDFFMPEG == false) ? "fpre":"vpre"; // rev.20435〜 or rev.20435より前
var X264_HIGH_HDTV = "-f mp4 -vcodec libx264" +
" -" + preset_flgname + " \"" + preset_fname + "\"" +
" -aspect 16:9 -s 1280x720 -b 2000k" +
" -acodec libfaac -ac 2 -ar 48000 -ab 128k -threads " + CPU_CORES;

var src_tmp = src + ".src_tmp";
var dst_tmp = src + ".dst_tmp";
var dst = fso.GetParentFolderName(src) + "\\" + fso.GetBaseName(src) + ".mp4";

g_Log.LogOut( "Info", "transcode-video(): " + src + " -> .mp4" );
var dt_start = new Date();
g_Log.LogOut( "Info", "Start " + dt_start.toString() );
if( fso.FileExists( src_tmp ) == true ) {
fso.DeleteFile( src_tmp );
}
fso.MoveFile( src, src_tmp );
try {
var cmd = base_path + "\\ffmpeg.exe -y -i \"" + src_tmp + "\" " + X264_HIGH_HDTV + " \"" + dst_tmp + "\"";
g_Log.LogOut( "Info", cmd );
var ret = shell.Run( cmd, g_ViewProgress, true );

if( ret == 0 ) {
if( fso.FileExists( dst ) == true ) {
fso.DeleteFile( dst );
}

fso.MoveFile( dst_tmp, dst );
if( fso.FileExists( src_tmp ) == true ) {
fso.DeleteFile( src_tmp );
}
g_Log.LogOut( "Info", "Success" );
}
else {
if( fso.FileExists( dst_tmp ) == true ) {
fso.DeleteFile( dst_tmp );
}
throw "Error Result=" + ret;
}
}
catch( e ) {
// エラーの場合は変換前のファイルに戻す
if( fso.FileExists( src ) == true ) {
fso.DeleteFile( src );
}
fso.MoveFile( src_tmp, src );
if( fso.FileExists( dst_tmp ) == true ) {
fso.DeleteFile( dst_tmp );
}
throw e;
}

var dt_end = new Date();
g_Log.LogOut( "Info", "End " + dt_end.toString() );
g_Log.LogOut( "Info", "DiffTime = " + ((dt_end - dt_start) / (1000*60)).toFixed(2) + "Min" );

}
finally {
delete shell;
delete fso;
}

return dst;
}

長いな〜、色々考え出すと長くなってしまいます
FTPのConnectの所が煩雑になっています
動画変換処理は時間がかかる為、一旦Close()して再接続したいのですが、
basp21.FTPはClose()すると、オブジェクトが解放?されるようで、
Close()->Connect()すると失敗します。
そのため、CreateObjectからやり直しています。
,
posted by sanahi at 23:30| 滋賀 曇り| Comment(0) | TrackBack(0) | JScript(WSH) | このブログの読者になる | 更新情報をチェックする

2012年04月03日

JSで自動メール印刷2

seesaa UI変わりすぎてよくわからんw

「JSで自動メール印刷」で課題のあったExecWB( 6, 2 )ですが、
本来はExecWB( 6, 2, 3 )とすれば完了待ちできるのですが、
この第3引数は、VARIANT of type VT_I2らしく
要するに2byte整数を保持するVARIANTを渡さないといけないらしいです。
これはJScriptでは無理っぽいです。

そこで、C++BuilderXE2でやってみました。

//---------------------------------------------------------------------------
//! IEで印刷
/*!
@param[in] fname
*/
void __fastcall TFormMain::PrintIE( UnicodeString fname )
{
Variant ie = Unassigned;

try {
//LogOut("IE起動...");
ie = Variant::CreateObject("InternetExplorer.Application");
ie.OlePropertySet("Visible", false);
//LogOut("IE起動...OK");

// ページロード
//LogOut("ページロード..." + fname);
ie.OleProcedure("Navigate", WideString(fname) );
if( IeBusyWait(ie, 60) == false ) {
throw new Exception( UnicodeString().sprintf(L"ie.Navigate() TimeOut(%s)", fname) );
}
//LogOut("ページロード...OK");

// 印刷(ブロッキング)
//LogOut("印刷...");
ie.OleProcedure("ExecWB", OLECMDID_PRINT, OLECMDEXECOPT_DONTPROMPTUSER, (short)3 );
//LogOut("印刷...OK");
}
__finally {
if( VarIsEmpty(ie) != true ) ie.OleProcedure("Quit");
ie = Unassigned;
}
}

たしかにコレなら待ってくれますね。。。
全ソースはコチラ
PrintAppSrc.7z
posted by sanahi at 22:04| 滋賀 晴れ| Comment(0) | TrackBack(0) | C++Builder | このブログの読者になる | 更新情報をチェックする

2012年04月01日

JSで自動メール印刷

自動で指定メアドからメールを受信し、
メールの内容を印刷するJScriptをがんばってみる

必要な環境は、IE,BASP21
IEは印刷のために使用
Excelでやり始めたのですが、画像をスケーラブルに印刷(サイズ,画像数可変)させる
方法が分からなかっただけです


main_print_mail.js
マズはメインのスクリプト
サブディレクトリにMailディレクトリを作成して、
メールを受信します
受信したメールファイルから内容を取得して印刷関数へ渡します

eval( GetAllTextFile("Log.js") );
eval( GetAllTextFile("PrintMail.js") );

//-------------------------------------------------------------------
function GetAllTextFile( js_path )
{
var obj_fs = WScript.CreateObject("Scripting.FileSystemObject");
try {
var fs_txt = obj_fs.OpenTextFile( js_path, 1);
try {
return fs_txt.ReadAll();
}
finally {
fs_txt.Close();
}
}
finally {
delete obj_fs;
}
}

WScript.Quit( main() );

function main()
{
var MailSvName = "MAIL POP3サーバー名";
var MailUserName = "MAIL ユーザー名";
var MailUserPass = "a " + "MAIL パスワード";

var fso = null;
var basp = null;
try {
var ret = "";

try {
fso = WScript.CreateObject("Scripting.FileSystemObject");
basp = WScript.CreateObject("basp21");

var mail_dir = fso.GetParentFolderName(WScript.ScriptFullName) + "\\Mail";

if( fso.FolderExists( mail_dir ) == false ) {
fso.CreateFolder( mail_dir );
}

var ret_array = basp.RcvMail(MailSvName, MailUserName, MailUserPass, "SAVEALL", "<" + mail_dir);
if( ret_array.length <= 0 ) {
return 0;
}
var out_mail = ret_array.toArray();

for ( var i in out_mail ) {
//WScript.Echo( out_mail[i] );
var attatch_dir = out_mail[i] + "_att";

if( fso.FolderExists( attatch_dir ) == false ) {
fso.CreateFolder( attatch_dir );
}
var out_data = basp.ReadMail( out_mail[i], "subject:from:date:", attatch_dir ).toArray();
var str_from = "";
var str_to = "";
var str_date = "";
var str_subject = "";
var str_body = "";
var str_attrs = new Array();

for ( var j=0; j<out_data.length; j++ ) {
var key = out_data[j].split(" ", 2)[0];
var val = out_data[j].substring( key.length+1, out_data[j].length );

if( key.match(/From\:/i) ) {
str_from = val;
}
else if( key.match(/To\:/i) ) {
str_to = val;
}
else if( key.match(/Date\:/i) ) {
str_date = val;
}
else if( key.match(/Subject\:/i) ) {
str_subject = val;
}
else if( key.match(/Body\:/i) ) {
str_body = val;
}
else if( key.match(/File\:/i) ) {
var cnt = str_attrs.length;
str_attrs[cnt] = val;
}
}

ret = PrintMail( str_subject, str_from, str_to, str_date, str_body, str_attrs );
if( ret != "" ) {
LogOut( "Error", ret );
}
}

}
finally {
delete basp;
delete fso;
}
}
catch( e ) {
LogOut( "Error", e );
return 255;
}

return 0;
}



PrintMail.js
次に印刷本体です
渡されてきた内容をHTMLとして保存して
魔法のパラメータ?ExecWB( 6, 2)で印刷させています

eval( GetAllTextFile("Log.js") );

//-------------------------------------------------------------------
function GetAllTextFile( js_path )
{
var obj_fs = WScript.CreateObject("Scripting.FileSystemObject");
try {
var fs_txt = obj_fs.OpenTextFile( js_path, 1);
try {
return fs_txt.ReadAll();
}
finally {
fs_txt.Close();
}
}
finally {
delete obj_fs;
}
}
//-------------------------------------------------------------------
function EscapeHtmlString( str )
{
str = str.replace("&","&amp;");
str = str.replace("<","&lt;");
str = str.replace(">","&gt;");
return str;
}

//-----------------------------------------------------------------------------
//! メール印刷
/*!
@param[in] str_subject subject
@param[in] str_from from
@param[in] str_to to
@param[in] str_date date
@param[in] str_body body
@param[in] str_attrs 添付ファイル名配列(絶対パス)
@return エラーメッセージ(空時は成功)
*/
function PrintMail( str_subject, str_from, str_to, str_date, str_body, str_attrs )
{
var TempHtml = "temp.html";
var fso = null;
var ie = null;
try {
fso = WScript.CreateObject("Scripting.FileSystemObject");
ie = WScript.CreateObject("InternetExplorer.Application");
ie.Visible = false;
//ie.Visible = true;

var temp_html = fso.GetParentFolderName(WScript.ScriptFullName) + "\\" + TempHtml;

if( fso.FileExists( temp_html ) == true ) {
fso.DeleteFile( temp_html );
}

var body = "";
body += "Subject:" + EscapeHtmlString(str_subject) + "<br />\r\n";
body += "From:" + EscapeHtmlString(str_from) + "<br />\r\n";
body += "To:" + EscapeHtmlString(str_to) + "<br />\r\n";
body += "Date:" + EscapeHtmlString(str_date) + "<br />\r\n";
body += "<pre>" + EscapeHtmlString(str_body) + "</pre>\r\n";

// 添付ファイル操作
for ( var i=0; i<str_attrs.length; i++ ) {
var fname = fso.GetFileName( str_attrs[i] );
body += "<p>\r\n";
// 添付ファイル名と、アンカーを付ける
body += "<a href=\"" + str_attrs[i] + "\" target=\"_blank\">[" + fname + "]</a><br />\r\n";
// 画像ファイルはイメージを表示する
if( fname.match(/.*\.jpg$/i) ) {
body += "<img src=\"" + str_attrs[i] + "\" alt=\"jpeg\" /><br />\r\n";
}
if( fname.match(/.*\.jpeg$/i) ) {
body += "<img src=\"" + str_attrs[i] + "\" alt=\"jpeg\" /><br />\r\n";
}
if( fname.match(/.*\.png$/i) ) {
body += "<img src=\"" + str_attrs[i] + "\" alt=\"png\" /><br />\r\n";
}
if( fname.match(/.*\.gif$/i) ) {
body += "<img src=\"" + str_attrs[i] + "\" alt=\"gif\" /><br />\r\n";
}
body += "</p>\r\n";
}

var tfo = null;
try {
tfo = fso.OpenTextFile(temp_html, 2, true);
tfo.WriteLine("<html>");
tfo.WriteLine("<head>");
tfo.WriteLine("<title>" + EscapeHtmlString(str_subject) + "</title>");
tfo.WriteLine("</head>");
tfo.WriteLine("<body>");
tfo.WriteLine( body );
tfo.WriteLine("</body>");
tfo.WriteLine("</html>");
}
finally {
if( tfo != null ) tfo.Close();
delete tfo;
}

// ページロード
ie.Navigate( temp_html );
if( IeBusyWait( ie, 60 ) == false ) {
throw "ie.Navigate() TimeOut(" + TempHtml + ")";
}

//WScript.Echo("page created");

// 印刷
ie.ExecWB( 6, 2 );
if( IeBusyWait( ie, 60 ) == false ) {
throw "ie.ExecWB() == false";
}

// ビジーウェイトでダメみたいなので
WScript.Sleep(60*1000);
}
finally {
delete fso;
if( ie != null ) ie.Quit();
delete ie;
}

return "";
}

function IeBusyWait( ie, timeout )
{
var ret = false;
for( var i=0; i<timeout; i++ ) {
WScript.Sleep(1*1000);
if( (ie.busy == true ) || ( ie.Document.readyState != "complete") ) {
}
else {
ret = true;
break;
}
}

return ret;
}

上記スクリプトには1点未解決の問題があります
ExecWB( 6, 2 )実行後に印刷データがスプールされるまでIEを終了させてはいけないようです
(終了させるとスプールされないので印刷しない)
スプールしたかどうかをチェックする方法が分かりませんでしたので適当にウェイトさせています
んー、どうすれば、、、


Log.js
これは共通のログ出力用関数
タスクスケジューラで動作させるため、イベントログへ記録するようにしています

//-----------------------------------------------------------------------------
//! ログ出力
/*!
@param[in] log_level log level "Debug" or "Info" or "Error"
@param[in] str log message
*/
function LogOut( log_level, str )
{
//var OutLogType = "Echo"; // Echoダイアログを出す
var OutLogType = "EventLog"; // イベントログに出力する
//var OutLogLevel = "Info,Error,Debug"; // 出力するlog_level
var OutLogLevel = "Error"; // 出力するlog_level

var wsh = null;
try {
wsh = WScript.CreateObject("WScript.Shell");

var event_log_type = 0;

if( (OutLogLevel.indexOf("Debug") >= 0) && (log_level == "Debug") ) {
event_log_type = 0;
}
else if( (OutLogLevel.indexOf("Info") >= 0) && (log_level == "Info") ) {
event_log_type = 4;
}
else if( (OutLogLevel.indexOf("Error") >= 0) && (log_level == "Error") ) {
event_log_type = 1;
}
else {
return;
}

if( OutLogType == "Echo" ) {
WScript.Echo( log_level + ":" + str );
}
else if( OutLogType == "EventLog" ) {
wsh.LogEvent(event_log_type, "[" + WScript.ScriptName + "] " + str);
}
else {
WScript.Echo( msg );
}

}
finally {
delete wsh;
}
}



main_print_mail.jsをタスクスケジューラへ突っ込んで
定期実行させます

posted by sanahi at 21:29| 滋賀 晴れ| Comment(0) | TrackBack(0) | JScript(WSH) | このブログの読者になる | 更新情報をチェックする

2012年02月20日

mediatombにBRAVIA対応2

いろいろなサイトを検索し、試行錯誤の結果
私の鯖で動くmediatombはこうなった

1.まず、過去記事の内容でmediatomb構築
mediatombにBRAVIA対応パッチ適用
http://sanahi.seesaa.net/article/220128357.html


2.トランスコードOFF
ネットブックで鯖を運用している性質上、トランスコードは再生スピードに
変換処理が合いませんでした
(1分30秒程の動画のトランスコードに2分かかるとか。。。)
mpeg2で変換が必要なのは私の環境ではBRAVIAだけで、
その他のDLNAクライアント(Androidスマートフォン)はmp4,flvでも再生できる
無理に鯖に負荷をかけることはやめました

/usr/local/etc/mediatomb/config.xml

...
<transcoding enabled="no">
<mimetype-profile-mappings>
<transcode mimetype="video/x-flv" using="video-common"/>
<transcode mimetype="video/quicktime" using="video-common"/>
...



3.静的にトランスコード
上記によりBRAVIAでの再生ができないのですが、
mediatomb公開ディレクトリのうちBRAVIA専用のディレクトリを設け、
そこのディレクトリの動画ファイルを置けば、mpeg2にcronで自動変させるようにしました

/usr/local/etc/mediatomb/transcode_all.sh

更新1(2012/02/26):
初期化パラメータ処理追加(多重起動チェックの改善)
リトライ時のAudioビットレートを64kから128kに変更
更新1(2012/03/18):
BRAVIAの設定をどうがんばっても横長になる動画があるのでアスペクト比を4:3固定に変更
(-aspect 4:3)


#!/bin/sh

TranscodeAllSeek(){
for ELEM in * ; do
if [ -f "$ELEM" ] ; then
#echo "$ELEM is File"
SRC_EXT=${ELEM##*.}
for ok_ext in $@ ; do
#echo "ext=${ok_ext}"
if [ $SRC_EXT = ${ok_ext} ] ; then
transcode-video "$ELEM";
fi
done
fi
if [ -d "$ELEM" ] ;then
#echo "$ELEM is Directory"
(cd "$ELEM";TranscodeAllSeek "$@";)
fi
done
}

transcode-video(){
src=$1
src_tmp=$src.src_tmp
dst_tmp=$src.dst_tmp
dst=${1%.*}.mpeg
echo "transcode-video(): $src -> .mpeg"
mv "$src" "$src_tmp"
/usr/local/bin/ffmpeg -threads 2 -i "$src_tmp" -vcodec mpeg2video -aspect 4:3 -b 4096k -acodec mp2 -ab 192k -ac 2 -f dvd -
> "$dst_tmp" 2>/dev/null
EXIT_CODE=$?

if [ $EXIT_CODE -ne 0 ] ; then
echo "Error: $EXIT_CODE ReTry -r 30"
/usr/local/bin/ffmpeg -threads 2 -i "$src_tmp" -vcodec mpeg2video -aspect 4:3 -b 4096k -acodec mp2 -ab 192k -ac 2 -r 30 -f

dvd - > "$dst_tmp" 2>/dev/null
EXIT_CODE=$?
fi

if [ $EXIT_CODE -ne 0 ] ; then
echo "Error: $EXIT_CODE Retry -ab 128k"
/usr/local/bin/ffmpeg -threads 2 -i "$src_tmp" -vcodec mpeg2video -aspect 4:3 -b 4096k -acodec mp2 -ab 128k -ac 2 -f dvd -
> "$dst_tmp" 2>/dev/null
EXIT_CODE=$?
fi

if [ $EXIT_CODE -ne 0 ] ; then
echo "Error: $EXIT_CODE Retry -r 30 -ab 128k"
/usr/local/bin/ffmpeg -threads 2 -i "$src_tmp" -vcodec mpeg2video -aspect 4:3 -b 4096k -acodec mp2 -ab 128k -ac 2 -r 30 -f
dvd - > "$dst_tmp" 2>/dev/null
EXIT_CODE=$?
fi

if [ $EXIT_CODE -eq 0 ] ; then
mv "$dst_tmp" "$dst"
rm "$src_tmp"
echo "Success"
else
rm "$dst_tmp"
echo "Error: $EXIT_CODE GiveUp!!"
fi
}


# check multiple boot Begin
SCRIPT_PID=~/${0##*/}.pid
if [ $1 = "-init" ] ; then
echo "initialize"
if [ -f "$SCRIPT_PID" ]; then
rm $SCRIPT_PID
fi
exit 0
fi
if [ -f $SCRIPT_PID ]; then
PID=`cat $SCRIPT_PID `
if (ps -aux | awk '{print $2}' | grep $PID >/dev/null); then
exit 1
fi
fi
echo $$ > $SCRIPT_PID


# main script
for TARG in "$@" ; do
cd "$TARG";
TranscodeAllSeek "flv FLV qt QT mov MOV mp4 MP4 avi AVI rmvb RMVB 3gp 3GP"
done


# check multiple boot End
rm $SCRIPT_PID

この変換スクリプトは、指定ディレクトリから再帰的にファイルを検索し、
変換対象のファイルがあれば、変換を行います
変換に失敗した場合、同じ処理を行わせないようになっています

変換中は、
[元ファイル名.[ext]].src_tmp
[元ファイル名.[ext]].dst_tmp
があります

変換成功した場合は
[元ファイル名.mpeg]
だけが残ります

変換失敗した場合は
[元ファイル名.[ext]].src_tmp
だけが残ります
再変換したい場合は、.src_tmpを消せばOK


この変換スクリプトにはいくつか問題があります

多重起動処理にごくまれなケースで問題があります(放置)

ffmpegでの変換処理は試行錯誤中です
失敗したら別のパラメータでリトライさせています

・30fpsでリトライ(15fpsの動画ファイル(mp4)で失敗)
・音声128kbpsでリトライ(192kbpsに失敗(3gp))
・上記2つを組み合わせたものでリトライ



cron

更新1(2012/02/26):
起動時に初期化処理追加(多重起動チェックの改善)


> crontab -l
MAILTO=root
@reboot /usr/local/etc/mediatomb/transcode_all.sh -init
*/30 * * * * /usr/local/etc/mediatomb/transcode_all.sh ~/public_html/data/Video/BRAVIA/

cronは30分毎にチェックさせています
posted by sanahi at 23:00| 滋賀 晴れ| Comment(0) | TrackBack(0) | FreeBSD | このブログの読者になる | 更新情報をチェックする

2011年08月14日

mediatombにBRAVIA対応パッチ適用

DLNAにハマり中


まず前提として、
portsから、mediatombをmake install済みとします

そこから、portsを利用したパッチ適用をして再インストールします


作者に感謝しつつ、BRAVIA対応パッチを所定の場所にDL

# cd /usr/ports/distfiles
# fetch http://blog-imgs-30.fc2.com/h/o/r/horip/mediatomb-0_12_1_bravia_support_0_1_patch.txt


次に、mediatombにMakefile.localを作成し、
パッチの適用内容を記述

# cd /usr/ports/net/mediatomb/
# ee Makefile.local <-- 新規作成


Makefile.local

PATCHFILES += mediatomb-0_12_1_bravia_support_0_1_patch.txt
PATCH_DIST_STRIP = -p0
NO_CHECKSUM = yes


あとは、

# portupgrade -f mediatomb
または
# portupgrade -rf mediatomb


これで、BRAVIAからmediatombが有効になる
・画像 jpeg OK
・音楽 mp3 OK
・動画 mpeg OK
FLVはffmpegでtranscode(mpeg2ts)したが、重すぎで使い物にならない

んー、FLVがメインなんだけどなぁ〜
どうしたものか。。。
posted by sanahi at 02:32| 滋賀 晴れ| Comment(3) | TrackBack(0) | FreeBSD | このブログの読者になる | 更新情報をチェックする

2011年06月06日

FreeBSD8.0R->8.2R(freebsd-update)

EoL(end-of-life)になってはや数ヶ月
ようやくFreeBSD8.0R->8.2RへUpgradeした

今回は初めてfreebsd-updateコマンドを使ってみた
実はZFS周りを調べるのが面倒だったとか?

freebsd-updateによるUpgradeは
Announcementに手順が書いてありました
8.0R->8.2Rもfreebsd-upgrade可能と記述されていたのでひとまず安心
http://www.freebsd.org/releases/8.2R/announce.html

と、実行する前にviが嫌いな私はeeにEDITOR環境変数を確認、設定しておく
設定のマージ作業で自動起動される場合がある

では、suで、
# freebsd-update upgrade -r 8.2-RELEASE
...
Does this look reasonable (y/n)? y
その後、延々と調査やDownloadが行われ、設定のマージへ進みます

設定がコンフリクトしたらエディタが起動して編集を強いられます
以下のような前と後の設定が両方表示されているので、
いい感じに設定ファイルとして問題ないように調整します
...
<<<<<<< current version
Port 220   --> Upgrade前の設定
=======
Port 22    --> Upgrade後の設定
>>>>>>> 8.2-RELEASE
その他は、
Does this look reasonable (y/n)? y
ずっとy

その後、更新するファイルがダラダラ表示
どうやら、ソースも取得されるらしい

終わったら、
# freebsd-update install
その後、再起動して
# freebsd-update install
念のためもう一回
# freebsd-update install

最新パッチも一応チェック
# freebsd-update fetch
Looking up update.FreeBSD.org mirrors... 4 mirrors found.
Fetching metadata signature for 8.2-RELEASE from update4.FreeBSD.org... done.
Fetching metadata index... done.
Inspecting system... done.
Preparing to download files... done.

No updates needed to update system to 8.2-RELEASE-p2.
バイナリはどうやら最新になるようだ

が、/usr/src/UPDATINGを見る限りは、ソースは最新ではない気がする
新しいcvsupファイル
/usr/share/examples/cvsup/standard-supfile
をコピーして、cvsup鯖を設定してcvsup
# cvsup -g -L 2 standard-supfile
Parsing supfile "standard-supfile"
Connecting to cvsup5.FreeBSD.org
Connected to cvsup5.FreeBSD.org
Server software version: SNAP_16_1h
Negotiating file attribute support
Exchanging collection information
Establishing multiplexed-mode data connection
Running
Updating collection src-all/cvs
 Edit src/UPDATING
  Add delta 1.632.2.19.2.3 2011.04.20.21.00.24 cperciva
  Add delta 1.632.2.19.2.4 2011.05.28.08.44.39 simon
...
やはり更新された
不要なのだろうが、念のため。。。

8.2Rにししたら、例のACPI関連エラー
http://sanahi.seesaa.net/article/136585685.html
が無くなった!
/boot/loader.confの設定消してもOK
バンザーイ!!
IdeaPadS9eとFreeBSD8.2Rの相性は良さそう
(ちなみにバッテリーは逝ってしまったのではずしている)

1つ問題が
dmesgのログから、
Setting date via ntp.
Error : hostname nor servname provided, or not known
 ********
ntpdate[519]: can't find host ntp.nict.jp
Error : hostname nor servname provided, or not known
 ********
ntpdate[519]: can't find host 0.freebsd.pool.ntp.org
Error : hostname nor servname provided, or not known
 ********
ntpdate[519]: can't find host 1.freebsd.pool.ntp.org
Error : hostname nor servname provided, or not known
 ********
ntpdate[519]: can't find host 2.freebsd.pool.ntp.org
 ********
ntpdate[519]: no servers can be used, exiting
起動時の時刻同期が失敗している模様

色々試した結果
ntpdate_enable="YES"
#ntpdate_flags="-b ntp.nict.jp" <--ダメ
#ntpdate_hosts="ntp.nict.jp"  <--ダメ
ntpdate_hosts="133.243.238.164" <--OK
で、
Setting date via ntp.
 ********
ntpdate[518]: sendto(133.243.238.164): Unknown error: 0
  ******** ntpdate[518]: step time server 133.243.238.164 offset 0.257807 sec
うまくいったようだ
posted by sanahi at 23:00| 滋賀 晴れ| Comment(0) | TrackBack(0) | FreeBSD | このブログの読者になる | 更新情報をチェックする

2011年01月01日

C++Builder2007でOLEエラー80131052, ClassID: {F8FEDD39-E3CE-4B8D-A657-9CA24686881F}.

C++Builder2005で作成されたプロジェクト(MHPTunnelのGUI部ソース)を
C++Builder2007で開こうとするとエラーになった


---------------------------
エラー
---------------------------
OLE エラー 80131052, ClassID: {F8FEDD39-E3CE-4B8D-A657-9CA24686881F}.
---------------------------
OK 詳細(D)>>
---------------------------

ぐぐると、まず以下のサイトが見つかるが。。。

Bdsproj2mak の OLE エラー ClassID {F8FEDD39-E3CE-4B8D-A657-9CA24686881F} について
http://support.embarcadero.com/article/36734

まず、ひらけないIDEはC++Builder2007であり2006ではない
そもそも、bdsproj2mak.exeが存在しない

.NetFrameworkは1.1は未インストール

と、いうことで解決には至らず

RADStudio2007 + RADStudio2010環境のRADStudio2007でNG
C++Builder6 + RADStudio2007環境のRADStudio2007では開けた

上記どちらも.NetFrameworkは、1.1は無しで、2.0 ,3.0, 3.5が入っている



んー、どうすれば。。。

とりあえず、開ける環境でやってみかな。。。
posted by sanahi at 02:32| 滋賀 曇り| Comment(0) | TrackBack(0) | C++Builder2007 | このブログの読者になる | 更新情報をチェックする

2010年09月11日

RAD Studio XE リリース

もう何日も経っていますが、
RAD Studio XE
http://www.embarcadero.com/jp/products/rad-studio
がリリースされたようです

詳しく見てませんが、
旧バージョンのライセンスとインストールイメージのダウンロードができるようですね。
http://www.embarcadero.com/jp/products/rad-studio/faq#4

Delphi7とC++Builder6が使えるようなので、コレはありがたいです。




ま、ソレと気になるのが

Delphi XE と C++Builder XE で修正された不具合一覧
http://edn.embarcadero.com/jp/article/40821
# Delphi XE と C++Builder XE には、XEのリリースにおいて修正された数千個の不具合のうち、
# Quality Central で報告されていた約千個の不具合の修正が含まれています。

結構古いバグも修正入ってるみたいでありがたいですが、
旧バージョン使ってバグ報告したら、次のバージョンで直ってるとか
結果的に有料バグ修正てことでしょうか?

なんだかな〜

RAD Studio 2010も購入して大して使わないままXEきちゃったし、
お布施ばっかりしちゃってるよなぁ〜

製品の完成度上げて欲しいわ。
もちろん無料で。
posted by sanahi at 22:41| 滋賀 | Comment(0) | TrackBack(0) | RadstudioXE | このブログの読者になる | 更新情報をチェックする

2010年06月09日

RADStudio2010 インストール

ようやくインストールした

RAD Studio 2007の入った環境にインストール
特に問題なし
InstallAware7も入れましたが、自動で
RAD Studio 2007のInstallAware6をUnInstallしてからInstallしてくれたもよう

私のデフォルトプロジェクトを以下のように設定

コンパイラオプション1

コンパイラオプション2

コンパイラオプション3

これでOKかとおもいきや

新規作成して、
新規作成して

プロジェクト見たら、

_TCHARのマップ先反映されて無いし、、、
反映されてないし

こういう使い方しないのかな・・・
タグ:C++Builder2010
posted by sanahi at 22:39| 滋賀 晴れ| Comment(0) | TrackBack(0) | RADStudio2010 | このブログの読者になる | 更新情報をチェックする

2010年05月08日

RADStudio2010

ようやく手に入れた〜
RAD Studio 2010 Upgrade
posted by sanahi at 21:49| 滋賀 | Comment(0) | TrackBack(0) | RADStudio2010 | このブログの読者になる | 更新情報をチェックする

Remarks
記事中にソースコードが含まれる場合、ソースコードの再利用、改変、「オレのものにする」等は自由ですが、
ソースコードを利用して起こった問題は一切責任を負いません。(自己責任での利用となります。)