/*  smplayer, GUI front-end for mplayer.
    Copyright (C) 2006-2018 Ricardo Villalba <rvm@users.sourceforge.net>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include <QDir>
#include <QDebug>
#include "inforeader.h"
#include "deviceinfo.h"

#define EQ_OLD 0
#define EQ_ANEQUALIZER 1
#define EQ_FIREQUALIZER 2
#define EQ_FIREQUALIZER_LIST 3
#define EQ_SUPEREQUALIZER 4
#define EQ_FEQUALIZER 5

#define USE_EQUALIZER EQ_FIREQUALIZER

#define LETTERBOX_OLD 1
#define LETTERBOX_PAD 2
#define LETTERBOX_PAD_WITH_ASPECT 3

#ifdef Q_OS_WIN
#define USE_LETTERBOX LETTERBOX_PAD_WITH_ASPECT
#else
#define USE_LETTERBOX LETTERBOX_PAD
#endif

#define OSD_PREFIX use_osd_in_commands ? "" : "no-osd"
//#define OSD_PREFIX ""

void MPVProcess::addArgument(const QString & /*a*/) {
}

void MPVProcess::initializeOptionVars() {
	qDebug("MPVProcess::initializeOptionVars");
	PlayerProcess::initializeOptionVars();

#ifdef OSD_WITH_TIMER
	osd_timer = new QTimer(this);
	osd_timer->setInterval(500);
	connect(osd_timer, SIGNAL(timeout()), this, SLOT(displayInfoOnOSD()));
#endif

	use_osd_in_commands = true;
}

void MPVProcess::setMedia(const QString & media, bool is_playlist) {
	arg << "--term-playing-msg="
			"MPV_VERSION=${=mpv-version:}\n"
			"INFO_VIDEO_WIDTH=${=width}\nINFO_VIDEO_HEIGHT=${=height}\n"
			"INFO_VIDEO_ASPECT=${=video-aspect}\n"
//			"INFO_VIDEO_DSIZE=${=dwidth}x${=dheight}\n"
			"INFO_VIDEO_FPS=${=container-fps:${=fps}}\n"
//			"INFO_VIDEO_BITRATE=${=video-bitrate}\n"
			"INFO_VIDEO_FORMAT=${=video-format}\n"
			"INFO_VIDEO_CODEC=${=video-codec}\n"

//			"INFO_AUDIO_BITRATE=${=audio-bitrate}\n"
//			"INFO_AUDIO_FORMAT=${=audio-format}\n" // old
			"INFO_AUDIO_FORMAT=${=audio-codec-name}\n"
			"INFO_AUDIO_CODEC=${=audio-codec}\n"
//			"INFO_AUDIO_RATE=${=audio-samplerate}\n" // old
			"INFO_AUDIO_RATE=${=audio-params/samplerate}\n"
//			"INFO_AUDIO_NCH=${=audio-channels}\n" // old
			"INFO_AUDIO_NCH=${=audio-params/channel-count}\n"

//			"INFO_LENGTH=${=length}\n"
			"INFO_LENGTH=${=duration:${=length}}\n"

			"INFO_DEMUXER=${=current-demuxer:${=demuxer}}\n"
			"INFO_SEEKABLE=${=seekable}\n"
			"INFO_TITLES=${=disc-titles}\n"
			"INFO_CHAPTERS=${=chapters}\n"
			"INFO_TRACKS_COUNT=${=track-list/count}\n"

			"METADATA_TITLE=${metadata/by-key/title:}\n"
			"METADATA_ARTIST=${metadata/by-key/artist:}\n"
			"METADATA_ALBUM=${metadata/by-key/album:}\n"
			"METADATA_GENRE=${metadata/by-key/genre:}\n"
			"METADATA_DATE=${metadata/by-key/date:}\n"
			"METADATA_TRACK=${metadata/by-key/track:}\n"
			"METADATA_COPYRIGHT=${metadata/by-key/copyright:}\n"

			"INFO_MEDIA_TITLE=${=media-title:}\n"
			"INFO_STREAM_PATH=${stream-path}\n";

#ifndef Q_OS_WIN
	arg << "--audio-client-name=SMPlayer";
#endif

#ifdef CUSTOM_STATUS
	arg << "--term-status-msg=STATUS: ${=time-pos} / ${=duration:${=length:0}} P: ${=pause} B: ${=paused-for-cache} I: ${=core-idle} VB: ${=video-bitrate:0} AB: ${=audio-bitrate:0}";
#endif

	if (is_playlist) {
		arg << "--playlist=" + media;
	} else {
		arg << media;
	}

#ifdef CAPTURE_STREAM
	capturing = false;
#endif

}

void MPVProcess::setFixedOptions() {
	arg << "--no-config";
	arg << "--no-quiet";
	arg << "--terminal";
	arg << "--no-msg-color";
	arg << "--input-file=/dev/stdin";
	//arg << "--no-osc";
	//arg << "--msg-level=vd=v";
	//arg << "--video-stereo-mode=no";
}

void MPVProcess::disableInput() {
	arg << "--no-input-default-bindings";
	if (isOptionAvailable("--input-vo-keyboard")) {
		arg << "--input-vo-keyboard=no";
	} else {
		arg << "--input-x11-keyboard=no";
	}
	arg << "--no-input-cursor";
	arg << "--cursor-autohide=no";
}

bool MPVProcess::isOptionAvailable(const QString & option) {
	static QStringList option_list;
	static QString mpv_bin;

	if (option_list.isEmpty() || mpv_bin != executable()) {
		InfoReader * ir = InfoReader::obj(executable());
		ir->getInfo();
		option_list = ir->optionList();
		mpv_bin = executable();
		//qDebug() << "MPVProcess::isOptionAvailable: option_list:" << option_list;
	}

	return option_list.contains(option);
}

void MPVProcess::addVFIfAvailable(const QString & vf, const QString & value) {
	InfoReader * ir = InfoReader::obj(executable());
	ir->getInfo();
	if (ir->vfList().contains(vf)) {
		QString s = "--vf-add=" + vf;
		if (!value.isEmpty()) s += "=" + value;
		arg << s;
	} else {
		QString f = vf +"="+ value;
		qDebug("MPVProcess::addVFIfAvailable: filter %s is not used because it's not available", f.toLatin1().constData());
	}
}

