From 175a3d511bb422649239f605b64abc8f3e733bb1 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Mardelle Date: Thu, 29 Dec 2011 14:46:09 +0100 Subject: [PATCH] Improvements to the clip jobs framework --- src/clipstabilize.cpp | 25 ++-- src/clipstabilize.h | 30 ++-- src/effectstackedit.cpp | 39 +++++- src/effectstackedit.h | 5 + src/effectstackview.cpp | 7 + src/effectstackview.h | 4 + src/mainwindow.cpp | 13 +- src/projectlist.cpp | 210 ++++++++++++++++------------ src/projectlist.h | 24 ++-- src/projectlistview.cpp | 10 +- src/projecttree/CMakeLists.txt | 1 + src/projecttree/abstractclipjob.cpp | 12 +- src/projecttree/abstractclipjob.h | 19 +-- src/projecttree/cutclipjob.cpp | 55 ++++++-- src/projecttree/cutclipjob.h | 6 +- src/projecttree/meltjob.cpp | 150 ++++++++++++++++++++ src/projecttree/meltjob.h | 61 ++++++++ src/projecttree/proxyclipjob.cpp | 101 ++++++++----- src/projecttree/proxyclipjob.h | 5 +- src/renderwidget.h | 3 +- 20 files changed, 574 insertions(+), 206 deletions(-) create mode 100644 src/projecttree/meltjob.cpp create mode 100644 src/projecttree/meltjob.h diff --git a/src/clipstabilize.cpp b/src/clipstabilize.cpp index 0702f77c..67ad688a 100644 --- a/src/clipstabilize.cpp +++ b/src/clipstabilize.cpp @@ -38,22 +38,22 @@ ClipStabilize::ClipStabilize(KUrl::List urls, const QString ¶ms, Mlt::Filter { setFont(KGlobalSettings::toolBarFont()); setupUi(this); - setAttribute(Qt::WA_DeleteOnClose); + if (filter) setAttribute(Qt::WA_DeleteOnClose); log_text->setHidden(true); setWindowTitle(i18n("Stabilize Clip")); auto_add->setText(i18np("Add clip to project", "Add clips to project", m_urls.count())); - m_profile = new Mlt::Profile(KdenliveSettings::current_profile().toUtf8().constData()); - filtername=params; + m_profile = new Mlt::Profile(KdenliveSettings::current_profile().toUtf8().constData()); + filtername = params; - QPalette p = palette(); - KColorScheme scheme(p.currentColorGroup(), KColorScheme::View, KSharedConfig::openConfig(KdenliveSettings::colortheme())); - QColor dark_bg = scheme.shade(KColorScheme::DarkShade); - QColor selected_bg = scheme.decoration(KColorScheme::FocusColor).color(); - QColor hover_bg = scheme.decoration(KColorScheme::HoverColor).color(); - QColor light_bg = scheme.shade(KColorScheme::LightShade); + QPalette p = palette(); + KColorScheme scheme(p.currentColorGroup(), KColorScheme::View, KSharedConfig::openConfig(KdenliveSettings::colortheme())); + QColor dark_bg = scheme.shade(KColorScheme::DarkShade); + QColor selected_bg = scheme.decoration(KColorScheme::FocusColor).color(); + QColor hover_bg = scheme.decoration(KColorScheme::HoverColor).color(); + QColor light_bg = scheme.shade(KColorScheme::LightShade); - QString stylesheet(QString("QProgressBar:horizontal {border: 1px solid %1;border-radius:0px;border-top-left-radius: 4px;border-bottom-left-radius: 4px;border-right: 0px;background:%4;padding: 0px;text-align:left center}\ + QString stylesheet(QString("QProgressBar:horizontal {border: 1px solid %1;border-radius:0px;border-top-left-radius: 4px;border-bottom-left-radius: 4px;border-right: 0px;background:%4;padding: 0px;text-align:left center}\ QProgressBar:horizontal#dragOnly {background: %1} QProgressBar:horizontal:hover#dragOnly {background: %3} QProgressBar:horizontal:hover {border: 1px solid %3;border-right: 0px;}\ QProgressBar::chunk:horizontal {background: %1;} QProgressBar::chunk:horizontal:hover {background: %3;}\ QProgressBar:horizontal[inTimeline=\"true\"] { border: 1px solid %2;border-right: 0px;background: %4;padding: 0px;text-align:left center } QProgressBar::chunk:horizontal[inTimeline=\"true\"] {background: %2;}\ @@ -301,6 +301,11 @@ void ClipStabilize::slotUpdateParams() } } +bool ClipStabilize::autoAddClip() const +{ + return auto_add->isChecked(); +} + void ClipStabilize::fillParameters(QStringList lst) { diff --git a/src/clipstabilize.h b/src/clipstabilize.h index 1e80b44a..3fe0600d 100644 --- a/src/clipstabilize.h +++ b/src/clipstabilize.h @@ -42,31 +42,33 @@ class ClipStabilize : public QDialog, public Ui::ClipStabilize_UI Q_OBJECT public: - ClipStabilize(KUrl::List urls, const QString ¶ms, Mlt::Filter* filter,QWidget * parent = 0); + ClipStabilize(KUrl::List urls, const QString ¶ms, Mlt::Filter* filter = NULL,QWidget * parent = 0); ~ClipStabilize(); + /** @brief Should the generated clip be added to current project. */ + bool autoAddClip() const; private slots: void slotShowStabilizeInfo(); void slotStartStabilize(); void slotStabilizeFinished(bool success); - void slotRunStabilize(); - void slotAbortStabilize(); - void slotUpdateParams(); + void slotRunStabilize(); + void slotAbortStabilize(); + void slotUpdateParams(); private: - QFuture m_stabilizeRun; - QString filtername; - Mlt::Profile *m_profile; - Mlt::Consumer *m_consumer; - Mlt::Playlist *m_playlist; + QFuture m_stabilizeRun; + QString filtername; + Mlt::Profile *m_profile; + Mlt::Consumer *m_consumer; + Mlt::Playlist *m_playlist; KUrl::List m_urls; int m_duration; - Mlt::Filter* m_filter; - QTimer *m_timer; - QHash > m_ui_params; - QVBoxLayout *vbox; - void fillParameters(QStringList); + Mlt::Filter* m_filter; + QTimer *m_timer; + QHash > m_ui_params; + QVBoxLayout *vbox; + void fillParameters(QStringList); signals: void addClip(KUrl url); diff --git a/src/effectstackedit.cpp b/src/effectstackedit.cpp index 7efab0f2..c274a8f8 100644 --- a/src/effectstackedit.cpp +++ b/src/effectstackedit.cpp @@ -262,7 +262,6 @@ void EffectStackEdit::transferParamDesc(const QDomElement &d, ItemInfo info, boo QString comment; if (!commentElem.isNull()) comment = i18n(commentElem.text().toUtf8().data()); - QWidget * toFillin = new QWidget(m_baseWidget); QString value = pa.attribute("value").isNull() ? pa.attribute("default") : pa.attribute("value"); @@ -289,7 +288,9 @@ void EffectStackEdit::transferParamDesc(const QDomElement &d, ItemInfo info, boo connect(this, SIGNAL(showComments(bool)), doubleparam, SLOT(slotShowComment(bool))); } else if (type == "list") { Listval *lsval = new Listval; + QWidget * toFillin = new QWidget(m_baseWidget); lsval->setupUi(toFillin); + m_vbox->addWidget(toFillin); QStringList listitems = pa.attribute("paramlist").split(';'); if (listitems.count() == 1) { // probably custom effect created before change to ';' as separator @@ -327,7 +328,9 @@ void EffectStackEdit::transferParamDesc(const QDomElement &d, ItemInfo info, boo m_uiItems.append(lsval); } else if (type == "bool") { Boolval *bval = new Boolval; + QWidget * toFillin = new QWidget(m_baseWidget); bval->setupUi(toFillin); + m_vbox->addWidget(toFillin); bval->checkBox->setCheckState(value == "0" ? Qt::Unchecked : Qt::Checked); bval->name->setText(paramName); bval->labelComment->setText(comment); @@ -479,7 +482,9 @@ void EffectStackEdit::transferParamDesc(const QDomElement &d, ItemInfo info, boo #endif } else if (type == "wipe") { Wipeval *wpval = new Wipeval; + QWidget * toFillin = new QWidget(m_baseWidget); wpval->setupUi(toFillin); + m_vbox->addWidget(toFillin); wipeInfo w = getWipeInfo(value); switch (w.start) { case UP: @@ -534,7 +539,9 @@ void EffectStackEdit::transferParamDesc(const QDomElement &d, ItemInfo info, boo m_uiItems.append(wpval); } else if (type == "url") { Urlval *cval = new Urlval; + QWidget * toFillin = new QWidget(m_baseWidget); cval->setupUi(toFillin); + m_vbox->addWidget(toFillin); cval->label->setText(paramName); cval->urlwidget->fileDialog()->setFilter(ProjectList::getExtensions()); m_valueItems[paramName] = cval; @@ -544,7 +551,9 @@ void EffectStackEdit::transferParamDesc(const QDomElement &d, ItemInfo info, boo m_uiItems.append(cval); } else if (type == "keywords") { Keywordval* kval = new Keywordval; + QWidget * toFillin = new QWidget(m_baseWidget); kval->setupUi(toFillin); + m_vbox->addWidget(toFillin); kval->label->setText(paramName); kval->lineeditwidget->setText(value); QDomElement klistelem = pa.firstChildElement("keywords"); @@ -571,19 +580,20 @@ void EffectStackEdit::transferParamDesc(const QDomElement &d, ItemInfo info, boo m_uiItems.append(kval); } else if (type == "fontfamily") { Fontval* fval = new Fontval; + QWidget * toFillin = new QWidget(m_baseWidget); fval->setupUi(toFillin); + m_vbox->addWidget(toFillin); fval->name->setText(paramName); fval->fontfamilywidget->setCurrentFont(QFont(value)); m_valueItems[paramName] = fval; connect(fval->fontfamilywidget, SIGNAL(currentFontChanged(const QFont &)), this, SLOT(collectAllParameters())) ; m_uiItems.append(fval); - } else { - delete toFillin; - toFillin = NULL; + } else if (type == "filterjob") { + QPushButton *button = new QPushButton(paramName, m_baseWidget); + m_vbox->addWidget(button); + m_valueItems[paramName] = button; + connect(button, SIGNAL(pressed()), this, SLOT(slotStartFilterJobAction())); } - - if (toFillin) - m_vbox->addWidget(toFillin); } if (stretch) @@ -892,3 +902,18 @@ void EffectStackEdit::slotSyncEffectsPos(int pos) { emit syncEffectsPos(pos); } + +void EffectStackEdit::slotStartFilterJobAction() +{ + QDomNodeList namenode = m_params.elementsByTagName("parameter"); + for (int i = 0; i < namenode.count() ; i++) { + QDomElement pa = namenode.item(i).toElement(); + QString type = pa.attribute("type"); + if (type == "filterjob") { + emit startFilterJob(pa.attribute("filtertag"), pa.attribute("filterparams"), pa.attribute("consumer"), pa.attribute("consumerparams"), pa.attribute("wantedproperties")); + kDebug()<<" - - -PROPS:\n"<isChecked()); } +void EffectStackView::slotStartFilterJob(const QString&filterName, const QString&filterParams, const QString&consumer, const QString&consumerParams, const QString&properties) +{ + if (!m_clipref) return; + emit startFilterJob(m_clipref->clipProducer(), filterName, filterParams, consumer, consumerParams, properties); +} + #include "effectstackview.moc" diff --git a/src/effectstackview.h b/src/effectstackview.h index 89055a23..e3412bd4 100644 --- a/src/effectstackview.h +++ b/src/effectstackview.h @@ -141,6 +141,9 @@ private slots: /** @brief Shows/Hides the comment box and emits showComments to notify the parameter widgets to do the same. */ void slotShowComments(); + + /** @brief Triggers a filter job on this clip. */ + void slotStartFilterJob(const QString&filterName, const QString&filterParams, const QString&consumer, const QString&consumerParams, const QString&properties); signals: void removeEffect(ClipItem*, int, QDomElement); @@ -161,6 +164,7 @@ signals: void updateClipRegion(ClipItem*, int, QString); void displayMessage(const QString&, int); void showComments(bool show); + void startFilterJob(const QString &clipId, const QString &filterName, const QString &filterParams, const QString &consumer, const QString &consumerParams, const QString &properties); }; #endif diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 927df0a9..164d5fe5 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -269,6 +269,7 @@ MainWindow::MainWindow(const QString &MltPath, const KUrl & Url, const QString & m_effectStack = new EffectStackView(m_projectMonitor); m_effectStackDock->setWidget(m_effectStack); addDockWidget(Qt::TopDockWidgetArea, m_effectStackDock); + connect(m_effectStack, SIGNAL(startFilterJob(const QString&,const QString&,const QString&,const QString&,const QString&,const QString&)), m_projectList, SLOT(slotStartFilterJob(const QString&,const QString&,const QString&,const QString&,const QString&,const QString&))); m_transitionConfigDock = new QDockWidget(i18n("Transition"), this); m_transitionConfigDock->setObjectName("transition"); @@ -3847,14 +3848,14 @@ void MainWindow::loadTranscoders() void MainWindow::slotStabilize(KUrl::List urls) { - QString condition,filtername; + QString condition,filtername; - if (urls.isEmpty()) { + if (urls.isEmpty()) { QAction *action = qobject_cast(sender()); - if (action){ - filtername=action->data().toString(); - urls = m_projectList->getConditionalUrls(condition); - } + if (action){ + filtername=action->data().toString(); + urls = m_projectList->getConditionalUrls(condition); + } } if (urls.isEmpty()) { m_messageLabel->setMessage(i18n("No clip to transcode"), ErrorMessage); diff --git a/src/projectlist.cpp b/src/projectlist.cpp index 4e78eecb..fb481bd7 100644 --- a/src/projectlist.cpp +++ b/src/projectlist.cpp @@ -22,6 +22,7 @@ #include "commands/addfoldercommand.h" #include "projecttree/proxyclipjob.h" #include "projecttree/cutclipjob.h" +#include "projecttree/meltjob.h" #include "kdenlivesettings.h" #include "slideshowclip.h" #include "ui_colorclip_ui.h" @@ -232,10 +233,16 @@ ProjectList::ProjectList(QWidget *parent) : m_infoLabel = new SmallInfoLabel(this); connect(this, SIGNAL(jobCount(int)), m_infoLabel, SLOT(slotSetJobCount(int))); m_jobsMenu = new QMenu(this); + connect(m_jobsMenu, SIGNAL(aboutToShow()), this, SLOT(slotPrepareJobsMenu())); QAction *cancelJobs = new QAction(i18n("Cancel All Jobs"), this); cancelJobs->setCheckable(false); connect(cancelJobs, SIGNAL(triggered()), this, SLOT(slotCancelJobs())); + connect(this, SIGNAL(checkJobProcess()), this, SLOT(slotCheckJobProcess())); + m_discardCurrentClipJobs = new QAction(i18n("Cancel Current Clip Jobs"), this); + m_discardCurrentClipJobs->setCheckable(false); + connect(m_discardCurrentClipJobs, SIGNAL(triggered()), this, SLOT(slotDiscardClipJobs())); m_jobsMenu->addAction(cancelJobs); + m_jobsMenu->addAction(m_discardCurrentClipJobs); m_infoLabel->setMenu(m_jobsMenu); box->addWidget(m_infoLabel); @@ -295,7 +302,6 @@ ProjectList::ProjectList(QWidget *parent) : connect(this, SIGNAL(updateJobStatus(const QString &, int, int, const QString &, const QString &, const QString)), this, SLOT(slotUpdateJobStatus(const QString &, int, int, const QString &, const QString &, const QString))); - connect(this, SIGNAL(checkJobProcess()), this, SLOT(slotCheckJobProcess())); connect(this, SIGNAL(gotProxy(const QString)), this, SLOT(slotGotProxyForId(const QString))); m_listViewDelegate = new ItemDelegate(m_listView); @@ -314,6 +320,9 @@ ProjectList::ProjectList(QWidget *parent) : ProjectList::~ProjectList() { m_abortAllJobs = true; + for (int i = 0; i < m_jobList.count(); i++) { + m_jobList.at(i)->setStatus(JOBABORTED); + } m_closing = true; m_thumbnailQueue.clear(); m_jobThreads.waitForFinished(); @@ -659,7 +668,7 @@ void ProjectList::slotReloadClip(const QString &id) continue; } item = static_cast (selected.at(i)); - if (item && !hasPendingProxy(item)) { + if (item && !hasPendingJob(item, PROXYJOB)) { DocClipBase *clip = item->referencedClip(); if (!clip || !clip->isClean() || m_render->isProcessing(item->clipId())) { kDebug()<<"//// TRYING TO RELOAD: "<clipId()<<", but it is busy"; @@ -1376,6 +1385,9 @@ void ProjectList::slotResetProjectList() { m_listView->blockSignals(true); m_abortAllJobs = true; + for (int i = 0; i < m_jobList.count(); i++) { + m_jobList.at(i)->setStatus(JOBABORTED); + } m_closing = true; m_jobThreads.waitForFinished(); m_jobThreads.clearFutures(); @@ -1493,7 +1505,7 @@ void ProjectList::updateAllClips(bool displayRatioChanged, bool fpsChanged, QStr clip->setProperty("proxy", "-"); replace = true; } - if (clip->isPlaceHolder() == false && !hasPendingProxy(item)) { + if (clip->isPlaceHolder() == false && !hasPendingJob(item, PROXYJOB)) { QDomElement xml = clip->toXML(); if (fpsChanged) { xml.removeAttribute("out"); @@ -1667,7 +1679,6 @@ void ProjectList::slotAddClip(const QList givenList, const QString &group void ProjectList::slotRemoveInvalidClip(const QString &id, bool replace) { ProjectItem *item = getItemById(id); - m_processingClips.removeAll(id); m_thumbnailQueue.removeAll(id); if (item) { item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsDropEnabled); @@ -1726,7 +1737,6 @@ void ProjectList::slotRemoveInvalidProxy(const QString &id, bool durationError) item->referencedClip()->reloadThumbProducer(); } } - m_processingClips.removeAll(id); m_thumbnailQueue.removeAll(id); } @@ -1836,12 +1846,14 @@ void ProjectList::setDocument(KdenliveDoc *doc) { m_listView->blockSignals(true); m_abortAllJobs = true; + for (int i = 0; i < m_jobList.count(); i++) { + m_jobList.at(i)->setStatus(JOBABORTED); + } m_closing = true; m_jobThreads.waitForFinished(); m_jobThreads.clearFutures(); m_thumbnailQueue.clear(); m_listView->clear(); - m_processingClips.clear(); m_listView->setSortingEnabled(false); emit clipSelected(NULL); @@ -2048,7 +2060,7 @@ void ProjectList::slotReplyGetFileProperties(const QString &clipId, Mlt::Produce item->setConditionalJobStatus(NOJOB, PROXYJOB); discardJobs(clipId, PROXYJOB); } - else if (useProxy() && !item->hasProxy() && !hasPendingProxy(item)) { + else if (useProxy() && !item->hasProxy() && !hasPendingJob(item, PROXYJOB)) { // proxy video and image clips int maxSize; CLIPTYPE t = item->clipType(); @@ -2568,26 +2580,26 @@ QMap ProjectList::getProxies() void ProjectList::slotCreateProxy(const QString id) { ProjectItem *item = getItemById(id); - if (!item || hasPendingProxy(item) || item->referencedClip()->isPlaceHolder()) return; + if (!item || hasPendingJob(item, PROXYJOB) || item->referencedClip()->isPlaceHolder()) return; QString path = item->referencedClip()->getProperty("proxy"); if (path.isEmpty()) { slotUpdateJobStatus(item, PROXYJOB, JOBCRASHED, i18n("Failed to create proxy, empty path.")); return; } - if (m_processingProxy.contains(path)) { - // Proxy is already being generated + ProxyJob *job = new ProxyJob(item->clipType(), id, QStringList() << path << item->clipUrl().path() << item->referencedClip()->producerProperty("_exif_orientation") << m_doc->getDocumentProperty("proxyparams").simplified() << QString::number(m_render->frameRenderWidth()) << QString::number(m_render->renderHeight())); + if (job->isExclusive() && hasPendingJob(item, job->jobType)) { + delete job; return; } + if (QFileInfo(path).size() > 0) { // Proxy already created setJobStatus(item, PROXYJOB, JOBDONE); slotGotProxy(path); return; } - m_processingProxy.append(path); - ProxyJob *job = new ProxyJob(item->clipType(), id, QStringList() << path << item->clipUrl().path() << item->referencedClip()->producerProperty("_exif_orientation") << m_doc->getDocumentProperty("proxyparams").simplified() << QString::number(m_render->frameRenderWidth()) << QString::number(m_render->renderHeight())); m_jobList.append(job); setJobStatus(item, job->jobType, JOBWAITING, 0, job->statusMessage()); slotCheckJobProcess(); @@ -2653,11 +2665,14 @@ void ProjectList::slotCutClipJob(const QString &id, QPoint zone) KdenliveSettings::setAdd_clip_cut(ui.add_clip->isChecked()); delete d; - m_processingProxy.append(dest); QStringList jobParams; jobParams << dest << item->clipUrl().path() << timeIn << timeOut << QString::number(duration) << QString::number(KdenliveSettings::add_clip_cut()); if (!extraParams.isEmpty()) jobParams << extraParams; CutClipJob *job = new CutClipJob(item->clipType(), id, jobParams); + if (job->isExclusive() && hasPendingJob(item, job->jobType)) { + delete job; + return; + } m_jobList.append(job); setJobStatus(item, job->jobType, JOBWAITING, 0, job->statusMessage()); @@ -2705,7 +2720,6 @@ void ProjectList::slotTranscodeClipJob(QStringList ids, QString params, QString if (!item || !item->referencedClip()) continue; QString src = item->clipUrl().path(); QString dest = params.section(' ', -1).replace("%1", src); - m_processingProxy.append(dest); QStringList jobParams; jobParams << dest << src << QString() << QString(); double clipFps = item->referencedClip()->getProperty("fps").toDouble(); @@ -2716,6 +2730,10 @@ void ProjectList::slotTranscodeClipJob(QStringList ids, QString params, QString jobParams << QString::number(KdenliveSettings::add_clip_cut()); jobParams << params.section(' ', 0, -2); CutClipJob *job = new CutClipJob(item->clipType(), id, jobParams); + if (job->isExclusive() && hasPendingJob(item, job->jobType)) { + delete job; + continue; + } m_jobList.append(job); setJobStatus(item, job->jobType, JOBWAITING, 0, job->statusMessage()); } @@ -2724,6 +2742,7 @@ void ProjectList::slotTranscodeClipJob(QStringList ids, QString params, QString } + void ProjectList::slotCheckJobProcess() { if (!m_jobThreads.futures().isEmpty()) { @@ -2801,86 +2820,35 @@ void ProjectList::slotProcessJobs() emit processLog(job->clipId(), 0, job->jobType, job->statusMessage()); // Make sure destination path is writable - QFile file(destination); - if (!file.open(QIODevice::WriteOnly)) { - emit updateJobStatus(job->clipId(), job->jobType, JOBCRASHED, i18n("Cannot write to path: %1", destination)); - m_processingProxy.removeAll(destination); - job->setStatus(JOBCRASHED); - continue; - } - file.close(); - QFile::remove(destination); - - //setJobStatus(processingItem, JOBWORKING, 0, job->jobType, job->statusMessage()); - - bool success; - QProcess *jobProcess = job->startJob(&success); - int result = -1; - if (jobProcess == NULL) { - // job is finished - if (success) { - emit updateJobStatus(job->clipId(), job->jobType, JOBDONE); - if (job->jobType == PROXYJOB) emit gotProxy(job->clipId()); - //TODO: set folder for transcoded clips - else if (job->jobType == CUTJOB) emit addClip(destination, QString(), QString()); - job->setStatus(JOBDONE); - } - else { - QFile::remove(destination); - emit updateJobStatus(job->clipId(), job->jobType, JOBCRASHED, job->errorMessage()); + if (!destination.isEmpty()) { + QFile file(destination); + if (!file.open(QIODevice::WriteOnly)) { + emit updateJobStatus(job->clipId(), job->jobType, JOBCRASHED, i18n("Cannot write to path: %1", destination)); job->setStatus(JOBCRASHED); - } - m_processingProxy.removeAll(destination); - continue; - } - else while (jobProcess->state() != QProcess::NotRunning) { - // building proxy file - if (m_abortAllJobs || job->jobStatus == JOBABORTED) { - job->jobStatus = JOBABORTED; - jobProcess->close(); - jobProcess->waitForFinished(); - QFile::remove(destination); - m_processingProxy.removeAll(destination); - if (!m_closing) { - emit cancelRunningJob(job->clipId(), job->cancelProperties()); - } - result = -2; continue; } - else { - int progress = job->processLogInfo(); - if (progress > 0) emit processLog(job->clipId(), progress, job->jobType); - } - jobProcess->waitForFinished(500); + file.close(); + QFile::remove(destination); } - jobProcess->waitForFinished(); - m_processingProxy.removeAll(destination); - if (result == -1) result = jobProcess->exitStatus(); - - if (result != -2 && QFileInfo(destination).size() == 0) { - job->processLogInfo(); - emit updateJobStatus(job->clipId(), job->jobType, JOBCRASHED, i18n("Failed to create file"), QString(), job->errorMessage()); - job->setStatus(JOBCRASHED); - result = -2; - } - if (result == QProcess::NormalExit) { - // proxy successfully created + connect(job, SIGNAL(jobProgress(QString, int, int)), this, SIGNAL(processLog(QString, int, int))); + connect(job, SIGNAL(cancelRunningJob(const QString, stringMap)), this, SIGNAL(cancelRunningJob(const QString, stringMap))); + + if (job->jobType == MLTJOB) { + MeltJob *jb = static_cast (job); + jb->setProducer(processingItem->referencedClip()->getProducer()); + } + job->startJob(); + if (job->jobStatus == JOBDONE) { emit updateJobStatus(job->clipId(), job->jobType, JOBDONE); + //TODO: replace with more generic clip replacement framework if (job->jobType == PROXYJOB) emit gotProxy(job->clipId()); - //TODO: set folder for transcoded clips - else if (job->jobType == CUTJOB) { - CutClipJob *cutJob = static_cast(job); - if (cutJob->addClipToProject) emit addClip(destination, QString(), QString()); + if (job->addClipToProject) { + emit addClip(destination, QString(), QString()); } - job->setStatus(JOBDONE); } - else if (result == QProcess::CrashExit) { - // Proxy process crashed - QFile::remove(destination); - emit updateJobStatus(job->clipId(), job->jobType, JOBCRASHED, i18n("Job crashed"), QString(), job->errorMessage()); - job->setStatus(JOBCRASHED); + else if (job->jobStatus == JOBCRASHED) { + emit updateJobStatus(job->clipId(), job->jobType, JOBCRASHED, job->errorMessage()); } - continue; } // Thread finished, cleanup & update count QTimer::singleShot(200, this, SIGNAL(checkJobProcess())); @@ -2906,7 +2874,7 @@ void ProjectList::updateProxyConfig() } CLIPTYPE t = item->clipType(); if ((t == VIDEO || t == AV || t == UNKNOWN) && item->referencedClip() != NULL) { - if (generateProxy() && useProxy() && !hasPendingProxy(item)) { + if (generateProxy() && useProxy() && !hasPendingJob(item, PROXYJOB)) { DocClipBase *clip = item->referencedClip(); if (clip->getProperty("frame_size").section('x', 0, 0).toInt() > m_doc->getDocumentProperty("proxyminsize").toInt()) { if (clip->getProperty("proxy").isEmpty()) { @@ -3139,6 +3107,9 @@ void ProjectList::processThumbOverlays(ProjectItem *item, QPixmap &pix) void ProjectList::slotCancelJobs() { m_abortAllJobs = true; + for (int i = 0; i < m_jobList.count(); i++) { + m_jobList.at(i)->setStatus(JOBABORTED); + } m_jobThreads.waitForFinished(); m_jobThreads.clearFutures(); QUndoCommand *command = new QUndoCommand(); @@ -3158,7 +3129,6 @@ void ProjectList::slotCancelJobs() } else delete command; if (!m_jobList.isEmpty()) qDeleteAll(m_jobList); - m_processingProxy.clear(); m_jobList.clear(); m_abortAllJobs = false; m_infoLabel->slotSetJobCount(0); @@ -3166,7 +3136,7 @@ void ProjectList::slotCancelJobs() void ProjectList::slotCancelRunningJob(const QString id, stringMap newProps) { - if (newProps.isEmpty()) return; + if (newProps.isEmpty() || m_closing) return; ProjectItem *item = getItemById(id); if (!item || !item->referencedClip()) return; QMap oldProps = item->referencedClip()->currentProperties(newProps); @@ -3176,7 +3146,7 @@ void ProjectList::slotCancelRunningJob(const QString id, stringMap newProps) m_commandStack->push(command); } -bool ProjectList::hasPendingProxy(ProjectItem *item) +bool ProjectList::hasPendingJob(ProjectItem *item, JOBTYPE type) { if (!item || !item->referencedClip() || m_abortAllJobs) return false; AbstractClipJob *job; @@ -3184,7 +3154,7 @@ bool ProjectList::hasPendingProxy(ProjectItem *item) for (int i = 0; i < m_jobList.count(); i++) { if (m_abortAllJobs) break; job = m_jobList.at(i); - if (job->clipId() == item->clipId() && job->jobType == PROXYJOB && (job->jobStatus == JOBWAITING || job->jobStatus == JOBWORKING)) return true; + if (job->clipId() == item->clipId() && job->jobType == type && (job->jobStatus == JOBWAITING || job->jobStatus == JOBWORKING)) return true; } return false; @@ -3194,7 +3164,6 @@ void ProjectList::deleteJobsForClip(const QString &clipId) { QMutexLocker lock(&m_jobMutex); for (int i = 0; i < m_jobList.count(); i++) { - if (m_abortAllJobs) break; if (m_jobList.at(i)->clipId() == clipId) { m_jobList.at(i)->setStatus(JOBABORTED); } @@ -3255,14 +3224,71 @@ void ProjectList::slotShowJobLog() #endif } +QStringList ProjectList::getPendingJobs(const QString &id) +{ + QStringList result; + QMutexLocker lock(&m_jobMutex); + for (int i = 0; i < m_jobList.count(); i++) { + if (m_jobList.at(i)->clipId() == id && (m_jobList.at(i)->jobStatus == JOBWAITING || m_jobList.at(i)->jobStatus == JOBWORKING)) { + // discard this job + result << m_jobList.at(i)->description; + } + } + return result; +} + void ProjectList::discardJobs(const QString &id, JOBTYPE type) { QMutexLocker lock(&m_jobMutex); for (int i = 0; i < m_jobList.count(); i++) { - if (m_jobList.at(i)->clipId() == id && m_jobList.at(i)->jobType == type) { + if (m_jobList.at(i)->clipId() == id && (m_jobList.at(i)->jobType == type || type == NOJOBTYPE)) { // discard this job m_jobList.at(i)->setStatus(JOBABORTED); } } } +void ProjectList::slotStartFilterJob(const QString&id, const QString&filterName, const QString&filterParams, const QString&consumer, const QString&consumerParams, const QString&properties) +{ + ProjectItem *item = getItemById(id); + if (!item) return; + QStringList jobParams; + jobParams << filterName << filterParams << consumer << consumerParams << properties; + kDebug()<<"// STARTING JOB: "<clipType(), id, jobParams); + if (job->isExclusive() && hasPendingJob(item, job->jobType)) { + delete job; + return; + } + m_jobList.append(job); + setJobStatus(item, job->jobType, JOBWAITING, 0, job->statusMessage()); + slotCheckJobProcess(); +} + +void ProjectList::slotPrepareJobsMenu() +{ + ProjectItem *item; + if (!m_listView->currentItem() || m_listView->currentItem()->type() == PROJECTFOLDERTYPE) + return; + if (m_listView->currentItem()->type() == PROJECTSUBCLIPTYPE) + item = static_cast (m_listView->currentItem()->parent()); + else + item = static_cast (m_listView->currentItem()); + if (item && (item->flags() & Qt::ItemIsDragEnabled)) { + QString id = item->clipId(); + m_discardCurrentClipJobs->setData(id); + QStringList jobs = getPendingJobs(id); + m_discardCurrentClipJobs->setEnabled(!jobs.isEmpty()); + } else { + m_discardCurrentClipJobs->setData(QString()); + m_discardCurrentClipJobs->setEnabled(false); + } +} + +void ProjectList::slotDiscardClipJobs() +{ + QString id = m_discardCurrentClipJobs->data().toString(); + if (id.isEmpty()) return; + discardJobs(id); +} + #include "projectlist.moc" diff --git a/src/projectlist.h b/src/projectlist.h index d0f181ea..1fb55de7 100644 --- a/src/projectlist.h +++ b/src/projectlist.h @@ -302,6 +302,8 @@ public slots: /** @brief Start a hard cut clip job. */ void slotCutClipJob(const QString &id, QPoint zone); void slotTranscodeClipJob(QStringList ids, QString params, QString desc); + /** @brief Start an MLT process job. */ + void slotStartFilterJob(const QString&,const QString&,const QString&,const QString&,const QString&,const QString&); private: ProjectListView *m_listView; @@ -316,6 +318,7 @@ private: FolderProjectItem *getFolderItemById(const QString &id); QAction *m_openAction; QAction *m_reloadAction; + QAction *m_discardCurrentClipJobs; QMenu *m_extractAudioAction; QMenu *m_transcodeAction; QMenu *m_stabilizeAction; @@ -332,9 +335,6 @@ private: QMap m_producerQueue; QList m_thumbnailQueue; QAction *m_proxyAction; - QStringList m_processingClips; - /** @brief Holds a list of proxy urls that are currently being created. */ - QStringList m_processingProxy; QMutex m_jobMutex; bool m_abortAllJobs; /** @brief We are cleaning up the project list, so stop processing signals. */ @@ -385,14 +385,14 @@ private: void getCachedThumbnail(SubProjectItem *item); /** @brief The clip is about to be reloaded, cancel thumbnail requests. */ void resetThumbsProducer(DocClipBase *clip); - /** @brief Check if it is necessary to start a job thread. */ - void slotCheckJobProcess(); - /** @brief Check if a clip has a running or pending proxy process. */ - bool hasPendingProxy(ProjectItem *item); + /** @brief Check if a clip has a running or pending job process. */ + bool hasPendingJob(ProjectItem *item, JOBTYPE type); /** @brief Delete pending jobs for a clip. */ void deleteJobsForClip(const QString &clipId); /** @brief Discard specific job type for a clip. */ - void discardJobs(const QString &id, JOBTYPE type); + void discardJobs(const QString &id, JOBTYPE type = NOJOBTYPE); + /** @brief Get the list of job names for current clip. */ + QStringList getPendingJobs(const QString &id); private slots: void slotClipSelected(); @@ -446,6 +446,12 @@ private slots: void slotShowJobLog(); /** @brief A proxy clip is ready. */ void slotGotProxyForId(const QString); + /** @brief Check if it is necessary to start a job thread. */ + void slotCheckJobProcess(); + /** @brief Fill the jobs menu with current clip's jobs. */ + void slotPrepareJobsMenu(); + /** @brief Discard all jobs for current clip. */ + void slotDiscardClipJobs(); signals: void clipSelected(DocClipBase *, QPoint zone = QPoint(), bool forceUpdate = false); @@ -475,8 +481,8 @@ signals: void processLog(const QString, int , int, const QString = QString()); void addClip(const QString, const QString &, const QString &); void updateJobStatus(const QString, int, int, const QString &label = QString(), const QString &actionName = QString(), const QString details = QString()); - void checkJobProcess(); void gotProxy(const QString); + void checkJobProcess(); }; #endif diff --git a/src/projectlistview.cpp b/src/projectlistview.cpp index e08fd4b7..c68bccf2 100644 --- a/src/projectlistview.cpp +++ b/src/projectlistview.cpp @@ -304,7 +304,15 @@ void ProjectListView::mousePressEvent(QMouseEvent *event) m_DragStartPosition = event->pos(); m_dragStarted = true; /*QTreeWidgetItem *underMouse = itemAt(event->pos()); - if (underMouse && underMouse->isSelected()) emit focusMonitor();*/ + ProjectItem *item = static_cast(underMouse); + if (item) { + QRect itemRect = visualItemRect(item); + if (item->underJobMenu(itemRect, event->pos())) { + emit display + } + + && underMouse->isSelected()) emit focusMonitor() + }*/ } QTreeWidget::mousePressEvent(event); } diff --git a/src/projecttree/CMakeLists.txt b/src/projecttree/CMakeLists.txt index f05f9fcc..ae5a8225 100644 --- a/src/projecttree/CMakeLists.txt +++ b/src/projecttree/CMakeLists.txt @@ -3,5 +3,6 @@ set(kdenlive_SRCS projecttree/abstractclipjob.cpp projecttree/proxyclipjob.cpp projecttree/cutclipjob.cpp + projecttree/meltjob.cpp PARENT_SCOPE ) diff --git a/src/projecttree/abstractclipjob.cpp b/src/projecttree/abstractclipjob.cpp index 81441db2..825c0724 100644 --- a/src/projecttree/abstractclipjob.cpp +++ b/src/projecttree/abstractclipjob.cpp @@ -32,6 +32,8 @@ AbstractClipJob::AbstractClipJob(JOBTYPE type, CLIPTYPE cType, const QString &id jobType(type), jobStatus(NOJOB), m_clipId(id), + addClipToProject(false), + replaceClip(false), m_jobProcess(NULL) { } @@ -55,9 +57,8 @@ const QString AbstractClipJob::errorMessage() const return m_errorMessage; } -QProcess *AbstractClipJob::startJob(bool */*ok*/) +void AbstractClipJob::startJob() { - return NULL; } const QString AbstractClipJob::destination() const @@ -70,9 +71,8 @@ stringMap AbstractClipJob::cancelProperties() return QMap (); } -int AbstractClipJob::processLogInfo() +void AbstractClipJob::processLogInfo() { - return -1; } const QString AbstractClipJob::statusMessage() @@ -80,4 +80,8 @@ const QString AbstractClipJob::statusMessage() return QString(); } +bool AbstractClipJob::isExclusive() +{ + return true; +} diff --git a/src/projecttree/abstractclipjob.h b/src/projecttree/abstractclipjob.h index 7292e38b..26c46a6d 100644 --- a/src/projecttree/abstractclipjob.h +++ b/src/projecttree/abstractclipjob.h @@ -26,7 +26,7 @@ #include "definitions.h" -enum JOBTYPE { NOJOBTYPE = 0, PROXYJOB = 1, CUTJOB = 2}; +enum JOBTYPE { NOJOBTYPE = 0, PROXYJOB = 1, CUTJOB = 2, MLTJOB = 3}; class AbstractClipJob : public QObject { @@ -35,29 +35,30 @@ class AbstractClipJob : public QObject public: AbstractClipJob(JOBTYPE type, CLIPTYPE cType, const QString &id, QStringList parameters); virtual ~ AbstractClipJob(); CLIPTYPE clipType; - CLIPJOBSTATUS jobStatus; JOBTYPE jobType; + CLIPJOBSTATUS jobStatus; QString m_clipId; QString description; + bool addClipToProject; + bool replaceClip; const QString clipId() const; const QString errorMessage() const; void setStatus(CLIPJOBSTATUS status); virtual const QString destination() const; - virtual QProcess *startJob(bool */*ok*/); + virtual void startJob(); virtual stringMap cancelProperties(); - virtual int processLogInfo(); + virtual void processLogInfo(); virtual const QString statusMessage(); + /** @brief Returns true if only one instance of this job can be run on a clip. */ + virtual bool isExclusive(); protected: QString m_errorMessage; QProcess *m_jobProcess; -private: - - signals: - void jobProgress(int progress); - + void jobProgress(QString, int, int); + void cancelRunningJob(const QString, stringMap); }; diff --git a/src/projecttree/cutclipjob.cpp b/src/projecttree/cutclipjob.cpp index 47c4daf0..31caf5fe 100644 --- a/src/projecttree/cutclipjob.cpp +++ b/src/projecttree/cutclipjob.cpp @@ -39,10 +39,11 @@ CutClipJob::CutClipJob(CLIPTYPE cType, const QString &id, QStringList parameters else description = i18n("Cut clip"); m_jobDuration = parameters.at(4).toInt(); addClipToProject = parameters.at(5).toInt(); + replaceClip = false; if (parameters.count() == 7) m_cutExtraParams = parameters.at(6).simplified(); } -QProcess *CutClipJob::startJob(bool *ok) +void CutClipJob::startJob() { // Special case: playlist clips (.mlt or .kdenlive project files) if (clipType == AV || clipType == AUDIO || clipType == VIDEO) { @@ -63,25 +64,51 @@ QProcess *CutClipJob::startJob(bool *ok) kDebug()<<"// STARTING CIUT JOS: "<start("ffmpeg", parameters); m_jobProcess->waitForStarted(); - QString log = m_jobProcess->readAll(); - if (!log.isEmpty()) m_errorMessage.append(log + '\n'); - return m_jobProcess; + while (m_jobProcess->state() != QProcess::NotRunning) { + processLogInfo(); + if (jobStatus == JOBABORTED) { + m_jobProcess->close(); + m_jobProcess->waitForFinished(); + QFile::remove(m_dest); + } + m_jobProcess->waitForFinished(400); + } + + if (jobStatus != JOBABORTED) { + int result = m_jobProcess->exitStatus(); + if (result == QProcess::NormalExit) { + if (QFileInfo(m_dest).size() == 0) { + // File was not created + processLogInfo(); + m_errorMessage.append(i18n("Failed to create file.")); + setStatus(JOBCRASHED); + } + else setStatus(JOBDONE); + } + else if (result == QProcess::CrashExit) { + // Proxy process crashed + QFile::remove(m_dest); + setStatus(JOBCRASHED); + } + } + delete m_jobProcess; + return; } else m_errorMessage = i18n("Cannot process this clip type."); - *ok = false; - return NULL; + setStatus(JOBCRASHED); + return; } -int CutClipJob::processLogInfo() +void CutClipJob::processLogInfo() { - if (!m_jobProcess || m_jobDuration == 0 || jobStatus == JOBABORTED) return JOBABORTED; + if (!m_jobProcess || m_jobDuration == 0 || jobStatus == JOBABORTED) return; QString log = m_jobProcess->readAll(); if (!log.isEmpty()) m_errorMessage.append(log + '\n'); int progress; // Parse FFmpeg output if (log.contains("frame=")) { - int progress = log.section("frame=", 1, 1).simplified().section(' ', 0, 0).toInt(); - return (int) (100.0 * progress / m_jobDuration); + progress = log.section("frame=", 1, 1).simplified().section(' ', 0, 0).toInt(); + emit jobProgress(m_clipId, (int) (100.0 * progress / m_jobDuration), jobType); } else if (log.contains("time=")) { QString time = log.section("time=", 1, 1).simplified().section(' ', 0, 0); @@ -90,9 +117,8 @@ int CutClipJob::processLogInfo() progress = numbers.at(0).toInt() * 3600 + numbers.at(1).toInt() * 60 + numbers.at(2).toDouble(); } else progress = (int) time.toDouble(); - return (int) (100.0 * progress / m_jobDuration); + emit jobProgress(m_clipId, (int) (100.0 * progress / m_jobDuration), jobType); } - return -1; } CutClipJob::~CutClipJob() @@ -128,3 +154,8 @@ const QString CutClipJob::statusMessage() return statusInfo; } +bool CutClipJob::isExclusive() +{ + return false; +} + diff --git a/src/projecttree/cutclipjob.h b/src/projecttree/cutclipjob.h index 16765317..e76c54cd 100644 --- a/src/projecttree/cutclipjob.h +++ b/src/projecttree/cutclipjob.h @@ -35,11 +35,11 @@ public: CutClipJob(CLIPTYPE cType, const QString &id, QStringList parameters); virtual ~ CutClipJob(); const QString destination() const; - QProcess *startJob(bool *ok); + void startJob(); stringMap cancelProperties(); - int processLogInfo(); - bool addClipToProject; + void processLogInfo(); const QString statusMessage(); + bool isExclusive(); private: QString m_dest; diff --git a/src/projecttree/meltjob.cpp b/src/projecttree/meltjob.cpp new file mode 100644 index 00000000..7e239e20 --- /dev/null +++ b/src/projecttree/meltjob.cpp @@ -0,0 +1,150 @@ +/*************************************************************************** + * * + * Copyright (C) 2011 by Jean-Baptiste Mardelle (jb@kdenlive.org) * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include "meltjob.h" +#include "kdenlivesettings.h" +#include "kdenlivedoc.h" + +#include +#include + +#include + + +static void consumer_frame_render(mlt_consumer, MeltJob * self, mlt_frame /*frame_ptr*/) +{ + // detect if the producer has finished playing. Is there a better way to do it? + self->emitFrameNumber(); +} + +MeltJob::MeltJob(CLIPTYPE cType, const QString &id, QStringList parameters) : AbstractClipJob(MLTJOB, cType, id, parameters), + m_producer(NULL), + m_profile(NULL), + m_consumer(NULL), + m_length(0) +{ + jobStatus = JOBWAITING; + m_params = parameters; + description = i18n("Process clip"); +} + +void MeltJob::setProducer(Mlt::Producer *producer) +{ + m_producer = producer; +} + +void MeltJob::startJob() +{ + if (!m_producer) { + m_errorMessage.append(i18n("No producer for this clip.")); + setStatus(JOBCRASHED); + return; + } + QString filter = m_params.takeFirst(); + QString filterParams = m_params.takeFirst(); + QString consumer = m_params.takeFirst(); + QString consumerParams = m_params.takeFirst(); + QString properties = m_params.takeFirst(); + m_profile = m_producer->profile(); + m_consumer = new Mlt::Consumer(*m_profile, consumer.toUtf8().constData()); + if (!m_consumer || !m_consumer->is_valid()) { + m_errorMessage.append(i18n("Cannot create consumer %1.", consumer)); + setStatus(JOBCRASHED); + return; + } + + m_consumer->set("terminate_on_pause", 1 ); + m_consumer->set("eof", "pause" ); + m_consumer->set("real_time", -1 ); + + QStringList list = consumerParams.split(' ', QString::SkipEmptyParts); + foreach(QString data, list) { + if (data.contains('=')) { + m_consumer->set(data.section('=', 0, 0).toUtf8().constData(), data.section('=', 1, 1).toUtf8().constData()); + } + } + + Mlt::Filter mltFilter(*m_profile, filter.toUtf8().data()); + list = filterParams.split(' ', QString::SkipEmptyParts); + foreach(QString data, list) { + if (data.contains('=')) { + mltFilter.set(data.section('=', 0, 0).toUtf8().constData(), data.section('=', 1, 1).toUtf8().constData()); + } + } + m_producer->attach(mltFilter); + m_length = m_producer->get_length(); + m_consumer->connect(*m_producer); + m_producer->set_speed(0); + m_producer->seek(0); + m_showFrameEvent = m_consumer->listen("consumer-frame-show", this, (mlt_listener) consumer_frame_render); + m_consumer->start(); + m_producer->set_speed(1); + while (jobStatus != JOBABORTED && !m_consumer->is_stopped()) { + + } + m_consumer->stop(); + QStringList wanted = properties.split(',', QString::SkipEmptyParts); + foreach(const QString key, wanted) { + QString value = mltFilter.get(key.toUtf8().constData()); + kDebug()<<"RESULT: "< props; + return props; +} + +const QString MeltJob::statusMessage() +{ + QString statusInfo; + switch (jobStatus) { + case JOBWORKING: + statusInfo = i18n("Processing clip"); + break; + case JOBWAITING: + statusInfo = i18n("Waiting - process clip"); + break; + default: + break; + } + return statusInfo; +} + +void MeltJob::emitFrameNumber() +{ + if (m_consumer && m_length > 0) { + emit jobProgress(m_clipId, (int) (100 * m_consumer->position() / m_length), jobType); + } +} \ No newline at end of file diff --git a/src/projecttree/meltjob.h b/src/projecttree/meltjob.h new file mode 100644 index 00000000..d2aba6fe --- /dev/null +++ b/src/projecttree/meltjob.h @@ -0,0 +1,61 @@ +/*************************************************************************** + * * + * Copyright (C) 2011 by Jean-Baptiste Mardelle (jb@kdenlive.org) * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef MELTJOB +#define MELTJOB + +#include +#include + +#include "abstractclipjob.h" + +namespace Mlt{ + class Profile; + class Producer; + class Consumer; + class Filter; + class Event; +}; + +class MeltJob : public AbstractClipJob +{ + Q_OBJECT + +public: + MeltJob(CLIPTYPE cType, const QString &id, QStringList parameters); + virtual ~ MeltJob(); + const QString destination() const; + void startJob(); + stringMap cancelProperties(); + bool addClipToProject; + const QString statusMessage(); + void setProducer(Mlt::Producer *producer); + void emitFrameNumber(); + +private: + Mlt::Producer *m_producer; + Mlt::Profile *m_profile; + Mlt::Consumer *m_consumer; + Mlt::Event *m_showFrameEvent; + QStringList m_params; + int m_length; +}; + +#endif diff --git a/src/projecttree/proxyclipjob.cpp b/src/projecttree/proxyclipjob.cpp index 36e9342a..3bd2e53c 100644 --- a/src/projecttree/proxyclipjob.cpp +++ b/src/projecttree/proxyclipjob.cpp @@ -37,9 +37,10 @@ ProxyJob::ProxyJob(CLIPTYPE cType, const QString &id, QStringList parameters) : m_proxyParams = parameters.at(3); m_renderWidth = parameters.at(4).toInt(); m_renderHeight = parameters.at(5).toInt(); + replaceClip = true; } -QProcess *ProxyJob::startJob(bool *ok) +void ProxyJob::startJob() { // Special case: playlist clips (.mlt or .kdenlive project files) m_jobDuration = 0; @@ -47,41 +48,41 @@ QProcess *ProxyJob::startJob(bool *ok) // change FFmpeg params to MLT format m_isFfmpegJob = false; QStringList mltParameters; - mltParameters << m_src; - mltParameters << "-consumer" << "avformat:" + m_dest; - QStringList params = m_proxyParams.split('-', QString::SkipEmptyParts); + mltParameters << m_src; + mltParameters << "-consumer" << "avformat:" + m_dest; + QStringList params = m_proxyParams.split('-', QString::SkipEmptyParts); - foreach(QString s, params) { - s = s.simplified(); - if (s.count(' ') == 0) { - s.append("=1"); - } - else s.replace(' ', '='); - mltParameters << s; - } + foreach(QString s, params) { + s = s.simplified(); + if (s.count(' ') == 0) { + s.append("=1"); + } + else s.replace(' ', '='); + mltParameters << s; + } - mltParameters.append(QString("real_time=-%1").arg(KdenliveSettings::mltthreads())); + mltParameters.append(QString("real_time=-%1").arg(KdenliveSettings::mltthreads())); - //TODO: currently, when rendering an xml file through melt, the display ration is lost, so we enforce it manualy - double display_ratio = KdenliveDoc::getDisplayRatio(m_src); - mltParameters << "aspect=" + QString::number(display_ratio); + //TODO: currently, when rendering an xml file through melt, the display ration is lost, so we enforce it manualy + double display_ratio = KdenliveDoc::getDisplayRatio(m_src); + mltParameters << "aspect=" + QString::number(display_ratio); - // Ask for progress reporting - mltParameters << "progress=1"; + // Ask for progress reporting + mltParameters << "progress=1"; - m_jobProcess = new QProcess; - m_jobProcess->setProcessChannelMode(QProcess::MergedChannels); - m_jobProcess->start(KdenliveSettings::rendererpath(), mltParameters); - m_jobProcess->waitForStarted(); - return m_jobProcess; + m_jobProcess = new QProcess; + m_jobProcess->setProcessChannelMode(QProcess::MergedChannels); + m_jobProcess->start(KdenliveSettings::rendererpath(), mltParameters); + m_jobProcess->waitForStarted(); } else if (clipType == IMAGE) { m_isFfmpegJob = false; // Image proxy QImage i(m_src); if (i.isNull()) { - *ok = false; - return NULL; + m_errorMessage.append(i18n("Cannot load image %1.", m_src)); + setStatus(JOBCRASHED); + return; } QImage proxy; @@ -123,8 +124,8 @@ QProcess *ProxyJob::startJob(bool *ok) processed.save(m_dest); } else proxy.save(m_dest); - *ok = true; - return NULL; + setStatus(JOBDONE); + return; } else { m_isFfmpegJob = true; @@ -139,19 +140,48 @@ QProcess *ProxyJob::startJob(bool *ok) parameters << m_dest; m_jobProcess = new QProcess; m_jobProcess->setProcessChannelMode(QProcess::MergedChannels); - m_jobProcess->start("ffmpeg", parameters); + m_jobProcess->start("ffmpeg", parameters, QIODevice::ReadOnly); m_jobProcess->waitForStarted(); - QString log = m_jobProcess->readAll(); - if (!log.isEmpty()) m_errorMessage.append(log + '\n'); - return m_jobProcess; } + while (m_jobProcess->state() != QProcess::NotRunning) { + processLogInfo(); + if (jobStatus == JOBABORTED) { + emit cancelRunningJob(m_clipId, cancelProperties()); + m_jobProcess->close(); + m_jobProcess->waitForFinished(); + QFile::remove(m_dest); + } + m_jobProcess->waitForFinished(400); + } + + if (jobStatus != JOBABORTED) { + int result = m_jobProcess->exitStatus(); + if (result == QProcess::NormalExit) { + if (QFileInfo(m_dest).size() == 0) { + // File was not created + processLogInfo(); + m_errorMessage.append(i18n("Failed to create file.")); + setStatus(JOBCRASHED); + } + else setStatus(JOBDONE); + } + else if (result == QProcess::CrashExit) { + // Proxy process crashed + QFile::remove(m_dest); + setStatus(JOBCRASHED); + } + } + + delete m_jobProcess; + return; } -int ProxyJob::processLogInfo() +void ProxyJob::processLogInfo() { - if (!m_jobProcess || jobStatus == JOBABORTED) return JOBABORTED; + if (!m_jobProcess || jobStatus == JOBABORTED) return; QString log = m_jobProcess->readAll(); if (!log.isEmpty()) m_errorMessage.append(log + '\n'); + else return; int progress; if (m_isFfmpegJob) { // Parse FFmpeg output @@ -169,17 +199,16 @@ int ProxyJob::processLogInfo() progress = numbers.at(0).toInt() * 3600 + numbers.at(1).toInt() * 60 + numbers.at(2).toDouble(); } else progress = (int) time.toDouble(); - return (int) (100.0 * progress / m_jobDuration); + emit jobProgress(m_clipId, (int) (100.0 * progress / m_jobDuration), jobType); } } else { // Parse MLT output if (log.contains("percentage:")) { progress = log.section(':', -1).simplified().toInt(); - return progress; + emit jobProgress(m_clipId, progress, jobType); } } - return -1; } ProxyJob::~ProxyJob() diff --git a/src/projecttree/proxyclipjob.h b/src/projecttree/proxyclipjob.h index 6a524630..beca4b1e 100644 --- a/src/projecttree/proxyclipjob.h +++ b/src/projecttree/proxyclipjob.h @@ -35,10 +35,11 @@ public: ProxyJob(CLIPTYPE cType, const QString &id, QStringList parameters); virtual ~ ProxyJob(); const QString destination() const; - QProcess *startJob(bool *ok); + void startJob(); stringMap cancelProperties(); - int processLogInfo(); const QString statusMessage(); + void processLogInfo(); + private: QString m_dest; diff --git a/src/renderwidget.h b/src/renderwidget.h index eba43211..4c4f0a4b 100644 --- a/src/renderwidget.h +++ b/src/renderwidget.h @@ -67,6 +67,7 @@ public: painter->drawText(r1, Qt::AlignLeft | Qt::AlignTop , index.data(Qt::UserRole).toString()); int progress = index.data(Qt::UserRole + 3).toInt(); if (progress > 0 && progress < 100) { + // draw progress bar QColor color = option.palette.alternateBase().color(); QColor fgColor = option.palette.text().color(); color.setAlpha(150); @@ -75,7 +76,7 @@ public: painter->setPen(QPen(fgColor)); int width = qMin(200, r1.width() - 4); QRect bgrect(r1.left() + 2, option.rect.bottom() - 6 - textMargin, width, 6); - painter->drawRect(bgrect); + painter->drawRoundedRect(bgrect, 3, 3); painter->setBrush(QBrush(fgColor)); bgrect.adjust(2, 2, 0, -1); painter->setPen(Qt::NoPen); -- 2.39.5