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) | 更新情報をチェックする
この記事へのコメント
コメントを書く
コチラをクリックしてください

この記事へのトラックバック
×

この広告は180日以上新しい記事の投稿がないブログに表示されております。