From 65c7aba23a5bfd81915ca01485967135a2349778 Mon Sep 17 00:00:00 2001 From: lordmulder Date: Sun, 29 Jan 2012 21:31:09 +0100 Subject: [PATCH] Implemented rudimental encoding support. --- gui/win_main.ui | 57 ++++++++++++ src/model_jobList.cpp | 2 +- src/model_logFile.cpp | 10 +++ src/model_logFile.h | 1 + src/thread_encode.cpp | 205 ++++++++++++++++++++++++++++++++++-------- src/thread_encode.h | 17 +++- src/win_addJob.cpp | 21 ++++- src/win_addJob.h | 4 +- src/win_main.cpp | 54 ++++++++++- src/win_main.h | 8 ++ 10 files changed, 331 insertions(+), 48 deletions(-) diff --git a/gui/win_main.ui b/gui/win_main.ui index eb9c1d7..b623694 100644 --- a/gui/win_main.ui +++ b/gui/win_main.ui @@ -163,6 +163,63 @@ + + + + + + + + + 126 + 126 + 126 + + + + + + + + + 126 + 126 + 126 + + + + + + + + + 120 + 120 + 120 + + + + + + + + (Version) + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + diff --git a/src/model_jobList.cpp b/src/model_jobList.cpp index ff6300b..0c42268 100644 --- a/src/model_jobList.cpp +++ b/src/model_jobList.cpp @@ -322,7 +322,7 @@ void JobListModel::updateProgress(const QUuid &jobId, unsigned int newProgress) if((index = m_jobs.indexOf(jobId)) >= 0) { - m_progress.insert(jobId, newProgress); + m_progress.insert(jobId, qBound(0U, newProgress, 100U)); emit dataChanged(createIndex(index, 2), createIndex(index, 2)); } } diff --git a/src/model_logFile.cpp b/src/model_logFile.cpp index 1fe72ec..5242037 100644 --- a/src/model_logFile.cpp +++ b/src/model_logFile.cpp @@ -26,6 +26,8 @@ LogFileModel::LogFileModel(void) { + m_lines << "Job not started yet."; + m_firstLine = true; } LogFileModel::~LogFileModel(void) @@ -81,10 +83,18 @@ QVariant LogFileModel::data(const QModelIndex &index, int role) const void LogFileModel::addLogMessage(const QUuid &jobId, const QString &text) { beginInsertRows(QModelIndex(), m_lines.count(), m_lines.count()); + + if(m_firstLine) + { + m_firstLine = false; + m_lines.clear(); + } + QStringList lines = text.split("\n"); for(int i = 0; i < lines.count(); i++) { m_lines.append(lines.at(i)); } + endInsertRows(); } diff --git a/src/model_logFile.h b/src/model_logFile.h index 3c3af17..3274b1d 100644 --- a/src/model_logFile.h +++ b/src/model_logFile.h @@ -45,6 +45,7 @@ public: protected: QStringList m_lines; + bool m_firstLine; public slots: void addLogMessage(const QUuid &jobId, const QString &text); diff --git a/src/thread_encode.cpp b/src/thread_encode.cpp index 905c129..ff7f07e 100644 --- a/src/thread_encode.cpp +++ b/src/thread_encode.cpp @@ -26,13 +26,34 @@ #include #include +#include +#include +#include -EncodeThread::EncodeThread(const QString &sourceFileName, const QString &outputFileName, const OptionsModel *options) +/* + * Win32 API definitions + */ +typedef HANDLE (WINAPI *CreateJobObjectFun)(__in_opt LPSECURITY_ATTRIBUTES lpJobAttributes, __in_opt LPCSTR lpName); +typedef BOOL (WINAPI *SetInformationJobObjectFun)(__in HANDLE hJob, __in JOBOBJECTINFOCLASS JobObjectInformationClass, __in_bcount(cbJobObjectInformationLength) LPVOID lpJobObjectInformation, __in DWORD cbJobObjectInformationLength); +typedef BOOL (WINAPI *AssignProcessToJobObjectFun)(__in HANDLE hJob, __in HANDLE hProcess); + +/* + * Static vars + */ +QMutex EncodeThread::m_mutex_startProcess; +HANDLE EncodeThread::m_handle_jobObject = NULL; + +/////////////////////////////////////////////////////////////////////////////// +// Constructor & Destructor +/////////////////////////////////////////////////////////////////////////////// + +EncodeThread::EncodeThread(const QString &sourceFileName, const QString &outputFileName, const OptionsModel *options, const QString &binDir) : m_jobId(QUuid::createUuid()), m_sourceFileName(sourceFileName), m_outputFileName(outputFileName), - m_options(new OptionsModel(*options)) + m_options(new OptionsModel(*options)), + m_binDir(binDir) { m_abort = false; } @@ -62,9 +83,13 @@ void EncodeThread::run(void) } } +/////////////////////////////////////////////////////////////////////////////// +// Encode functions +/////////////////////////////////////////////////////////////////////////////// + void EncodeThread::encode(void) { - Sleep(1500); + Sleep(500); //Print some basic info log(tr("Job started at %1, %2.\n").arg(QDate::currentDate().toString(Qt::ISODate), QTime::currentTime().toString( Qt::ISODate))); @@ -76,51 +101,157 @@ void EncodeThread::encode(void) log(tr("Tuning: %1").arg(m_options->tune())); log(tr("Profile: %1").arg(m_options->profile())); log(tr("Custom: %1").arg(m_options->custom().isEmpty() ? tr("(None)") : m_options->custom())); + + //Detect source info log(tr("\n[Input Properties]")); + log(tr("Not implemented yet, sorry ;-)\n")); - for(int i = 0; i <= 100; i += 5) + QStringList cmdLine; + QProcess process; + + cmdLine = buildCommandLine(); + + log("Creating process:"); + if(!startProcess(process, QString("%1/x264.exe").arg(m_binDir), cmdLine)) { - emit progressChanged(m_jobId, i); - emit statusChanged(m_jobId, (i % 2) ? JobStatus_Indexing : JobStatus_Running_Pass1); - emit messageLogged(m_jobId, QUuid::createUuid().toString()); - - for(int j = 0; j < 3; j++) - { - emit detailsChanged(m_jobId, QUuid::createUuid().toString()); - Sleep(120); - } + emit statusChanged(m_jobId, JobStatus_Failed); + return; + } + emit statusChanged(m_jobId, JobStatus_Running); + QRegExp regExp("\\[(\\d+)\\.\\d+%\\].+frames"); + + bool bTimeout = false; + bool bAborted = false; + + while(process.state() != QProcess::NotRunning) + { if(m_abort) { - Sleep(500); - emit statusChanged(m_jobId, JobStatus_Aborted); - return; + process.kill(); + bAborted = true; + log("\nABORTED BY USER !!!"); + break; + } + process.waitForReadyRead(m_processTimeoutInterval); + if(!process.bytesAvailable() && process.state() == QProcess::Running) + { + process.kill(); + qWarning("x264 process timed out <-- killing!"); + log("\nPROCESS TIMEOUT !!!"); + bTimeout = true; + break; + } + while(process.bytesAvailable() > 0) + { + QByteArray line = process.readLine(); + QString text = QString::fromUtf8(line.constData()).simplified(); + if(regExp.lastIndexIn(text) >= 0) + { + bool ok = false; + unsigned int progress = regExp.cap(1).toUInt(&ok); + if(ok) + { + emit progressChanged(m_jobId, progress); + emit detailsChanged(m_jobId, line); + } + } + else if(!text.isEmpty()) + { + log(text); + } } } - Sleep(1500); - - for(int i = 0; i <= 100; i += 5) + process.waitForFinished(); + if(process.state() != QProcess::NotRunning) { - emit progressChanged(m_jobId, i); - emit statusChanged(m_jobId, (i % 2) ? JobStatus_Indexing : JobStatus_Running_Pass2); - emit messageLogged(m_jobId, QUuid::createUuid().toString()); - - for(int j = 0; j < 3; j++) - { - emit detailsChanged(m_jobId, QUuid::createUuid().toString()); - Sleep(120); - } - - if(m_abort) - { - Sleep(500); - emit statusChanged(m_jobId, JobStatus_Aborted); - return; - } + process.kill(); + process.waitForFinished(-1); } - Sleep(250); - + if(bTimeout || bAborted || process.exitCode() != EXIT_SUCCESS) + { + emit statusChanged(m_jobId, JobStatus_Failed); + return; + } + + emit progressChanged(m_jobId, 100); emit statusChanged(m_jobId, JobStatus_Completed); } + +QStringList EncodeThread::buildCommandLine(void) +{ + QStringList cmdLine; + + cmdLine << "--crf" << QString::number(m_options->quantizer()); + + if(m_options->tune().compare("none", Qt::CaseInsensitive)) + { + cmdLine << "--tune" << m_options->tune().toLower(); + } + + cmdLine << "--preset" << m_options->preset().toLower(); + cmdLine << "--output" << m_outputFileName; + cmdLine << m_sourceFileName; + + return cmdLine; +} + +/////////////////////////////////////////////////////////////////////////////// +// Misc functions +/////////////////////////////////////////////////////////////////////////////// + +bool EncodeThread::startProcess(QProcess &process, const QString &program, const QStringList &args) +{ + static AssignProcessToJobObjectFun AssignProcessToJobObjectPtr = NULL; + + QMutexLocker lock(&m_mutex_startProcess); + log(commandline2string(program, args) + "\n"); + + if(!AssignProcessToJobObjectPtr) + { + QLibrary Kernel32Lib("kernel32.dll"); + AssignProcessToJobObjectPtr = (AssignProcessToJobObjectFun) Kernel32Lib.resolve("AssignProcessToJobObject"); + } + + process.setProcessChannelMode(QProcess::MergedChannels); + process.setReadChannel(QProcess::StandardOutput); + process.start(program, args); + + if(process.waitForStarted()) + { + + if(AssignProcessToJobObjectPtr) + { + AssignProcessToJobObjectPtr(m_handle_jobObject, process.pid()->hProcess); + } + if(!SetPriorityClass(process.pid()->hProcess, BELOW_NORMAL_PRIORITY_CLASS)) + { + SetPriorityClass(process.pid()->hProcess, IDLE_PRIORITY_CLASS); + } + + lock.unlock(); + return true; + } + + log("Process creation has failed :-("); + QString errorMsg= process.errorString().trimmed(); + if(!errorMsg.isEmpty()) log(errorMsg); + + process.kill(); + process.waitForFinished(-1); + return false; +} + +QString EncodeThread::commandline2string(const QString &program, const QStringList &arguments) +{ + QString commandline = (program.contains(' ') ? QString("\"%1\"").arg(program) : program); + + for(int i = 0; i < arguments.count(); i++) + { + commandline += (arguments.at(i).contains(' ') ? QString(" \"%1\"").arg(arguments.at(i)) : QString(" %1").arg(arguments.at(i))); + } + + return commandline; +} diff --git a/src/thread_encode.h b/src/thread_encode.h index 317f3f5..b8d5721 100644 --- a/src/thread_encode.h +++ b/src/thread_encode.h @@ -23,8 +23,11 @@ #include #include +#include +#include class OptionsModel; +class QProcess; class EncodeThread : public QThread { @@ -45,7 +48,7 @@ public: JobStatus_Aborted = 9 }; - EncodeThread(const QString &sourceFileName, const QString &outputFileName, const OptionsModel *options); + EncodeThread(const QString &sourceFileName, const QString &outputFileName, const OptionsModel *options, const QString &binDir); ~EncodeThread(void); QUuid getId(void) { return this->m_jobId; }; @@ -55,19 +58,29 @@ public: void abortJob(void) { m_abort = true; } protected: + static QMutex m_mutex_startProcess; + static void *m_handle_jobObject; + + static const int m_processTimeoutInterval = 600000; + const QUuid m_jobId; const QString m_sourceFileName; const QString m_outputFileName; const OptionsModel *m_options; + const QString m_binDir; volatile bool m_abort; virtual void run(void); //Encode functions void encode(void); - + QStringList buildCommandLine(void); + //Auxiallary Stuff void log(const QString &text) { emit messageLogged(m_jobId, text); } + bool startProcess(QProcess &process, const QString &program, const QStringList &args); + + static QString commandline2string(const QString &program, const QStringList &arguments); signals: void statusChanged(const QUuid &jobId, EncodeThread::JobStatus newStatus); diff --git a/src/win_addJob.cpp b/src/win_addJob.cpp index 41a36c4..a439f10 100644 --- a/src/win_addJob.cpp +++ b/src/win_addJob.cpp @@ -31,6 +31,7 @@ #include #include #include +#include /////////////////////////////////////////////////////////////////////////////// // Validator @@ -175,7 +176,7 @@ void AddJobDialog::browseButtonClicked(void) if(!(filePath.isNull() || filePath.isEmpty())) { - editSource->setText(filePath); + editSource->setText(QDir::toNativeSeparators(filePath)); QString path = QFileInfo(filePath).path(); QString name = QFileInfo(filePath).completeBaseName(); @@ -191,7 +192,7 @@ void AddJobDialog::browseButtonClicked(void) } } - editOutput->setText(outPath); + editOutput->setText(QDir::toNativeSeparators(outPath)); } } else if(QObject::sender() == buttonBrowseOutput) @@ -205,11 +206,25 @@ void AddJobDialog::browseButtonClicked(void) if(!(filePath.isNull() || filePath.isEmpty())) { - editOutput->setText(filePath); + editOutput->setText(QDir::toNativeSeparators(filePath)); } } } +/////////////////////////////////////////////////////////////////////////////// +// Public functions +/////////////////////////////////////////////////////////////////////////////// + +QString AddJobDialog::sourceFile(void) +{ + return QDir::fromNativeSeparators(editSource->text()); +} + +QString AddJobDialog::outputFile(void) +{ + return QDir::fromNativeSeparators(editOutput->text()); +} + /////////////////////////////////////////////////////////////////////////////// // Private functions /////////////////////////////////////////////////////////////////////////////// diff --git a/src/win_addJob.h b/src/win_addJob.h index a945eeb..4440579 100644 --- a/src/win_addJob.h +++ b/src/win_addJob.h @@ -33,8 +33,8 @@ public: AddJobDialog(QWidget *parent, OptionsModel *options); ~AddJobDialog(void); - QString sourceFile(void) { return editSource->text(); } - QString outputFile(void) { return editOutput->text(); } + QString sourceFile(void); + QString outputFile(void); QString preset(void) { return cbxPreset->itemText(cbxPreset->currentIndex()); } QString tuning(void) { return cbxTuning->itemText(cbxTuning->currentIndex()); } QString profile(void) { return cbxProfile->itemText(cbxProfile->currentIndex()); } diff --git a/src/win_main.cpp b/src/win_main.cpp index a110f87..47f42d6 100644 --- a/src/win_main.cpp +++ b/src/win_main.cpp @@ -32,6 +32,7 @@ #include #include #include +#include const char *home_url = "http://mulder.brhack.net/"; @@ -41,7 +42,9 @@ const char *home_url = "http://mulder.brhack.net/"; MainWindow::MainWindow(bool x64supported) : - m_x64supported(x64supported) + m_x64supported(x64supported), + m_appDir(QApplication::applicationDirPath()), + m_firstShow(true) { //Init the dialog, from the .ui file setupUi(this); @@ -55,7 +58,7 @@ MainWindow::MainWindow(bool x64supported) setMinimumSize(size()); //Update title - setWindowTitle(QString("%1 [%2]").arg(windowTitle(), x264_version_date().toString(Qt::ISODate))); + labelBuildDate->setText(tr("Built on %1 at %2").arg(x264_version_date().toString(Qt::ISODate), QString::fromLatin1(x264_version_time()))); if(m_x64supported) setWindowTitle(QString("%1 (x64)").arg(windowTitle())); //Create model @@ -94,6 +97,12 @@ MainWindow::~MainWindow(void) { X264_DELETE(m_jobList); X264_DELETE(m_options); + + while(!m_toolsList.isEmpty()) + { + QFile *temp = m_toolsList.takeFirst(); + X264_DELETE(temp); + } } /////////////////////////////////////////////////////////////////////////////// @@ -112,7 +121,8 @@ void MainWindow::addButtonPressed(void) ( addDialog->sourceFile(), addDialog->outputFile(), - m_options + m_options, + QString("%1/toolset").arg(m_appDir) ); QModelIndex newIndex = m_jobList->insertJob(thrd); @@ -256,10 +266,48 @@ void MainWindow::launchNextJob(void) qWarning("No enqueued jobs left!"); } +void MainWindow::init(void) +{ + static const char *binFiles = "x264.exe:x264_x64.exe:avs2yuv.exe:pipebuf.exe"; + QStringList binaries = QString::fromLatin1(binFiles).split(":", QString::SkipEmptyParts); + + while(!binaries.isEmpty()) + { + QString current = binaries.takeFirst(); + QFile *file = new QFile(QString("%1/toolset/%2").arg(m_appDir, current)); + if(file->open(QIODevice::ReadOnly)) + { + m_toolsList << file; + } + else + { + X264_DELETE(file); + QMessageBox::critical(this, tr("File Not Found!"), tr("At least on required tool could not be found:
%1