void MPVProcess::messageFilterNotSupported(const QString & filter_name) {
	QString text = tr("the '%1' filter is not supported by mpv").arg(filter_name);
	writeToStdin(QString("show_text \"%1\" 3000").arg(text));
}

void MPVProcess::enableScreenshots(const QString & dir, const QString & templ, const QString & format) {
	if (!templ.isEmpty()) {
		arg << "--screenshot-template=" + templ;
	}

	if (!format.isEmpty()) {
		arg << "--screenshot-format=" + format;
	}

	if (!dir.isEmpty()) {
		QString d = QDir::toNativeSeparators(dir);
		if (!isOptionAvailable("--screenshot-directory")) {
			qDebug() << "MPVProcess::enableScreenshots: the option --screenshot-directory is not available in this version of mpv";
			qDebug() << "MPVProcess::enableScreenshots: changing working directory to" << d;
			setWorkingDirectory(d);
		} else {
			arg << "--screenshot-directory=" + d;
		}
	}
}

void MPVProcess::setOption(const QString & option_name, const QVariant & value) {
	if (option_name == "cache") {
		int cache = value.toInt();
		if (cache > 31) {
			arg << "--cache=" + value.toString();
		} else {
			arg << "--cache=no";
		}
	}
	else
	if (option_name == "cache_auto") {
		arg << "--cache=auto";
	}
	else
	if (option_name == "ss") {
		arg << "--start=" + value.toString();
	}
	else
	if (option_name == "endpos") {
		arg << "--length=" + value.toString();
	}
	else
	if (option_name == "loop") {
		QString loop = "--loop";
		if (isOptionAvailable("--loop-file")) loop = "--loop-file";
		QString o = value.toString();
		if (o == "0") o = "inf";
		arg << loop + "=" + o;
	}
	else
	if (option_name == "ass") {
		arg << "--sub-ass";
	}
	else
	if (option_name == "noass") {
		arg << "--no-sub-ass";
	}
	else
	if (option_name == "sub-fuzziness") {
		QString v;
		switch (value.toInt()) {
			case 1: v = "fuzzy"; break;
			case 2: v = "all"; break;
			default: v = "exact";
		}
		arg << "--sub-auto=" + v;
	}
	else
	if (option_name == "audiofile") {
		arg << "--audio-file=" + value.toString();
	}
	else
	if (option_name == "delay") {
		arg << "--audio-delay=" + value.toString();
	}
	else
	if (option_name == "subdelay") {
		arg << "--sub-delay=" + value.toString();
	}
	else
	if (option_name == "sub") {
		arg << "--sub-file=" + value.toString();
	}
	else
	if (option_name == "subpos") {
		arg << "--sub-pos=" + value.toString();
	}
	else
	if (option_name == "font") {
		arg << "--osd-font=" + value.toString();
	}
	else
	if (option_name == "subcp") {
		QString cp = value.toString();
		if (!cp.startsWith("enca")) cp = "utf8:" + cp;
		arg << "--sub-codepage=" + cp;
	}
	else
	if (option_name == "osd-level") {
		arg << "--osd-level=" + value.toString();
	}
	else
	if (option_name == "osd-fractions") {
		bool use_fractions = value.toBool();
		if (use_fractions) arg << "--osd-fractions";
	}
	else
	if (option_name == "osd-bar-pos") {
		if (isOptionAvailable("--osd-bar-align-y")) {
			if (value.toInt() >= 0 && value.toInt() <= 100) {
				double position = double (value.toInt() * 2 - 100) / 100;
				arg << "--osd-bar-align-y=" + QString::number(position);
			}
		}
	}
	else
	if (option_name == "sws") {
		arg << "--sws-scaler=lanczos";
	}
	else
	if (option_name == "channels") {
		arg << "--audio-channels=" + value.toString();
	}
	else
	if (option_name == "subfont-text-scale" || option_name == "ass-font-scale") {
		arg << "--sub-scale=" + value.toString();
	}
	else
	if (option_name == "ass-line-spacing") {
		QString line_spacing = "--ass-line-spacing";
		if (isOptionAvailable("--sub-ass-line-spacing")) line_spacing = "--sub-ass-line-spacing";
		arg << line_spacing + "=" + value.toString();
	}
	else
	if (option_name == "ass-force-style") {
		QString ass_force_style = "--ass-force-style";
		if (isOptionAvailable("--sub-ass-force-style")) ass_force_style = "--sub-ass-force-style";
		arg << ass_force_style + "=" + value.toString();
	}
	else
	if (option_name == "stop-xscreensaver") {
		bool stop_ss = value.toBool();
		if (stop_ss) arg << "--stop-screensaver"; else arg << "--no-stop-screensaver";
	}
	else
	if (option_name == "correct-pts") {
		bool b = value.toBool();
		if (b) arg << "--correct-pts"; else arg << "--no-correct-pts";
	}
	else
	if (option_name == "idx") {
		arg << "--index=default";
	}
	else
	if (option_name == "softvol") {
		if (value.toString() == "off") {
			if (isOptionAvailable("--volume-max")) {
				arg << "--volume-max=100";
			}
		} else {
			int v = value.toInt();
			if (v < 100) v = 100;
			if (isOptionAvailable("--volume-max")) {
				arg << "--volume-max=" + QString::number(v);
			} else {
				arg << "--softvol=yes";
				arg << "--softvol-max=" + QString::number(v);
			}
		}
	}
	else
	if (option_name == "subfps") {
		arg << "--sub-fps=" + value.toString();
	}
	else
	if (option_name == "forcedsubsonly") {
		arg << "--sub-forced-only";
	}
	else
	if (option_name == "prefer-ipv4" || option_name == "prefer-ipv6" ||
	    option_name == "dr" || option_name == "double" ||
	    option_name == "adapter" || option_name == "edl" ||
	    option_name == "slices" || option_name == "colorkey" ||
	    option_name == "subcc" || option_name == "vobsub" ||
	    option_name == "zoom" || option_name == "flip-hebrew" ||
	    option_name == "autoq")
	{
		// Ignore
	}
	else
	if (option_name == "tsprog") {
		// Unsupported
	}
	else
	if (option_name == "dvdangle") {
		/*
		arg << "--dvd-angle=" + value.toString();
		*/
	}
	else
	if (option_name == "threads") {
		arg << "--vd-lavc-threads=" + value.toString();
	}
	else
	if (option_name == "skiploopfilter") {
		arg << "--vd-lavc-skiploopfilter=all";
	}
	else
	if (option_name == "keepaspect" || option_name == "fs") {
		bool b = value.toBool();
		if (b) arg << "--" + option_name; else arg << "--no-" + option_name;
	}
	else
	if (option_name == "vo") {
		QString vo = value.toString();
		if (vo.endsWith(",")) vo.chop(1);
		#ifndef Q_OS_WIN
		if (isOptionAvailable("--xv-adaptor")) {
			QRegExp rx("xv:adaptor=(\\d+)");
			if (rx.indexIn(vo) > -1) {
				QString adaptor = rx.cap(1);
				vo = "xv";
				arg << "--xv-adaptor=" + adaptor;
			}
		}
		#endif

		// Remove options (used by mplayer)
		int pos = vo.indexOf(":");
		if (pos > -1) vo = vo.left(pos);

		if (vo == "gl") vo = "opengl";

		arg << "--vo=" + vo + ",";
	}
	else
	if (option_name == "ao") {
		QString ao = value.toString();

		QStringList l;
		if (ao.contains(":")) l = DeviceInfo::extractDevice(ao);
		if (l.count() > 0) ao = l[0];

		if (isOptionAvailable("--audio-device")) {
			if (l.count() == 3) {
				#ifndef Q_OS_WIN
				if (l[0] == "pulse") {
					arg << "--audio-device=pulse/" + l[2];
				}
				#if USE_MPV_ALSA_DEVICES
				else
				if (l[0] == "alsa") {
					arg << "--audio-device=alsa/" + l[1];
				}
				#endif
				#else
				if (l[0] == "dsound") {
					arg << "--audio-device=dsound/" + l[1];
				}
				else
				if (l[0] == "wasapi") {
					arg << "--audio-device=wasapi/" + l[1];
				}
				#endif
			}
		} else {
			#ifndef Q_OS_WIN
			if (l.count() > 1) {
				if (l[0] == "alsa") {
					ao = "alsa:device=[hw:" + l[1] + "]";
				}
				else
				if (l[0] == "pulse") {
					ao = "pulse::" + l[1];
				}
			}
			#endif
		}

		// Remove options (used by mplayer)
		int pos = ao.indexOf(":");
		if (pos > -1) ao = ao.left(pos);

		arg << "--ao=" + ao + ",";
	}
	else
	if (option_name == "vc") {
		qDebug() << "MPVProcess::setOption: video codec ignored";
	}
	else
	if (option_name == "ac") {
		qDebug() << "MPVProcess::setOption: audio codec ignored";
	}
	else
	if (option_name == "afm") {
		QString s = value.toString();
		if (s == "hwac3") arg << "--audio-spdif=ac3,dts-hd,truehd";
	}
	else
	if (option_name == "enable_streaming_sites_support") {
		if (isOptionAvailable("--ytdl")) {
			if (value.toBool()) arg << "--ytdl"; else arg << "--ytdl=no";
		}
	}
	else
	if (option_name == "ytdl_quality") {
		if (isOptionAvailable("--ytdl-format")) {
			QString quality = value.toString();
			if (!quality.isEmpty()) arg << "-ytdl-format=" + quality;
		}
	}
	else
	if (option_name == "fontconfig") {
		if (isOptionAvailable("--use-text-osd")) {
			bool b = value.toBool();
			if (b) arg << "--use-text-osd=yes"; else arg << "--use-text-osd=no";
		}
	}
	else
	if (option_name == "verbose") {
		arg << "-v";
		verbose = true;
	}
	else
	if (option_name == "mute") {
		arg << "--mute=yes";
	}
	else
	if (option_name == "scaletempo") {
		if (isOptionAvailable("--audio-pitch-correction")) {
			bool enabled = value.toBool();
			if (enabled) arg << "--audio-pitch-correction=yes"; else arg << "--audio-pitch-correction=no";
		}
	}
	else
	if (option_name == "vf-add") {
		if (!value.isNull()) arg << "--vf-add=" + value.toString();
	}
	else
	if (option_name == "af-add") {
		if (!value.isNull()) arg << "--af-add=" + value.toString();
	}
	else
	if (option_name == "aid" || option_name == "sid" || option_name == "secondary-sid" || option_name == "vid") {
		int v = value.toInt();
		arg << QString("--%1=%2").arg(option_name).arg(v > -1 ? value.toString() : "no");
	}
	else
	if (option_name == "wid" ||
	    option_name == "alang" || option_name == "slang" ||
	    option_name == "volume" ||
	    option_name == "ass-styles" ||
	    option_name == "embeddedfonts" ||
	    option_name == "osd-scale" ||
	    option_name == "speed" ||
	    option_name == "contrast" || option_name == "brightness" ||
	    option_name == "hue" || option_name == "saturation" || option_name == "gamma" ||
	    option_name == "monitorpixelaspect" || option_name == "monitoraspect" ||
	    option_name == "mc" ||
	    option_name == "framedrop" ||
	    option_name == "priority" ||
	    option_name == "hwdec" ||
	    option_name == "autosync" ||
	    option_name == "dvd-device" || option_name == "cdrom-device" ||
	    option_name == "demuxer" ||
	    option_name == "frames" ||
	    option_name == "user-agent" || option_name == "referrer" ||
	    option_name == "ab-loop-a" || option_name == "ab-loop-b")
	{
		QString s = "--" + option_name;
		if (!value.isNull()) s += "=" + value.toString();
		arg << s;
	}
	else
	{
		qDebug() << "MPVProcess::setOption: unknown option:" << option_name;
	}
}

