diff --git a/src/jobs/loadjob.cpp b/src/jobs/loadjob.cpp
index 634eb96fc..f56cc888b 100644
--- a/src/jobs/loadjob.cpp
+++ b/src/jobs/loadjob.cpp
@@ -1,619 +1,622 @@
/***************************************************************************
* Copyright (C) 2017 by Nicolas Carion *
* This file is part of Kdenlive. See www.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) version 3 or any later version accepted by the *
* membership of KDE e.V. (or its successor approved by the membership *
* of KDE e.V.), which shall act as a proxy defined in Section 14 of *
* version 3 of the license. *
* *
* 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, see . *
***************************************************************************/
#include "loadjob.hpp"
#include "bin/projectclip.h"
#include "bin/projectfolder.h"
#include "bin/projectitemmodel.h"
#include "core.h"
#include "doc/kdenlivedoc.h"
#include "doc/kthumb.h"
#include "kdenlivesettings.h"
#include "klocalizedstring.h"
#include "macros.hpp"
#include "profiles/profilemodel.hpp"
#include "project/dialogs/slideshowclip.h"
#include "monitor/monitor.h"
#include "xml/xml.hpp"
#include
#include
#include
#include
#include
LoadJob::LoadJob(const QString &binId, const QDomElement &xml, const std::function &readyCallBack)
: AbstractClipJob(LOADJOB, binId)
, m_xml(xml)
, m_readyCallBack(readyCallBack)
{
}
const QString LoadJob::getDescription() const
{
return i18n("Loading clip %1", m_clipId);
}
namespace {
ClipType::ProducerType getTypeForService(const QString &id, const QString &path)
{
if (id.isEmpty()) {
QString ext = path.section(QLatin1Char('.'), -1);
if (ext == QLatin1String("mlt") || ext == QLatin1String("kdenlive")) {
return ClipType::Playlist;
}
return ClipType::Unknown;
}
if (id == QLatin1String("color") || id == QLatin1String("colour")) {
return ClipType::Color;
}
if (id == QLatin1String("kdenlivetitle")) {
return ClipType::Text;
}
if (id == QLatin1String("qtext")) {
return ClipType::QText;
}
if (id == QLatin1String("xml") || id == QLatin1String("consumer")) {
return ClipType::Playlist;
}
if (id == QLatin1String("webvfx")) {
return ClipType::WebVfx;
}
return ClipType::Unknown;
}
// Read the properties of the xml and pass them to the producer. Note that some properties like resource are ignored
void processProducerProperties(const std::shared_ptr &prod, const QDomElement &xml)
{
// TODO: there is some duplication with clipcontroller > updateproducer that also copies properties
QString value;
QStringList internalProperties;
internalProperties << QStringLiteral("bypassDuplicate") << QStringLiteral("resource") << QStringLiteral("mlt_service") << QStringLiteral("audio_index")
<< QStringLiteral("video_index") << QStringLiteral("mlt_type") << QStringLiteral("length");
QDomNodeList props;
if (xml.tagName() == QLatin1String("producer")) {
props = xml.childNodes();
} else {
props = xml.firstChildElement(QStringLiteral("producer")).childNodes();
}
for (int i = 0; i < props.count(); ++i) {
if (props.at(i).toElement().tagName() != QStringLiteral("property")) {
continue;
}
QString propertyName = props.at(i).toElement().attribute(QStringLiteral("name"));
if (!internalProperties.contains(propertyName) && !propertyName.startsWith(QLatin1Char('_'))) {
value = props.at(i).firstChild().nodeValue();
if (propertyName.startsWith(QLatin1String("kdenlive-force."))) {
// this is a special forced property, pass it
propertyName.remove(0, 15);
}
prod->set(propertyName.toUtf8().constData(), value.toUtf8().constData());
}
}
}
} // namespace
// static
std::shared_ptr LoadJob::loadResource(QString &resource, const QString &type)
{
if (!resource.startsWith(type)) {
resource.prepend(type);
}
return std::make_shared(pCore->getCurrentProfile()->profile(), nullptr, resource.toUtf8().constData());
}
std::shared_ptr LoadJob::loadPlaylist(QString &resource)
{
std::unique_ptr xmlProfile(new Mlt::Profile());
xmlProfile->set_explicit(0);
std::unique_ptr producer(new Mlt::Producer(*xmlProfile, "xml", resource.toUtf8().constData()));
if (!producer->is_valid()) {
qDebug() << "////// ERROR, CANNOT LOAD SELECTED PLAYLIST: " << resource;
return nullptr;
}
- if (pCore->getCurrentProfile()->isCompatible(xmlProfile.get())) {
+ std::unique_ptr prof(new ProfileParam(xmlProfile.get()));
+ if (static_cast(pCore->getCurrentProfile().get()) == prof.get()) {
// We can use the "xml" producer since profile is the same (using it with different profiles corrupts the project.
// Beware that "consumer" currently crashes on audio mixes!
//resource.prepend(QStringLiteral("xml:"));
} else {
// This is currently crashing so I guess we'd better reject it for now
- m_errorMessage.append(i18n("Playlist has a different framerate (%1/%2fps), not recommended.", xmlProfile->frame_rate_num(), xmlProfile->frame_rate_den()));
+ if (!pCore->getCurrentProfile()->isCompatible(xmlProfile.get())) {
+ m_errorMessage.append(i18n("Playlist has a different framerate (%1/%2fps), not recommended.", xmlProfile->frame_rate_num(), xmlProfile->frame_rate_den()));
+ }
QString loader = resource;
loader.prepend(QStringLiteral("consumer:"));
pCore->getCurrentProfile()->set_explicit(1);
return std::make_shared(pCore->getCurrentProfile()->profile(), loader.toUtf8().constData());
}
pCore->getCurrentProfile()->set_explicit(1);
return std::make_shared(pCore->getCurrentProfile()->profile(), "xml", resource.toUtf8().constData());
}
void LoadJob::checkProfile(const QString &clipId, const QDomElement &xml, const std::shared_ptr &producer)
{
// Check if clip profile matches
QString service = producer->get("mlt_service");
// Check for image producer
if (service == QLatin1String("qimage") || service == QLatin1String("pixbuf")) {
// This is an image, create profile from image size
int width = producer->get_int("meta.media.width");
if (width % 8 > 0) {
width += 8 - width % 8;
}
int height = producer->get_int("meta.media.height");
height += height % 2;
if (width > 100 && height > 100) {
std::unique_ptr projectProfile(new ProfileParam(pCore->getCurrentProfile().get()));
projectProfile->m_width = width;
projectProfile->m_height = height;
projectProfile->m_sample_aspect_num = 1;
projectProfile->m_sample_aspect_den = 1;
projectProfile->m_display_aspect_num = width;
projectProfile->m_display_aspect_den = height;
projectProfile->m_description.clear();
pCore->currentDoc()->switchProfile(projectProfile, clipId, xml);
} else {
// Very small image, we probably don't want to use this as profile
}
} else if (service.contains(QStringLiteral("avformat"))) {
std::unique_ptr blankProfile(new Mlt::Profile());
blankProfile->set_explicit(0);
blankProfile->from_producer(*producer);
std::unique_ptr clipProfile(new ProfileParam(blankProfile.get()));
std::unique_ptr projectProfile(new ProfileParam(pCore->getCurrentProfile().get()));
clipProfile->adjustDimensions();
if (*clipProfile.get() == *projectProfile.get()) {
if (KdenliveSettings::default_profile().isEmpty()) {
// Confirm default project format
KdenliveSettings::setDefault_profile(pCore->getCurrentProfile()->path());
}
} else {
// Profiles do not match, propose profile adjustment
pCore->currentDoc()->switchProfile(clipProfile, clipId, xml);
}
}
}
void LoadJob::processSlideShow()
{
int ttl = Xml::getXmlProperty(m_xml, QStringLiteral("ttl")).toInt();
QString anim = Xml::getXmlProperty(m_xml, QStringLiteral("animation"));
if (!anim.isEmpty()) {
auto *filter = new Mlt::Filter(pCore->getCurrentProfile()->profile(), "affine");
if ((filter != nullptr) && filter->is_valid()) {
int cycle = ttl;
QString geometry = SlideshowClip::animationToGeometry(anim, cycle);
if (!geometry.isEmpty()) {
if (anim.contains(QStringLiteral("low-pass"))) {
auto *blur = new Mlt::Filter(pCore->getCurrentProfile()->profile(), "boxblur");
if ((blur != nullptr) && blur->is_valid()) {
m_producer->attach(*blur);
}
}
filter->set("transition.geometry", geometry.toUtf8().data());
filter->set("transition.cycle", cycle);
m_producer->attach(*filter);
}
}
}
QString fade = Xml::getXmlProperty(m_xml, QStringLiteral("fade"));
if (fade == QLatin1String("1")) {
// user wants a fade effect to slideshow
auto *filter = new Mlt::Filter(pCore->getCurrentProfile()->profile(), "luma");
if ((filter != nullptr) && filter->is_valid()) {
if (ttl != 0) {
filter->set("cycle", ttl);
}
QString luma_duration = Xml::getXmlProperty(m_xml, QStringLiteral("luma_duration"));
QString luma_file = Xml::getXmlProperty(m_xml, QStringLiteral("luma_file"));
if (!luma_duration.isEmpty()) {
filter->set("duration", luma_duration.toInt());
}
if (!luma_file.isEmpty()) {
filter->set("luma.resource", luma_file.toUtf8().constData());
QString softness = Xml::getXmlProperty(m_xml, QStringLiteral("softness"));
if (!softness.isEmpty()) {
int soft = softness.toInt();
filter->set("luma.softness", (double)soft / 100.0);
}
}
m_producer->attach(*filter);
}
}
QString crop = Xml::getXmlProperty(m_xml, QStringLiteral("crop"));
if (crop == QLatin1String("1")) {
// user wants to center crop the slides
auto *filter = new Mlt::Filter(pCore->getCurrentProfile()->profile(), "crop");
if ((filter != nullptr) && filter->is_valid()) {
filter->set("center", 1);
m_producer->attach(*filter);
}
}
}
bool LoadJob::startJob()
{
if (m_done) {
return true;
}
pCore->getMonitor(Kdenlive::ClipMonitor)->resetPlayOrLoopZone(m_clipId);
m_resource = Xml::getXmlProperty(m_xml, QStringLiteral("resource"));
int duration = 0;
ClipType::ProducerType type = static_cast(m_xml.attribute(QStringLiteral("type")).toInt());
QString service = Xml::getXmlProperty(m_xml, QStringLiteral("mlt_service"));
if (type == ClipType::Unknown) {
type = getTypeForService(service, m_resource);
}
switch (type) {
case ClipType::Color:
m_producer = loadResource(m_resource, QStringLiteral("color:"));
break;
case ClipType::Text:
case ClipType::TextTemplate: {
QFile txtfile(m_resource);
QDomDocument txtdoc(QStringLiteral("titledocument"));
if (txtfile.open(QIODevice::ReadOnly) && txtdoc.setContent(&txtfile)) {
txtfile.close();
if (txtdoc.documentElement().hasAttribute(QStringLiteral("duration"))) {
duration = txtdoc.documentElement().attribute(QStringLiteral("duration")).toInt();
} else if (txtdoc.documentElement().hasAttribute(QStringLiteral("out"))) {
duration = txtdoc.documentElement().attribute(QStringLiteral("out")).toInt();
}
}
m_producer = loadResource(m_resource, QStringLiteral("kdenlivetitle:"));
if (duration <= 0) {
duration = pCore->currentDoc()->getFramePos(KdenliveSettings::title_duration()) - 1;
}
m_producer->set("length", duration);
m_producer->set("kdenlive:duration", duration);
m_producer->set("out", duration - 1);
}
break;
case ClipType::QText:
m_producer = loadResource(m_resource, QStringLiteral("qtext:"));
break;
case ClipType::Playlist: {
m_producer = loadPlaylist(m_resource);
if (!m_errorMessage.isEmpty()) {
QMetaObject::invokeMethod(pCore.get(), "displayBinMessage", Qt::QueuedConnection, Q_ARG(const QString &, m_errorMessage),
Q_ARG(int, (int)KMessageWidget::Warning));
}
if (m_resource.endsWith(QLatin1String(".kdenlive"))) {
QFile f(m_resource);
QDomDocument doc;
doc.setContent(&f, false);
f.close();
QDomElement pl = doc.documentElement().firstChildElement(QStringLiteral("playlist"));
if (!pl.isNull()) {
QString offsetData = Xml::getXmlProperty(pl, QStringLiteral("kdenlive:docproperties.seekOffset"));
if (offsetData.isEmpty() && Xml::getXmlProperty(pl, QStringLiteral("kdenlive:docproperties.version")) == QLatin1String("0.98")) {
offsetData = QStringLiteral("30000");
}
if (!offsetData.isEmpty()) {
bool ok = false;
int offset = offsetData.toInt(&ok);
if (ok) {
qDebug()<<" / / / FIXING OFFSET DATA: "<get_playtime() - offset - 1;
m_producer->set("out", offset - 1);
m_producer->set("length", offset);
m_producer->set("kdenlive:duration", offset);
}
} else {
qDebug()<<"// NO OFFSET DAT FOUND\n\n";
}
} else {
qDebug()<<":_______\n______(pCore->getCurrentProfile()->profile(), nullptr, m_resource.toUtf8().constData());
}
break;
}
if (!m_producer || m_producer->is_blank() || !m_producer->is_valid()) {
qCDebug(KDENLIVE_LOG) << " / / / / / / / / ERROR / / / / // CANNOT LOAD PRODUCER: " << m_resource;
m_done = true;
m_successful = false;
if (m_producer) {
m_producer.reset();
}
QMetaObject::invokeMethod(pCore.get(), "displayBinMessage", Qt::QueuedConnection, Q_ARG(const QString &, i18n("Cannot open file %1", m_resource)),
Q_ARG(int, (int)KMessageWidget::Warning));
m_errorMessage.append(i18n("ERROR: Could not load clip %1: producer is invalid", m_resource));
return false;
}
processProducerProperties(m_producer, m_xml);
QString clipName = Xml::getXmlProperty(m_xml, QStringLiteral("kdenlive:clipname"));
if (clipName.isEmpty()) {
clipName = QFileInfo(Xml::getXmlProperty(m_xml, QStringLiteral("kdenlive:originalurl"))).fileName();
}
m_producer->set("kdenlive:clipname", clipName.toUtf8().constData());
QString groupId = Xml::getXmlProperty(m_xml, QStringLiteral("kdenlive:folderid"));
if (!groupId.isEmpty()) {
m_producer->set("kdenlive:folderid", groupId.toUtf8().constData());
}
int clipOut = 0;
if (m_xml.hasAttribute(QStringLiteral("out"))) {
clipOut = m_xml.attribute(QStringLiteral("out")).toInt();
}
// setup length here as otherwise default length (currently 15000 frames in MLT) will be taken even if outpoint is larger
if (duration == 0 && (type == ClipType::Color || type == ClipType::Text || type == ClipType::TextTemplate || type == ClipType::QText || type == ClipType::Image ||
type == ClipType::SlideShow)) {
int length;
if (m_xml.hasAttribute(QStringLiteral("length"))) {
length = m_xml.attribute(QStringLiteral("length")).toInt();
clipOut = qMax(1, length - 1);
} else {
length = Xml::getXmlProperty(m_xml, QStringLiteral("length")).toInt();
clipOut -= m_xml.attribute(QStringLiteral("in")).toInt();
if (length < clipOut) {
length = clipOut == 1 ? 1 : clipOut + 1;
}
}
// Pass duration if it was forced
if (m_xml.hasAttribute(QStringLiteral("duration"))) {
duration = m_xml.attribute(QStringLiteral("duration")).toInt();
if (length < duration) {
length = duration;
if (clipOut > 0) {
clipOut = length - 1;
}
}
}
if (duration == 0) {
duration = length;
}
m_producer->set("length", m_producer->frames_to_time(length, mlt_time_clock));
int kdenlive_duration = m_producer->time_to_frames(Xml::getXmlProperty(m_xml, QStringLiteral("kdenlive:duration")).toUtf8().constData());
if (kdenlive_duration > 0) {
m_producer->set("kdenlive:duration", m_producer->frames_to_time(kdenlive_duration, mlt_time_clock));
} else {
m_producer->set("kdenlive:duration", m_producer->get("length"));
}
}
if (clipOut > 0) {
m_producer->set_in_and_out(m_xml.attribute(QStringLiteral("in")).toInt(), clipOut);
}
if (m_xml.hasAttribute(QStringLiteral("templatetext"))) {
m_producer->set("templatetext", m_xml.attribute(QStringLiteral("templatetext")).toUtf8().constData());
}
if (type == ClipType::SlideShow) {
processSlideShow();
}
int vindex = -1;
double fps = -1;
const QString mltService = m_producer->get("mlt_service");
if (mltService == QLatin1String("xml") || mltService == QLatin1String("consumer")) {
// MLT playlist, create producer with blank profile to get real profile info
QString tmpPath = m_resource;
if (tmpPath.startsWith(QLatin1String("consumer:"))) {
tmpPath = "xml:" + tmpPath.section(QLatin1Char(':'), 1);
}
Mlt::Profile original_profile;
std::unique_ptr tmpProd(new Mlt::Producer(original_profile, nullptr, tmpPath.toUtf8().constData()));
original_profile.set_explicit(1);
double originalFps = original_profile.fps();
fps = originalFps;
if (originalFps > 0 && !qFuzzyCompare(originalFps, pCore->getCurrentFps())) {
int originalLength = tmpProd->get_length();
int fixedLength = (int)(originalLength * pCore->getCurrentFps() / originalFps);
m_producer->set("length", fixedLength);
m_producer->set("out", fixedLength - 1);
}
} else if (mltService == QLatin1String("avformat")) {
// check if there are multiple streams
vindex = m_producer->get_int("video_index");
// List streams
int streams = m_producer->get_int("meta.media.nb_streams");
m_audio_list.clear();
m_video_list.clear();
for (int i = 0; i < streams; ++i) {
QByteArray propertyName = QStringLiteral("meta.media.%1.stream.type").arg(i).toLocal8Bit();
QString stype = m_producer->get(propertyName.data());
if (stype == QLatin1String("audio")) {
m_audio_list.append(i);
} else if (stype == QLatin1String("video")) {
m_video_list.append(i);
}
}
if (vindex > -1) {
char property[200];
snprintf(property, sizeof(property), "meta.media.%d.stream.frame_rate", vindex);
fps = m_producer->get_double(property);
}
if (fps <= 0) {
if (m_producer->get_double("meta.media.frame_rate_den") > 0) {
fps = m_producer->get_double("meta.media.frame_rate_num") / m_producer->get_double("meta.media.frame_rate_den");
} else {
fps = m_producer->get_double("source_fps");
}
}
}
if (fps <= 0 && type == ClipType::Unknown) {
// something wrong, maybe audio file with embedded image
QMimeDatabase db;
QString mime = db.mimeTypeForFile(m_resource).name();
if (mime.startsWith(QLatin1String("audio"))) {
m_producer->set("video_index", -1);
vindex = -1;
}
}
m_done = m_successful = true;
return true;
}
void LoadJob::processMultiStream()
{
auto m_binClip = pCore->projectItemModel()->getClipByBinID(m_clipId);
// We retrieve the folder containing our clip, because we will set the other streams in the same
auto parent = pCore->projectItemModel()->getRootFolder()->clipId();
if (auto ptr = m_binClip->parentItem().lock()) {
parent = std::static_pointer_cast(ptr)->clipId();
} else {
qDebug() << "Warning, something went wrong while accessing parent of bin clip";
}
// This helper lambda request addition of a given stream
auto addStream = [this, parentId = std::move(parent)](int vindex, int aindex, Fun &undo, Fun &redo) {
auto clone = ProjectClip::cloneProducer(m_producer);
clone->set("video_index", vindex);
clone->set("audio_index", aindex);
QString id;
pCore->projectItemModel()->requestAddBinClip(id, clone, parentId, undo, redo);
};
Fun undo = []() { return true; };
Fun redo = []() { return true; };
if (KdenliveSettings::automultistreams()) {
for (int i = 1; i < m_video_list.count(); ++i) {
int vindex = m_video_list.at(i);
int aindex = 0;
if (i <= m_audio_list.count() - 1) {
aindex = m_audio_list.at(i);
}
addStream(vindex, aindex, undo, redo);
}
return;
}
int width = 60.0 * pCore->getCurrentDar();
if (width % 2 == 1) {
width++;
}
QScopedPointer dialog(new QDialog(qApp->activeWindow()));
dialog->setWindowTitle(QStringLiteral("Multi Stream Clip"));
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
QWidget *mainWidget = new QWidget(dialog.data());
auto *mainLayout = new QVBoxLayout;
dialog->setLayout(mainLayout);
mainLayout->addWidget(mainWidget);
QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
okButton->setDefault(true);
okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
dialog->connect(buttonBox, &QDialogButtonBox::accepted, dialog.data(), &QDialog::accept);
dialog->connect(buttonBox, &QDialogButtonBox::rejected, dialog.data(), &QDialog::reject);
okButton->setText(i18n("Import selected clips"));
QLabel *lab1 = new QLabel(i18n("Additional streams for clip\n %1", m_resource), mainWidget);
mainLayout->addWidget(lab1);
QList groupList;
QList comboList;
// We start loading the list at 1, video index 0 should already be loaded
for (int j = 1; j < m_video_list.count(); ++j) {
m_producer->set("video_index", m_video_list.at(j));
// TODO this keyframe should be cached
QImage thumb = KThumb::getFrame(m_producer.get(), 0, width, 60);
QGroupBox *streamFrame = new QGroupBox(i18n("Video stream %1", m_video_list.at(j)), mainWidget);
mainLayout->addWidget(streamFrame);
streamFrame->setProperty("vindex", m_video_list.at(j));
groupList << streamFrame;
streamFrame->setCheckable(true);
streamFrame->setChecked(true);
auto *vh = new QVBoxLayout(streamFrame);
QLabel *iconLabel = new QLabel(mainWidget);
mainLayout->addWidget(iconLabel);
iconLabel->setPixmap(QPixmap::fromImage(thumb));
vh->addWidget(iconLabel);
if (m_audio_list.count() > 1) {
auto *cb = new QComboBox(mainWidget);
mainLayout->addWidget(cb);
for (int k = 0; k < m_audio_list.count(); ++k) {
cb->addItem(i18n("Audio stream %1", m_audio_list.at(k)), m_audio_list.at(k));
}
comboList << cb;
cb->setCurrentIndex(qMin(j, m_audio_list.count() - 1));
vh->addWidget(cb);
}
mainLayout->addWidget(streamFrame);
}
m_producer->set("video_index", m_video_list.at(0));
mainLayout->addWidget(buttonBox);
if (dialog->exec() == QDialog::Accepted) {
// import selected streams
for (int i = 0; i < groupList.count(); ++i) {
if (groupList.at(i)->isChecked()) {
int vindex = groupList.at(i)->property("vindex").toInt();
int ax = qMin(i, comboList.size() - 1);
int aindex = -1;
if (ax >= 0) {
// only check audio index if we have several audio streams
aindex = comboList.at(ax)->itemData(comboList.at(ax)->currentIndex()).toInt();
}
addStream(vindex, aindex, undo, redo);
}
}
pCore->pushUndo(undo, redo, i18n("Add additional streams for clip"));
}
}
bool LoadJob::commitResult(Fun &undo, Fun &redo)
{
qDebug() << "################### loadjob COMMIT";
Q_ASSERT(!m_resultConsumed);
if (!m_done) {
qDebug() << "ERROR: Trying to consume invalid results";
return false;
}
m_resultConsumed = true;
auto m_binClip = pCore->projectItemModel()->getClipByBinID(m_clipId);
if (!m_successful) {
// TODO: Deleting cannot happen at this stage or we endup in a mutex lock
pCore->projectItemModel()->requestBinClipDeletion(m_binClip, undo, redo);
return false;
}
if (m_xml.hasAttribute(QStringLiteral("_checkProfile")) && m_producer->get_int("video_index") > -1) {
checkProfile(m_clipId, m_xml, m_producer);
}
if (m_video_list.size() > 1) {
processMultiStream();
}
// note that the image is moved into lambda, it won't be available from this class anymore
auto operation = [clip = m_binClip, prod = std::move(m_producer)]() {
clip->setProducer(prod, true);
return true;
};
auto reverse = []() {
// This is probably not invertible, leave as is.
return true;
};
bool ok = operation();
if (ok) {
m_readyCallBack();
if (pCore->projectItemModel()->clipsCount() == 1) {
// Always select first added clip
pCore->selectBinClip(m_clipId);
}
UPDATE_UNDO_REDO_NOLOCK(operation, reverse, undo, redo);
}
return ok;
}
diff --git a/src/jobs/stabilizejob.cpp b/src/jobs/stabilizejob.cpp
index fba736fef..108587445 100644
--- a/src/jobs/stabilizejob.cpp
+++ b/src/jobs/stabilizejob.cpp
@@ -1,158 +1,158 @@
/***************************************************************************
* Copyright (C) 2011 by Jean-Baptiste Mardelle (jb@kdenlive.org) *
* Copyright (C) 2017 by Nicolas Carion *
* *
* This file is part of Kdenlive. See www.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) version 3 or any later version accepted by the *
* membership of KDE e.V. (or its successor approved by the membership *
* of KDE e.V.), which shall act as a proxy defined in Section 14 of *
* version 3 of the license. *
* *
* 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, see . *
***************************************************************************/
#include "stabilizejob.hpp"
#include "bin/clipcreator.hpp"
#include "bin/projectclip.h"
#include "bin/projectfolder.h"
#include "bin/projectitemmodel.h"
#include "core.h"
#include "jobmanager.h"
#include "kdenlivesettings.h"
#include "project/clipstabilize.h"
#include
#include
#include
StabilizeJob::StabilizeJob(const QString &binId, const QString &filterName, QString destUrl, std::unordered_map filterParams)
- : MeltJob(binId, STABILIZEJOB, true, -1, -1)
+ : MeltJob(binId, STABILIZEJOB, false, -1, -1)
, m_filterName(filterName)
, m_destUrl(std::move(destUrl))
, m_filterParams(std::move(filterParams))
{
Q_ASSERT(supportedFilters().count(filterName) > 0);
}
const QString StabilizeJob::getDescription() const
{
return i18n("Stabilize clips");
}
void StabilizeJob::configureConsumer()
{
m_consumer = std::make_unique(*m_profile.get(), "xml", m_destUrl.toUtf8().constData());
m_consumer->set("all", 1);
m_consumer->set("title", "Stabilized");
m_consumer->set("real_time", -KdenliveSettings::mltthreads());
}
void StabilizeJob::configureFilter()
{
m_filter = std::make_unique(*m_profile.get(), m_filterName.toUtf8().data());
if ((m_filter == nullptr) || !m_filter->is_valid()) {
m_errorMessage.append(i18n("Cannot create filter %1", m_filterName));
return;
}
// Process filter params
for (const auto &it : m_filterParams) {
m_filter->set(it.first.toUtf8().constData(), it.second.toUtf8().constData());
}
QString targetFile = m_destUrl + QStringLiteral(".trf");
m_filter->set("filename", targetFile.toUtf8().constData());
}
// static
std::unordered_set StabilizeJob::supportedFilters()
{
return {QLatin1String("vidstab"), QLatin1String("videostab2"), QLatin1String("videostab")};
}
// static
int StabilizeJob::prepareJob(const std::shared_ptr &ptr, const std::vector &binIds, int parentId, QString undoString,
const QString &filterName)
{
Q_ASSERT(supportedFilters().count(filterName) > 0);
if (filterName == QLatin1String("vidstab") || filterName == QLatin1String("videostab2") || filterName == QLatin1String("videostab")) {
// vidstab
QScopedPointer d(new ClipStabilize(binIds, filterName, 100000));
if (d->exec() == QDialog::Accepted) {
std::unordered_map filterParams = d->filterParams();
QString destination = d->destination();
std::unordered_map destinations; // keys are binIds, values are path to target files
for (const auto &binId : binIds) {
auto binClip = pCore->projectItemModel()->getClipByBinID(binId);
if (binIds.size() == 1) {
// We only have one clip, destination points to the final url
destinations[binId] = destination;
} else {
// Filter several clips, destination points to a folder
QString mltfile = destination + QFileInfo(binClip->url()).fileName() + QStringLiteral(".mlt");
destinations[binId] = mltfile;
}
}
// Now we have to create the jobs objects. This is trickier than usual, since the parameters are different for each job (each clip has its own
// destination). We have to construct a lambda that does that.
auto createFn = [dest = std::move(destinations), fName = filterName, fParams = std::move(filterParams)](const QString &id) {
return std::make_shared(id, fName, dest.at(id), fParams);
};
// We are now all set to create the job. Note that we pass all the parameters directly through the lambda, hence there are no extra parameters to
// the function
using local_createFn_t = std::function(const QString &)>;
return ptr->startJob(binIds, parentId, std::move(undoString), local_createFn_t(std::move(createFn)));
}
}
return -1;
}
bool StabilizeJob::commitResult(Fun &undo, Fun &redo)
{
Q_ASSERT(!m_resultConsumed);
if (!m_done) {
qDebug() << "ERROR: Trying to consume invalid results";
return false;
}
m_resultConsumed = true;
if (!m_successful) {
return false;
}
auto binClip = pCore->projectItemModel()->getClipByBinID(m_clipId);
// We store the stabilized clips in a sub folder with this name
const QString folderName(i18n("Stabilized"));
QString folderId = QStringLiteral("-1");
bool found = false;
// We first try to see if it exists
auto containingFolder = std::static_pointer_cast(binClip->parent());
for (int i = 0; i < containingFolder->childCount(); ++i) {
auto currentItem = std::static_pointer_cast(containingFolder->child(i));
if (currentItem->itemType() == AbstractProjectItem::FolderItem && currentItem->name() == folderName) {
found = true;
folderId = currentItem->clipId();
break;
}
}
if (!found) {
// if it was not found, we create it
pCore->projectItemModel()->requestAddFolder(folderId, folderName, binClip->parent()->clipId(), undo, redo);
}
auto id = ClipCreator::createClipFromFile(m_destUrl, folderId, pCore->projectItemModel(), undo, redo);
return id != QStringLiteral("-1");
}