Please re-install the program in order to fix the problem!
").arg(QDir::toNativeSeparators(QString("%1/toolset/%2").arg(m_appDir, current))).replace("-", "−")); + qFatal(QString("Binary not found: %1/toolset/%2").arg(m_appDir, current).toLatin1().constData()); + return; + } + } + + qsrand(time(NULL)); int rnd = qrand() % 3; + int val = QMessageBox::warning(this, tr("Pre-Release Version"), tr("Note: This is a pre-release version. Please do NOT use for production!

Click the button #%1 in order to continue...").arg(QString::number(rnd + 1)), tr("(1)"), tr("(2)"), tr("(3)"), qrand() % 3); + if(rnd != val) { close(); } +} + /////////////////////////////////////////////////////////////////////////////// // Event functions /////////////////////////////////////////////////////////////////////////////// +void MainWindow::showEvent(QShowEvent *e) +{ + QMainWindow::showEvent(e); + + if(m_firstShow) + { + m_firstShow = false; + QTimer::singleShot(0, this, SLOT(init())); + } +} + void MainWindow::closeEvent(QCloseEvent *e) { if(havePendingJobs()) diff --git a/src/win_main.h b/src/win_main.h index 7deda5e..dbf30ae 100644 --- a/src/win_main.h +++ b/src/win_main.h @@ -26,6 +26,7 @@ class JobListModel; class OptionsModel; +class QFile; class MainWindow: public QMainWindow, private Ui::MainWindow { @@ -37,11 +38,17 @@ public: protected: virtual void closeEvent(QCloseEvent *e); + virtual void showEvent(QShowEvent *e); private: + bool m_firstShow; + JobListModel *m_jobList; OptionsModel *m_options; + QList m_toolsList; + const bool m_x64supported; + const QString m_appDir; void updateButtons(EncodeThread::JobStatus status); bool havePendingJobs(void); @@ -50,6 +57,7 @@ private slots: void addButtonPressed(void); void startButtonPressed(void); void abortButtonPressed(void); + void init(void); void jobSelected(const QModelIndex & current, const QModelIndex & previous); void jobChangedData(const QModelIndex &top, const QModelIndex &bottom); void jobLogExtended(const QModelIndex & parent, int start, int end);