void MPVProcess::addUserOption(const QString & option) {
	qDebug() << "MPVProcess::addUserOption:" << option;

	// Remove quotes
	QString s = option;
	if (s.count("=\"") == 1 && s.endsWith("\"")) {
		s.replace("=\"", "=");
		s.chop(1);
	}
	else
	if (s.startsWith("\"") && s.endsWith("\"")) {
		s.remove(0, 1);
		s.chop(1);
	}

	qDebug() << "MPVProcess::addUserOption: s:" << s;

	arg << s;
	if (option == "-v") {
		verbose = true;
	}
}

void MPVProcess::setSubEncoding(const QString & codepage, const QString & enca_lang) {
	Q_UNUSED(enca_lang)
	if (!codepage.isEmpty()) {
		arg << "--sub-codepage=" + codepage;
	}
}

void MPVProcess::addVF(const QString & filter_name, const QVariant & value) {
	QString option = value.toString();

	QString lavfi_filter = lavfi(filter_name, value);
	if (!lavfi_filter.isEmpty()) {
		arg << "--vf-add=" + lavfi_filter;
	}
	else

#if USE_LETTERBOX == LETTERBOX_OLD
	if (filter_name == "letterbox") {
		QSize desktop_size = value.toSize();
		double aspect = (double) desktop_size.width() / desktop_size.height();
		arg << "--vf-add=" + QString("expand=aspect=%1").arg(aspect);
	}
	else
#endif

	if ((filter_name == "harddup") || (filter_name == "hue")) {
		// ignore
	}
	else
	/*
	if (filter_name == "eq2") {
		#ifdef USE_OLD_VIDEO_EQ
		arg << "--vf-add=eq";
		#else
		arg << "--vf-add=@eq:lavfi=[eq]";
		#endif
	}
	else
	*/
	if (filter_name == "subs_on_screenshots") {
		// Ignore
	}
	else {
		if (filter_name == "pp") {
			QString s;
			if (option.isEmpty()) s = "[pp]"; else s = "[pp=" + option + "]";
			addVFIfAvailable("lavfi", s);
		} else {
			QString s = filter_name;
			if (!option.isEmpty()) s += "=" + option;
			arg << "--vf-add=" + s;
		}
	}
}

