/*****************************************************************************
 *
 *  interfaceffmpeg.cpp -- Interface to FFmpeg CLI.
 *
 *  Copyright (C) 2010 Starbuck <starbuck@labdv.com>
 *  All rights reserved.
 *
 *  This file is part of the Encoder module of the DVDx 4 application.
 *
 *  GNU Lesser General Public License Usage
 *  This file may be used under the terms of the GNU Lesser General
 *  Public License version 2.1 as published by the Free Software
 *  Foundation and appearing in the file LICENSE.LGPL included in the
 *  packaging of this file.  Please review the following information to
 *  ensure the GNU Lesser General Public License version 2.1 requirements
 *  will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
 *
 *  GNU General Public License Usage
 *  Alternatively, this file may be used under the terms of the GNU
 *  General Public License version 3.0 as published by the Free Software
 *  Foundation and appearing in the file LICENSE.GPL included in the
 *  packaging of this file.  Please review the following information to
 *  ensure the GNU General Public License version 3.0 requirements will be
 *  met: http://www.gnu.org/copyleft/gpl.html.
 *
 *****************************************************************************/

#define	RX_PARSE_FFMPEG_SIGNATURE		"(FFmpeg)\\s+([^\\s]+)"
#ifdef WIN32
#define	RX_PARSE_FFMPEG_CODECS_EV		"\\s(?:D|\\s)EV....([^=]\\w+)\\s+([^\\s][^\\n]*)\\r\\n"
#define	RX_PARSE_FFMPEG_CODECS_EA		"\\s(?:D|\\s)EA....([^=]\\w+)\\s+([^\\s][^\\n]*)\\r\\n"
#define	RX_PARSE_FFMPEG_CODECS_ES		"\\s(?:D|\\s)ES....([^=]\\w+)\\s+([^\\s][^\\n]*)\\r\\n"
#define	RX_PARSE_FFMPEG_MUXERS_E		"\\s(?:D|\\s)E.([^=]\\w+)\\s+([^\\s][^\\n]*)\\r\\n"
#define	RX_PARSE_FFMPEG_OUTPUT_HELP		"([^\\s]+)\\s+-\\s+([^\\n]*)\\r\\n"
#else
#define	RX_PARSE_FFMPEG_CODECS_EV		"\\s(?:D|\\s)EV....([^=]\\w+)\\s+([^\\s][^\\n]*)\\n"
#define	RX_PARSE_FFMPEG_CODECS_EA		"\\s(?:D|\\s)EA....([^=]\\w+)\\s+([^\\s][^\\n]*)\\n"
#define	RX_PARSE_FFMPEG_CODECS_ES		"\\s(?:D|\\s)ES....([^=]\\w+)\\s+([^\\s][^\\n]*)\\n"
#define	RX_PARSE_FFMPEG_MUXERS_E		"\\s(?:D|\\s)E.([^=]\\w+)\\s+([^\\s][^\\n]*)\\n"
#define	RX_PARSE_FFMPEG_OUTPUT_HELP		"([^\\s]+)\\s+-\\s+([^\\n]*)\\n"
#endif

#define	RX_PARSE_FFMPEG_PROGRESS		"frame=\\s*([\\d\\.]*).*fps=\\s*([\\d\\.]*).*size=\\s*([\\d\\.]*).*time=\\s*([\\d\\.]*)"
#define	RX_PARSE_FFMPEG_PROGRESS_AO		"size=\\s*([\\d\\.]*).*time=\\s*([\\d\\.]*).*bitrate=\\s*([\\d\\.]*)"
#define	RX_PARSE_FFMPEG_OUTPUT_VALID	"size=(.*)time=(.*)bitrate=(.*)"

#include "../video/video_functions.h"
#include "interfaceffmpeg.h"
#include "macros.h"
#include <math.h>
#include <QProcess>
#include <QDir>
#include <QList>
#include <QRegExp>
#include <QTime>
#include <QDebug>

