1 ///////////////////////////////////////////////////////////////////////////////
2 // Simple x264 Launcher
3 // Copyright (C) 2004-2015 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 "model_options.h"
27 #include "model_preferences.h"
31 #include <MUtils/Sound.h>
38 static const char *KEY_ENTRY_COUNT
= "entry_count";
39 static const char *KEY_SOURCE_FILE
= "source_file";
40 static const char *KEY_OUTPUT_FILE
= "output_file";
41 static const char *KEY_ENC_OPTIONS
= "enc_options";
43 static const char *JOB_TEMPLATE
= "job_%08x";
45 #define VALID_INDEX(INDEX) ((INDEX).isValid() && ((INDEX).row() >= 0) && ((INDEX).row() < m_jobs.count()))
47 JobListModel::JobListModel(PreferencesModel
*preferences
)
49 m_preferences
= preferences
;
52 JobListModel::~JobListModel(void)
54 while(!m_jobs
.isEmpty())
56 QUuid id
= m_jobs
.takeFirst();
57 EncodeThread
*thread
= m_threads
.value(id
, NULL
);
58 LogFileModel
*logFile
= m_logFile
.value(id
, NULL
);
59 MUTILS_DELETE(thread
);
60 MUTILS_DELETE(logFile
);
64 ///////////////////////////////////////////////////////////////////////////////
66 ///////////////////////////////////////////////////////////////////////////////
68 int JobListModel::columnCount(const QModelIndex
&parent
) const
73 int JobListModel::rowCount(const QModelIndex
&parent
) const
75 return m_jobs
.count();
78 QVariant
JobListModel::headerData(int section
, Qt::Orientation orientation
, int role
) const
80 if((orientation
== Qt::Horizontal
) && (role
== Qt::DisplayRole
))
85 return QVariant::fromValue
<QString
>(tr("Job"));
88 return QVariant::fromValue
<QString
>(tr("Status"));
91 return QVariant::fromValue
<QString
>(tr("Progress"));
94 return QVariant::fromValue
<QString
>(tr("Details"));
105 QModelIndex
JobListModel::index(int row
, int column
, const QModelIndex
&parent
) const
107 return createIndex(row
, column
, NULL
);
110 QModelIndex
JobListModel::parent(const QModelIndex
&index
) const
112 return QModelIndex();
115 QVariant
JobListModel::data(const QModelIndex
&index
, int role
) const
117 if(role
== Qt::DisplayRole
)
119 if(index
.row() >= 0 && index
.row() < m_jobs
.count())
121 switch(index
.column())
124 return m_name
.value(m_jobs
.at(index
.row()));
127 switch(m_status
.value(m_jobs
.at(index
.row())))
129 case JobStatus_Enqueued
:
130 return QVariant::fromValue
<QString
>(tr("Enqueued."));
132 case JobStatus_Starting
:
133 return QVariant::fromValue
<QString
>(tr("Starting..."));
135 case JobStatus_Indexing
:
136 return QVariant::fromValue
<QString
>(tr("Indexing..."));
138 case JobStatus_Running
:
139 return QVariant::fromValue
<QString
>(tr("Running..."));
141 case JobStatus_Running_Pass1
:
142 return QVariant::fromValue
<QString
>(tr("Running... (Pass 1)"));
144 case JobStatus_Running_Pass2
:
145 return QVariant::fromValue
<QString
>(tr("Running... (Pass 2)"));
147 case JobStatus_Completed
:
148 return QVariant::fromValue
<QString
>(tr("Completed."));
150 case JobStatus_Failed
:
151 return QVariant::fromValue
<QString
>(tr("Failed!"));
153 case JobStatus_Pausing
:
154 return QVariant::fromValue
<QString
>(tr("Pausing..."));
156 case JobStatus_Paused
:
157 return QVariant::fromValue
<QString
>(tr("Paused."));
159 case JobStatus_Resuming
:
160 return QVariant::fromValue
<QString
>(tr("Resuming..."));
162 case JobStatus_Aborting
:
163 return QVariant::fromValue
<QString
>(tr("Aborting..."));
165 case JobStatus_Aborted
:
166 return QVariant::fromValue
<QString
>(tr("Aborted!"));
169 return QVariant::fromValue
<QString
>(tr("(Unknown)"));
174 return QString().sprintf("%d%%", m_progress
.value(m_jobs
.at(index
.row())));
177 return m_details
.value(m_jobs
.at(index
.row()));
185 else if(role
== Qt::DecorationRole
)
187 if(index
.row() >= 0 && index
.row() < m_jobs
.count() && index
.column() == 0)
189 switch(m_status
.value(m_jobs
.at(index
.row())))
191 case JobStatus_Enqueued
:
192 return QIcon(":/buttons/hourglass.png");
194 case JobStatus_Starting
:
195 return QIcon(":/buttons/lightning.png");
197 case JobStatus_Indexing
:
198 return QIcon(":/buttons/find.png");
200 case JobStatus_Running
:
201 case JobStatus_Running_Pass1
:
202 case JobStatus_Running_Pass2
:
203 return QIcon(":/buttons/play.png");
205 case JobStatus_Completed
:
206 return QIcon(":/buttons/accept.png");
208 case JobStatus_Failed
:
209 return QIcon(":/buttons/exclamation.png");
211 case JobStatus_Pausing
:
212 return QIcon(":/buttons/clock_pause.png");
214 case JobStatus_Paused
:
215 return QIcon(":/buttons/suspended.png");
217 case JobStatus_Resuming
:
218 return QIcon(":/buttons/clock_play.png");
220 case JobStatus_Aborting
:
221 return QIcon(":/buttons/clock_stop.png");
223 case JobStatus_Aborted
:
224 return QIcon(":/buttons/error.png");
236 ///////////////////////////////////////////////////////////////////////////////
238 ///////////////////////////////////////////////////////////////////////////////
240 QModelIndex
JobListModel::insertJob(EncodeThread
*thread
)
242 QUuid id
= thread
->getId();
244 if(m_jobs
.contains(id
))
246 return QModelIndex();
249 QString config
= "N/A";
251 switch(thread
->options()->rcMode())
253 case OptionsModel::EncType_X264
:
254 config
= QString("x264");
256 case OptionsModel::EncType_X265
:
257 config
= QString("x265");
261 switch(thread
->options()->rcMode())
263 case OptionsModel::RCMode_CRF
:
264 config
= QString("%1 CRF@%2") .arg(config
, QString::number(thread
->options()->quantizer()));
266 case OptionsModel::RCMode_CQ
:
267 config
= QString("%1 CQ@%2") .arg(config
, QString::number(qRound(thread
->options()->quantizer())));
269 case OptionsModel::RCMode_2Pass
:
270 config
= QString("%1 2Pass@%2").arg(config
, QString::number(thread
->options()->bitrate()));
272 case OptionsModel::RCMode_ABR
:
273 config
= QString("%1 ABR@%2") .arg(config
, QString::number(thread
->options()->bitrate()));
278 QString jobName
= QString("%1 (%2)").arg(QFileInfo(thread
->sourceFileName()).completeBaseName().simplified(), config
);
283 for(int i
= 0; i
< m_jobs
.count(); i
++)
285 if(m_name
.value(m_jobs
.at(i
)).compare(jobName
, Qt::CaseInsensitive
) == 0)
293 jobName
= QString("%1 %2 (%3)").arg(QFileInfo(thread
->sourceFileName()).completeBaseName().simplified(), QString::number(n
++), config
);
299 LogFileModel
*logFile
= new LogFileModel(thread
->sourceFileName(), thread
->outputFileName(), config
);
301 beginInsertRows(QModelIndex(), m_jobs
.count(), m_jobs
.count());
303 m_name
.insert(id
, jobName
);
304 m_status
.insert(id
, JobStatus_Enqueued
);
305 m_progress
.insert(id
, 0);
306 m_threads
.insert(id
, thread
);
307 m_logFile
.insert(id
, logFile
);
308 m_details
.insert(id
, tr("Not started yet."));
311 connect(thread
, SIGNAL(statusChanged(QUuid
, JobStatus
)), this, SLOT(updateStatus(QUuid
, JobStatus
)), Qt::QueuedConnection
);
312 connect(thread
, SIGNAL(progressChanged(QUuid
, unsigned int)), this, SLOT(updateProgress(QUuid
, unsigned int)), Qt::QueuedConnection
);
313 connect(thread
, SIGNAL(messageLogged(QUuid
, QString
)), logFile
, SLOT(addLogMessage(QUuid
, QString
)), Qt::QueuedConnection
);
314 connect(thread
, SIGNAL(detailsChanged(QUuid
, QString
)), this, SLOT(updateDetails(QUuid
, QString
)), Qt::QueuedConnection
);
316 return createIndex(m_jobs
.count() - 1, 0, NULL
);
319 bool JobListModel::startJob(const QModelIndex
&index
)
321 if(VALID_INDEX(index
))
323 QUuid id
= m_jobs
.at(index
.row());
324 if(m_status
.value(id
) == JobStatus_Enqueued
)
326 updateStatus(id
, JobStatus_Starting
);
327 updateDetails(id
, tr("Starting up, please wait..."));
328 m_threads
.value(id
)->start();
336 bool JobListModel::pauseJob(const QModelIndex
&index
)
338 if(VALID_INDEX(index
))
340 QUuid id
= m_jobs
.at(index
.row());
341 JobStatus status
= m_status
.value(id
);
342 if((status
== JobStatus_Indexing
) || (status
== JobStatus_Running
) ||
343 (status
== JobStatus_Running_Pass1
) || (status
== JobStatus_Running_Pass2
))
345 updateStatus(id
, JobStatus_Pausing
);
346 m_threads
.value(id
)->pauseJob();
354 bool JobListModel::resumeJob(const QModelIndex
&index
)
356 if(VALID_INDEX(index
))
358 QUuid id
= m_jobs
.at(index
.row());
359 JobStatus status
= m_status
.value(id
);
360 if(status
== JobStatus_Paused
)
362 updateStatus(id
, JobStatus_Resuming
);
363 m_threads
.value(id
)->resumeJob();
371 bool JobListModel::abortJob(const QModelIndex
&index
)
373 if(VALID_INDEX(index
))
375 QUuid id
= m_jobs
.at(index
.row());
376 if(m_status
.value(id
) == JobStatus_Indexing
|| m_status
.value(id
) == JobStatus_Running
||
377 m_status
.value(id
) == JobStatus_Running_Pass1
|| JobStatus_Running_Pass2
)
379 updateStatus(id
, JobStatus_Aborting
);
380 m_threads
.value(id
)->abortJob();
388 bool JobListModel::deleteJob(const QModelIndex
&index
)
390 if(VALID_INDEX(index
))
392 QUuid id
= m_jobs
.at(index
.row());
393 if(m_status
.value(id
) == JobStatus_Completed
|| m_status
.value(id
) == JobStatus_Failed
||
394 m_status
.value(id
) == JobStatus_Aborted
|| m_status
.value(id
) == JobStatus_Enqueued
)
396 int idx
= index
.row();
397 QUuid id
= m_jobs
.at(idx
);
398 EncodeThread
*thread
= m_threads
.value(id
, NULL
);
399 LogFileModel
*logFile
= m_logFile
.value(id
, NULL
);
400 if((thread
== NULL
) || (!thread
->isRunning()))
403 beginRemoveRows(QModelIndex(), idx
, idx
);
404 m_jobs
.removeAt(index
.row());
406 m_threads
.remove(id
);
408 m_progress
.remove(id
);
409 m_logFile
.remove(id
);
410 m_details
.remove(id
);
412 MUTILS_DELETE(thread
);
413 MUTILS_DELETE(logFile
);
422 bool JobListModel::moveJob(const QModelIndex
&index
, const int &direction
)
424 if(VALID_INDEX(index
))
426 if((direction
== MOVE_UP
) && (index
.row() > 0))
428 beginMoveRows(QModelIndex(), index
.row(), index
.row(), QModelIndex(), index
.row() - 1);
429 m_jobs
.swap(index
.row(), index
.row() - 1);
433 if((direction
== MOVE_DOWN
) && (index
.row() < m_jobs
.size() - 1))
435 beginMoveRows(QModelIndex(), index
.row(), index
.row(), QModelIndex(), index
.row() + 2);
436 m_jobs
.swap(index
.row(), index
.row() + 1);
445 LogFileModel
*JobListModel::getLogFile(const QModelIndex
&index
)
447 if(index
.isValid() && index
.row() >= 0 && index
.row() < m_jobs
.count())
449 return m_logFile
.value(m_jobs
.at(index
.row()));
455 const QString
&JobListModel::getJobSourceFile(const QModelIndex
&index
)
457 static QString nullStr
;
459 if(index
.isValid() && index
.row() >= 0 && index
.row() < m_jobs
.count())
461 EncodeThread
*thread
= m_threads
.value(m_jobs
.at(index
.row()));
462 return (thread
!= NULL
) ? thread
->sourceFileName() : nullStr
;
468 const QString
&JobListModel::getJobOutputFile(const QModelIndex
&index
)
470 static QString nullStr
;
472 if(index
.isValid() && index
.row() >= 0 && index
.row() < m_jobs
.count())
474 EncodeThread
*thread
= m_threads
.value(m_jobs
.at(index
.row()));
475 return (thread
!= NULL
) ? thread
->outputFileName() : nullStr
;
481 JobStatus
JobListModel::getJobStatus(const QModelIndex
&index
)
483 if(index
.isValid() && index
.row() >= 0 && index
.row() < m_jobs
.count())
485 return m_status
.value(m_jobs
.at(index
.row()));
488 return static_cast<JobStatus
>(-1);
491 unsigned int JobListModel::getJobProgress(const QModelIndex
&index
)
493 if(index
.isValid() && index
.row() >= 0 && index
.row() < m_jobs
.count())
495 return m_progress
.value(m_jobs
.at(index
.row()));
501 const OptionsModel
*JobListModel::getJobOptions(const QModelIndex
&index
)
503 static QString nullStr
;
505 if(index
.isValid() && index
.row() >= 0 && index
.row() < m_jobs
.count())
507 EncodeThread
*thread
= m_threads
.value(m_jobs
.at(index
.row()));
508 return (thread
!= NULL
) ? thread
->options() : NULL
;
514 QModelIndex
JobListModel::getJobIndexById(const QUuid
&id
)
516 if(m_jobs
.contains(id
))
518 return createIndex(m_jobs
.indexOf(id
), 0);
521 return QModelIndex();
524 ///////////////////////////////////////////////////////////////////////////////
526 ///////////////////////////////////////////////////////////////////////////////
528 void JobListModel::updateStatus(const QUuid
&jobId
, JobStatus newStatus
)
532 if((index
= m_jobs
.indexOf(jobId
)) >= 0)
534 m_status
.insert(jobId
, newStatus
);
535 emit
dataChanged(createIndex(index
, 0), createIndex(index
, 1));
537 if(m_preferences
->getEnableSounds())
541 case JobStatus_Completed
:
542 MUtils::Sound::play_sound("tada", true);
544 case JobStatus_Aborted
:
545 MUtils::Sound::play_sound("shattering", true);
547 case JobStatus_Failed
:
548 MUtils::Sound::play_sound("failure", true);
555 void JobListModel::updateProgress(const QUuid
&jobId
, unsigned int newProgress
)
559 if((index
= m_jobs
.indexOf(jobId
)) >= 0)
561 m_progress
.insert(jobId
, qBound(0U, newProgress
, 100U));
562 emit
dataChanged(createIndex(index
, 2), createIndex(index
, 2));
566 void JobListModel::updateDetails(const QUuid
&jobId
, const QString
&details
)
570 if((index
= m_jobs
.indexOf(jobId
)) >= 0)
572 m_details
.insert(jobId
, details
);
573 emit
dataChanged(createIndex(index
, 3), createIndex(index
, 3));
577 size_t JobListModel::saveQueuedJobs(void)
579 const QString appDir
= x264_data_path();
580 QSettings
settings(QString("%1/queue.ini").arg(appDir
), QSettings::IniFormat
);
583 settings
.setValue(KEY_ENTRY_COUNT
, 0);
584 size_t jobCounter
= 0;
586 for(QList
<QUuid
>::ConstIterator iter
= m_jobs
.constBegin(); iter
!= m_jobs
.constEnd(); iter
++)
588 if(m_status
.value(*iter
) == JobStatus_Enqueued
)
590 if(const EncodeThread
*thread
= m_threads
.value(*iter
))
592 settings
.beginGroup(QString().sprintf(JOB_TEMPLATE
, jobCounter
++));
593 settings
.setValue(KEY_SOURCE_FILE
, thread
->sourceFileName());
594 settings
.setValue(KEY_OUTPUT_FILE
, thread
->outputFileName());
596 settings
.beginGroup(KEY_ENC_OPTIONS
);
597 OptionsModel::saveOptions(thread
->options(), settings
);
602 settings
.setValue(KEY_ENTRY_COUNT
, jobCounter
);
611 size_t JobListModel::loadQueuedJobs(const SysinfoModel
*sysinfo
)
613 const QString appDir
= x264_data_path();
614 QSettings
settings(QString("%1/queue.ini").arg(appDir
), QSettings::IniFormat
);
617 const size_t jobCounter
= settings
.value(KEY_ENTRY_COUNT
, 0).toUInt(&ok
);
619 if((!ok
) || (jobCounter
< 1))
624 const QStringList groups
= settings
.childGroups();
625 for(size_t i
= 0; i
< jobCounter
; i
++)
627 if(!groups
.contains(QString().sprintf(JOB_TEMPLATE
, i
)))
633 size_t jobsCreated
= 0;
634 for(size_t i
= 0; i
< jobCounter
; i
++)
636 settings
.beginGroup(QString().sprintf(JOB_TEMPLATE
, i
));
637 const QString sourceFileName
= settings
.value(KEY_SOURCE_FILE
, QString()).toString().trimmed();
638 const QString outputFileName
= settings
.value(KEY_OUTPUT_FILE
, QString()).toString().trimmed();
640 if(sourceFileName
.isEmpty() || outputFileName
.isEmpty())
646 settings
.beginGroup(KEY_ENC_OPTIONS
);
647 OptionsModel
options(sysinfo
);
648 const bool okay
= OptionsModel::loadOptions(&options
, settings
);
655 EncodeThread
*thread
= new EncodeThread(sourceFileName
, outputFileName
, &options
, sysinfo
, m_preferences
);
664 void JobListModel::clearQueuedJobs(void)
666 const QString appDir
= x264_data_path();
667 QSettings
settings(QString("%1/queue.ini").arg(appDir
), QSettings::IniFormat
);
669 settings
.setValue(KEY_ENTRY_COUNT
, 0);