void MPVProcess::addStereo3DFilter(const QString & in, const QString & out) {
	QString input;
	if (!in.isEmpty()) input = "in=" + in + ":";

	QString output;
	if (!out.isEmpty()) output = "out=" + out;

	arg << "--vf-add=lavfi=[stereo3d=" + input + output + "]";
}

void MPVProcess::setVideoEqualizerOptions(int contrast, int brightness, int hue, int saturation, int gamma, bool soft_eq) {
#ifndef USE_OLD_VIDEO_EQ
	use_soft_eq = soft_eq;
#endif

	if (soft_eq) {
		#ifdef USE_OLD_VIDEO_EQ
		arg << "--vf-add=lavfi=[eq]";
		#else
		current_soft_eq = SoftVideoEq(contrast, brightness, hue, saturation, gamma);
		QString f = videoEqualizerFilter(current_soft_eq);
		arg << "--vf-add=" + f;
		previous_soft_eq = current_soft_eq;
		#endif
	}
#ifndef USE_OLD_VIDEO_EQ
	else
#endif
	{
		if (contrast != 0) arg << "--contrast=" + QString::number(contrast);
		if (brightness != 0) arg << "--brightness=" + QString::number(brightness);
		if (hue != 0) arg << "--hue=" + QString::number(hue);
		if (saturation != 0) arg << "--saturation=" + QString::number(saturation);
		if (gamma != 0) arg << "--gamma=" + QString::number(gamma);
	}
}

void MPVProcess::addAF(const QString & filter_name, const QVariant & value) {
	QString option = value.toString();

	QString lavfi_filter = lavfi(filter_name, value);
	if (!lavfi_filter.isEmpty()) {
		arg << "--af-add=" + lavfi_filter;
	}
	else

	if (filter_name == "equalizer") {
		AudioEqualizerList al = value.toList();
		QString f = audioEqualizerFilter(al);
		arg << "--af-add=" + f;
		previous_eq = f;
		previous_eq_list = al;
	}
	else {
		QString s = filter_name;
		if (!option.isEmpty()) s += "=" + option;
		arg << "--af-add=" + s;
	}
}

void MPVProcess::quit() {
	writeToStdin("quit 0");
}

void MPVProcess::setVolume(int v) {
	writeToStdin("set volume " + QString::number(v));
}

void MPVProcess::setOSD(int o) {
	writeToStdin("no-osd set osd-level " + QString::number(o));
}

void MPVProcess::setAudio(int ID) {
	writeToStdin(QString("%1 set aid %2").arg(OSD_PREFIX).arg(ID));
}

void MPVProcess::setVideo(int ID) {
	writeToStdin(QString("%1 set vid %2").arg(OSD_PREFIX).arg(ID));
}

void MPVProcess::setSubtitle(int type, int ID) {
	Q_UNUSED(type);
	writeToStdin(QString("%1 set sid %2").arg(OSD_PREFIX).arg(ID));
}

void MPVProcess::disableSubtitles() {
	writeToStdin("no-osd set sid no");
}

void MPVProcess::setSecondarySubtitle(int ID) {
	writeToStdin(QString("%1 set secondary-sid %2").arg(OSD_PREFIX).arg(ID));
}

void MPVProcess::disableSecondarySubtitles() {
	writeToStdin("no-osd set secondary-sid no");
}

void MPVProcess::setSubtitlesVisibility(bool b) {
	writeToStdin(QString("no-osd set sub-visibility %1").arg(b ? "yes" : "no"));
}

void MPVProcess::seek(double secs, int mode, bool precise) {
	QString s = "seek " + QString::number(secs) + " ";
	switch (mode) {
		case 0 : s += "relative "; break;
		case 1 : s += "absolute-percent "; break;
		case 2 : s += "absolute "; break;
	}
	if (precise) s += "exact"; else s += "keyframes";
	writeToStdin(s);
}

