/*
 *	app.c
 *
 *	Sofdec2EncHelper.exe - sofdec2enc.exeヘルパー
 *	Copyright (C) 2016 Naoyuki Sawa
 *
 *	* Wed Aug 24 21:05:06 JST 2016 Naoyuki Sawa
 *	- 1st リリース。
 *	  sofdec2enc.exeはパーセンテージ指定のスケーリングが出来ないので、補助ツールとして当ツールを作成しました。
 *	  また、sofdec2enc.exeはオプション指定が複雑なので、簡略化するために当ツールを作成しました。
 *	  詳細は、usage()を参照して下さい。
 *	* Wed Aug 31 23:56:52 JST 2016 Naoyuki Sawa
 *	- '-h'オプションで表示される'USAGE:'のプログラム名が間違っていたので、修正しました。
 *	  ツールの動作には、変更有りません。
 *	* Fri Sep 09 21:13:53 JST 2016 Naoyuki Sawa
 *	- '-o'オプションを追加しました。
 *	  これまでは、'入力ファイル名.usm'というファイル名でしか出力出来ませんでしたが、今後は、入力ファイルと異なる名前でも出力出来るようになりました。
 *	- '-q'オプションに、'-qオプション文字列'形式を追加しました。
 *	  このオプションを追加した目的は、'-q1'と'-q2'の差が大きすぎて、'-qmf_range'等を明示的にもう少し細かく調整したいケースが生じたからです。
 *	* Tue Dec 20 21:28:56 JST 2016 Naoyuki Sawa
 *	- ライブラリをバージョンアップした事に伴い、/clip/tool/SofdecHelper/ をリビルドした事に伴い、当ツールもリビルドしました。
 *	  当ツールのソースコード,及び,当ツールの動作には変更有りません。
 *	* Thu Dec 22 09:49:41 JST 2016 Naoyuki Sawa
 *	- ライブラリをバージョンアップした事に伴い、/clip/tool/SofdecHelper/ をリビルドした事に伴い、当ツールもリビルドしました。
 *	  当ツールのソースコード,及び,当ツールの動作には変更有りません。
 *	* Fri Dec 30 21:09:49 JST 2016 Naoyuki Sawa
 *	- 入力ファイル名のサフィックスによる自動スケーリング指定に対応しました。
 *	  関連する処理は、/clip/test.win/grp/MkTexNo.batの'Sat Sep 10 21:46:35 JST 2016'のコメント、及び、/clip/DxlibHelper.cppの'Sat Sep 10 21:46:35 JST 2016'のコメントを参照して下さい。
 *	  具体的には、以下の通りです。
 *	- これまでは、個々のプロジェクトのバッチスクリプトで、ムービーをエンコードする時にファイル名のサフィックスを判断してサイズオプションを指定していました。
 *	  しかし、入力ファイル名のサフィックスをラベルから削除する処理は既に/clip/test.win/grp/MkTexNo.batに含めてあるし、実行時の自動スケーリング処理は既に/clip/DxlibHelper.cppに含めてあるのに、
 *	  ファイル名のサフィックスを判断する処理だけが個々のプロジェクトのバッチスクリプトに依存するのは、統一性が無くて管理しづらいと思いました。
 *	  そこで、ファイル名のサフィックスを判断する処理も当ツールに含める事にしました。(以前からそうすべきだとは思っていたのですが、対応が遅れていただけです…)
 */
#include "app.h"
#define VERSION		"20161230"	//最終更新日
/****************************************************************************
 *	main
 ****************************************************************************/