InterfaceFFmpeg::InterfaceFFmpeg()
{
	setVideoSynonyms();
	setAudioSynonyms();
	setSubtitleSynonyms();
	setMuxerSynonyms();

	// Supported codecs and muxers
	muxers = new QStringList;
	videos = new QStringList;
	audios = new QStringList;
	subtitles = new QStringList;

	*muxers << "3g2" << "3gp" << "ac3" << "adts" << "aiff" << "amr" 
		<< "asf" << "au" << "avi" << "avm2" 
		<< "dts" << "dv" << "dvd" << "f64be" << "f64le" 
		<< "filmstrip" << "flac" << "flv" 
		<< "h263" << "h264" << "ipod" << "m4a" << "m4v" 
		<< "matroska" << "mov" << "mp2" << "mp3" << "mp4"
		<< "mpeg" << "mpeg1video" << "mpeg2video" << "mpegts" 
		<< "multi" << "mxf" << "nut" << "ogg" << "psp"
		<< "rawvideo" << "rcv" << "rm" << "RoQ" 
		<< "s16be" << "s16le" << "s32be" << "s32le" << "s64be" << "s64le" << "spdif" 
		<< "svcd" << "swf" << "vcd" << "vob" << "wav" << "webm";

	*videos << "copy" << "dvvideo" << "dnxhd" << "ffv1" << "ffvhuff" << "flashsv" << "flv" 
		<< "h263" << "huffyuv"
		<< "libtheora" << "libx264" << "libxvid" << "libvpx" 
		<< "ljpeg" << "mjpeg"  << "mpeg1video" << "mpeg2video" 
		<< "mpeg4" << "msmpeg4" << "msmpeg4v1" << "msmpeg4v2" << "novideo"
		<< "qtrle" << "rawvideo" << "roqvideo" << "rv10" << "rv20" 
		<< "snow" << "svq1" << "wmv1" << "wmv2";

	*audios /*<< "aac"*/ << "ac3" << "alac" 
		<< "adpcm_ima_qt" << "adpcm_ima_wav" << "adpcm_ms" << "adpcm_swf"
		<< "copy" << "flac" << "g726"
		/*<< "libfaac"*/ << "libgsm" << "libmp3lame" << "libopencore_amrnb" << "libvorbis" 
		<< "mp2" << "multi" << "noaudio" << "pcm" << "pcm_alaw" 
		<< "pcm_s16be" << "pcm_s16le" << "pcm_s32be" << "pcm_s32le" << "pcm_f64be" << "pcm_f64le" 
		<< "roq_dpcm" << "sonic" << "sonicls" << "vorbis" << "wmav1" << "wmav2";

	*subtitles << "copy" << "dvdsub" << "inlay" << "nosubtitle" << "xsub";

	for (int i = 0; i < muxers->count(); i++)
	{
		muxerVideos.insert( (*muxers)[i], QStringList() );
		muxerAudios.insert( (*muxers)[i], QStringList() );
		muxerSubtitles.insert( (*muxers)[i], QStringList() );
	}

	// Virtual muxers
	*muxers << "mpeg_vcd";

	// Muxer -> codecs
	muxerVideos["3g2"] << "h263" << "x264" << "x264_lr" /*<< "xvid"*/ << "mpeg4" << "novideo";
	muxerAudios["3g2"] << "aac" << "aac_lr" << "libopencore_amrnb" << "noaudio";
	muxerSubtitles["3g2"] << "inlay" << "nosubtitle";

	muxerVideos["3gp"] << "h263" << "x264" << "x264_lr" /*<< "xvid"*/ << "mpeg4" << "novideo";
	muxerAudios["3gp"] << "aac_lr" << "libopencore_amrnb" << "noaudio";
	muxerSubtitles["3gp"] << "inlay" << "nosubtitle";

	/*experimental
	muxerVideos["asf"] << "wmv1" << "wmv2";
	muxerAudios["asf"] << "wmav1" << "wmav2";
	muxerSubtitles["asf"] << "inlay";
	*/

	muxerVideos["avi"] /*<< "dvvideo" << "dnxhd" << "ffv1" << "ffvhuff" << "huffyuv" */
						<< "xvid" /*<< "mpeg4" << "msmpeg4" << "msmpeg4v1" << "msmpeg4v2"*/;
	muxerAudios["avi"] << "aac" << "ac3" /*<< "adpcm_ms" */ 
						/*<< "ljpeg" << "mjpeg" << "flac" << "g726"*/
						<< "libfaac" /*<< "libgsm" << "libmp3lame"*/ << "mp3"
						/*<< "mp2" << "multi" */
						<< "pcm" /*<< "pcm_s16le" << "pcm_s32le"<< "pcm_f64le"*/;
	muxerSubtitles["avi"] << "inlay" /*<< "xsub"*/;

	/*experimental
	muxerVideos["dv"] << "dvvideo";
	muxerAudios["dv"] << "ac3" 
						<< "pcm" << "pcm_s16le";
	muxerSubtitles["dv"] << "inlay";
	*/

	muxerVideos["flv"] << "x264" << "x264_lr" << "x264_hd" /*<< "svq1"*/;
	muxerAudios["flv"] << "aac_flv" /*<< "adpcm_swf"*/ << "mp3_flv" << "mp3_flv_lr"
						/*<< "pcm" << "pcm_s16le" << "pcm_s32le" << "pcm_f64le"*/;
	muxerSubtitles["flv"] << "inlay";

	muxerVideos["ipod"] << "copy" << "x264" << "x264_lr" << "x264_hd" /*<< "xvid"*/ << "mpeg4";
	muxerAudios["ipod"]  << "copy" << "aac" << "aac_hq" << "aac_lr";
	muxerSubtitles["ipod"] << "inlay";

	muxerVideos["matroska"] /*<< "dvvideo" << "dnxhd" << "ffv1" << "ffvhuff" << "huffyuv" */
							<< "x264" /*<< "x264_lr"*/ << "x264_hd" << "x264_divx"
							/*<< "mpeg4" << "msmpeg4" << "msmpeg4v1" << "msmpeg4v2"*/
							<< "vp8" << "libvpx"
							<< "theora" << "xvid";
	muxerAudios["matroska"] /*<< "aac" << "aac_hq"*/ << "ac3"/* << "alac" << "adpcm_ms" */ 
							<< "flac" /*<< "g726"*/
							/*<< "libfaac" *<< "libgsm"*/ << "libmp3lame" 
							<< "mp2" /*<< "multi" */
							<< "pcm" << "pcm_s32be" << "pcm_s32le" << "pcm_f64be" << "pcm_f64le"
							<< "vorbis";
	muxerSubtitles["matroska"] << "inlay" /*<< "xsub"*/;

	muxerVideos["mov"] /*<< "h263"*/ << "copy" << "x264"  << "x264_lr" << "x264_hd" /*<< "xvid"*/ << "mpeg4"
						/*<< "qtrle" << "svq1"*/;
	muxerAudios["mov"] << "copy" << "aac" << "aac_hq" << "aac_lr" 
						<< "alac" /*< "adpcm_ima_qt"*/
						<< "amrnb" << "libopencore_amrnb"
						<<"libmp3lame" << "mp3" << "mp3_lr" /*<< "mp2"*/
						<< "pcm" << "pcm_s16le" << "pcm_s32le" << "pcm_s64le" << "pcm_alaw";
	muxerSubtitles["mov"] << "inlay";

	muxerVideos["mp4"] << "h263" << "x264"  << "x264_lr" << "x264_hd" /*<< "xvid"*/ << "mpeg4";
	muxerAudios["mp4"]   << "aac" << "aac_hq" << "ac3" << "libmp3lame" << "mp3_lr" << "mp2";
	muxerSubtitles["mp4"] << "inlay";

	muxerVideos["mpeg"] << "mpeg2video";
	muxerAudios["mpeg"] << "mp2" /*<< "ac3" << "libmp3lame" << "mp3" << "multi"*/ 
						<< "pcm" << "pcm_s16be" << "pcm_s32be";
	muxerSubtitles["mpeg"] /*<< "dvdsub"*/ << "inlay";

	muxerVideos["mpeg_vcd"] << "mpeg1video";
	muxerAudios["mpeg_vcd"] << "mp2_vcd";
	muxerSubtitles["mpeg_vcd"] << "inlay";

	//*experimental will be worth it with H.264 working
	//muxerVideos["mpegts"] << "mpeg1video" << "mpeg2video" << "mpeg4"
	//						/*<< "x264" << "x264_lr" << "x264_hd" << "xvid"*/;
	//muxerAudios["mpegts"] /*<< "aac" << "ac3" << "libmp3lame"*/ << "mp2" /*<< "multi"*/ 
	//						/*<< "pcm" << "pcm_s16be" << "pcm_s32be"*/;
	//muxerSubtitles["mpegts"] /*<< "dvdsub"*/ << "inlay";

	/*experimental
	muxerVideos["mxf"] << "dnxhd" << "dvvideo" << "mpeg2video";
	muxerAudios["mxf"] << "aac" << "ac3" << "libmp3lame" << "mp2" << "multi"
						<< "pcm" << "pcm_s16le" << "pcm_s24le";
	muxerSubtitles["mxf"] << "inlay" << "nosubtitle";
	*/

	muxerVideos["ogg"] << "theora";				// since 2009, OGG is Theora+Vorbis for HTML5 (Firefox)
	muxerAudios["ogg"] << "flac" << "vorbis";	// we don't want ogv, ogm and oga (sounds no future)
	muxerSubtitles["ogg"] << "inlay";

	muxerVideos["psp"] << "x264_psp" << "x264_lr";
	muxerAudios["psp"]  << "aac" << "aac_lr";
	muxerSubtitles["psp"] << "inlay";

	/*experimental
	muxerVideos["rm"] << "rv10" << "rv20" ;
	muxerAudios["rm"] << "aac" << "libvorbis";
	muxerSubtitles["rm"] << "inlay";
	*/
	/*experimental
	muxerVideos["swf"] << "flashsv" << "flv" << "svq1";
	muxerAudios["swf"] << "aac" << "faac" << "libfaac" << "adpcm_swf" << "libmp3lame" << "mp3";
	muxerSubtitles["swf"] << "inlay";
	*/

	muxerVideos["webm"] << "vp8" << "libvpx";
	muxerAudios["webm"] << "vorbis";
	muxerSubtitles["webm"] << "inlay";

	// Audio formats
	muxerAudios["ac3"] << "ac3";
	muxerAudios["au"] << "pcm" << "pcm_s16be" << "pcm_s16le" << "pcm_s32be" << "pcm_s32le";
	muxerAudios["mp3"] << "libmp3lame"  << "mp3_lr";
	muxerAudios["m4a"] << "aac" << "alac";
	muxerAudios["wav"] << "adpcm_ms" << "adpcm_ima_wav" << "libmp3lame" << "pcm"
						<< "pcm" /*<< "pcm_s16le" << "pcm_s32le"<< "pcm_f64le"*/;
}

void InterfaceFFmpeg::setMuxerSynonyms()
{
	muxerSynonyms.clear();

	muxerSynonyms[ "mkv" ] = "matroska";
	muxerSynonyms[ "wmv" ] = "asf";
}