void MPVProcess::mute(bool b) {
	writeToStdin(QString("set mute %1").arg(b ? "yes" : "no"));
}

void MPVProcess::setPause(bool b) {
	writeToStdin(QString("set pause %1").arg(b ? "yes" : "no"));
}

void MPVProcess::frameStep() {
	writeToStdin("frame_step");
}

void MPVProcess::frameBackStep() {
	writeToStdin("frame_back_step");
}

void MPVProcess::showOSDText(const QString & text, int duration, int level) {
	if (use_osd_in_commands) {
		QString str = QString("show_text \"%1\" %2 %3").arg(text).arg(duration).arg(level);
		writeToStdin(str);
	}
}

void MPVProcess::showFilenameOnOSD(int duration) {
#ifdef OSD_WITH_TIMER
	osd_timer->stop();
#endif
	showOSDText("${filename}", duration, 0);
}

void MPVProcess::showMediaInfoOnOSD() {
#ifdef OSD_WITH_TIMER
	toggleInfoOnOSD();
#else
	showOSDText("${filename}", 2000, 0);
#endif
}

void MPVProcess::showTimeOnOSD() {
#ifdef OSD_WITH_TIMER
	osd_timer->stop();
#endif
	writeToStdin("show_text \"${time-pos} ${?duration:/ ${duration} (${percent-pos}%)}\" 2000 0");
}

#ifdef OSD_WITH_TIMER
void MPVProcess::toggleInfoOnOSD() {
	if (!osd_timer->isActive()) {
		osd_timer->start();
		displayInfoOnOSD();
	} else {
		osd_timer->stop();
		showOSDText("", 100, 0);
	}
}

void MPVProcess::displayInfoOnOSD() {
	QString b1 = "{\\\\b1}";
	QString b0 = "{\\\\b0}";
	QString tab = "\\\\h\\\\h\\\\h\\\\h\\\\h";
	QString nl = "\\n";

	QString s = "${osd-ass-cc/0}{\\\\fs14}" +
		b1 + tr("File:") + b0 +" ${filename}" + nl +
		"${time-pos} ${?duration:/ ${duration} (${percent-pos}%)}" + nl + nl +
		//b1 + tr("Title:") + b0 + " ${media-title}" + nl + nl +
		b1 + tr("Video:") + b0 + " ${video-codec}" + nl +
		tab + b1 + tr("Resolution:") + b0 +" ${=width}x${=height}" + nl +
		tab + b1 + tr("Frames per second:") + b0 + " ${container-fps:${fps}} " + b1 + tr("Estimated:") + b0 + " ${estimated-vf-fps}" + nl +
		//tab + b1 + tr("Display FPS:") + b0 + " ${display-fps}" + nl +
		tab + b1 + tr("Aspect Ratio:") + b0 + " ${video-params/aspect}" + nl +
		tab + b1 + tr("Bitrate:") + b0 + " ${video-bitrate}" + nl +
		tab + b1 + tr("Dropped frames:") + b0 + " ${decoder-frame-drop-count:${drop-frame-count}}" + nl +
		nl +

		b1 + tr("Audio:") + b0 + " ${audio-codec}" + nl + 
		tab + b1 + tr("Bitrate:") + b0 + " ${audio-bitrate}" + nl +
		tab + b1 + tr("Sample Rate:") + b0 + " ${audio-params/samplerate} Hz" + nl +
		tab + b1 + tr("Channels:") + b0 + " ${audio-params/channel-count}" + nl +
		nl +

		b1 + tr("Audio/video synchronization:") + b0 + " ${avsync}" + nl +
		b1 + tr("Cache fill:") + b0 + " ${cache:0}%" + nl +
		b1 + tr("Used cache:") + b0 + " ${cache-used:0}" + nl;

	if (!osd_media_info.isEmpty()) s = osd_media_info;

	showOSDText(s, 2000, 0);

	if (!isRunning()) osd_timer->stop();
}
#endif

void MPVProcess::setContrast(int value) {
#ifndef USE_OLD_VIDEO_EQ
	if (use_soft_eq) {
		current_soft_eq.contrast = value;
		updateSoftVideoEqualizerFilter();
	}
	else
#endif
	writeToStdin("set contrast " + QString::number(value));
}

void MPVProcess::setBrightness(int value) {
#ifndef USE_OLD_VIDEO_EQ
	if (use_soft_eq) {
		current_soft_eq.brightness = value;
		updateSoftVideoEqualizerFilter();
	}
	else
#endif
	writeToStdin("set brightness " + QString::number(value));
}

void MPVProcess::setHue(int value) {
#ifndef USE_OLD_VIDEO_EQ
	if (use_soft_eq) {
		current_soft_eq.hue = value;
		updateSoftVideoEqualizerFilter();
	}
	else
#endif
	writeToStdin("set hue " + QString::number(value));
}

void MPVProcess::setSaturation(int value) {
#ifndef USE_OLD_VIDEO_EQ
	if (use_soft_eq) {
		current_soft_eq.saturation = value;
		updateSoftVideoEqualizerFilter();
	}
	else
#endif
	writeToStdin("set saturation " + QString::number(value));
}

void MPVProcess::setGamma(int value) {
#ifndef USE_OLD_VIDEO_EQ
	if (use_soft_eq) {
		current_soft_eq.gamma = value;
		updateSoftVideoEqualizerFilter();
	}
	else
#endif
	writeToStdin("set gamma " + QString::number(value));
}

void MPVProcess::setChapter(int ID) {
	writeToStdin("set chapter " + QString::number(ID));
}

void MPVProcess::nextChapter() {
	writeToStdin("add chapter 1");
}

void MPVProcess::previousChapter() {
	writeToStdin("add chapter -1");
}

void MPVProcess::setExternalSubtitleFile(const QString & filename) {
	writeToStdin("sub_add \""+ filename +"\"");
	//writeToStdin("print_text ${track-list}");
	writeToStdin("print_text \"INFO_TRACKS_COUNT=${=track-list/count}\"");
}

void MPVProcess::setSubPos(int pos) {
	writeToStdin("set sub-pos " + QString::number(pos));
}

