/*
 *	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'等を明示的にもう少し細かく調整したいケースが生じたからです。
 */
#include "app.h"
#define VERSION		"20160909"	//最終更新日
/****************************************************************************
 *	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");
	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);
				}
				//'-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;
}