int main(int argc, char* argv[]) {
	int exitCode = EXIT_SUCCESS;
#ifdef  _DEBUG
	remove("syslog.out");			//毎回ログを削除する事にした。
    #ifndef USE_JOB_DEBUG
	openlog(NULL, LOG_PERROR, LOG_USER);	//デバッグビルドでは、システムログを標準エラーにも出力する。ただし、デバッグジョブを使用する場合は、デバッグジョブのcurses表示と衝突しないよう、標準エラーには出力しない。
    #endif//USE_JOB_DEBUG
#else //_DEBUG
	setlogmask(LOG_UPTO(LOG_INFO));		//リリースビルドでは、LOG_DEBUGを抑制する。
#endif//_DEBUG
#if     (defined(GC_H) && !defined(USE_BISON_FLEX))
//	putenv("GC_LOG_FILE=CON");//ログ出力先を指定する。
//	GC_set_find_leak(1);//メモリリーク検出を開始する。
#endif//(defined(GC_H) && !defined(USE_BISON_FLEX))
#ifdef  __CLIP_SEH_H__
	SEH_init();
#endif//__CLIP_SEH_H__
	app_init(argc, argv);
	exitCode = app_main(argc, argv);
	app_exit();
#if     (defined(GC_H) && !defined(USE_BISON_FLEX))
//	CHECK_LEAKS();//メモリリークを検出する。
#endif//(defined(GC_H) && !defined(USE_BISON_FLEX))
	return exitCode;
}
/****************************************************************************
 *	usage
 ****************************************************************************/