void MPVProcess::setSubScale(double value) {
	writeToStdin("set sub-scale " + QString::number(value));
}

void MPVProcess::setSubStep(int value) {
	writeToStdin("sub_step " + QString::number(value));
}

void MPVProcess::seekSub(int value) {
	writeToStdin("sub-seek " + QString::number(value));
}

void MPVProcess::setSubForcedOnly(bool b) {
	writeToStdin(QString("set sub-forced-only %1").arg(b ? "yes" : "no"));
}

void MPVProcess::setSpeed(double value) {
	writeToStdin("set speed " + QString::number(value));
}

void MPVProcess::enableKaraoke(bool b) {
	changeAF("karaoke", b);

	//messageFilterNotSupported("karaoke");
}

void MPVProcess::enableExtrastereo(bool b) {
	changeAF("extrastereo", b);
}

void MPVProcess::enableVolnorm(bool b, const QString & option) {
	changeAF("volnorm", b, option);
}

void MPVProcess::enableEarwax(bool b) {
	changeAF("earwax", b);
}

void MPVProcess::setAudioEqualizer(AudioEqualizerList l) {
	qDebug("MPVProcess::setAudioEqualizer");

	#if !defined(SIMPLE_EQUALIZER) && USE_EQUALIZER == EQ_ANEQUALIZER

	QStringList commands = AudioEqualizerHelper::equalizerListForCommand(l, previous_eq_list, AudioEqualizerHelper::Anequalizer);
	foreach(QString command, commands) {
		writeToStdin("af-command \"anequalizer\" \"change\" \"" + command + "\"");
	}

	previous_eq_list = l;

	#elif !defined(SIMPLE_EQUALIZER) && USE_EQUALIZER == EQ_FIREQUALIZER

	QStringList commands = AudioEqualizerHelper::equalizerListForCommand(l, previous_eq_list, AudioEqualizerHelper::Firequalizer);
	foreach(QString command, commands) {
		writeToStdin("af-command \"firequalizer\" \"gain_entry\" \"" + command + "\"");
	}

	previous_eq_list = l;

	#else

	QString eq_filter = audioEqualizerFilter(l);
	if (previous_eq == eq_filter) return;

	if (!previous_eq.isEmpty()) {
		writeToStdin("af del \"" + previous_eq + "\"");
	}

	writeToStdin("af add \"" + eq_filter + "\"");
	previous_eq = eq_filter;

#endif
}

void MPVProcess::setAudioDelay(double delay) {
	writeToStdin("set audio-delay " + QString::number(delay));
}

void MPVProcess::setSubDelay(double delay) {
	writeToStdin("set sub-delay " + QString::number(delay));
}

void MPVProcess::setLoop(int v) {
	QString o;
	switch (v) {
		case -1: o = "no"; break;
		case 0: o = "inf"; break;
		default: o = QString::number(v);
	}
	writeToStdin(QString("set loop-file %1").arg(o));
}

void MPVProcess::setAMarker(int sec) {
	writeToStdin(QString("set ab-loop-a %1").arg(sec));
}

void MPVProcess::setBMarker(int sec) {
	writeToStdin(QString("set ab-loop-b %1").arg(sec));
}

void MPVProcess::clearABMarkers() {
	writeToStdin("set ab-loop-a no");
	writeToStdin("set ab-loop-b no");
}

void MPVProcess::takeScreenshot(ScreenshotType t, bool include_subtitles) {
	writeToStdin(QString("screenshot %1 %2").arg(include_subtitles ? "subtitles" : "video").arg(t == Single ? "single" : "each-frame"));
}

#ifdef CAPTURE_STREAM
void MPVProcess::switchCapturing() {
	if (!capture_filename.isEmpty()) {
		if (!capturing) {
			QString f = capture_filename;
			#ifdef Q_OS_WIN
			// I hate Windows
			f = f.replace("\\", "\\\\");
			#endif
			writeToStdin("set stream-capture \"" + f + "\"");
		} else {
			writeToStdin("set stream-capture \"\"");
		}
		capturing = !capturing;
	}
}
#endif

void MPVProcess::setTitle(int ID) {
	writeToStdin("set disc-title " + QString::number(ID));
}

#if DVDNAV_SUPPORT
void MPVProcess::discSetMousePos(int x, int y) {
	qDebug("MPVProcess::discSetMousePos: %d %d", x, y);
	//writeToStdin(QString("discnav mouse_move %1 %2").arg(x).arg(y));
	// mouse_move doesn't accept options :?

	// For some reason this doesn't work either...
	// So it's not possible to select options in the dvd menus just
	// because there's no way to pass the mouse position to mpv, or it
	// ignores it.
	writeToStdin(QString("mouse %1 %2").arg(x).arg(y));
	//writeToStdin("discnav mouse_move");
}

void MPVProcess::discButtonPressed(const QString & button_name) {
	writeToStdin("discnav " + button_name);
}
#endif

void MPVProcess::setAspect(double aspect) {
	writeToStdin("set video-aspect " + QString::number(aspect));
}

void MPVProcess::setFullscreen(bool b) {
	writeToStdin(QString("set fullscreen %1").arg(b ? "yes" : "no"));
}

#if PROGRAM_SWITCH
void MPVProcess::setTSProgram(int ID) {
	qDebug("MPVProcess::setTSProgram: function not supported");
}
#endif

void MPVProcess::toggleDeinterlace() {
	writeToStdin("cycle deinterlace");
}

void MPVProcess::askForLength() {
	writeToStdin("print_text \"INFO_LENGTH=${=length}\"");
}

void MPVProcess::setOSDScale(double value) {
	writeToStdin("set osd-scale " + QString::number(value));
}

void MPVProcess::setOSDFractions(bool active) {
	writeToStdin(QString("no-osd set osd-fractions %1").arg(active ? "yes" : "no"));
}