void InterfaceFFmpeg::setVideoSynonyms()
{
	videoSynonyms.clear();

	videoSynonyms[ "dv" ] = "dvvideo";
	videoSynonyms[ "theora" ] = "libtheora";
	videoSynonyms[ "x264" ] = "libx264";
	videoSynonyms[ "x264_lr" ] = "libx264";
	videoSynonyms[ "x264_hd" ] = "libx264";
	videoSynonyms[ "xvid" ] = "libxvid";
}

void InterfaceFFmpeg::setAudioSynonyms()
{
	audioSynonyms.clear();

	audioSynonyms[ "aac" ] = "libfaac";
	audioSynonyms[ "aac_lr" ] = "libfaac";
	audioSynonyms[ "aac_flv" ] = "libfaac";
	audioSynonyms[ "amrnb" ] = "libopencore_amrnb";
	audioSynonyms[ "gsm" ] = "libgsm";
	videoSynonyms[ "mp2_vcd" ] = "mp2";
	audioSynonyms[ "mp3" ] = "libmp3lame";
	audioSynonyms[ "mp3_flv" ] = "libmp3lame";
	audioSynonyms[ "vorbis" ] = "libvorbis";
}

void InterfaceFFmpeg::setSubtitleSynonyms()
{
	subtitleSynonyms.clear();
}

int InterfaceFFmpeg::getEstimatedOutputFileSize(const int frames)
{
	double KBR(1.0);	// to correct size estimation for VBR
	int bitrate(0);

	if (settings->output.profile.level == Video::Profile::User)
	{
		if (!settings->output.video.codec.isEmpty() 
			&& settings->output.video.codec != "novideo" )
			bitrate += settings->output.video.bitrate;
		
		if (!settings->output.audio.codec.isEmpty()
			&& settings->output.audio.codec != "noaudio" )
			bitrate += settings->output.audio.kbps;
	}
	else if (settings->output.profile.level != Video::Profile::None)
	{
		if (!settings->output.profile.video.codec.isEmpty() 
			&& settings->output.profile.video.codec != "novideo" )
			bitrate += settings->output.profile.video.bitrate;
		
		if (!settings->output.profile.audio.codec.isEmpty() 
			&& settings->output.profile.audio.codec != "noaudio" )
			bitrate += settings->output.profile.audio.bitrate;
	}

	// KBR could be adjusted depending on video codec, picture size and bitrate
	// (for example, h.263 352x288 64kbps will be encoded at ~100kbps so KBR would be about 1.30)  	
	//if (bitrate < 128)
	//{
	//	KBR = 1.30;
	//}
	//else if (bitrate < 256)
	//{
	//	KBR = 1.20;
	//}
	int fps = !settings->input.video.codec.isEmpty() && settings->input.video.fps > 0 ?
			settings->input.video.fps : 25.0;

	int fileSize = int((double)frames / settings->input.video.fps * (double)bitrate * KBR / 8.0 * 1000.0);
		
	return fileSize;
}

