1 ///////////////////////////////////////////////////////////////////////////////
2 // Simple x264 Launcher
3 // Copyright (C) 2004-2022 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 ///////////////////////////////////////////////////////////////////////////////
24 #include "model_jobList.h"
25 #include "thread_encode.h"
26 #include "encoder_factory.h"
27 #include "model_options.h"
28 #include "model_preferences.h"
32 #include <MUtils/Sound.h>
39 static const char *KEY_ENTRY_COUNT
= "entry_count";
40 static const char *KEY_SOURCE_FILE
= "source_file";
41 static const char *KEY_OUTPUT_FILE
= "output_file";
42 static const char *KEY_ENC_OPTIONS
= "enc_options";
44 static const char *JOB_TEMPLATE
= "job_%08x";
46 #define VALID_INDEX(INDEX) ((INDEX).isValid() && ((INDEX).row() >= 0) && ((INDEX).row() < m_jobs.count()))
48 JobListModel::JobListModel(PreferencesModel
*preferences
)
50 m_preferences
= preferences
;
53 JobListModel::~JobListModel(void)
55 while(!m_jobs
.isEmpty())
57 QUuid id
= m_jobs
.takeFirst();
58 EncodeThread
*thread
= m_threads
.value(id
, NULL
);
59 LogFileModel
*logFile
= m_logFile
.value(id
, NULL
);
60 MUTILS_DELETE(thread
);
61 MUTILS_DELETE(logFile
);
65 ///////////////////////////////////////////////////////////////////////////////
67 ///////////////////////////////////////////////////////////////////////////////
69 int JobListModel::columnCount(const QModelIndex
&parent
) const
74 int JobListModel::rowCount(const QModelIndex
&parent
) const
76 return m_jobs
.count();
79 QVariant
JobListModel::headerData(int section
, Qt::Orientation orientation
, int role
) const
81 if((orientation
== Qt::Horizontal
) && (role
== Qt::DisplayRole
))
86 return QVariant::fromValue
<QString
>(tr("Job"));
89 return QVariant::fromValue
<QString
>(tr("Status"));
92 return QVariant::fromValue
<QString
>(tr("Progress"));
95 return QVariant::fromValue
<QString
>(tr("Details"));
106 QModelIndex
JobListModel::index(int row
, int column
, const QModelIndex
&parent
) const
108 return createIndex(row
, column
, NULL
);
111 QModelIndex
JobListModel::parent(const QModelIndex
&index
) const
113 return QModelIndex();
116 QVariant
JobListModel::data(const QModelIndex
&index
, int role
) const
118 if(role
== Qt::DisplayRole
)
120 if(index
.row() >= 0 && index
.row() < m_jobs
.count())
122 switch(index
.column())
125 return m_name
.value(m_jobs
.at(index
.row()));
128 switch(m_status
.value(m_jobs
.at(index
.row())))
130 case JobStatus_Enqueued
:
131 return QVariant::fromValue
<QString
>(tr("Enqueued."));
133 case JobStatus_Starting
:
134 return QVariant::fromValue
<QString
>(tr("Starting..."));
136 case JobStatus_Indexing
:
137 return QVariant::fromValue
<QString
>(tr("Indexing..."));
139 case JobStatus_Running
:
140 return QVariant::fromValue
<QString
>(tr("Running..."));
142 case JobStatus_Running_Pass1
:
143 return QVariant::fromValue
<QString
>(tr("Running... (Pass 1)"));
145 case JobStatus_Running_Pass2
:
146 return QVariant::fromValue
<QString
>(tr("Running... (Pass 2)"));
148 case JobStatus_Completed
:
149 return QVariant::fromValue
<QString
>(tr("Completed."));
151 case JobStatus_Failed
:
152 return QVariant::fromValue
<QString
>(tr("Failed!"));
154 case JobStatus_Pausing
:
155 return QVariant::fromValue
<QString
>(tr("Pausing..."));
157 case JobStatus_Paused
:
158 return QVariant::fromValue
<QString
>(tr("Paused."));
160 case JobStatus_Resuming
:
161 return QVariant::fromValue
<QString
>(tr("Resuming..."));
163 case JobStatus_Aborting
:
164 return QVariant::fromValue
<QString
>(tr("Aborting..."));
166 case JobStatus_Aborted
:
167 return QVariant::fromValue
<QString
>(tr("Aborted!"));
170 return QVariant::fromValue
<QString
>(tr("(Unknown)"));
175 return QString().sprintf("%d%%", m_progress
.value(m_jobs
.at(index
.row())));
178 return m_details
.value(m_jobs
.at(index
.row()));
186 else if(role
== Qt::DecorationRole
)
188 if(index
.row() >= 0 && index
.row() < m_jobs
.count() && index
.column() == 0)
190 switch(m_status
.value(m_jobs
.at(index
.row())))
192 case JobStatus_Enqueued
:
193 return QIcon(":/buttons/hourglass.png");
195 case JobStatus_Starting
:
196 return QIcon(":/buttons/lightning.png");
198 case JobStatus_Indexing
:
199 return QIcon(":/buttons/find.png");
201 case JobStatus_Running
:
202 case JobStatus_Running_Pass1
:
203 case JobStatus_Running_Pass2
:
204 return QIcon(":/buttons/play.png");
206 case JobStatus_Completed
:
207 return QIcon(":/buttons/accept.png");
209 case JobStatus_Failed
:
210 return QIcon(":/buttons/exclamation.png");
212 case JobStatus_Pausing
:
213 return QIcon(":/buttons/clock_pause.png");
215 case JobStatus_Paused
:
216 return QIcon(":/buttons/suspended.png");
218 case JobStatus_Resuming
:
219 return QIcon(":/buttons/clock_play.png");
221 case JobStatus_Aborting
:
222 return QIcon(":/buttons/clock_stop.png");
224 case JobStatus_Aborted
:
225 return QIcon(":/buttons/error.png");
237 ///////////////////////////////////////////////////////////////////////////////
239 ///////////////////////////////////////////////////////////////////////////////
241 QModelIndex
JobListModel::insertJob(EncodeThread
*thread
)
243 const QUuid id
= thread
->getId();
244 if(m_jobs
.contains(id
))
246 return QModelIndex();
249 const AbstractEncoderInfo
&encoderInfo
= EncoderFactory::getEncoderInfo(thread
->options()->encType());
250 const QStringList encoderNameParts
= encoderInfo
.getName().simplified().split(' ', QString::SkipEmptyParts
);
251 QString config
= encoderNameParts
.isEmpty() ? encoderInfo
.getName() : encoderNameParts
.first();
252 switch(encoderInfo
.rcModeToType(thread
->options()->rcMode()))
254 case AbstractEncoderInfo::RC_TYPE_QUANTIZER
:
255 config
.append(QString(", %1@%2").arg(encoderInfo
.rcModeToString(thread
->options()->rcMode()), QString::number(qRound(thread
->options()->quantizer()))));
257 case AbstractEncoderInfo::RC_TYPE_RATE_KBPS
:
258 case AbstractEncoderInfo::RC_TYPE_MULTIPASS
:
259 config
.append(QString(", %1@%2").arg(encoderInfo
.rcModeToString(thread
->options()->rcMode()), QString::number(thread
->options()->bitrate())));
264 QString jobName
= QString("%1 [%2]").arg(QFileInfo(thread
->sourceFileName()).completeBaseName().simplified(), config
);
268 for(int i
= 0; i
< m_jobs
.count(); i
++)
270 if(m_name
.value(m_jobs
.at(i
)).compare(jobName
, Qt::CaseInsensitive
) == 0)
278 jobName
= QString("%1 %2 [%3]").arg(QFileInfo(thread
->sourceFileName()).completeBaseName().simplified(), QString::number(n
++), config
);
284 LogFileModel
*logFile
= new LogFileModel(thread
->sourceFileName(), thread
->outputFileName(), config
);
286 beginInsertRows(QModelIndex(), m_jobs
.count(), m_jobs
.count());
288 m_name
.insert(id
, jobName
);
289 m_status
.insert(id
, JobStatus_Enqueued
);
290 m_progress
.insert(id
, 0);
291 m_threads
.insert(id
, thread
);
292 m_logFile
.insert(id
, logFile
);
293 m_details
.insert(id
, tr("Not started yet."));
296 connect(thread
, SIGNAL(statusChanged(QUuid
, JobStatus
)), this, SLOT(updateStatus(QUuid
, JobStatus
)), Qt::QueuedConnection
);
297 connect(thread
, SIGNAL(progressChanged(QUuid
, unsigned int)), this, SLOT(updateProgress(QUuid
, unsigned int)), Qt::QueuedConnection
);
298 connect(thread
, SIGNAL(messageLogged(QUuid
, qint64
, QString
)), logFile
, SLOT(addLogMessage(QUuid
, qint64
, QString
)), Qt::QueuedConnection
);
299 connect(thread
, SIGNAL(detailsChanged(QUuid
, QString
)), this, SLOT(updateDetails(QUuid
, QString
)), Qt::QueuedConnection
);
301 return createIndex(m_jobs
.count() - 1, 0, NULL
);
304 bool JobListModel::startJob(const QModelIndex
&index
)
306 if(VALID_INDEX(index
))
308 QUuid id
= m_jobs
.at(index
.row());
309 if(m_status
.value(id
) == JobStatus_Enqueued
)
311 updateStatus(id
, JobStatus_Starting
);
312 updateDetails(id
, tr("Starting up, please wait..."));
313 m_threads
.value(id
)->start();
321 bool JobListModel::pauseJob(const QModelIndex
&index
)
323 if(VALID_INDEX(index
))
325 QUuid id
= m_jobs
.at(index
.row());
326 JobStatus status
= m_status
.value(id
);
327 if((status
== JobStatus_Indexing
) || (status
== JobStatus_Running
) ||
328 (status
== JobStatus_Running_Pass1
) || (status
== JobStatus_Running_Pass2
))
330 updateStatus(id
, JobStatus_Pausing
);
331 m_threads
.value(id
)->pauseJob();
339 bool JobListModel::resumeJob(const QModelIndex
&index
)
341 if(VALID_INDEX(index
))
343 QUuid id
= m_jobs
.at(index
.row());
344 JobStatus status
= m_status
.value(id
);
345 if(status
== JobStatus_Paused
)
347 updateStatus(id
, JobStatus_Resuming
);
348 m_threads
.value(id
)->resumeJob();
356 bool JobListModel::abortJob(const QModelIndex
&index
)
358 if(VALID_INDEX(index
))
360 QUuid id
= m_jobs
.at(index
.row());
361 if(m_status
.value(id
) == JobStatus_Indexing
|| m_status
.value(id
) == JobStatus_Running
||
362 m_status
.value(id
) == JobStatus_Running_Pass1
|| JobStatus_Running_Pass2
)
364 updateStatus(id
, JobStatus_Aborting
);
365 m_threads
.value(id
)->abortJob();
373 bool JobListModel::deleteJob(const QModelIndex
&index
)
375 if(VALID_INDEX(index
))
377 QUuid id
= m_jobs
.at(index
.row());
378 if(m_status
.value(id
) == JobStatus_Completed
|| m_status
.value(id
) == JobStatus_Failed
||
379 m_status
.value(id
) == JobStatus_Aborted
|| m_status
.value(id
) == JobStatus_Enqueued
)
381 int idx
= index
.row();
382 QUuid id
= m_jobs
.at(idx
);
383 EncodeThread
*thread
= m_threads
.value(id
, NULL
);
384 LogFileModel
*logFile
= m_logFile
.value(id
, NULL
);
385 if((thread
== NULL
) || (!thread
->isRunning()))
388 beginRemoveRows(QModelIndex(), idx
, idx
);
389 m_jobs
.removeAt(index
.row());
391 m_threads
.remove(id
);
393 m_progress
.remove(id
);
394 m_logFile
.remove(id
);
395 m_details
.remove(id
);
397 MUTILS_DELETE(thread
);
398 MUTILS_DELETE(logFile
);
407 bool JobListModel::moveJob(const QModelIndex
&index
, const int &direction
)
409 if(VALID_INDEX(index
))
411 if((direction
== MOVE_UP
) && (index
.row() > 0))
413 beginMoveRows(QModelIndex(), index
.row(), index
.row(), QModelIndex(), index
.row() - 1);
414 m_jobs
.swap(index
.row(), index
.row() - 1);
418 if((direction
== MOVE_DOWN
) && (index
.row() < m_jobs
.size() - 1))
420 beginMoveRows(QModelIndex(), index
.row(), index
.row(), QModelIndex(), index
.row() + 2);
421 m_jobs
.swap(index
.row(), index
.row() + 1);
430 LogFileModel
*JobListModel::getLogFile(const QModelIndex
&index
)
432 if(index
.isValid() && index
.row() >= 0 && index
.row() < m_jobs
.count())
434 return m_logFile
.value(m_jobs
.at(index
.row()));
440 const QString
&JobListModel::getJobSourceFile(const QModelIndex
&index
)
442 static QString nullStr
;
444 if(index
.isValid() && index
.row() >= 0 && index
.row() < m_jobs
.count())
446 EncodeThread
*thread
= m_threads
.value(m_jobs
.at(index
.row()));
447 return (thread
!= NULL
) ? thread
->sourceFileName() : nullStr
;
453 const QString
&JobListModel::getJobOutputFile(const QModelIndex
&index
)
455 static QString nullStr
;
457 if(index
.isValid() && index
.row() >= 0 && index
.row() < m_jobs
.count())
459 EncodeThread
*thread
= m_threads
.value(m_jobs
.at(index
.row()));
460 return (thread
!= NULL
) ? thread
->outputFileName() : nullStr
;
466 JobStatus
JobListModel::getJobStatus(const QModelIndex
&index
)
468 if(index
.isValid() && index
.row() >= 0 && index
.row() < m_jobs
.count())
470 return m_status
.value(m_jobs
.at(index
.row()));
473 return static_cast<JobStatus
>(-1);
476 unsigned int JobListModel::getJobProgress(const QModelIndex
&index
)
478 if(index
.isValid() && index
.row() >= 0 && index
.row() < m_jobs
.count())
480 return m_progress
.value(m_jobs
.at(index
.row()));
486 const OptionsModel
*JobListModel::getJobOptions(const QModelIndex
&index
)
488 static QString nullStr
;
490 if(index
.isValid() && index
.row() >= 0 && index
.row() < m_jobs
.count())
492 EncodeThread
*thread
= m_threads
.value(m_jobs
.at(index
.row()));
493 return (thread
!= NULL
) ? thread
->options() : NULL
;
499 QModelIndex
JobListModel::getJobIndexById(const QUuid
&id
)
501 if(m_jobs
.contains(id
))
503 return createIndex(m_jobs
.indexOf(id
), 0);
506 return QModelIndex();
509 ///////////////////////////////////////////////////////////////////////////////
511 ///////////////////////////////////////////////////////////////////////////////
513 void JobListModel::updateStatus(const QUuid
&jobId
, JobStatus newStatus
)
517 if((index
= m_jobs
.indexOf(jobId
)) >= 0)
519 m_status
.insert(jobId
, newStatus
);
520 emit
dataChanged(createIndex(index
, 0), createIndex(index
, 1));
522 if(m_preferences
->getEnableSounds())
526 case JobStatus_Completed
:
527 MUtils::Sound::play_sound("tada", true);
529 case JobStatus_Aborted
:
530 MUtils::Sound::play_sound("shattering", true);
532 case JobStatus_Failed
:
533 MUtils::Sound::play_sound("failure", true);
540 void JobListModel::updateProgress(const QUuid
&jobId
, unsigned int newProgress
)
544 if((index
= m_jobs
.indexOf(jobId
)) >= 0)
546 m_progress
.insert(jobId
, qBound(0U, newProgress
, 100U));
547 emit
dataChanged(createIndex(index
, 2), createIndex(index
, 2));
551 void JobListModel::updateDetails(const QUuid
&jobId
, const QString
&details
)
555 if((index
= m_jobs
.indexOf(jobId
)) >= 0)
557 m_details
.insert(jobId
, details
);
558 emit
dataChanged(createIndex(index
, 3), createIndex(index
, 3));
562 size_t JobListModel::saveQueuedJobs(void)
564 const QString appDir
= x264_data_path();
565 QSettings
settings(QString("%1/queue.ini").arg(appDir
), QSettings::IniFormat
);
568 settings
.setValue(KEY_ENTRY_COUNT
, 0);
569 size_t jobCounter
= 0;
571 for(QList
<QUuid
>::ConstIterator iter
= m_jobs
.constBegin(); iter
!= m_jobs
.constEnd(); iter
++)
573 if(m_status
.value(*iter
) == JobStatus_Enqueued
)
575 if(const EncodeThread
*thread
= m_threads
.value(*iter
))
577 settings
.beginGroup(QString().sprintf(JOB_TEMPLATE
, jobCounter
++));
578 settings
.setValue(KEY_SOURCE_FILE
, thread
->sourceFileName());
579 settings
.setValue(KEY_OUTPUT_FILE
, thread
->outputFileName());
581 settings
.beginGroup(KEY_ENC_OPTIONS
);
582 OptionsModel::saveOptions(thread
->options(), settings
);
587 settings
.setValue(KEY_ENTRY_COUNT
, jobCounter
);
596 size_t JobListModel::loadQueuedJobs(const SysinfoModel
*sysinfo
)
598 const QString appDir
= x264_data_path();
599 QSettings
settings(QString("%1/queue.ini").arg(appDir
), QSettings::IniFormat
);
602 const size_t jobCounter
= settings
.value(KEY_ENTRY_COUNT
, 0).toUInt(&ok
);
604 if((!ok
) || (jobCounter
< 1))
609 const QStringList groups
= settings
.childGroups();
610 for(size_t i
= 0; i
< jobCounter
; i
++)
612 if(!groups
.contains(QString().sprintf(JOB_TEMPLATE
, i
)))
618 size_t jobsCreated
= 0;
619 for(size_t i
= 0; i
< jobCounter
; i
++)
621 settings
.beginGroup(QString().sprintf(JOB_TEMPLATE
, i
));
622 const QString sourceFileName
= settings
.value(KEY_SOURCE_FILE
, QString()).toString().trimmed();
623 const QString outputFileName
= settings
.value(KEY_OUTPUT_FILE
, QString()).toString().trimmed();
625 if(sourceFileName
.isEmpty() || outputFileName
.isEmpty())
631 settings
.beginGroup(KEY_ENC_OPTIONS
);
632 OptionsModel
options(sysinfo
);
633 const bool okay
= OptionsModel::loadOptions(&options
, settings
);
640 EncodeThread
*thread
= new EncodeThread(sourceFileName
, outputFileName
, &options
, sysinfo
, m_preferences
);
649 void JobListModel::clearQueuedJobs(void)
651 const QString appDir
= x264_data_path();
652 QSettings
settings(QString("%1/queue.ini").arg(appDir
), QSettings::IniFormat
);
654 settings
.setValue(KEY_ENTRY_COUNT
, 0);