void MPVProcess::changeVF(const QString & filter, bool enable, const QVariant & option) {
	qDebug() << "MPVProcess::changeVF:" << filter << enable;

	QString f;

	QString lavfi_filter = lavfi(filter, option);
	if (!lavfi_filter.isEmpty()) {
		f = lavfi_filter;
	}

#if USE_LETTERBOX == LETTERBOX_OLD
	else
	if (filter == "letterbox") {
		QSize desktop_size = option.toSize();
		double aspect = (double) desktop_size.width() / desktop_size.height();
		f = QString("expand=aspect=%1").arg(aspect);
	}
#endif

	else {
		qDebug() << "MPVProcess::changeVF: unknown filter:" << filter;
	}

	if (!f.isEmpty()) {
		writeToStdin(QString("vf %1 \"%2\"").arg(enable ? "add" : "del").arg(f));
	}
}

void MPVProcess::changeAF(const QString & filter, bool enable, const QVariant & option) {
	qDebug() << "MPVProcess::changeAF:" << filter << enable;

	QString f;

	QString lavfi_filter = lavfi(filter, option);
	if (!lavfi_filter.isEmpty()) {
		f = lavfi_filter;
	}

	if (!f.isEmpty()) {
		writeToStdin(QString("af %1 \"%2\"").arg(enable ? "add" : "del").arg(f));
	}
}

void MPVProcess::changeStereo3DFilter(bool enable, const QString & in, const QString & out) {
	QString input;
	if (!in.isEmpty()) input = "in=" + in + ":";

	QString output;
	if (!out.isEmpty()) output = "out=" + out;

	QString filter = "lavfi=[stereo3d=" + input + output + "]";
	writeToStdin(QString("vf %1 \"%2\"").arg(enable ? "add" : "del").arg(filter));
}

#if 0

#define SUBOPTION(name, alternative1, alternative2) \
	QString name = alternative2;

#else

#define SUBOPTION(name, alternative1, alternative2) \
	QString name; \
	if (isOptionAvailable(alternative1)) name = alternative1; \
	else \
	if (isOptionAvailable(alternative2)) name = alternative2;

#endif

void MPVProcess::setSubStyles(const AssStyles & styles, const QString &) {
	SUBOPTION(sub_font, "--sub-font", "--sub-text-font");
	SUBOPTION(sub_color, "--sub-color", "--sub-text-color");

	QString sub_shadow_color = "--sub-text-shadow-color";
	if (isOptionAvailable("--sub-shadow-color")) sub_shadow_color = "--sub-shadow-color";

	QString sub_back_color = "--sub-text-back-color";
	if (isOptionAvailable("--sub-back-color")) sub_back_color = "--sub-back-color";

	SUBOPTION(sub_border_color, "--sub-border-color", "--sub-text-border-color");
	SUBOPTION(sub_border_size, "--sub-border-size", "--sub-text-border-size");
	SUBOPTION(sub_shadow_offset, "--sub-shadow-offset", "--sub-text-shadow-offset");

	SUBOPTION(sub_font_size, "--sub-font-size", "--sub-text-font-size");
	SUBOPTION(sub_bold, "--sub-bold", "--sub-text-bold");
	SUBOPTION(sub_italic, "--sub-italic", "--sub-text-italic");
	SUBOPTION(sub_align_x, "--sub-align-x", "--sub-text-align-x");
	SUBOPTION(sub_align_y, "--sub-align-y", "--sub-text-align-y");

	if (!sub_font.isEmpty()) {
		QString font = styles.fontname;
		//arg << "--sub-text-font=" + font.replace(" ", "");
		arg << sub_font + "=" + font;
	}

	if (!sub_color.isEmpty()) {
		arg << sub_color + "=#" + ColorUtils::colorToAARRGGBB(styles.primarycolor);
	}

	arg << sub_shadow_color + "=#" + ColorUtils::colorToAARRGGBB(styles.backcolor);

	if (styles.borderstyle == AssStyles::Opaque) {
		arg << sub_back_color + "=#" + ColorUtils::colorToAARRGGBB(styles.backgroundcolor);
	}

	if (!sub_border_color.isEmpty()) {
		arg << sub_border_color + "=#" + ColorUtils::colorToAARRGGBB(styles.outlinecolor);
	}

	if (!sub_border_size.isEmpty()) {
		arg << sub_border_size + "=" + QString::number(styles.outline * 2.5);
	}

	if (!sub_shadow_offset.isEmpty()) {
		arg << sub_shadow_offset + "=" + QString::number(styles.shadow * 2.5);
	}

	if (!sub_font_size.isEmpty()) {
		arg << sub_font_size + "=" + QString::number(styles.fontsize * 2.5);
	}

	if (!sub_bold.isEmpty()) {
		arg << QString("%1=%2").arg(sub_bold).arg(styles.bold ? "yes" : "no");
	}

	if (!sub_italic.isEmpty()) {
		arg << QString("%1=%2").arg(sub_italic).arg(styles.italic ? "yes" : "no");
	}

	QString halign;
	switch (styles.halignment) {
		case AssStyles::Left: halign = "left"; break;
		case AssStyles::Right: halign = "right"; break;
	}

	QString valign;
	switch (styles.valignment) {
		case AssStyles::VCenter: valign = "center"; break;
		case AssStyles::Top: valign = "top"; break;
	}

	if (!sub_align_x.isEmpty() && !halign.isEmpty()) {
		arg << sub_align_x + "=" + halign;
	}
	
	if (!sub_align_y.isEmpty() && !valign.isEmpty()) {
		arg << sub_align_y + "=" + valign;
	}
}

void MPVProcess::setChannelsFile(const QString & filename) {
	arg << "--dvbin-file=" + filename;
}