QStringList InterfaceFFmpeg::profileCommandLine(const encoder_profile_t *profile, const QString &output,
	int frames, int seekPosition)
{
	// Note: we can't use FFmpeg with -target vcd because ite creates MPEG-1 16:9 (16:9 isn't VCD compliant)

	QStringList args;

	args << "-y";

	args << "-i" << settings->input.source.path;

	if ( profile->muxer == "ipod" )
	{
		args << "-f" << profile->muxer;
	}
	else if ( profile->muxer == "webm" )
	{
		args << "-f" << profile->muxer;
	}
	if (settings->input.video.display_aspect > 1.31 && settings->input.video.display_aspect < 1.35)
	{
		args << "-aspect" << "4:3";
	}
	else if (settings->input.video.display_aspect > 1.75 && settings->input.video.display_aspect < 1.79)
	{
		args << "-aspect" << "16:9";
	}

	if ( settings->output.video.fps > 0.0 )
	{
		args << "-r" << QString("%1").arg(settings->output.video.fps);
	}
	else if ( settings->input.video.fps > 0.0 )
	{
		args << "-r" << QString("%1").arg(settings->input.video.fps);  // required for AVC in MPEG-TS
	}

	if ( frames > 0 )
	{
		//args << "-fs" << QString::number(getEstimatedOutputFileSize(frames));

		double seconds_to_encode = (double)frames / 25.0;  // assuming avg fps is 25.0

		if (seconds_to_encode == 0.0) seconds_to_encode = 1.0;  // a minimum to encode

		QTime time_to_encode( 0, 0, floor(seconds_to_encode), seconds_to_encode - floor(seconds_to_encode) );

		args << "-t" << time_to_encode.toString( "hh:mm:ss.z" );

		if (!settings->input.no_video)
		{
			args << "-vframes" << QString::number(frames);
		}
		else if (!settings->input.no_audio)
		{
			args << "-aframes" << QString::number(frames);
		}
	}
	
	if ( seekPosition > 0 )
	{
		args << "-ss" << QString::number(seekPosition);
	}

	// Encoder options
	QMapIterator<QString, QString> optionIterator(settings->output.options);
	while (optionIterator.hasNext())
	{
		optionIterator.next();
		args << optionIterator.key() << optionIterator.value();
	}

	if ( profile->video.codec.isEmpty() || profile->video.codec == "novideo" )
	{
		args << "-vn";
	}
	else
	{
		QStringList vf;

		if ( profile->video.codec == "h263" || profile->video.codec == "mpeg1video" )
		{
			// H.263 is CIF/SIF 4:3 display
			if (settings->input.video.display_aspect >= 1.35)
			{
				QSize rez(settings->input.video.scaledRez);
				rez.scale( profile->video.rez, Qt::KeepAspectRatio );

				int sarCorrection(rez.height());

				// Pixel Aspect Ratio correction for analog-to-digital conversion
				if (profile->standard == "PAL" 
					|| profile->video.rez.height() == 288 
					|| profile->video.rez.height() == 576)
				{
					sarCorrection = int((double)rez.height() * 12.0 / 11.0);
				}
				else
				{
					sarCorrection = int((double)rez.width() * 11.0 / 10.0);
				}
				rez.setHeight(sarCorrection);

				int vPad = (profile->video.rez.height() - rez.height()) / 2;

				if (settings->input.video.display_aspect >= 1.35)
				{
					vf << QString("scale=%1:%2,pad=%3:%4:0:%5:000000")
								.arg(rez.width())
								.arg(rez.height())
								.arg(profile->video.rez.width())
								.arg(profile->video.rez.height())
								.arg(vPad);
				}
			}
			else
			{
				args << "-s" << QString("%1x%2").arg(profile->video.rez.width()).arg(profile->video.rez.height());
			}
		}
		else
		{
			args << "-s" << QString("%1x%2").arg(profile->video.rez.width()).arg(profile->video.rez.height());
		}

		if ( !vf.isEmpty() )
		{
			args << "-vf" << vf.join(",");
		}

		args << "-vb" << QString("%1k").arg(profile->video.bitrate);

		// h236 parameters
		if ( profile->video.codec == "h263" )
		{
			args << "-flags" << "+obmc+aic";
		}

		// mpeg4 presets
		if ( profile->video.codec == "mpeg4" )
		{
			if ( profile->muxer == "avi" )
			{
				args << "-vtag" << "XVID";  // for appliance compatibility
			}

			if ( settings->input.video.fps < 15.0 )
			{
				args << "-r" << QString::number(10);
			}
			else if ( settings->input.video.fps < 23.96 )
			{
				args << "-r" << QString::number(15);
			}
			else if ( settings->input.video.fps < 24.0 )
			{
				args << "-r" << "24000/1001";
			}
			else if ( settings->input.video.fps < 24.5 )
			{
				args << "-r" << QString::number(24);
			}
			else if ( settings->input.video.fps < 29.0 )
			{
				args << "-r" << QString::number(25);
			}
			else if ( settings->input.video.fps < 30.0 )
			{
				args << "-r" << "30000/1001";
			}
			else
			{
				args << "-r" << QString::number(30);
			}

			// High quality recommendations from FFmpeg FAQ
			args << "-mbd" << "rd";
			args << "-flags" << "+mv4+aic";
			args << "-trellis" << QString::number(2);
			args << "-cmp" << QString::number(2);
			args << "-subcmp" << QString::number(2);
			args << "-g" << QString::number(300);
		}
		
		// xvid presets
		if ( profile->video.codec == "xvid" )
		{
			args << "-vtag" << "XVID";

			//*experimental we should use FFmpeg for MPEG-4 and MEncoder for Xvid
			// Profiles are from slowest to fastest but not sure better quality
			switch (profile->level)
			{
			case Video::Profile::Low:
				args << "-g" << "250" << "-bf" << "2" << "-me_method" << "phods" 
					<< "-qmin" << "2" << "-qmax" << "15";
				break;
			case Video::Profile::Medium:
				args << "-g" << "250" << "-flags" << "+mv4+mv0+umv" << "-bf" << "2" << "-me_method" << "epzs" 
					<< "-qmax" << "6";
				break;
			case Video::Profile::High:
				args << "-g" << "250" << "-qmin" << "2" << "-qmax" << "8" << "-bf" << "0" 
					<< "-mbd" << "rd" << "-me_method" << "hex";
				break;
			}
		}

		// x264 presets
		if ( profile->video.codec == "x264" 
			|| profile->video.codec == "x264_lr" || profile->video.codec == "x264_hd"  
			|| profile->video.codec == "x264_psp"  || profile->video.codec == "x264_divx")
		{
			// DivX Plus HD
			if (profile->video.codec == "x264_divx")
			{
				args << "-bufsize" << "25000";
				args << "-bf" << "3";
			}
			else if ( profile->muxer == "mov" || profile->muxer == "ipod" )
			{
				// QuickTime 7 requirements
				args << "-bf" << "1";
			}
			else if ( profile->muxer == "psp" )
			{
				/*args << "-strict" << "very";*/
				args << "-bf" << "3";
				args << "-bufsize" << "2000";
				args << "-profile" << "main";
				args << "-level" << "3.0";
				args << "-wpredp" << "0";
				args << "-flags2" << "-dct8x8+wpred-mixed_refs-wpred+fastpskip-mbtree";
				args << "-x264opts" << "cqm=flat:cabac=0:b_pyramid=0";
			}

			QString preset("medium"); 

			if (profile->video.codec == "x264_lr")
			{
				switch (profile->level)
				{
				case Video::Profile::Low:
					preset = "ultrafast"; 
					break;
				case Video::Profile::Medium:
					preset = "superfast"; 
					break;
				case Video::Profile::High:
					preset = "fast"; 
					break;
				default:
					preset = "medium"; 
					break;
				}
			}
			else if (profile->video.codec == "x264_hd" || profile->video.codec == "x264_divx")
			{
				switch (profile->level)
				{
				case Video::Profile::Low:
					preset = "ultrafast"; 
					break;
				case Video::Profile::Medium:
					preset = "ultrafast"; 
					break;
				case Video::Profile::High:
					preset = "superfast"; 
					break;
				default:
					preset = "ultrafast"; 
					break;
				}
			}
			else
			{
				switch (profile->level)
				{
				case Video::Profile::Low:
					preset = "ultrafast"; 
					break;
				case Video::Profile::Medium:
					preset = "superfast"; 
					break;
				case Video::Profile::High:
					preset = "fast"; 
					break;
				default:
					preset = "fast"; 
					break;
				}
			}

			if ( NPass > 1 && currentPass == 1 )
			{
				preset.append("_firstpass");
			}

			if ( profile->muxer != "psp" )
			{
#ifdef WIN32
			args << "-fpre" << QString("%1/presets/libx264-%2.ffpreset")
									.arg(QFileInfo(exec).absolutePath()).arg(preset);
#else
			args << "-fpre" << QString("%1/../share/ffmpeg/libx264-%2.ffpreset")
									.arg(QFileInfo(exec).absolutePath()).arg(preset);
#endif
			}

			// x264 profile must follow preset
			QString h264Profile("");  // FFmpeg default is High

			if ( profile->muxer == "ipod" 
				&& profile->video.rez.width() <= 640 && profile->video.rez.height() <= 480 )
			{
				h264Profile = "baseline";
			}
			else if ( profile->muxer == "psp" )
			{
				h264Profile = "main";
			}
			else if ( profile->video.rez.height() > 480 )
			{
				h264Profile = "high";
			}
			else
			{
				switch (profile->level)
				{
				case Video::Profile::Low:
					h264Profile = "baseline";
					break;
				case Video::Profile::Medium:
					h264Profile = "main";
					break;
				}
			}

			// High is FFmpeg default
			if ( profile->muxer == "psp" )
			{
				//args << "-fpre" << QString("%1/presets/libx264-%2.ffpreset")
				//					.arg(QFileInfo(exec).absolutePath()).arg(h264Profile);
			}
			else if ( !h264Profile.isEmpty() && h264Profile != "high" )
			{
				args << "-profile" << h264Profile;
/*experimental
#ifdef WIN32
				args << "-vpre" << QString("%1/presets/libx264-%2.ffpreset")
									.arg(QFileInfo(exec).absolutePath()).arg(h264Profile);
				//args << "-vpre" << h264Profile;
#else
				args << "-vpre" << QString("libx264-%1").arg(h264Profile);
#endif
				*/
			}
		}
		
		QString videoCodec(profile->video.codec);

		// Codec name translations (synonyms)
		if ( videoCodec == "x264" 
			|| videoCodec == "x264_lr" || videoCodec == "x264_hd" 
			|| videoCodec == "x264_psp" || videoCodec == "x264_divx" )
		{
			videoCodec = "libx264";
		}
		else if ( videoCodec == "xvid" )
		{
			videoCodec = "libxvid";
		}
		else if ( videoCodec == "theora" )
		{
			videoCodec = "libtheora";
		}
		else if ( videoCodec == "vp8" )
		{
			videoCodec = "libvpx";
		}
		
		args << "-vcodec" << videoCodec;
	}
	
	if ( profile->audio.codec.isEmpty() || profile->audio.codec == "noaudio" )
	{
		args << "-an";
	}
	else
	{
		// Volume correction: 0db -> 256, dB=20log(corr/256)
		if ( settings->output.audio.volume != 0 )
			args << "-vol" << QString::number( int(256.0 * pow(10.0, double(settings->output.audio.volume)/20.0)) );

		if ( profile->audio.codec == "amrnb" )
		{
			args << "-ac" << QString::number(1);  // stereo to mono downmix (mono only for AMR-NB)
		}
		else if ( profile->audio.codec == "pcm" && profile->audio.bitrate < 1536 )
		{
			args << "-ac" << QString::number(1);  // stereo to mono downmix (PCM compliance)
		}
		else if ( profile->audio.codec == "pcm" )
		{
			args << "-ac" << QString::number(2);  // 5.1 downmix (PCM compliance)
		}
		else if ( profile->audio.codec == "ac3" && profile->audio.bitrate <= 256 )
		{
			args << "-ac" << QString::number(2);  // 5.1 downmix (AC3 compliance)
		}
		else if ( profile->audio.sampling != settings->input.audio.sampling )
		{
			args << "-ac" << QString::number(2);  // 5.1 downmix (FFmpeg can't resample audio with more than 2 channels)
		}

		if (profile->audio.sampling > 0) 
			args << "-ar" << QString::number(profile->audio.sampling);

		if (profile->audio.bitrate > 0.0) 
			args << "-ab" << QString("%1k").arg(profile->audio.bitrate);

		QString audioCodec(profile->audio.codec);

		// Codec name translations (synonyms)
		if ( audioCodec == "mp3" || audioCodec == "mp3_lr"
			|| audioCodec == "mp3_flv" || audioCodec == "mp3_flv_lr" )
		{
			audioCodec = "libmp3lame";
		}
		else if ( audioCodec == "mp2_vcd" )
		{
			audioCodec = "mp2";
		}
		else if ( audioCodec == "vorbis" )
		{
			audioCodec = "libvorbis";
		}
		else if ( audioCodec == "aac" || audioCodec == "aac_lr" || audioCodec == "aac_hq"
				|| audioCodec == "aac_flv" )
		{
			audioCodec = "aac";
		}
		else if ( audioCodec == "amrnb" )
		{
			audioCodec = "libopencore_amrnb";
		}
		else if ( audioCodec == "pcm" && (profile->muxer == "au" || profile->muxer == "mpeg") )
		{
			audioCodec = "pcm_s16be";
		}
		else if ( audioCodec == "pcm" )
		{
			switch (profile->level)
			{
			case Video::Profile::Low:
				audioCodec = "pcm_s16le";
				break;
			case Video::Profile::Medium:
				audioCodec = "pcm_s16le";
				break;
			case Video::Profile::High:
				audioCodec = "pcm_s32le";
				break;
			}
		}
		
		// AAC still experimental
		if ( audioCodec == "aac" )
			args << "-strict" << "experimental";

		args << "-acodec" << audioCodec;
	}

	args << output;

	return args;
}

