/*****************************************************************************
 *
 *  ffmpeg.cpp -- FFmpeg interface.
 *
 *  Copyright (C) 2010 Starbuck <starbuck@labdv.com>
 *  All rights reserved.
 *
 *  This file is part of the JobManager 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"
#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"
#endif

#include "ffmpeg.h"
#include <QDebug>
#include <QDir>
#include <QRegExp>
#include <QString>
#include <QStringList>

FFmpeg::FFmpeg()
{
	m_interfaceName = "FFmpeg";
	m_interfaceState = Idle;
}

bool FFmpeg::getCommandSplit(QString *cmd, QStringList *args, QString *err,
		const QString &inputFile, const QString &outputFile, const QString &position, qint64 size)
{
	err->clear();

	QString fileSize;
	if (size < 1000000)	
	{
		fileSize = QString::number(size);
	}
	else
	{
		int megaByteSize = size / qint64(1000000);
		fileSize = QString("%1M").arg(megaByteSize);
	}

	*args = QStringList()
		<< "-vcodec" << "copy"
		<< "-acodec" << "copy"
		<< "-ss" << position;

	if (size > 0) *args << "-fs" << fileSize;

	*args << "-i" << inputFile << "-y" << outputFile;
		 
	*cmd = m_interfacePath.toLatin1();

	return true;
}

void FFmpeg::interfaceReset()
{
	m_interfaceState = Idle;
}

bool FFmpeg::interfaceStart(const QString &path, const QString &libPath)  // IProcessor interface
{
	if (path.isEmpty()) return m_interfaceState;

	if (m_interfacePath == path)
	{
		if (m_interfaceState == Failed) return false;
		if (m_interfaceState == Ready) return true;
	}

	m_interfaceState = Idle;
	m_interfacePath = path.toLatin1();
	m_interfaceLibPath = libPath;
	
	m_availableAudioEncoders.clear();
	m_availableFormatEncoders.clear();
	m_availableSubtitleEncoders.clear();
	m_availableVideoEncoders.clear();

	m_console->log(Console::Verbose, tr("FFmpeg: start interface."));
	qDebug() << "FFmpeg: start interface" << m_interfacePath;

	QString err, stdErr, stdOut;
	QStringList args;

	// Get FFmpeg version
	args = QStringList() << "-version";

	if (!run(&args, &stdOut, &stdErr, &err))
	{
		m_console->log(tr("FFmpeg: %1.").arg(err));
		qDebug() << "FFmpeg: failed to start interface" << err << m_interfacePath;
		m_interfaceState = Failed;
		return false;
	}

	m_console->stdOut(stdOut);
	m_console->stdErr(stdErr);

	parseInterfaceVersion(&stdOut);
	
	// Get available format muxers
	args = QStringList() << "-formats";

	if (!run(&args, &stdOut, &stdErr, &err))
	{
		m_console->log(tr("FFmpeg: %1.").arg(err));
		qDebug() << "FFmpeg: failed to start interface" << err << m_interfacePath;
		m_interfaceState = Failed;
		return false;
	}

	m_console->stdOut(stdOut);
	m_console->stdErr(stdErr);

	parseEncoderFormats(&stdOut);

	// Get available encoders
	args = QStringList() << "-codecs";

	if (!run(&args, &stdOut, &stdErr, &err))
	{
		m_console->log(tr("FFmpeg: error %1.").arg(err));
		qDebug() << "FFmpeg: failed to start interface" << err << m_interfacePath;
		m_interfaceState = Failed;
		return false;
	}

	m_console->stdOut(stdOut);
	m_console->stdErr(stdErr);

	parseEncoderCodecs(&stdOut);

	m_console->log(Console::Verbose, tr("FFmpeg: interface ready."));
	qDebug() << "FFmpeg: interface ready" << m_interfacePath;
	m_interfaceState = Ready;
	return true;
}

void FFmpeg::parseEncoderCodecs(QString *cmdOutput)
{
	// Video codecs
	QRegExp rx(RX_PARSE_FFMPEG_CODECS_EV);
	rx.setCaseSensitivity(Qt::CaseSensitive);
	rx.setMinimal(false);
	m_availableVideoEncoders.insert( "auto", "Encoder chooses appropriate codec" );
	m_availableVideoEncoders.insert( "copy", "Stream copy" );
	int pos(0);
	while ((pos = rx.indexIn(*cmdOutput, pos)) != -1) 
	{
		qDebug() << "FFmpeg: found available video encoder:" << rx.cap(1) << rx.cap(2);

		m_availableVideoEncoders.insert(rx.cap(1), rx.cap(2));
		pos += rx.matchedLength();
	}

	// Audio codecs
	rx.setPattern(RX_PARSE_FFMPEG_CODECS_EA);
	m_availableAudioEncoders.insert( "auto", "Encoder chooses appropriate codec" );
	m_availableAudioEncoders.insert( "copy", "Stream copy" );
	m_availableAudioEncoders.insert( "multi", "Multi-stream" );
	pos = 0;
	while ((pos = rx.indexIn(*cmdOutput, pos)) != -1) 
	{
		qDebug() << "FFmpeg: found available audio encoder:" << rx.cap(1) << rx.cap(2);

		m_availableAudioEncoders.insert(rx.cap(1), rx.cap(2));
		pos += rx.matchedLength();
	}

	// Subtitle codecs
	rx.setPattern(RX_PARSE_FFMPEG_CODECS_ES);
	m_availableSubtitleEncoders.insert( "auto", "Encoder chooses appropriate codec" );
	m_availableSubtitleEncoders.insert( "copy", "Stream copy" );
	m_availableSubtitleEncoders.insert( "multi", "Multi-stream" );
	pos = 0;
	while ((pos = rx.indexIn(*cmdOutput, pos)) != -1) 
	{
		qDebug() << "FFmpeg: found available subtitle encoder:" << rx.cap(1) << rx.cap(2);

		m_availableSubtitleEncoders.insert(rx.cap(1), rx.cap(2));
		pos += rx.matchedLength();
	}
}

void FFmpeg::parseEncoderFormats(QString *cmdOutput)
{
	QRegExp rx(RX_PARSE_FFMPEG_MUXERS_E);
	rx.setCaseSensitivity(Qt::CaseSensitive);
	rx.setMinimal(true);
	int pos(0);
	while ((pos = rx.indexIn(*cmdOutput, pos)) != -1) 
	{
		qDebug() << "FFmpeg: found available format muxer:" << rx.cap(1) << rx.cap(2);

		m_availableFormatEncoders.insert(rx.cap(1), rx.cap(2));
		pos += rx.matchedLength();
	}
}

void FFmpeg::parseInterfaceVersion(QString *cmdOutput)
{
	QRegExp rx(RX_PARSE_FFMPEG_SIGNATURE);
	rx.setCaseSensitivity(Qt::CaseInsensitive);
	rx.setMinimal(false);
	rx.indexIn(*cmdOutput);
	if (rx.numCaptures() != 2 || rx.cap(1).length() == 0 || rx.cap(2).length() == 0)
	{
		m_console->log(Console::Verbose, tr("FFmpeg: failed to parse signature."));
		qDebug() << "FFmpeg: failed to parse signature" << m_interfacePath;
		return;
	}

	m_interfaceExec = rx.cap(1);
	m_interfaceVersion = rx.cap(2);
}