#define SOFDEC2ENC_DEFAULTPATH	"C:\\cri\\tools\\Sofdec2\\sofdec2enc.exe"
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
__declspec(noreturn) static void usage() {
	fprintf(stderr, "Sofdec2EncHelper.exe - sofdec2enc.exeヘルパー (%s)\n", VERSION);
	fprintf(stderr, "Copyright (C) 2016 Naoyuki Sawa\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "USAGE:\n");
	fprintf(stderr, "  Sofdec2EncHelper.exe [option ...] [aviファイル名 ...]\n");
	fprintf(stderr, "  ・aviファイル名は、ワイルドカードによる複数指定も可能です。\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "OPTIONS:\n");
	fprintf(stderr, "    -o出力ファイル名    出力ファイル名をフルパスで指定します。\n");
	fprintf(stderr, "                        このオプションを指定しなければ、'入力ファイル名.usm'で出力します。\n");
	fprintf(stderr, "                        入力ファイルに、複数のaviファイルを指定した場合は、このオプションを指定してはいけません。\n");
	fprintf(stderr, "    -w横ピクセル数      <例> -w128     横サイズを128ピクセルにスケーリングします。\n");
	fprintf(stderr, "    -w横拡大率%%         <例> -w200%%    横サイズを入力画像の200%%にスケーリングします。\n");
	fprintf(stderr, "    -w分子/分母         <例> -w128/640 横サイズを128/640の割合でスケーリングします。\n");
	fprintf(stderr, "    -h縦ピクセル数      <例> -h88      縦サイズを88ピクセルにスケーリングします。\n");
	fprintf(stderr, "    -h縦拡大率%%         <例> -h75%%     縦サイズを入力画像の75%%にスケーリングします。\n");
	fprintf(stderr, "    -h分子/分母         <例> -w88/480  縦サイズを88/480の割合でスケーリングします。\n");
	fprintf(stderr, "    -s縦横ピクセル数    <例> -s256     縦横サイズを256ピクセルにスケーリングします。\n");
	fprintf(stderr, "    -s縦横拡大率%%       <例> -s50%%     縦横サイズを入力画像の50%%にスケーリングします。\n");
	fprintf(stderr, "    -s分子/分母         <例> -s2/3     縦横サイズを2/3の割合でスケーリングします。\n");
	fprintf(stderr, "    -q0                 最低画質でエンコードします。\n");
	fprintf(stderr, "    -q1                 低画質でエンコードします。(デフォルト)\n");
	fprintf(stderr, "    -q2                 高画質でエンコードします。\n");
	fprintf(stderr, "    -qオプション文字列  sofdec2enc.exeの任意のオプションを直接指定します。複数指定する場合は\"～\"で囲って下さい。\n");
	fprintf(stderr, "    -a0                 アルファ無しでエンコードします。\n");
	fprintf(stderr, "    -a1                 アルファ付きでエンコードします。ただしアルファ無しaviの場合は無視されます。(デフォルト)\n");
	fprintf(stderr, "    -pツールのパス名    sofdec2enc.exeのフルパス名を指定します。(デフォルト=%s)\n", SOFDEC2ENC_DEFAULTPATH);
	fprintf(stderr, "    -v                  コマンドライン文字列と終了ステータスを表示します。\n");
	fprintf(stderr, "    -n                  コマンドライン文字列だけを表示し、コマンドを実行しません。\n");
	fprintf(stderr, "    -h                  使い方を表示します。\n");
//{{2016/12/30追加:入力ファイル名のサフィックスによる自動スケーリング指定に対応しました。
	fprintf(stderr, "\n");
	fprintf(stderr, "    ファイル名サフィックスによる、自動スケーリング指定も可能です。\n");
	fprintf(stderr, "    ・ファイル名の中に'-1x'～'-9x'が含まれていたら、サイズオプション'-s1/1'～'-s1/9'が指定されたのと同じと見なします。\n");
	fprintf(stderr, "    ・明示的なサイズオプション指定は、ファイル名サフィックスによる自動スケーリング指定よりも優先されます。\n");
//}}2016/12/30追加:入力ファイル名のサフィックスによる自動スケーリング指定に対応しました。
	fprintf(stderr, "\n");
	fprintf(stderr, "EXAMPLE:\n");
	fprintf(stderr, "    ①Sofdec2EncHelper.exe -s50%% -q0 -pC:\\usr\\bin\\sofdec2enc.exe *.avi\n");
	fprintf(stderr, "      カレントフォルダに有る全てのaviファイルを、50%%にスケーリングして最低画質でエンコードします。\n");
	fprintf(stderr, "      sofdec2enc.exeが、C:\\usr\\bin\\sofdec2enc.exeに存在するものとします。\n");
	fprintf(stderr, "    ②Sofdec2EncHelper.exe -q\"-bitrate=0 -br_range=0,0 -qmf_range=0.0,2.5 -alpha_quality=0\" -oOut\\Output.usm In\\Input.avi\n");
	fprintf(stderr, "      In\\Input.avi.aviをエンコードして、Out\\Output.usmを出力します。\n");
	fprintf(stderr, "      品質オプションを明示的に指定して、'-q1'と'-q2'の中間位の品質とします。\n");
	exit(EXIT_FAILURE);
}
/****************************************************************************
 *	
 ****************************************************************************/
void app_init(int argc, char* argv[]) {
//{{アプリケーション特有の処理
	{
		/** no job **/
	}
//}}アプリケーション特有の処理
}
/*--------------------------------------------------------------------------*/
void app_exit() {
//{{アプリケーション特有の処理
	{
		/** no job **/
	}
//}}アプリケーション特有の処理
}
/*--------------------------------------------------------------------------*/
//\clip\tool\dpbmppp\dpbmppp.cを元にして作成しました。
int opt_w1;			//幅指定
int opt_w2;			//  指定無し      w1=0         , w2=0
				//  ピクセル指定  w1=ピクセル数, w2=0
				//  %指定         w1=%         , w2=100
				//  分数指定      w1=分子      , w2=分母
				//  (どの場合も、解析後に分子/分母に設定し直します)
int opt_h1;			//高さ指定
int opt_h2;			//  (opt_w1,w2と同様。上コメントを参照してください)
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#define Q_0	"-bitrate=0 -br_range=0,0 -qmf_range=0.0,0.0 -alpha_quality=0 -hcfilter=ON"	//0=最低画質
#define Q_1	"-bitrate=0 -br_range=0,0 -qmf_range=0.0,0.0 -alpha_quality=0"			//1=低画質
#define Q_2	""										//2=高画質
const char* const TBL_q[]={Q_0,Q_1,Q_2};
const char* opt_q = Q_1;	//品質オプション。実際には、この文字列をsofdec2enc.exeへそのまま引き渡すので、'-qオプション文字列'形式で指定すれば、品質以外のオプションも指定可能です。
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
int opt_a = 1;			//0=アルファ無し,1=アルファ付き。
				//ただし、アルファ付きを指定しても、アルファ無しaviだったならば、sofdec2enc.exeが無視して、エラーにもならず安全なので、デフォルトでアルファ付きとしました。
				//特別なケースとして、アルファ付きaviでも、確実にアルファ無しusmを出力したい場合のみ、'-a0'オプションを指定して下さい。
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
int opt_v;			//コマンドを実行する前にコマンドライン文字列を表示し、コマンドを実行した後に終了ステータスを表示する。
int opt_n;			//コマンドライン文字列だけを表示して、コマンドを実行しない。
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
const char* opt_o;				//出力ファイルのフルパス名。'-o'オプションが指定されなければ、'入力ファイル名.usm'とします。
const char* opt_p = SOFDEC2ENC_DEFAULTPATH;	//sofdec2enc.exeのパス名。デフォルトと異なる場所に有る場合は、'-p'オプションでsofdec2enc.exeのフルパス名を指定して下さい。
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
//\clip\tool\dpbmppp\dpbmppp.cのcmdline_whs()を元にして作成しました。
static void cmdline_whs(char* arg, int* p1, int* p2) {
	if(*p1 || *p2) { usage(); }			//'-w'が二回指定された,又は,'-h'が二回指定されたらエラー。
	*p1 = strtol(arg, &arg, 10);			//ピクセル数,又は,パーセンテージ,又は,分子を取得する。
	if(*p1 <= 0) { usage(); }			//数値の書式エラー,又は,0以下の値が指定されたらエラー。
	if(*arg) {					//数値の後に文字が有れば…
		switch(*arg) {
		case '%':				//パーセンテージ指定ならば…
			arg++;				//'%'の次へ進める。
			*p2 = 100;			//分母を100とする。
			break;
		case '/':				//分数指定ならば…
			arg++;				//'/'の次へ進める。
			*p2 = strtol(arg, &arg, 10);	//分母を取得する。
			if(*p2 <= 0) usage();		//数値の書式エラー,又は,0以下の値が指定されたらエラー。
			break;
		//default:
		//	//↓すぐ下でまとめてエラー扱い
		}
		if(*arg) { usage(); }			//'%',又は,分母の後に余分な文字が有ればエラー。
	}
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
int app_main(int argc, char* argv[]) {
	int exitCode = EXIT_SUCCESS;
	{
		char* endptr;
		int opt;
		opterr = 0; //オプションエラーは当プログラム内で処理するので、getopt()内でのエラー表示は不要。
		while((opt = getopt(argc, argv, "o:x:y:s:q:a:p:vnh")) != -1) {	//コマンドラインオプションを処理する。
			switch(opt) {
			case 'o':
				{
					opt_o = optarg;
				}
				break;
			case 'x':
				{
					cmdline_whs(optarg, &opt_w1, &opt_w2);
				}
				break;
			case 'y':
				{
					cmdline_whs(optarg, &opt_h1, &opt_h2);
				}
				break;
			case 's':
				{
					cmdline_whs(optarg, &opt_w1, &opt_w2);
					cmdline_whs(optarg, &opt_h1, &opt_h2);
				}
				break;
			case 'q':
				{
					if(!strcmp(optarg, "0") ||	//-q0
					   !strcmp(optarg, "1") ||	//-q1
					   !strcmp(optarg, "2")) {	//-q2
						opt_q = TBL_q[*optarg - '0'];
					} else {
						//最低限のエラーチェックを行う。
						while(isblank(*optarg)) { optarg++; }	//┬空白を除いた最初の文字が'-'で始まっていなければ、明らかにsofdec2enc.exeへの有効なオプションではない。
						if(*optarg != '-') { usage(); }		//┘ただし、このエラーチェックを行っても、複数のオプションを指定した場合の二つ目以降のエラーは検出出来ないので、使用者が注意する事。
						opt_q = optarg;		//-qオプション文字列
					}
				}
				break;
			case 'a':
				{
					opt_a = strtol(optarg, &endptr, 10);
					if(*endptr || ((unsigned)opt_a > 1)) { usage(); }	//数値の書式エラー,又は,0～1の範囲外ならばエラー。
				}
				break;
			case 'p':
				{
					opt_p = optarg;
				}
				break;
			case 'v':
				{
					opt_v = 1;
				}
				break;
			case 'n':
				{
					opt_n = 1;
				}
				break;
			case 'h':
			default:
				usage();
			}
		}
	}
	if(optind >= argc) { usage(); }				//少なくとも一つの入力ファイルが指定されていなければいけない。
	if(opt_o && ((argc - optind) >= 2)) { usage(); }	//複数の一つの入力ファイルを指定した場合は、'-o'オプションを指定してはいけない。
	{
		//VFWを初期化する。
		AVIFileInit();
		//各aviファイルについて…
		while(optind < argc) {
			//aviファイルのストリーム情報を取得する。
			char*		pAviFileName = argv[optind];
			PAVIFILE	pAviFile;
			PAVISTREAM	pAviStream;
			AVISTREAMINFO	AviStreamInfo;
			HRESULT		hr;
			hr = AVIFileOpen(&pAviFile, pAviFileName, OF_READ|OF_SHARE_DENY_WRITE, NULL);
			if(hr) { error(EXIT_FAILURE, 0, "%s: AVIFileOpenが失敗しました。", pAviFileName); }
			hr = AVIFileGetStream(pAviFile, &pAviStream, streamtypeVIDEO, 0);
			if(hr) { error(EXIT_FAILURE, 0, "%s: AVIFileGetStreamが失敗しました。", pAviFileName); }
			hr = AVIStreamInfo(pAviStream, &AviStreamInfo, sizeof AviStreamInfo);
			if(hr) { error(EXIT_FAILURE, 0, "%s: AVIStreamInfoが失敗しました。", pAviFileName); }
			hr = AVIStreamRelease(pAviStream);
			if(hr) { error(EXIT_FAILURE, 0, "%s: AVIStreamReleaseが失敗しました。", pAviFileName); }
			hr = AVIFileRelease(pAviFile);
			if(hr) { error(EXIT_FAILURE, 0, "%s: AVIFileReleaseが失敗しました。", pAviFileName); }
			{
				GString* s = g_string_new(NULL);
				//コマンド名を指定する。
			//{{※要検討
			//不可	g_string_append_printf(s, "\"%s\"", opt_p);	//本当はこうしたいのだが、「ファイル名、ディレクトリ名、またはボリューム ラベルの構文が間違っています。」というエラーになってしまった。
				g_string_append_printf(s, "%s", opt_p);		//仕方が無いのでこうした。もしコマンドのプルパス名の途中に空白文字が有ると、上手く動かないと思うが…
			//}}※要検討
				//入力ファイルを指定する。
				g_string_append_printf(s, " -video00=\"%s\"", pAviFileName);
				//'-a'オプションを反映する。
				switch(opt_a) {
				default:DIE();
				case 0:	//アルファ無し
					/** no job **/
					break;
				case 1:	//アルファ有り
					g_string_append_printf(s, " -alpha00=\"%s\"", pAviFileName);
					break;
				}
				//出力ファイルを指定する。
				if(opt_o) {	//'-o'オプションが指定されていたら…
					g_string_append_printf(s, " -out=\"%s\"", opt_o);
				} else {	//'-o'オプションが指定されていなければ…
					char path[MAX_PATH], drive[MAX_DRIVE], dir[MAX_DIR], fname[MAX_FNAME];
					_splitpath(pAviFileName, drive, dir, fname, NULL);
					_makepath(path, drive, dir, fname, ".usm");
					g_string_append_printf(s, " -out=\"%s\"", path);
				}
				//サイズオプションが一つでも指定されていたら、ピクセル数を指定する。
				if((opt_w1 || opt_w2) || (opt_h1 || opt_h2)) {
					int w = AviStreamInfo.rcFrame.right - AviStreamInfo.rcFrame.left;
					int h = AviStreamInfo.rcFrame.bottom - AviStreamInfo.rcFrame.top;
					if(opt_w1) {
						if(opt_w2) {
							w = ((w * opt_w1) + (opt_w2 - 1)) / opt_w2;	//パーセンテージ指定,又は,分数指定の場合
						} else {
							w = opt_w1;	//ピクセル指定の場合
						}
					}
					if(opt_h1) {
						if(opt_h2) {
							h = ((h * opt_h1) + (opt_h2 - 1)) / opt_h2;	//パーセンテージ指定,又は,分数指定の場合
						} else {
							h = opt_h1;	//ピクセル指定の場合
						}
					}
					g_string_append_printf(s, " -scale=%d,%d", w, h);
//{{2016/12/30追加:入力ファイル名のサフィックスによる自動スケーリング指定に対応しました。
				//サイズオプションが一つも指定されていなければ…(明示的なサイズオプションと、サフィックスによる自動スケーリング指定の両方が指定されていた場合は、明示的なサイズオプションを優先する事にした。)
				} else {
				    //{{この範囲の判定処理は、DxlibHelper.cppのGetAutoScale()と同じにしました。DxlibHelper.cppのGetAutoScale()の判定処理と、当ツールの判定処理を、一致させておく必要が有ります。
					//ファイル名の中に、'-1x'～'-9x'が有るか?(もっと厳密に検査する方が望ましいのだが、実用上はこれでも充分だろう。)
					regexp* prog = regcomp("-([1-9])x");
					if(regexec(prog, pAviFileName)) {
						int AutoScale = *prog->startp[1] - '0';	//ファイル名の中に'-1x'～'-9x'が有れば、自動スケーリング値と見なす。
				    //}}この範囲の判定処理は、DxlibHelper.cppのGetAutoScale()と同じにしました。DxlibHelper.cppのGetAutoScale()の判定処理と、当ツールの判定処理を、一致させておく必要が有ります。
						int w = AviStreamInfo.rcFrame.right - AviStreamInfo.rcFrame.left;	//┐
						int h = AviStreamInfo.rcFrame.bottom - AviStreamInfo.rcFrame.top;	//│
						w = (w + (AutoScale - 1)) / AutoScale;					//├'-s1/1'～'-s1/9'が指定された場合と、同じ結果になります。
						h = (h + (AutoScale - 1)) / AutoScale;					//│
						g_string_append_printf(s, " -scale=%d,%d", w, h);			//┘
					}
//}}2016/12/30追加:入力ファイル名のサフィックスによる自動スケーリング指定に対応しました。
				}
				//'-q'オプションを反映する。
				if(*opt_q) {	//空文字でなければ…(空文字でも下記の処理を行っても害は無いのだが、無駄な空白の連続を避けるためにこの判定を行う事にした)
					g_string_append_printf(s, " %s", opt_q);
				}
				//コマンドを実行する。
				// - \clip\tool\dpcc33\app.cのchildproc()を元にして作成しました。
				{
					FILE* fp;
					//'-v',又は,'-n'オプションが指定されていたら、コマンドライン文字列を表示する。
					if(opt_v || opt_n) {
						fprintf(stderr, "%s\n", s->str);
					}
					//'-n'オプションが指定されていなければ…
					if(!opt_n) {
						//パイプを作成してコマンドを実行する。
						fp = _popen(s->str, "r");
						if(!fp) { error(EXIT_FAILURE, 0, "%sを実行できません。", s->str); }
						//コマンドの標準出力を処理する。
						{
							char* glbuf = NULL;	//┬getline()用
							int   glcap = 0;	//┘
							while(getline(&glbuf, &glcap, fp) != EOF) {
								fputs(glbuf, stdout);
							}
							free(glbuf);	//getline()用バッファを開放する。
						}
						//パイプを閉じる。
						exitCode = _pclose(fp);
						//'-v'オプションが指定されていたら、終了ステータスを表示する。
						if(opt_v) {
							printf("Exitcode = %d\n", exitCode);
						}
					}
				}
				//コマンドラインの文字列を開放する。
				g_string_free(s, 1/*free_segment*/);
				//コマンドがエラー終了していたら、ループを抜ける。
				if(exitCode) { break; }
			}
			//次の入力ファイルへ進める。
			optind++;
		}
		//VFWを終了する。
		AVIFileExit();
	}
	return exitCode;
}