void InterfaceFFmpeg::initialize(const QString &p, 
		encoder_engine_capability_t *c, encoder_settings_t *m, encoder_engine_status_t *s)
{
	exec = p;
	capability = c;
	settings = m;
	status = s;

	NPass = 1;	// 1-pass encoding
	currentPass = 1;
}

void InterfaceFFmpeg::setBinPath(const QString &binPath)
{
	exec = binPath;
}

void InterfaceFFmpeg::cliCmdGetStatus(QString *cmd, QStringList *args)
{
	cmd->clear();
	args->clear();

	// Command
	*cmd = exec.toLatin1();

	// Query video and audio supported
	*args << "-ovc" << "help" << "-oac" << "help" << "-of" << "help";

	qDebug() << "InterfaceFFmpeg::cliCmdGetStatus() returns command:" << *cmd << "with options" << *args;
}

void InterfaceFFmpeg::cliCmdGetVersion(QString *cmd, QStringList *args)
{
	cmd->clear();
	args->clear();

	// Command
	*cmd = exec.toLatin1();

	*args << "-version";

	qDebug() << "InterfaceFFmpeg::cliCmdGetVersion() returns command:" << *cmd << "with options" << *args;
}

void InterfaceFFmpeg::cliCmdGetCodecs(QString *cmd, QStringList *args)
{
	cmd->clear();
	args->clear();

	// Command
	*cmd = exec.toLatin1();

	// Query video and audio supported
	*args << "-codecs";

	qDebug() << "InterfaceFFmpeg::cliCmdGetCodecs() returns command:" << *cmd << "with options" << *args;
}

void InterfaceFFmpeg::cliCmdGetMuxers(QString *cmd, QStringList *args)
{
	cmd->clear();
	args->clear();

	// Command
	*cmd = exec.toLatin1();

	// Query video and audio supported
	*args << "-formats";

	qDebug() << "InterfaceFFmpeg::cliCmdGetMuxers() returns command:" << *cmd << "with options" << *args;
}

void InterfaceFFmpeg::parseVersion()
{
	qDebug() << "InterfaceFFmpeg::parseVersion() starts parsing version raw data";

	// Parse standard output

	QRegExp rx(RX_PARSE_FFMPEG_SIGNATURE);
	rx.setCaseSensitivity(Qt::CaseInsensitive);
	rx.setMinimal(false);
	rx.indexIn(status->cout);
	if (rx.numCaptures() != 2 || rx.cap(1).length() == 0 || rx.cap(2).length() == 0)
	{
		qDebug("InterfaceFFmpeg::parseVersion() - Failed to parse FFmpeg standard output (signature)");
		status->state = Video::Status::ParseFail;
		return;
	}
	status->encoder = rx.cap(1);
	status->version = rx.cap(2);
}

void InterfaceFFmpeg::parseCodecs()
{
	qDebug() << "InterfaceFFmpeg::parseCodecs() starts parsing status raw data";

	if (status->state != Video::Status::RawStatus) 
	{
		status->state = Video::Status::ParseFail;
		return;
	}

	// Parse standard output
	qDebug() << "InterfaceFFmpeg::parseCodecs() - available codecs:" << status->cout;

	// Video codecs
	QRegExp rx(RX_PARSE_FFMPEG_CODECS_EV);
	rx.setCaseSensitivity(Qt::CaseSensitive);
	rx.setMinimal(false);
	capability->video->clear();
	capability->video->insert( "auto", "Encoder chooses appropriate codec" );
	capability->video->insert( "copy", "Stream copy" );
	int pos(0);
	while ((pos = rx.indexIn(status->cout, pos)) != -1) 
	{
		if ( videos->contains( rx.cap(1) ) )
		{
			capability->video->insert( rx.cap(1), rx.cap(2));

			qDebug() << "InterfaceFFmpeg::parseCodecs() - found video codec available:" << rx.cap(1) << rx.cap(2);
		}
		else
		{
			qDebug() << "InterfaceFFmpeg::parseCodecs() - ignore video codec available (not supported):" << rx.cap(1) << rx.cap(2);
		}

		pos += rx.matchedLength();
	}

	// Audio codecs
	rx.setPattern(RX_PARSE_FFMPEG_CODECS_EA);
	capability->audio->clear();
	capability->audio->insert( "auto", "Encoder chooses appropriate codec" );
	capability->audio->insert( "copy", "Stream copy" );
	capability->audio->insert( "multi", "Multi-stream" );
	pos = 0;
	while ((pos = rx.indexIn(status->cout, pos)) != -1) 
	{
		if ( audios->contains( rx.cap(1) ) )
		{
			capability->audio->insert( rx.cap(1), rx.cap(2));

			qDebug() << "InterfaceFFmpeg::parseCodecs() - found audio codec available:" << rx.cap(1) << rx.cap(2);
		}
		else
		{
			qDebug() << "InterfaceFFmpeg::parseCodecs() - ignore audio codec available (not supported):" << rx.cap(1) << rx.cap(2);
		}

		pos += rx.matchedLength();
	}

	// Subtitle codecs
	rx.setPattern(RX_PARSE_FFMPEG_CODECS_ES);
	capability->subtitle->clear();
	capability->subtitle->insert( "auto", "Encoder chooses appropriate codec" );
	capability->subtitle->insert( "copy", "Stream copy" );
	capability->subtitle->insert( "multi", "Multi-stream" );
	pos = 0;
	while ((pos = rx.indexIn(status->cout, pos)) != -1) 
	{
		if ( subtitles->contains( rx.cap(1) ) )
		{
			capability->subtitle->insert( rx.cap(1), rx.cap(2));

			qDebug() << "InterfaceFFmpeg::parseCodecs() - found subtitle codec available:" << rx.cap(1) << rx.cap(2);
		}
		else
		{
			qDebug() << "InterfaceFFmpeg::parseCodecs() - ignore subtitle codec available (not supported):" << rx.cap(1) << rx.cap(2);
		}

		pos += rx.matchedLength();
	}
}

