+void ProjectList::processThumbOverlays(ProjectItem *item, QPixmap &pix)
+{
+ if (item->hasProxy()) {
+ QPainter p(&pix);
+ QColor c(220, 220, 10, 200);
+ QRect r(0, 0, 12, 12);
+ p.fillRect(r, c);
+ QFont font = p.font();
+ font.setBold(true);
+ p.setFont(font);
+ p.setPen(Qt::black);
+ p.drawText(r, Qt::AlignCenter, i18nc("The first letter of Proxy, used as abbreviation", "P"));
+ }
+}
+
+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();
+ command->setText(i18np("Cancel job", "Cancel jobs", m_jobList.count()));
+ m_jobMutex.lock();
+ for (int i = 0; i < m_jobList.count(); ++i) {
+ DocClipBase *currentClip = m_doc->clipManager()->getClipById(m_jobList.at(i)->clipId());
+ if (!currentClip) continue;
+ QMap <QString, QString> newProps = m_jobList.at(i)->cancelProperties();
+ if (newProps.isEmpty()) continue;
+ QMap <QString, QString> oldProps = currentClip->currentProperties(newProps);
+ new EditClipCommand(this, m_jobList.at(i)->clipId(), oldProps, newProps, true, command);
+ }
+ m_jobMutex.unlock();
+ if (command->childCount() > 0) {
+ m_doc->commandStack()->push(command);
+ }
+ else delete command;
+ if (!m_jobList.isEmpty()) qDeleteAll(m_jobList);
+ m_jobList.clear();
+ m_abortAllJobs = false;
+ m_infoLabel->slotSetJobCount(0);
+}
+
+void ProjectList::slotCancelRunningJob(const QString id, stringMap newProps)
+{
+ if (newProps.isEmpty() || m_closing) return;
+ DocClipBase *currentClip = m_doc->clipManager()->getClipById(id);
+ if (!currentClip) return;
+ QMap <QString, QString> oldProps = currentClip->currentProperties(newProps);
+ if (newProps == oldProps) return;
+ QMapIterator<QString, QString> i(oldProps);
+ EditClipCommand *command = new EditClipCommand(this, id, oldProps, newProps, true);
+ m_commandStack->push(command);
+}
+
+bool ProjectList::hasPendingJob(ProjectItem *item, JOBTYPE type)
+{
+ if (!item || !item->referencedClip() || m_abortAllJobs) return false;
+ AbstractClipJob *job;
+ QMutexLocker lock(&m_jobMutex);
+ 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 == type && (job->status() == JobWaiting || job->status() == JobWorking)) return true;
+ }
+
+ return false;
+}
+
+void ProjectList::deleteJobsForClip(const QString &clipId)
+{
+ QMutexLocker lock(&m_jobMutex);
+ for (int i = 0; i < m_jobList.count(); ++i) {
+ if (m_jobList.at(i)->clipId() == clipId) {
+ m_jobList.at(i)->setStatus(JobAborted);
+ }
+ }
+}
+
+void ProjectList::slotUpdateJobStatus(const QString id, int type, int status, const QString label, const QString actionName, const QString details)
+{
+ ProjectItem *item = getItemById(id);
+ if (!item) return;
+ slotUpdateJobStatus(item, type, status, label, actionName, details);
+
+}
+
+void ProjectList::slotUpdateJobStatus(ProjectItem *item, int type, int status, const QString &label, const QString &actionName, const QString details)
+{
+ item->setJobStatus((JOBTYPE) type, (ClipJobStatus) status);
+ if (status != JobCrashed) return;
+#if KDE_IS_VERSION(4,7,0)
+ QList<QAction *> actions = m_infoMessage->actions();
+ if (m_infoMessage->isHidden()) {
+ m_infoMessage->setText(label);
+ m_infoMessage->setWordWrap(m_infoMessage->text().length() > 35);
+ m_infoMessage->setMessageType(KMessageWidget::Warning);
+ }
+
+ if (!actionName.isEmpty()) {
+ QAction *action = NULL;
+ QList< KActionCollection * > collections = KActionCollection::allCollections();
+ for (int i = 0; i < collections.count(); ++i) {
+ KActionCollection *coll = collections.at(i);
+ action = coll->action(actionName);
+ if (action) break;
+ }
+ if (action && !actions.contains(action)) m_infoMessage->addAction(action);
+ }
+ if (!details.isEmpty()) {
+ m_errorLog.append(details);
+ if (!actions.contains(m_logAction)) m_infoMessage->addAction(m_logAction);
+ }
+ m_infoMessage->animatedShow();
+#else
+ // warning for KDE < 4.7
+ KPassivePopup *passivePop = new KPassivePopup( this );
+ passivePop->setAutoDelete(true);
+ connect(passivePop, SIGNAL(clicked()), this, SLOT(slotClosePopup()));
+ m_errorLog.append(details);
+ KVBox *vb = new KVBox( passivePop );
+ KHBox *vh1 = new KHBox( vb );
+ KIcon icon("dialog-warning");
+ QLabel *iconLabel = new QLabel(vh1);
+ iconLabel->setPixmap(icon.pixmap(m_listView->iconSize()));
+ (void) new QLabel( label, vh1);
+ KHBox *box = new KHBox( vb );
+ QPushButton *but = new QPushButton( "Show log", box );
+ connect(but, SIGNAL(clicked(bool)), this, SLOT(slotShowJobLog()));
+
+ passivePop->setView( vb );
+ passivePop->show();
+
+#endif
+}
+
+void ProjectList::slotShowJobLog()
+{
+ KDialog d(this);
+ d.setButtons(KDialog::Close);
+ QTextEdit t(&d);
+ for (int i = 0; i < m_errorLog.count(); ++i) {
+ if (i > 0) t.insertHtml("<br><hr /><br>");
+ t.insertPlainText(m_errorLog.at(i));
+ }
+ t.setReadOnly(true);
+ d.setMainWidget(&t);
+ d.exec();
+}
+
+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)->status() == JobWaiting || m_jobList.at(i)->status() == 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 || type == NOJOBTYPE)) {
+ // discard this job
+ m_jobList.at(i)->setStatus(JobAborted);
+ }
+ }
+}
+
+void ProjectList::slotStartFilterJob(ItemInfo info, const QString&id, const QString&filterName, const QString&filterParams, const QString&consumer, const QString&consumerParams, const QMap <QString, QString> &extraParams)
+{
+ ProjectItem *item = getItemById(id);
+ if (!item) return;
+ QStringList jobParams;
+ jobParams << QString::number((int) info.cropStart.frames(m_fps)) << QString::number((int) (info.cropStart + info.cropDuration).frames(m_fps));
+ jobParams << QString() << filterName << filterParams << consumer << consumerParams << QString::number((int) info.startPos.frames(m_fps)) << QString::number(info.track);
+ MeltJob *job = new MeltJob(item->clipType(), id, jobParams, extraParams);
+ if (job->isExclusive() && hasPendingJob(item, job->jobType)) {
+ delete job;
+ return;
+ }
+ job->description = i18n("Filter %1", extraParams.value("finalfilter"));
+ m_jobList.append(job);
+ setJobStatus(item, job->jobType, JobWaiting, 0, job->statusMessage());
+ slotCheckJobProcess();
+}
+
+void ProjectList::startClipFilterJob(const QString &filterName, const QString &condition)
+{
+ QMap <QString, QString> ids = getConditionalIds(condition);
+ QStringList destination;
+ QMap<QString, QString>::const_iterator first = ids.constBegin();
+ if (first == ids.constEnd()) {
+ emit displayMessage(i18n("Cannot find clip to process filter %1", filterName), -2, ErrorMessage);
+ return;
+ }
+ ProjectItem *item = getItemById(first.key());
+ if (!item) {
+ emit displayMessage(i18n("Cannot find clip to process filter %1", filterName), -2, ErrorMessage);
+ return;
+ }
+ if (ids.count() == 1) {
+ destination << item->clipUrl().path();
+ }
+ else {
+ destination = ids.values();
+ }
+ if (filterName == "framebuffer") {
+ Mlt::Profile profile;
+ QStringList keys = ids.keys();
+ int ix = 0;
+ foreach(const QString &url, destination) {
+ QString prodstring = QString("framebuffer:" + url + "?-1");
+ Mlt::Producer *reversed = new Mlt::Producer(profile, prodstring.toUtf8().constData());
+ if (!reversed || !reversed->is_valid()) {
+ emit displayMessage(i18n("Cannot reverse clip"), -2, ErrorMessage);
+ continue;
+ }
+ QString dest = url + ".mlt";
+ if (QFile::exists(dest)) {
+ if (KMessageBox::questionYesNo(this, i18n("File %1 already exists.\nDo you want to overwrite it?", dest)) == KMessageBox::No) continue;
+ }
+ Mlt::Consumer *cons = new Mlt::Consumer(profile, "xml", dest.toUtf8().constData());
+ if (cons == NULL || !cons->is_valid()) {
+ emit displayMessage(i18n("Cannot render reversed clip"), -2, ErrorMessage);
+ continue;
+ }
+ Mlt::Playlist list;
+ list.insert_at(0, reversed, 0);
+ delete reversed;
+ cons->connect(list);
+ cons->run();
+ delete cons;
+ QString groupId;
+ QString groupName;
+ DocClipBase *base = m_doc->clipManager()->getClipById(keys.at(ix));
+ if (base) {
+ groupId = base->getProperty("groupid");
+ groupName = base->getProperty("groupname");
+ }
+ emit addClip(dest, groupId, groupName);
+ ix++;
+ }
+ return;
+ }
+
+ if (filterName == "motion_est") {
+ // Show config dialog
+ QPointer<QDialog> d = new QDialog(this);
+ Ui::SceneCutDialog_UI ui;
+ ui.setupUi(d);
+ // Set up categories
+ for (int i = 0; i < 5; ++i) {
+ ui.marker_type->insertItem(i, i18n("Category %1", i));
+ ui.marker_type->setItemData(i, CommentedTime::markerColor(i), Qt::DecorationRole);
+ }
+ ui.marker_type->setCurrentIndex(KdenliveSettings::default_marker_type());
+ if (d->exec() != QDialog::Accepted) {
+ delete d;
+ return;
+ }
+ // Autosplit filter
+ QStringList jobParams;
+ // Producer params
+ jobParams << QString();
+ // Filter params, use a smaller region of the image to speed up operation
+ // In fact, it's faster to rescale whole image than using part of it (bounding=\"25%x25%:15%x15\")
+ jobParams << filterName << "shot_change_list=0 denoise=0";
+ // Consumer
+ jobParams << "null" << "all=1 terminate_on_pause=1 real_time=-1 rescale=nearest deinterlace_method=onefield top_field_first=-1";
+ QMap <QString, QString> extraParams;
+ extraParams.insert("key", "shot_change_list");
+ extraParams.insert("projecttreefilter", "1");
+ QString keyword("%count");
+ extraParams.insert("resultmessage", i18n("Found %1 scenes.", keyword));
+ extraParams.insert("resize_profile", "160");
+ if (ui.store_data->isChecked()) {
+ // We want to save result as clip metadata
+ extraParams.insert("storedata", "1");
+ }
+ if (ui.zone_only->isChecked()) {
+ // We want to analyze only clip zone
+ extraParams.insert("zoneonly", "1");
+ }
+ if (ui.add_markers->isChecked()) {
+ // We want to create markers
+ extraParams.insert("addmarkers", QString::number(ui.marker_type->currentIndex()));
+ }
+ if (ui.cut_scenes->isChecked()) {
+ // We want to cut scenes
+ extraParams.insert("cutscenes", "1");
+ }
+ delete d;
+ processClipJob(ids.keys(), QString(), false, jobParams, i18n("Auto split"), extraParams);
+ }
+ else {
+ QPointer<ClipStabilize> d = new ClipStabilize(destination, filterName);
+ if (d->exec() == QDialog::Accepted) {
+ QMap <QString, QString> extraParams;
+ extraParams.insert("producer_profile", "1");
+ processClipJob(ids.keys(), d->destination(), d->autoAddClip(), d->params(), d->desc(), extraParams);
+ }
+ delete d;
+ }
+}
+
+void ProjectList::processClipJob(QStringList ids, const QString&destination, bool autoAdd, QStringList jobParams, const QString &description, QMap <QString, QString> extraParams)
+{
+ QStringList preParams;
+ // in and out
+ preParams << QString::number(0) << QString::number(-1);
+ // producer params
+ preParams << jobParams.takeFirst();
+ // filter name
+ preParams << jobParams.takeFirst();
+ // filter params
+ preParams << jobParams.takeFirst();
+ // consumer
+ QString consumer = jobParams.takeFirst();
+
+ foreach(const QString&id, ids) {
+ ProjectItem *item = getItemById(id);
+ if (!item) continue;
+ QStringList jobArgs;
+ if (extraParams.contains("zoneonly")) {
+ // Analyse clip zone only, remove in / out and replace with zone
+ preParams.takeFirst();
+ preParams.takeFirst();
+ QPoint zone = item->referencedClip()->zone();
+ jobArgs << QString::number(zone.x()) << QString::number(zone.y());
+ }
+ jobArgs << preParams;
+ if (ids.count() == 1) {
+ jobArgs << consumer + ':' + destination;
+ }
+ else {
+ jobArgs << consumer + ':' + destination + item->clipUrl().fileName() + ".mlt";
+ }
+ jobArgs << jobParams;
+
+ MeltJob *job = new MeltJob(item->clipType(), id, jobArgs, extraParams);
+ if (autoAdd) {
+ job->setAddClipToProject(true);
+ kDebug()<<"// ADDING TRUE";
+ }
+ else kDebug()<<"// ADDING FALSE!!!";
+
+ if (job->isExclusive() && hasPendingJob(item, job->jobType)) {
+ delete job;
+ return;
+ }
+ job->description = description;
+ 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() == ProjectFoldeType)
+ return;
+ if (m_listView->currentItem()->type() == ProjectSubclipType)
+ item = static_cast <ProjectItem*>(m_listView->currentItem()->parent());
+ else
+ item = static_cast <ProjectItem*>(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);
+}
+
+void ProjectList::updatePalette()
+{
+ m_infoLabel->setStyleSheet(SmallInfoLabel::getStyleSheet(QApplication::palette()));
+ m_listView->updateStyleSheet();
+}
+
+void ProjectList::slotResetInfoMessage()
+{
+#if KDE_IS_VERSION(4,7,0)
+ m_errorLog.clear();
+ QList<QAction *> actions = m_infoMessage->actions();
+ for (int i = 0; i < actions.count(); ++i) {
+ m_infoMessage->removeAction(actions.at(i));
+ }
+#endif
+}
+
+void ProjectList::slotClosePopup()
+{
+ m_errorLog.clear();
+}
+
+void ProjectList::slotGotFilterJobResults(QString id, int , int , stringMap results, stringMap filterInfo)
+{
+ // Currently, only the first value of results is used
+ //kDebug()<<"// FILTER RES:\n"<<filterInfo<<"\n--------------\n"<<results;
+ ProjectItem *clip = getItemById(id);
+ if (!clip) return;
+
+ // Check for return value
+ int markersType = -1;
+ if (filterInfo.contains("addmarkers")) markersType = filterInfo.value("addmarkers").toInt();
+ if (results.isEmpty()) {
+ emit displayMessage(i18n("No data returned from clip analysis"), 0, ErrorMessage);
+ return;
+ }
+ bool dataProcessed = false;
+ QString key = filterInfo.value("key");
+ int offset = filterInfo.value("offset").toInt();
+ QStringList value = results.value(key).split(';', QString::SkipEmptyParts);
+ kDebug()<<"// RESULT; "<<key<<" = "<<value;
+ if (filterInfo.contains("resultmessage")) {
+ QString mess = filterInfo.value("resultmessage");
+ mess.replace("%count", QString::number(value.count()));
+ emit displayMessage(mess, 0, InformationMessage);
+ }
+ else emit displayMessage(i18n("Processing data analysis"), 0, InformationMessage);
+ if (filterInfo.contains("cutscenes")) {
+ // Check if we want to cut scenes from returned data
+ dataProcessed = true;
+ int cutPos = 0;
+ QUndoCommand *command = new QUndoCommand();
+ command->setText(i18n("Auto Split Clip"));
+ foreach (QString pos, value) {
+ if (!pos.contains("=")) continue;
+ int newPos = pos.section("=", 0, 0).toInt();
+ // Don't use scenes shorter than 1 second
+ if (newPos - cutPos < 24) continue;
+ (void) new AddClipCutCommand(this, id, cutPos + offset, newPos + offset, QString(), true, false, command);
+ cutPos = newPos;
+ }
+ if (command->childCount() == 0)
+ delete command;
+ else m_commandStack->push(command);
+ }
+ if (markersType >= 0) {
+ // Add markers from returned data
+ dataProcessed = true;
+ int cutPos = 0;
+ QUndoCommand *command = new QUndoCommand();
+ command->setText(i18n("Add Markers"));
+ QList <CommentedTime> markersList;
+ int index = 1;
+ foreach (QString pos, value) {
+ if (!pos.contains("=")) continue;
+ int newPos = pos.section("=", 0, 0).toInt();
+ // Don't use scenes shorter than 1 second
+ if (newPos - cutPos < 24) continue;
+ CommentedTime m(GenTime(newPos + offset, m_fps), QString::number(index), markersType);
+ markersList << m;
+ index++;
+ cutPos = newPos;
+ }
+ emit addMarkers(id, markersList);
+ }
+ if (!dataProcessed || filterInfo.contains("storedata")) {
+ // Store returned data as clip extra data
+ clip->referencedClip()->setAnalysisData(filterInfo.contains("displaydataname") ? filterInfo.value("displaydataname") : key, results.value(key), filterInfo.value("offset").toInt());
+ emit updateAnalysisData(clip->referencedClip());
+ }
+}
+
+
+/*
+// Work in progress: apply filter based on clip's camcorder
+void ProjectList::checkCamcorderFilters(DocClipBase *clip, QMap <QString, QString> meta)
+{
+ KConfig conf("camcorderfilters.rc", KConfig::CascadeConfig, "appdata");
+ QStringList groups = conf.groupList();
+ foreach(QString grp, groups) {
+ if (!meta.contains(grp)) continue;
+ KConfigGroup group(&conf, grp);
+ QString value = group.readEntry(meta.value(grp));
+ if (value.isEmpty()) continue;
+ clip->setProperty(value.section(' ', 0, 0), value.section(' ', 1));
+ break;
+ }
+}*/
+