QString MPVProcess::lavfi(const QString & filter_name, const QVariant & option) {
	QString f;

	if (filter_name == "flip") {
		f = "vflip";
	}
	else
	if (filter_name == "mirror") {
		f = "hflip";
	}
	else
	if (filter_name == "noise") {
		f = "noise=alls=9:allf=t";
	}
	else
	if (filter_name == "blur") {
		f = "unsharp=la=-1.5:ca=-1.5";
	}
	else
	if (filter_name == "sharpen") {
		f = "unsharp=la=1.5:ca=1.5";
	}
	else
	if (filter_name == "deblock") {
		f = "pp=" + option.toString();
	}
	else
	if (filter_name == "dering") {
		f = "pp=dr";
	}
	else
	if (filter_name == "postprocessing") {
		f = "pp";
	}
	else
	if (filter_name == "lb" || filter_name == "l5") {
		f = "pp=" + filter_name;
	}
	else

	if (filter_name == "yadif") {
		if (option.toString() == "1") {
			f = "yadif=mode=send_field";
		} else {
			f = "yadif=mode=send_frame";
		}
	}
	else

	if (filter_name == "scale" || filter_name == "gradfun" ||
       filter_name == "hqdn3d" || filter_name == "kerndeint" ||
       filter_name == "phase" || filter_name == "extrastereo" ||
       filter_name == "earwax")
	{
		f = filter_name;
		QString o = option.toString();
		if (!o.isEmpty()) f += "=" + o;
	}
	else
	if (filter_name == "letterbox") {
		QSize desktop_size = option.toSize();
		#if USE_LETTERBOX == LETTERBOX_PAD_WITH_ASPECT
		double aspect = (double) desktop_size.width() / desktop_size.height();
		f = QString("pad=aspect=%1:x=(ow-iw)/2:y=(oh-ih)/2").arg(aspect);
		#endif
		#if USE_LETTERBOX == LETTERBOX_PAD
		//f = QString("pad=ih*%1/%2:ih:(ow-iw)/2:(oh-ih)/2").arg(desktop_size.width()).arg(desktop_size.height());
		f = QString("pad=iw:iw*sar/%1*%2:0:(oh-ih)/2").arg(desktop_size.width()).arg(desktop_size.height());
		#endif
	}
	else
	if (filter_name == "rotate") {
		QString o = option.toString();
		if (o == "0") {
			f = "rotate=3*PI/2:ih:iw,vflip";
		}
		else
		if (o == "1") {
			f = "rotate=PI/2:ih:iw"; // 90º
		}
		else
		if (o == "2") {
			f = "rotate=3*PI/2:ih:iw"; // 270º
		}
		else
		if (o == "3") {
			f = "rotate=PI/2:ih:iw,vflip";
		}
	}
	else
	if (filter_name == "volnorm") {
		f = "acompressor";
		QString o = option.toString();
		if (!o.isEmpty()) f += "=" + o;
	}
	else
	if (filter_name == "karaoke") {
		f = "stereotools=mlev=0.015625";
	}
	else
	if (filter_name == "stereo-mode") {
		QString o = option.toString();
		if (o == "left") f = "pan=mono|c0=c0";
		else
		if (o == "right") f = "pan=mono|c0=c1";
		else
		if (o == "reverse") f = "pan=stereo|c0=c1|c1=c0";
		else
		if (o == "mono") f = "pan=mono|c0=.5*c0+.5*c1";
	}

	if (!f.isEmpty()) f = "lavfi=[" + f + "]";
	return f;
}

QString MPVProcess::audioEqualizerFilter(AudioEqualizerList l) {
	QString f;

#if USE_EQUALIZER == EQ_OLD
	QString values = AudioEqualizerHelper::equalizerListToString(l);
	f = "equalizer=" + values;
#endif

#if USE_EQUALIZER == EQ_ANEQUALIZER
	QString values = AudioEqualizerHelper::equalizerListToString(l, AudioEqualizerHelper::Anequalizer);
	f = "lavfi=[anequalizer=" + values + "]";
	#ifndef SIMPLE_EQUALIZER
	f = "@anequalizer:" + f;
	#endif
#endif

#if USE_EQUALIZER == EQ_FIREQUALIZER
	QString values = AudioEqualizerHelper::equalizerListToString(l, AudioEqualizerHelper::Firequalizer);
	f = "lavfi=[firequalizer=gain='cubic_interpolate(f)':zero_phase=on:wfunc=tukey:delay=0.027:" + values + "]";
	#ifndef SIMPLE_EQUALIZER
	f = "@firequalizer:" + f;
	#endif
#endif

#ifndef SIMPLE_EQUALIZER
#if USE_EQUALIZER == EQ_SUPEREQUALIZER
	QString values = AudioEqualizerHelper::equalizerListToString(l, AudioEqualizerHelper::Superequalizer);
	f = "lavfi=[superequalizer=" + values + "]";
#endif

#if USE_EQUALIZER == EQ_FIREQUALIZER_LIST
	QStringList e = AudioEqualizerHelper::equalizerListToStringList(l, AudioEqualizerHelper::Firequalizer);
	foreach(QString option, e) {
		if (!f.isEmpty()) f += ",";
		f += "firequalizer=" + option;
	}
	f = "@firequalizer:lavfi=[" + f + "]";
#endif

#if USE_EQUALIZER == EQ_FEQUALIZER
	QStringList e = AudioEqualizerHelper::equalizerListToStringList(l, AudioEqualizerHelper::FEqualizer);
	foreach(QString option, e) {
		if (!f.isEmpty()) f += ",";
		f += "equalizer=" + option;
	}
	f = "lavfi=[aresample=44100," + f + "]";
#endif
#endif

	return f;
}

#ifndef USE_OLD_VIDEO_EQ
QString MPVProcess::videoEqualizerFilter(SoftVideoEq eq) {
	QString f;

	double brightness = (double) eq.brightness / 100;
	double contrast = (double) (eq.contrast + 100) / 100;
	double saturation = qMax(0.0, (double) (eq.saturation + 50) / 50);
	double gamma = qMax(0.1, (double) (eq.gamma + (100/9)) / (100/9));

	f = QString("%1:%2:%3:%4").arg(contrast).arg(brightness).arg(saturation).arg(gamma);

	f += ",hue=h=" + QString::number(eq.hue);

	f = "lavfi=[eq=" + f + "]";

	return f;
}

void MPVProcess::updateSoftVideoEqualizerFilter() {
	QString f = videoEqualizerFilter(previous_soft_eq);
	writeToStdin("vf del \"" + f + "\"");

	f = videoEqualizerFilter(current_soft_eq);
	writeToStdin("vf add \"" + f + "\"");

	previous_soft_eq = current_soft_eq;
}
#endif