void InterfaceFFmpeg::parseMuxers()
{
	qDebug() << "InterfaceFFmpeg::parseMuxers() starts parsing status raw data";

	if (status->state != Video::Status::RawStatus) 
	{
		status->state = Video::Status::ParseFail;
		return;
	}

	// Parse standard output
	qDebug() << "InterfaceFFmpeg::parseMuxers() - available muxers:" << status->cout;

	QRegExp rx(RX_PARSE_FFMPEG_MUXERS_E);
	rx.setCaseSensitivity(Qt::CaseSensitive);
	rx.setMinimal(true);

	// Muxers
	capability->muxer->clear();
	int pos(0);
	while ((pos = rx.indexIn(status->cout, pos)) != -1) 
	{
		if ( muxers->contains( rx.cap(1) ) )
		{
			capability->muxer->insert( rx.cap(1), rx.cap(2));

			qDebug() << "InterfaceFFmpeg::parseMuxers() - found muxer available:" << rx.cap(1) << rx.cap(2);
		}
		else
		{
			qDebug() << "InterfaceFFmpeg::parseMuxers() - ignore muxer available (not supported):" << rx.cap(1) << rx.cap(2);
		}

		pos += rx.matchedLength();
	}
}

bool InterfaceFFmpeg::parseCommandCheck( QByteArray *cout, QByteArray *cerr )
{
	qDebug() << "InterfaceFFmpeg::parseCommandCheck() starts parsing standard error";
	qDebug() << *cerr;

	QRegExp rx(RX_PARSE_FFMPEG_OUTPUT_VALID);
	rx.setCaseSensitivity(Qt::CaseInsensitive);
	rx.setMinimal(true);
	rx.indexIn(*cerr);

	return (rx.numCaptures() == 3) && (rx.cap(1).length() > 0);
}

bool InterfaceFFmpeg::parseOutput(QString *output, QString *warning, 
	int *frame, double *time, int *percent, int *remain, double *speed, int *size)
{
	QStringList lines = output->split( QRegExp("\\n"), QString::SkipEmptyParts );

	QString str, warn;
	QStringListIterator lineIterator(lines);
	while (lineIterator.hasNext())
	{
		QString buffStr = lineIterator.next();

		if ( buffStr.startsWith("frame=") || buffStr.startsWith("size=") 
			|| buffStr.startsWith("video:") || buffStr.startsWith("audio:") )
		{
			str = buffStr;
		}
		else
		{
			warn.append( buffStr );
		}
	}

	if ( str.isEmpty() && warn.isEmpty() ) return false;

	QRegExp rx(RX_PARSE_FFMPEG_PROGRESS);
	rx.setMinimal(false);
	rx.indexIn(str);
		
	if (rx.numCaptures() == 4 && !rx.cap(1).isEmpty())
	{
		if (rx.cap(1).length() > 0) *frame = rx.cap(1).toInt();
		if (rx.cap(2).length() > 0) *speed = rx.cap(2).toDouble();
		if (rx.cap(3).length() > 0) *size = rx.cap(3).toInt();
		if (rx.cap(4).length() > 0) *time = rx.cap(4).toDouble();
	}
	else
	{
		rx.setPattern(RX_PARSE_FFMPEG_PROGRESS_AO);  // audio only
		rx.setMinimal(false);
		rx.indexIn(str);
		
		if (rx.numCaptures() == 3 && !rx.cap(1).isEmpty())
		{
			if (rx.cap(1).length() > 0) *size = rx.cap(1).toInt();
			if (rx.cap(2).length() > 0) *time = rx.cap(2).toDouble();
		}
		else 
		{
			// summary at end
			if (str.contains("global headers:"))
			{
				*percent = 100;
				warn.append( str );
			}
		}
	}

	*warning = warn;

	return true;
}

bool InterfaceFFmpeg::muxerSupport(const QString &muxer)
{
	bool support(false);

	support =  (muxerVideos.contains(muxer) && !muxerVideos[muxer].isEmpty())
			|| (muxerAudios.contains(muxer) && !muxerAudios[muxer].isEmpty())
			|| (muxerSubtitles.contains(muxer) && !muxerSubtitles[muxer].isEmpty());

	if ( !support )
	{
		QStringList synonyms = muxerSynonyms.values(muxer);
		QStringListIterator m(synonyms);
		while (m.hasNext())
		{
			QString mux = m.next();

			support =  (muxerVideos.contains(mux) && !muxerVideos[mux].isEmpty())
					|| (muxerAudios.contains(mux) && !muxerAudios[mux].isEmpty())
					|| (muxerSubtitles.contains(mux) && !muxerSubtitles[mux].isEmpty());

			if (support) break;
		}
	}

	return support;
}

bool InterfaceFFmpeg::muxerSupportVideo(const QString &muxer, const QString &video)
{
	bool support(false);

	support = muxerVideos[muxer].contains(video);
	
	if ( !support )
	{
		QStringList synonyms = videoSynonyms.values(video);
		QStringListIterator v(synonyms);
		while (v.hasNext())
		{
			QString vid = v.next();

			support = muxerVideos[muxer].contains(vid);

			if (support) break;
		}
	}

	return support;
}

bool InterfaceFFmpeg::muxerSupportAudio(const QString &muxer, const QString &audio)
{
	bool support(false);

	support = muxerAudios[muxer].contains(audio);
	
	if ( !support )
	{
		QStringList synonyms = audioSynonyms.values(audio);
		QStringListIterator a(synonyms);
		while (a.hasNext())
		{
			QString aud = a.next();

			support = muxerAudios[muxer].contains(aud);

			if (support) break;
		}
	}

	return support;
}

bool InterfaceFFmpeg::muxerSupportSubtitle(const QString &muxer, const QString &subtitle)
{
	bool support(false);

	support = muxerSubtitles[muxer].contains(subtitle);
	
	if ( !support )
	{
		QStringList synonyms = subtitleSynonyms.values(subtitle);
		QStringListIterator s(synonyms);
		while (s.hasNext())
		{
			QString sub = s.next();

			support = muxerSubtitles[muxer].contains(sub);

			if (support) break;
		}
	}

	return support;
}

bool InterfaceFFmpeg::supportMultiAudio()
{
	return audios->contains( "multi" );
}

bool InterfaceFFmpeg::supportMultiSubtitle()
{
	return subtitles->contains( "multi" );
}

