1 ///////////////////////////////////////////////////////////////////////////////
2 // Simple x264 Launcher
3 // Copyright (C) 2004-2014 LoRd_MuldeR <MuldeR2@GMX.de>
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 2 of the License, or
8 // (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License along
16 // with this program; if not, write to the Free Software Foundation, Inc.,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 // http://www.gnu.org/licenses/gpl-2.0.txt
20 ///////////////////////////////////////////////////////////////////////////////
23 #include "model_jobList.h"
24 #include "thread_encode.h"
25 #include "model_options.h"
26 #include "model_preferences.h"
33 static const char *KEY_ENTRY_COUNT = "entry_count";
34 static const char *KEY_SOURCE_FILE = "source_file";
35 static const char *KEY_OUTPUT_FILE = "output_file";
36 static const char *KEY_ENC_OPTIONS = "enc_options";
38 static const char *JOB_TEMPLATE = "job_%08x";
40 #define VALID_INDEX(INDEX) ((INDEX).isValid() && ((INDEX).row() >= 0) && ((INDEX).row() < m_jobs.count()))
42 JobListModel::JobListModel(PreferencesModel *preferences)
44 m_preferences = preferences;
47 JobListModel::~JobListModel(void)
49 while(!m_jobs.isEmpty())
51 QUuid id = m_jobs.takeFirst();
52 EncodeThread *thread = m_threads.value(id, NULL);
53 LogFileModel *logFile = m_logFile.value(id, NULL);
59 ///////////////////////////////////////////////////////////////////////////////
61 ///////////////////////////////////////////////////////////////////////////////
63 int JobListModel::columnCount(const QModelIndex &parent) const
68 int JobListModel::rowCount(const QModelIndex &parent) const
70 return m_jobs.count();
73 QVariant JobListModel::headerData(int section, Qt::Orientation orientation, int role) const
75 if((orientation == Qt::Horizontal) && (role == Qt::DisplayRole))
80 return QVariant::fromValue<QString>(tr("Job"));
83 return QVariant::fromValue<QString>(tr("Status"));
86 return QVariant::fromValue<QString>(tr("Progress"));
89 return QVariant::fromValue<QString>(tr("Details"));
100 QModelIndex JobListModel::index(int row, int column, const QModelIndex &parent) const
102 return createIndex(row, column, NULL);
105 QModelIndex JobListModel::parent(const QModelIndex &index) const
107 return QModelIndex();
110 QVariant JobListModel::data(const QModelIndex &index, int role) const
112 if(role == Qt::DisplayRole)
114 if(index.row() >= 0 && index.row() < m_jobs.count())
116 switch(index.column())
119 return m_name.value(m_jobs.at(index.row()));
122 switch(m_status.value(m_jobs.at(index.row())))
124 case JobStatus_Enqueued:
125 return QVariant::fromValue<QString>(tr("Enqueued."));
127 case JobStatus_Starting:
128 return QVariant::fromValue<QString>(tr("Starting..."));
130 case JobStatus_Indexing:
131 return QVariant::fromValue<QString>(tr("Indexing..."));
133 case JobStatus_Running:
134 return QVariant::fromValue<QString>(tr("Running..."));
136 case JobStatus_Running_Pass1:
137 return QVariant::fromValue<QString>(tr("Running... (Pass 1)"));
139 case JobStatus_Running_Pass2:
140 return QVariant::fromValue<QString>(tr("Running... (Pass 2)"));
142 case JobStatus_Completed:
143 return QVariant::fromValue<QString>(tr("Completed."));
145 case JobStatus_Failed:
146 return QVariant::fromValue<QString>(tr("Failed!"));
148 case JobStatus_Pausing:
149 return QVariant::fromValue<QString>(tr("Pausing..."));
151 case JobStatus_Paused:
152 return QVariant::fromValue<QString>(tr("Paused."));
154 case JobStatus_Resuming:
155 return QVariant::fromValue<QString>(tr("Resuming..."));
157 case JobStatus_Aborting:
158 return QVariant::fromValue<QString>(tr("Aborting..."));
160 case JobStatus_Aborted:
161 return QVariant::fromValue<QString>(tr("Aborted!"));
164 return QVariant::fromValue<QString>(tr("(Unknown)"));
169 return QString().sprintf("%d%%", m_progress.value(m_jobs.at(index.row())));
172 return m_details.value(m_jobs.at(index.row()));
180 else if(role == Qt::DecorationRole)
182 if(index.row() >= 0 && index.row() < m_jobs.count() && index.column() == 0)
184 switch(m_status.value(m_jobs.at(index.row())))
186 case JobStatus_Enqueued:
187 return QIcon(":/buttons/hourglass.png");
189 case JobStatus_Starting:
190 return QIcon(":/buttons/lightning.png");
192 case JobStatus_Indexing:
193 return QIcon(":/buttons/find.png");
195 case JobStatus_Running:
196 case JobStatus_Running_Pass1:
197 case JobStatus_Running_Pass2:
198 return QIcon(":/buttons/play.png");
200 case JobStatus_Completed:
201 return QIcon(":/buttons/accept.png");
203 case JobStatus_Failed:
204 return QIcon(":/buttons/exclamation.png");
206 case JobStatus_Pausing:
207 return QIcon(":/buttons/clock_pause.png");
209 case JobStatus_Paused:
210 return QIcon(":/buttons/suspended.png");
212 case JobStatus_Resuming:
213 return QIcon(":/buttons/clock_play.png");
215 case JobStatus_Aborting:
216 return QIcon(":/buttons/clock_stop.png");
218 case JobStatus_Aborted:
219 return QIcon(":/buttons/error.png");
231 ///////////////////////////////////////////////////////////////////////////////
233 ///////////////////////////////////////////////////////////////////////////////
235 QModelIndex JobListModel::insertJob(EncodeThread *thread)
237 QUuid id = thread->getId();
239 if(m_jobs.contains(id))
241 return QModelIndex();
244 QString config = "N/A";
246 switch(thread->options()->rcMode())
248 case OptionsModel::EncType_X264:
249 config = QString("x264");
251 case OptionsModel::EncType_X265:
252 config = QString("x265");
256 switch(thread->options()->rcMode())
258 case OptionsModel::RCMode_CRF:
259 config = QString("%1 CRF@%2") .arg(config, QString::number(thread->options()->quantizer()));
261 case OptionsModel::RCMode_CQ:
262 config = QString("%1 CQ@%2") .arg(config, QString::number(qRound(thread->options()->quantizer())));
264 case OptionsModel::RCMode_2Pass:
265 config = QString("%1 2Pass@%2").arg(config, QString::number(thread->options()->bitrate()));
267 case OptionsModel::RCMode_ABR:
268 config = QString("%1 ABR@%2") .arg(config, QString::number(thread->options()->bitrate()));
273 QString jobName = QString("%1 (%2)").arg(QFileInfo(thread->sourceFileName()).completeBaseName().simplified(), config);
278 for(int i = 0; i < m_jobs.count(); i++)
280 if(m_name.value(m_jobs.at(i)).compare(jobName, Qt::CaseInsensitive) == 0)
288 jobName = QString("%1 %2 (%3)").arg(QFileInfo(thread->sourceFileName()).completeBaseName().simplified(), QString::number(n++), config);
294 LogFileModel *logFile = new LogFileModel(thread->sourceFileName(), thread->outputFileName(), config);
296 beginInsertRows(QModelIndex(), m_jobs.count(), m_jobs.count());
298 m_name.insert(id, jobName);
299 m_status.insert(id, JobStatus_Enqueued);
300 m_progress.insert(id, 0);
301 m_threads.insert(id, thread);
302 m_logFile.insert(id, logFile);
303 m_details.insert(id, tr("Not started yet."));
306 connect(thread, SIGNAL(statusChanged(QUuid, JobStatus)), this, SLOT(updateStatus(QUuid, JobStatus)), Qt::QueuedConnection);
307 connect(thread, SIGNAL(progressChanged(QUuid, unsigned int)), this, SLOT(updateProgress(QUuid, unsigned int)), Qt::QueuedConnection);
308 connect(thread, SIGNAL(messageLogged(QUuid, QString)), logFile, SLOT(addLogMessage(QUuid, QString)), Qt::QueuedConnection);
309 connect(thread, SIGNAL(detailsChanged(QUuid, QString)), this, SLOT(updateDetails(QUuid, QString)), Qt::QueuedConnection);
311 return createIndex(m_jobs.count() - 1, 0, NULL);
314 bool JobListModel::startJob(const QModelIndex &index)
316 if(VALID_INDEX(index))
318 QUuid id = m_jobs.at(index.row());
319 if(m_status.value(id) == JobStatus_Enqueued)
321 updateStatus(id, JobStatus_Starting);
322 updateDetails(id, tr("Starting up, please wait..."));
323 m_threads.value(id)->start();
331 bool JobListModel::pauseJob(const QModelIndex &index)
333 if(VALID_INDEX(index))
335 QUuid id = m_jobs.at(index.row());
336 JobStatus status = m_status.value(id);
337 if((status == JobStatus_Indexing) || (status == JobStatus_Running) ||
338 (status == JobStatus_Running_Pass1) || (status == JobStatus_Running_Pass2))
340 updateStatus(id, JobStatus_Pausing);
341 m_threads.value(id)->pauseJob();
349 bool JobListModel::resumeJob(const QModelIndex &index)
351 if(VALID_INDEX(index))
353 QUuid id = m_jobs.at(index.row());
354 JobStatus status = m_status.value(id);
355 if(status == JobStatus_Paused)
357 updateStatus(id, JobStatus_Resuming);
358 m_threads.value(id)->resumeJob();
366 bool JobListModel::abortJob(const QModelIndex &index)
368 if(VALID_INDEX(index))
370 QUuid id = m_jobs.at(index.row());
371 if(m_status.value(id) == JobStatus_Indexing || m_status.value(id) == JobStatus_Running ||
372 m_status.value(id) == JobStatus_Running_Pass1 || JobStatus_Running_Pass2)
374 updateStatus(id, JobStatus_Aborting);
375 m_threads.value(id)->abortJob();
383 bool JobListModel::deleteJob(const QModelIndex &index)
385 if(VALID_INDEX(index))
387 QUuid id = m_jobs.at(index.row());
388 if(m_status.value(id) == JobStatus_Completed || m_status.value(id) == JobStatus_Failed ||
389 m_status.value(id) == JobStatus_Aborted || m_status.value(id) == JobStatus_Enqueued)
391 int idx = index.row();
392 QUuid id = m_jobs.at(idx);
393 EncodeThread *thread = m_threads.value(id, NULL);
394 LogFileModel *logFile = m_logFile.value(id, NULL);
395 if((thread == NULL) || (!thread->isRunning()))
398 beginRemoveRows(QModelIndex(), idx, idx);
399 m_jobs.removeAt(index.row());
401 m_threads.remove(id);
403 m_progress.remove(id);
404 m_logFile.remove(id);
405 m_details.remove(id);
408 X264_DELETE(logFile);
417 bool JobListModel::moveJob(const QModelIndex &index, const int &direction)
419 if(VALID_INDEX(index))
421 if((direction == MOVE_UP) && (index.row() > 0))
423 beginMoveRows(QModelIndex(), index.row(), index.row(), QModelIndex(), index.row() - 1);
424 m_jobs.swap(index.row(), index.row() - 1);
428 if((direction == MOVE_DOWN) && (index.row() < m_jobs.size() - 1))
430 beginMoveRows(QModelIndex(), index.row(), index.row(), QModelIndex(), index.row() + 2);
431 m_jobs.swap(index.row(), index.row() + 1);
440 LogFileModel *JobListModel::getLogFile(const QModelIndex &index)
442 if(index.isValid() && index.row() >= 0 && index.row() < m_jobs.count())
444 return m_logFile.value(m_jobs.at(index.row()));
450 const QString &JobListModel::getJobSourceFile(const QModelIndex &index)
452 static QString nullStr;
454 if(index.isValid() && index.row() >= 0 && index.row() < m_jobs.count())
456 EncodeThread *thread = m_threads.value(m_jobs.at(index.row()));
457 return (thread != NULL) ? thread->sourceFileName() : nullStr;
463 const QString &JobListModel::getJobOutputFile(const QModelIndex &index)
465 static QString nullStr;
467 if(index.isValid() && index.row() >= 0 && index.row() < m_jobs.count())
469 EncodeThread *thread = m_threads.value(m_jobs.at(index.row()));
470 return (thread != NULL) ? thread->outputFileName() : nullStr;
476 JobStatus JobListModel::getJobStatus(const QModelIndex &index)
478 if(index.isValid() && index.row() >= 0 && index.row() < m_jobs.count())
480 return m_status.value(m_jobs.at(index.row()));
483 return static_cast<JobStatus>(-1);
486 unsigned int JobListModel::getJobProgress(const QModelIndex &index)
488 if(index.isValid() && index.row() >= 0 && index.row() < m_jobs.count())
490 return m_progress.value(m_jobs.at(index.row()));
496 const OptionsModel *JobListModel::getJobOptions(const QModelIndex &index)
498 static QString nullStr;
500 if(index.isValid() && index.row() >= 0 && index.row() < m_jobs.count())
502 EncodeThread *thread = m_threads.value(m_jobs.at(index.row()));
503 return (thread != NULL) ? thread->options() : NULL;
509 QModelIndex JobListModel::getJobIndexById(const QUuid &id)
511 if(m_jobs.contains(id))
513 return createIndex(m_jobs.indexOf(id), 0);
516 return QModelIndex();
519 ///////////////////////////////////////////////////////////////////////////////
521 ///////////////////////////////////////////////////////////////////////////////
523 void JobListModel::updateStatus(const QUuid &jobId, JobStatus newStatus)
527 if((index = m_jobs.indexOf(jobId)) >= 0)
529 m_status.insert(jobId, newStatus);
530 emit dataChanged(createIndex(index, 0), createIndex(index, 1));
532 if(m_preferences->getEnableSounds())
536 case JobStatus_Completed:
537 x264_play_sound(IDR_WAVE4, true);
539 case JobStatus_Aborted:
540 x264_play_sound(IDR_WAVE5, true);
542 case JobStatus_Failed:
543 x264_play_sound(IDR_WAVE6, true);
550 void JobListModel::updateProgress(const QUuid &jobId, unsigned int newProgress)
554 if((index = m_jobs.indexOf(jobId)) >= 0)
556 m_progress.insert(jobId, qBound(0U, newProgress, 100U));
557 emit dataChanged(createIndex(index, 2), createIndex(index, 2));
561 void JobListModel::updateDetails(const QUuid &jobId, const QString &details)
565 if((index = m_jobs.indexOf(jobId)) >= 0)
567 m_details.insert(jobId, details);
568 emit dataChanged(createIndex(index, 3), createIndex(index, 3));
572 size_t JobListModel::saveQueuedJobs(void)
574 const QString appDir = x264_data_path();
575 QSettings settings(QString("%1/queue.ini").arg(appDir), QSettings::IniFormat);
578 settings.setValue(KEY_ENTRY_COUNT, 0);
579 size_t jobCounter = 0;
581 for(QList<QUuid>::ConstIterator iter = m_jobs.constBegin(); iter != m_jobs.constEnd(); iter++)
583 if(m_status.value(*iter) == JobStatus_Enqueued)
585 if(const EncodeThread *thread = m_threads.value(*iter))
587 settings.beginGroup(QString().sprintf(JOB_TEMPLATE, jobCounter++));
588 settings.setValue(KEY_SOURCE_FILE, thread->sourceFileName());
589 settings.setValue(KEY_OUTPUT_FILE, thread->outputFileName());
591 settings.beginGroup(KEY_ENC_OPTIONS);
592 OptionsModel::saveOptions(thread->options(), settings);
597 settings.setValue(KEY_ENTRY_COUNT, jobCounter);
606 size_t JobListModel::loadQueuedJobs(const SysinfoModel *sysinfo)
608 const QString appDir = x264_data_path();
609 QSettings settings(QString("%1/queue.ini").arg(appDir), QSettings::IniFormat);
612 const size_t jobCounter = settings.value(KEY_ENTRY_COUNT, 0).toUInt(&ok);
614 if((!ok) || (jobCounter < 1))
619 const QStringList groups = settings.childGroups();
620 for(size_t i = 0; i < jobCounter; i++)
622 if(!groups.contains(QString().sprintf(JOB_TEMPLATE, i)))
628 size_t jobsCreated = 0;
629 for(size_t i = 0; i < jobCounter; i++)
631 settings.beginGroup(QString().sprintf(JOB_TEMPLATE, i));
632 const QString sourceFileName = settings.value(KEY_SOURCE_FILE, QString()).toString().trimmed();
633 const QString outputFileName = settings.value(KEY_OUTPUT_FILE, QString()).toString().trimmed();
635 if(sourceFileName.isEmpty() || outputFileName.isEmpty())
641 settings.beginGroup(KEY_ENC_OPTIONS);
642 OptionsModel options(sysinfo);
643 const bool okay = OptionsModel::loadOptions(&options, settings);
650 EncodeThread *thread = new EncodeThread(sourceFileName, outputFileName, &options, sysinfo, m_preferences);
659 void JobListModel::clearQueuedJobs(void)
661 const QString appDir = x264_data_path();
662 QSettings settings(QString("%1/queue.ini").arg(appDir), QSettings::IniFormat);
664 settings.setValue(KEY_ENTRY_COUNT, 0);