void InterfaceFFmpeg::cliCmdEncode(QString *cmd, QStringList *args, const QString &output,
	int frames, int seekPosition)
{
	cmd->clear();
	args->clear();

	// Command
	*cmd = exec.toLatin1();

	// Profile set?
	if ( settings->output.profile.level != Video::Profile::None 
		&& settings->output.profile.level != Video::Profile::User ) 
	{
		*args = profileCommandLine(&settings->output.profile, output, frames, seekPosition);

		qDebug() << "InterfaceFFmpeg::cliCmdEncode() returns command:" << *cmd << "with options" << *args;	
		return;
	}
	
	// Input
	*args << "-y";

	*args << "-i" << settings->input.source.path;

	// Encoder options
	QMapIterator<QString, QString> optionIterator(settings->output.options);
	while (optionIterator.hasNext())
	{
		optionIterator.next();
		if (optionIterator.value().isEmpty())
			*args << optionIterator.key();
		else
			*args << optionIterator.key() << optionIterator.value();
	}

	if ( settings->output.muxer == "ipod" )
	{
		*args << "-f" << settings->output.muxer;
	}

	if (settings->input.video.display_aspect > 1.31 && settings->input.video.display_aspect < 1.35)
	{
		*args << "-aspect" << "4:3";
	}
	else if (settings->input.video.display_aspect > 1.75 && settings->input.video.display_aspect < 1.79)
	{
		*args << "-aspect" << "16:9";
	}

	if ( frames > 0 )
	{
		double seconds_to_encode = (double)frames / 25.0;  // assuming avg fps is 25.0

		if (seconds_to_encode == 0.0) seconds_to_encode = 1.0;  // a minimum to encode

		QTime time_to_encode( 0, 0, floor(seconds_to_encode), seconds_to_encode - floor(seconds_to_encode) );


		*args << "-t" << time_to_encode.toString( "hh:mm:ss.z" );

		if (!settings->input.no_video)
		{
			*args << "-vframes" << QString::number(frames);
		}
		else if (!settings->input.no_audio)
		{
			*args << "-aframes" << QString::number(frames);
		}
	}
	
	if ( seekPosition > 0 )
	{
		*args << "-ss" << QString::number(seekPosition);
	}

	if ( settings->output.video.fps > 0.0 )
	{
		*args << "-r" << QString("%1").arg(settings->output.video.fps);
	}
	else if ( settings->input.video.fps > 0.0 )
	{
		*args << "-r" << QString("%1").arg(settings->input.video.fps);  // required for AVC in MPEG-TS
	}

	if ( settings->output.video.codec.isEmpty() || settings->output.video.codec == "novideo" )
	{
		*args << "-vn";
	}
	else
	{
		QStringList vf;

		if ( settings->output.video.codec == "h263" || settings->output.video.codec == "mpeg1video" )
		{
			// H.263 is CIF/SIF 4:3 display
			if (settings->input.video.display_aspect >= 1.35)
			{
				QSize rez(settings->input.video.scaledRez);
				rez.scale( settings->output.video.rez, Qt::KeepAspectRatio );

				int sarCorrection(rez.height());

				// Pixel Aspect Ratio correction for analog-to-digital conversion
				if (settings->output.video.standard == "PAL" 
					|| settings->output.video.rez.height() == 288 
					|| settings->output.video.rez.height() == 576)
				{
					sarCorrection = int((double)rez.height() * 12.0 / 11.0);
				}
				else
				{
					sarCorrection = int((double)rez.width() * 11.0 / 10.0);
				}
				rez.setHeight(sarCorrection);

				int vPad = (settings->output.video.rez.height() - rez.height()) / 2;

				if (settings->input.video.display_aspect >= 1.35)
				{
					vf << QString("scale=%1:%2,pad=%3:%4:0:%5:000000")
								.arg(rez.width())
								.arg(rez.height())
								.arg(settings->output.video.rez.width())
								.arg(settings->output.video.rez.height())
								.arg(vPad);
				}
			}
			else
			{
				*args << "-s" << QString("%1x%2").arg(settings->output.video.rez.width()).arg(settings->output.video.rez.height());
			}
		}
		else
		{
			*args << "-s" << QString("%1x%2").arg(settings->output.video.rez.width()).arg(settings->output.video.rez.height());
		}

		if ( !vf.isEmpty() )
		{
			*args << "-vf" << vf.join(",");
		}

		*args << "-vb" << QString("%1k").arg(settings->output.video.bitrate);

		// h263 parameters
		if ( settings->output.video.codec == "h263" )
		{
			*args << "-flags" << "+obmc+aic";
		}

		// mpeg4 presets
		if ( settings->output.video.codec == "mpeg4" )
		{
			if ( settings->output.muxer == "avi" )
			{
				*args << "-vtag" << "XVID";  // for appliance compatibility
			}

			if ( settings->input.video.fps < 15.0 )
			{
				*args << "-r" << QString::number(10);
			}
			else if ( settings->input.video.fps < 23.96 )
			{
				*args << "-r" << QString::number(15);
			}
			else if ( settings->input.video.fps < 24.0 )
			{
				*args << "-r" << "24000/1001";
			}
			else if ( settings->input.video.fps < 24.5 )
			{
				*args << "-r" << QString::number(24);
			}
			else if ( settings->input.video.fps < 29.0 )
			{
				*args << "-r" << QString::number(25);
			}
			else if ( settings->input.video.fps < 30.0 )
			{
				*args << "-r" << "30000/1001";
			}
			else
			{
				*args << "-r" << QString::number(30);
			}

			// High quality recommendations from FFmpeg FAQ
			*args << "-mbd" << "rd";
			*args << "-flags" << "+mv4+aic";
			*args << "-trellis" << QString::number(2);
			*args << "-cmp" << QString::number(2);
			*args << "-subcmp" << QString::number(2);
			*args << "-g" << QString::number(300);
		}
		
		// xvid presets
		if ( settings->output.video.codec == "xvid" )
		{
			*args << "-vtag" << "XVID";
		}

		QString videoCodec(settings->output.video.codec);

		// x264 presets
		if ( videoCodec == "x264" 
			|| videoCodec == "x264_lr" || videoCodec == "x264_hd"  
			|| videoCodec == "x264_psp" || videoCodec == "x264_divx")
		{
			// DivX Plus HD
			if (videoCodec == "x264_divx")
			{
				*args << "-bufsize" << "25000";
				*args << "-bf" << "3";
			}
			else if ( settings->output.muxer == "mov" || settings->output.muxer == "ipod" )
			{
				// QuickTime 7 requirements
				*args << "-bf" << "1";
			}
			else if ( videoCodec == "x264_psp" )
			{
				/**args << "-strict" << "very";*/
				*args << "-bf" << "3";
				*args << "-bufsize" << "2000";
				*args << "-profile" << "main";
				*args << "-level" << "3.0";
				*args << "-wpredp" << "0";
				*args << "-flags2" << "-dct8x8+wpred-mixed_refs-wpred+fastpskip-mbtree";
				*args << "-x264opts" << "cqm=flat:cabac=0:b_pyramid=0";
			}
			
			QString preset( settings->output.video.preset ); 

			if (preset.isEmpty()) preset = "ultrafast";

			if ( NPass > 1 && currentPass == 1 )
			{
				preset.append("_firstpass");
			}

#ifdef WIN32
			*args << "-fpre" << QString("%1/presets/libx264-%2.ffpreset")
									.arg(QFileInfo(exec).absolutePath()).arg(preset);
#else
			*args << "-fpre" << QString("%1/../share/ffmpeg/libx264-%2.ffpreset")
									.arg(QFileInfo(exec).absolutePath()).arg(preset);
#endif

			// x264 profile must follow preset 
			QString profile( settings->output.video.profile.mid(5) ); // remove x264_ from profile name

			// High is FFmpeg default
			if ( !profile.isEmpty() )
			{
				*args << "-profile" << profile;
/*experimental
#ifdef WIN32
			*args << "-vpre" << QString("%1/presets/libx264-%2.ffpreset")
									.arg(QFileInfo(exec).absolutePath()).arg(profile);
#else
			*args << "-vpre" << QString("%1/../share/ffmpeg/libx264-%2.ffpreset")
									.arg(QFileInfo(exec).absolutePath()).arg(profile);
#endif
*/
			}
		}

		// Codec name translations (synonyms)
		// Codec name translations (synonyms)
		if ( videoCodec == "x264" 
			|| videoCodec == "x264_lr" || videoCodec == "x264_hd" 
			|| videoCodec == "x264_psp" || videoCodec == "x264_divx" )
		{
			videoCodec = "libx264";
		}
		else if ( videoCodec == "xvid" )
		{
			videoCodec = "libxvid";
		}
		else if ( videoCodec == "theora" )
		{
			videoCodec = "libtheora";
		}
		else if ( videoCodec == "vp8" )
		{
			videoCodec = "libvpx";
		}
		
		*args << "-vcodec" << videoCodec;

	}
	
	if ( settings->output.audio.codec.isEmpty() || settings->output.audio.codec == "noaudio" )
	{
		*args << "-an";
	}
	else
	{
		// Volume correction: 0db -> 256, dB=20log(corr/256)
		if ( settings->output.audio.volume != 0 )
			*args << "-vol" << QString::number( int(256.0 * pow(10.0, double(settings->output.audio.volume)/20.0)) );

		if ( settings->output.audio.codec == "amrnb" )
		{
			*args << "-ac" << QString::number(1);  // stereo to mono downmix (mono only for AMR-NB)
		}
		else if ( settings->output.audio.codec == "pcm" && settings->output.audio.kbps < 1536 )
		{
			*args << "-ac" << QString::number(1);  // stereo to mono downmix (PCM compliance)
		}
		else if ( settings->output.audio.codec == "pcm" )
		{
			*args << "-ac" << QString::number(2);  // 5.1 downmix (PCM compliance)
		}
		else if ( settings->output.audio.codec == "ac3" && settings->output.audio.kbps <= 256 )
		{
			*args << "-ac" << QString::number(2);  // 5.1 downmix (AC3 compliance)
		}
		else if ( settings->output.audio.sampling != settings->input.audio.sampling )
		{
			*args << "-ac" << QString::number(2);  // 5.1 downmix (FFmpeg can't resample audio with more than 2 channels)
		}

		if (settings->output.audio.sampling > 0) 
			*args << "-ar" << QString::number(settings->output.audio.sampling);
		
		if (settings->output.audio.kbps > 0.0) 
			*args << "-ab" << QString("%1k").arg(settings->output.audio.kbps);
		
		QString audioCodec(settings->output.audio.codec);

		// Codec name translations (synonyms)
		if ( audioCodec == "mp3" || audioCodec == "mp3_lr"
			|| audioCodec == "mp3_flv" || audioCodec == "mp3_flv_lr" )
		{
			audioCodec = "libmp3lame";
		}
		else if ( audioCodec == "mp2_vcd" )
		{
			audioCodec = "mp2";
		}
		else if ( audioCodec == "vorbis" )
		{
			audioCodec = "libvorbis";
		}
		else if ( audioCodec == "aac" || audioCodec == "aac_lr" || audioCodec == "aac_hq"
				|| audioCodec == "aac_flv" )
		{
			audioCodec = "aac";
		}
		else if ( audioCodec == "amrnb" )
		{
			audioCodec = "libopencore_amrnb";
		}
		else if ( audioCodec == "pcm" && (settings->output.muxer == "au" || settings->output.muxer == "au") )
		{
			audioCodec = "pcm_s16be";
		}
		else if ( audioCodec == "pcm" )
		{
			audioCodec = "pcm_s16le";
		}

		// AAC stil experimental
		if ( audioCodec == "aac" )
			*args << "-strict" << "experimental";

		*args << "-acodec" << audioCodec;
	}

	// Output 
	*args << output;
	
	qDebug() << "InterfaceFFmpeg::cliCmdEncode() returns command:" << *cmd << "with options" << *args;	
}

void InterfaceFFmpeg::cliCorrectAspectRatio(QString *cmd, QStringList *args,
	const QString &input, const QString &output, double aspect_ratio)
{
	cmd->clear();
	args->clear();

	// Command
	*cmd = exec.toLatin1();

	QString aspect;

	if (aspect_ratio > 1.31 && aspect_ratio < 1.35)
	{
		aspect = "4:3";
	}
	else if (aspect_ratio > 1.75 && aspect_ratio < 1.79)
	{
		aspect = "16:9";
	}
	else
	{
		aspect = QString::number(aspect_ratio);
	}
	
	// Input
	*args << "-i" << input;

	// Copy stream and correct aspect ratio
	*args << "-aspect" << aspect << "-vcodec" << "copy" << "-acodec" << "copy";

	// Output 
	*args << "-y";   // force rewrite output
	*args << output;
	
	qDebug() << "InterfaceFFmpeg::cliCmdEncode() returns command:" << *cmd << "with options" << *args;	
}

void InterfaceFFmpeg::cliExtractAudioFromAvi(QString *cmd, QStringList *args,
	const QString &input, const QString &output)
{
	cmd->clear();
	args->clear();

	// Command
	*cmd = exec.toLatin1();

	// Input
	*args << "-i" << input;

	// Copy stream and correct aspect ratio
	*args << "-vn" << "-acodec" << "copy";

	// Output 
	*args << "-y";   // force rewrite output
	*args << output;
	
	qDebug() << "InterfaceFFmpeg::cliCmdEncode() returns command:" << *cmd << "with options" << *args;	
}

bool InterfaceFFmpeg::cliCmdSplitFile(QString *cmd, QStringList *args,
		const QString &inputFileName, const qint64 inputFileSize, 
		const int partNumber, const qint64 partSize)
{
	cmd->clear();
	args->clear();

	// Command
	*cmd = exec.toLatin1();

	*args << "-i" << inputFileName;
	
	
	qDebug() << "InterfaceFFmpeg::cliCmdSplitFile() returns command:" << *cmd << "with options" << *args;	
	return false;
}

void InterfaceFFmpeg::cliCmdTargetVcd(QString *cmd, QStringList *args,
		const QString &in, const QString &out, const QString &standard)
{
	cmd->clear();
	args->clear();

	// Command
	*cmd = exec.toLatin1();

	*args << "-i" << in;

	if (standard == "NTSC")
	{
		*args << "-target" << "ntsc-vcd";
	}
	else if (standard == "	PAL")
	{
		*args << "-target" << "pal-vcd";
	}
	else
	{
		*args << "-target" << "vcd";
	}

	*args << "-y" ;  // overwrite output files to not block the process on I/O
	*args << out;

	qDebug() << "InterfaceFFmpeg::cliCmdTargetVcd() returns command:" << *cmd << "with options" << *args;
}

void InterfaceFFmpeg::cliCmdTargetSvcd(QString *cmd, QStringList *args,
		const QString &in, const QString &out, const QString &standard)
{
	cmd->clear();
	args->clear();

	// Command
	*cmd = exec.toLatin1();

	*args << "-i" << in;

	if (standard == "NTSC")
	{
		*args << "-target" << "ntsc-svcd";
	}
	else if (standard == "	PAL")
	{
		*args << "-target" << "pal-svcd";
	}
	else
	{
		*args << "-target" << "svcd";
	}

	*args << "-y" ;  // overwrite output files to not block the process on I/O

	*args << "-y";   // force rewrite output
	*args << out;

	qDebug() << "InterfaceFFmpeg::cliCmdTargetSvcd() returns command:" << *cmd << "with options" << *args;
}

void InterfaceFFmpeg::cliCmdRemux(const QString &in, const QString &out, QString *cmd, QStringList *args)
{
	cmd->clear();
	args->clear();

	// Command
	*cmd = exec.toLatin1();

	// Input
	*args << "-i" << in;

	// Copy streams
	*args << "-vcodec" << "copy" << "-acodec" << "copy";

	// Output 
	*args << "-y";   // force rewrite output
	*args << out;

	qDebug() << "InterfaceFFmpeg::cliCmdRemux() returns command:" << *cmd << "with options" << *args;
}
