Commit daa2c64c authored by Jean-Baptiste Mardelle's avatar Jean-Baptiste Mardelle
Browse files

Fixes and improvements for clips load / audio thumb jobs

parent 00f0c3a5
......@@ -281,6 +281,9 @@ bool AbstractTreeModel::checkConsistency()
Fun AbstractTreeModel::addItem_lambda(const std::shared_ptr<TreeItem> &new_item, int parentId)
{
return [this, new_item, parentId]() {
if (new_item->m_isInvalid) {
return true;
}
/* Insertion is simply setting the parent of the item.*/
std::shared_ptr<TreeItem> parent;
if (parentId != -1) {
......@@ -302,6 +305,10 @@ Fun AbstractTreeModel::removeItem_lambda(int id)
is captured by the reverse operation.
Actual deletions occurs when the undo object is destroyed.
*/
if (m_allItems.count(id) == 0) {
// Invalid item, might have been deleted on insert
return true;
}
auto item = m_allItems[id].lock();
Q_ASSERT(item);
if (!item) {
......
......@@ -32,6 +32,7 @@ TreeItem::TreeItem(QList<QVariant> data, const std::shared_ptr<AbstractTreeModel
, m_id(id == -1 ? AbstractTreeModel::getNextId() : id)
, m_isInModel(false)
, m_isRoot(isRoot)
, m_isInvalid(false)
{
}
......
......@@ -167,9 +167,9 @@ protected:
std::weak_ptr<AbstractTreeModel> m_model;
int m_depth;
int m_id;
bool m_isInModel;
bool m_isRoot;
bool m_isInvalid;
};
template <class T, class BinaryOperation> T TreeItem::accumulate(T init, BinaryOperation op)
......
......@@ -42,6 +42,7 @@ AssetParameterModel::AssetParameterModel(std::unique_ptr<Mlt::Properties> asset,
, m_active(false)
, m_asset(std::move(asset))
, m_keyframes(nullptr)
, m_filterProgress(0)
{
Q_ASSERT(m_asset->is_valid());
QDomNodeList parameterNodes = assetXml.elementsByTagName(QStringLiteral("parameter"));
......@@ -469,6 +470,8 @@ QVariant AssetParameterModel::data(const QModelIndex &index, int role) const
return parseAttribute(m_ownerId, QStringLiteral("consumerparams"), element);
case FilterJobParamsRole:
return parseSubAttributes(QStringLiteral("jobparam"), element);
case FilterProgressRole:
return m_filterProgress;
case AlternateNameRole: {
QDomNode child = element.firstChildElement(QStringLiteral("name"));
if (child.toElement().hasAttribute(QStringLiteral("conditional"))) {
......@@ -542,7 +545,7 @@ QVariant AssetParameterModel::data(const QModelIndex &index, int role) const
int AssetParameterModel::rowCount(const QModelIndex &parent) const
{
qDebug() << "===================================================== Requested rowCount" << parent << m_rows.size();
//qDebug() << "===================================================== Requested rowCount" << parent << m_rows.size();
if (parent.isValid()) return 0;
return m_rows.size();
}
......@@ -990,7 +993,7 @@ const QVector<QPair<QString, QVariant>> AssetParameterModel::loadPreset(const QS
return params;
}
void AssetParameterModel::setParameters(const QVector<QPair<QString, QVariant>> &params, bool update)
void AssetParameterModel::setParameters(const paramVector &params, bool update)
{
ObjectType itemId;
if (!update) {
......@@ -1053,6 +1056,12 @@ void AssetParameterModel::passProperties(Mlt::Properties &target)
target.set_lcnumeric(m_asset->get_lcnumeric());
}
void AssetParameterModel::setProgress(int progress)
{
m_filterProgress = progress;
emit dataChanged(index(0, 0), index(m_rows.count() - 1, 0), {AssetParameterModel::FilterProgressRole});
}
Mlt::Properties *AssetParameterModel::getAsset()
{
return m_asset.get();
......
......@@ -34,6 +34,8 @@
class KeyframeModelList;
typedef QVector<QPair<QString, QVariant>> paramVector;
enum class ParamType {
Double,
List, // Value can be chosen from a list of pre-defined ones
......@@ -109,6 +111,7 @@ public:
FactorRole,
FilterRole,
FilterJobParamsRole,
FilterProgressRole,
FilterParamsRole,
FilterConsumerParamsRole,
ScaleRole,
......@@ -162,11 +165,6 @@ public:
const QStringList getPresetList(const QString &presetFile) const;
const QVector<QPair<QString, QVariant>> loadPreset(const QString &presetFile, const QString &presetName);
/** @brief Sets the value of a list of parameters
@param params contains the pairs (parameter name, parameter value)
*/
void setParameters(const QVector<QPair<QString, QVariant>> &params, bool update = true);
/* Which monitor is attached to this asset (clip/project)
*/
Kdenlive::MonitorId monitorId;
......@@ -194,10 +192,17 @@ public:
/** @brief Returns the current value of an effect parameter */
const QString getParam(const QString &paramName);
/** @brief Returns the current asset */
Mlt::Properties *getAsset();
public slots:
/** @brief Sets the value of a list of parameters
@param params contains the pairs (parameter name, parameter value)
*/
void setParameters(const paramVector &params, bool update = true);
/** @brief Set a filter job's progress */
void setProgress(int progress);
protected:
/** @brief Helper function to retrieve the type of a parameter given the string corresponding to it*/
static ParamType paramTypeFromStr(const QString &type);
......@@ -244,6 +249,8 @@ protected:
bool m_hideKeyframesByDefault;
/** @brief true if this is an audio effect, used to prevent unnecessary monitor refresh / timeline invalidate */
bool m_isAudio;
/** @brief Store a filter's job progress */
int m_filterProgress;
/** @brief Set the parameter with given name to the given value. This should be called when first
* building an effect in the constructor, so that we don't call shared_from_this
......
......@@ -21,7 +21,7 @@
#include "buttonparamwidget.hpp"
#include "assets/model/assetparametermodel.hpp"
#include "jobs/filterclipjob.h"
#include "jobs/filtertask.h"
#include "jobs/jobmanager.h"
#include "assets/model/assetcommand.hpp"
#include "core.h"
......@@ -30,6 +30,7 @@
#include <KMessageWidget>
#include <QPushButton>
#include <QVBoxLayout>
#include <QProgressBar>
ButtonParamWidget::ButtonParamWidget(std::shared_ptr<AssetParameterModel> model, QModelIndex index, QWidget *parent)
: AbstractParamWidget(std::move(model), index, parent)
......@@ -86,6 +87,11 @@ ButtonParamWidget::ButtonParamWidget(std::shared_ptr<AssetParameterModel> model,
layout->setSpacing(0);
m_button = new QPushButton(m_displayConditional ? m_buttonName : m_alternatebuttonName, this);
layout->addWidget(m_button);
m_progress = new QProgressBar(this);
m_progress->setMaximumHeight(m_button->height() / 5);
m_progress->setTextVisible(false);
layout->addWidget(m_progress);
m_progress->setVisible(false);
setMinimumHeight(m_button->sizeHint().height() + (m_label != nullptr ? m_label->sizeHint().height() : 0));
// emit the signal of the base class when appropriate
......@@ -134,11 +140,16 @@ ButtonParamWidget::ButtonParamWidget(std::shared_ptr<AssetParameterModel> model,
fParams.insert({fparam.section(QLatin1Char('='), 0, 0), fparam.section(QLatin1Char('='), 1)});
}
}
emit pCore->jobManager()->startJob<FilterClipJob>({binId}, -1, QString(), owner, m_model, assetId, in, out, assetId, fParams, fData, consumerParams);
if (m_label) {
m_label->setVisible(false);
if (m_progress->value() > 0 && m_progress->value() < 100) {
// The task is in progress, abort it
pCore->taskManager.discardJobs(owner, AbstractTask::FILTERCLIPJOB);
} else {
FilterTask::start(owner, binId, m_model, assetId, in, out, assetId, fParams, fData, consumerParams, this);
if (m_label) {
m_label->setVisible(false);
}
m_button->setEnabled(false);
}
m_button->setEnabled(false);
});
}
......@@ -163,8 +174,21 @@ void ButtonParamWidget::slotRefresh()
if (m_label) {
m_label->setVisible(m_displayConditional);
}
m_button->setText(m_displayConditional ? m_buttonName : m_alternatebuttonName);
m_button->setEnabled(true);
// Check running job percentage
int progress = m_model->data(m_index, AssetParameterModel::FilterProgressRole).toInt();
if (progress > 0 && progress < 100) {
m_progress->setValue(progress);
if (!m_progress->isVisible()) {
m_button->setText(i18n("Abort processing"));
m_progress->setVisible(true);
}
} else {
m_button->setText(m_displayConditional ? m_buttonName : m_alternatebuttonName);
m_progress->setValue(0);
m_progress->setVisible(false);
}
updateGeometry();
}
......@@ -172,3 +196,4 @@ bool ButtonParamWidget::getValue()
{
return true;
}
......@@ -26,6 +26,7 @@
#include <QWidget>
class QPushButton;
class QProgressBar;
class KMessageWidget;
/** @brief This class represents a parameter that requires
......@@ -58,6 +59,7 @@ public slots:
private:
QPushButton *m_button;
QProgressBar *m_progress;
KMessageWidget *m_label;
QString m_keyParam;
QString m_buttonName;
......
......@@ -21,8 +21,6 @@
#include "clickablelabelwidget.hpp"
#include "assets/model/assetparametermodel.hpp"
#include "jobs/filterclipjob.h"
#include "jobs/jobmanager.h"
#include "core.h"
#include <QPushButton>
......
......@@ -289,7 +289,6 @@ std::shared_ptr<AbstractProjectItem> AbstractProjectItem::getEnclosingFolder(boo
bool AbstractProjectItem::selfSoftDelete(Fun &undo, Fun &redo)
{
pCore->jobManager()->slotDiscardClipJobs(clipId());
Fun local_undo = []() { return true; };
Fun local_redo = []() { return true; };
for (const auto &child : m_childItems) {
......@@ -339,3 +338,11 @@ void AbstractProjectItem::setRating(uint rating)
{
m_rating = rating;
}
Fun AbstractProjectItem::getAudio_lambda()
{
return []() {
qDebug()<<"============\n\nABSTRACT AUDIO CHECK\n\n===========";
return true;
};
}
......@@ -86,7 +86,7 @@ public:
@param Undo,Redo are the lambdas accumulating the update.
*/
virtual bool selfSoftDelete(Fun &undo, Fun &redo);
virtual Fun getAudio_lambda();
/** @brief Returns the clip's id. */
const QString &clipId() const;
virtual QPoint zone() const;
......
......@@ -45,6 +45,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "projectsubclip.h"
#include "timecode.h"
#include "timeline2/model/snapmodel.hpp"
#include "macros.hpp"
#include "utils/thumbnailcache.hpp"
#include "xml/xml.hpp"
......@@ -123,7 +124,7 @@ ProjectClip::ProjectClip(const QString &id, const QIcon &thumb, const std::share
if (m_clipStatus == FileStatus::StatusProxy || m_clipStatus == FileStatus::StatusReady || m_clipStatus == FileStatus::StatusProxyOnly) {
// Generate clip thumbnail
ClipLoadTask::start({ObjectType::BinClip,m_binId.toInt()}, QDomElement(), true, this);
// Generate audio levels
// Generate audio thumbnail
AudioLevelsTask::start({ObjectType::BinClip, m_binId.toInt()}, this, false);
}
}
......@@ -179,11 +180,6 @@ std::shared_ptr<ProjectClip> ProjectClip::construct(const QString &id, const QDo
ProjectClip::~ProjectClip()
{
// controller is deleted in bincontroller
m_thumbMutex.lock();
m_requestedThumbs.clear();
m_thumbMutex.unlock();
m_thumbThread.waitForFinished();
}
void ProjectClip::connectEffectStack()
......@@ -1672,6 +1668,18 @@ QList<int> ProjectClip::timelineInstances() const
bool ProjectClip::selfSoftDelete(Fun &undo, Fun &redo)
{
Fun operation = [this]() {
// Free audio thumb data and timeline producers
pCore->taskManager.discardJobs({ObjectType::BinClip, m_binId.toInt()});
m_audioLevels.clear();
m_disabledProducer.reset();
m_audioProducers.clear();
m_videoProducers.clear();
m_timewarpProducers.clear();
return true;
};
operation();
auto toDelete = m_registeredClips; // we cannot use m_registeredClips directly, because it will be modified during loop
for (const auto &clip : toDelete) {
if (m_registeredClips.count(clip.first) == 0) {
......@@ -1687,9 +1695,22 @@ bool ProjectClip::selfSoftDelete(Fun &undo, Fun &redo)
return false;
}
}
PUSH_LAMBDA(operation, redo);
qDebug()<<"===== REMOVING MASTER PRODUCER; CURRENT COUNT: "<<m_masterProducer.use_count()<<"\n:::::::::::::::::::::::::::";
return AbstractProjectItem::selfSoftDelete(undo, redo);
}
Fun ProjectClip::getAudio_lambda()
{
return [this]() {
if (KdenliveSettings::audiothumbnails() && (m_clipType == ClipType::AV || m_clipType == ClipType::Audio || m_clipType == ClipType::Playlist) && m_audioLevels.isEmpty()) {
// Generate audio levels
AudioLevelsTask::start({ObjectType::BinClip, m_binId.toInt()}, this, false);
}
return true;
};
}
bool ProjectClip::isIncludedInTimeline()
{
return m_registeredClips.size() > 0;
......@@ -2035,3 +2056,8 @@ void ProjectClip::updateJobProgress()
std::static_pointer_cast<ProjectItemModel>(ptr)->onItemUpdated(m_binId, AbstractProjectItem::JobProgress);
}
}
void ProjectClip::setInvalid()
{
m_isInvalid = true;
}
......@@ -103,6 +103,8 @@ public:
bool selfSoftDelete(Fun &undo, Fun &redo) override;
Fun getAudio_lambda() override;
/** @brief Returns true if item has both audio and video enabled. */
bool hasAudioAndVideo() const override;
......@@ -240,6 +242,8 @@ public:
static const QByteArray getFolderHash(QDir dir, QString fileName);
/** @brief Check if the clip is included in timeline and reset its occurrences on producer reload. */
void updateTimelineOnReload();
/** @brief If a clip is invalid on load, mark it as such so we don't try to re-insert it on undo/redo. */
void setInvalid();
int getRecordTime();
protected:
......@@ -295,8 +299,6 @@ private:
const QString getFileHash();
QMutex m_producerMutex;
QMutex m_thumbMutex;
QFuture<void> m_thumbThread;
QList<int> m_requestedThumbs;
const QString geometryWithOffset(const QString &data, int offset);
QMap <QString, QByteArray> m_audioLevels;
/** @brief If true, all timeline occurrences of this clip will be replaced from a fresh producer on reload. */
......
......@@ -604,9 +604,6 @@ bool ProjectItemModel::requestBinClipDeletion(const std::shared_ptr<AbstractProj
binId = ptr->clipId();
}
bool isSubClip = clip->itemType() == AbstractProjectItem::SubClipItem;
if (!isSubClip) {
pCore->taskManager.discardJobs({ObjectType::BinClip, clip->clipId().toInt()});
}
clip->selfSoftDelete(undo, redo);
int id = clip->getId();
Fun operation = removeItem_lambda(id);
......@@ -625,6 +622,9 @@ bool ProjectItemModel::requestBinClipDeletion(const std::shared_ptr<AbstractProj
update_doc();
PUSH_LAMBDA(update_doc, operation);
PUSH_LAMBDA(update_doc, reverse);
} else {
Fun checkAudio = clip->getAudio_lambda();
PUSH_LAMBDA(checkAudio, reverse);
}
UPDATE_UNDO_REDO(operation, reverse, undo, redo);
}
......@@ -692,6 +692,9 @@ bool ProjectItemModel::addItem(const std::shared_ptr<AbstractProjectItem> &item,
bool res = operation();
Q_ASSERT(item->isInModel());
if (res) {
Fun checkAudio = item->getAudio_lambda();
checkAudio();
PUSH_LAMBDA(checkAudio, operation);
UPDATE_UNDO_REDO(operation, reverse, undo, redo);
}
return res;
......
......@@ -91,6 +91,7 @@ bool Core::build(bool testMode)
qRegisterMetaType<QVector<int>>();
qRegisterMetaType<QDomElement>("QDomElement");
qRegisterMetaType<requestClipInfo>("requestClipInfo");
qRegisterMetaType<QVector<QPair<QString, QVariant>>>("paramVector");
if (!testMode) {
// Check if we had a crash
......
......@@ -8,6 +8,7 @@ set(kdenlive_SRCS
jobs/cliploadtask.cpp
jobs/proxytask.cpp
jobs/transcodetask.cpp
jobs/filtertask.cpp
jobs/jobmanager.cpp
jobs/cachejob.cpp
jobs/loadjob.cpp
......
......@@ -66,7 +66,8 @@ AbstractTask::AbstractTask(const ObjectId &owner, JOBTYPE type, QObject* object)
void AbstractTask::cancelJob()
{
m_isCanceled = true;
m_isCanceled.testAndSetAcquire(0, 1);
qDebug()<<"====== SETTING TACK CANCELED: "<<m_isCanceled;
emit jobCanceled();
}
......
......@@ -22,6 +22,7 @@
#include "definitions.h"
#include <QRunnable>
#include <QAtomicInt>
#include <QMutex>
#include <QObject>
......@@ -56,11 +57,10 @@ protected:
QObject* m_object;
int m_progress;
bool m_successful;
bool m_isCanceled;
QAtomicInt m_isCanceled;
bool m_isForce;
bool m_running;
void run() override;
QMutex m_runMutex;
void cleanup();
private:
......
......@@ -63,7 +63,6 @@ void AudioLevelsTask::start(const ObjectId &owner, QObject* object, bool force)
void AudioLevelsTask::run()
{
QMutexLocker lk(&m_runMutex);
m_running = true;
// 2 channels interleaved of uchar values
if (m_isCanceled) {
......@@ -210,38 +209,40 @@ void AudioLevelsTask::run()
m_audioLevels << uchar(255 * v / maxLevel);
}*/
m_progress = 100;
if (m_isCanceled) {
mltLevels.clear();
}
QMetaObject::invokeMethod(m_object, "updateJobProgress");
if (!m_isCanceled) {
if (mltLevels.size() > 0 && !m_isCanceled) {
QVector <uint8_t>* levelsCopy = new QVector <uint8_t>(mltLevels);
producer->lock();
QString key = QString("_kdenlive:audio%1").arg(stream);
producer->set(key.toUtf8().constData(), levelsCopy, 0, (mlt_destructor) deleteQVariantList);
producer->unlock();
qDebug()<<"=== FINISHED PRODUCING AUDIO FOR: "<<key<<", SIZE: "<<levelsCopy->size();
QMetaObject::invokeMethod(m_object, "updateAudioThumbnail");
// Put into an image for caching.
int count = mltLevels.size();
QImage image((count + 3) / 4 / channels, channels, QImage::Format_ARGB32);
int n = image.width() * image.height();
for (int i = 0; i < n; i ++) {
QRgb p;
if ((4*i + 3) < count) {
p = qRgba(mltLevels.at(4*i), mltLevels.at(4*i+1), mltLevels.at(4*i+2), mltLevels.at(4*i+3));
} else {
int last = mltLevels.last();
int r = (4*i+0) < count? mltLevels.at(4*i+0) : last;
int g = (4*i+1) < count? mltLevels.at(4*i+1) : last;
int b = (4*i+2) < count? mltLevels.at(4*i+2) : last;
int a = last;
p = qRgba(r, g, b, a);
}
image.setPixel(i / 2, i % channels, p);
if (mltLevels.size() > 0) {
QVector <uint8_t>* levelsCopy = new QVector <uint8_t>(mltLevels);
producer->lock();
QString key = QString("_kdenlive:audio%1").arg(stream);
producer->set(key.toUtf8().constData(), levelsCopy, 0, (mlt_destructor) deleteQVariantList);
producer->unlock();
qDebug()<<"=== FINISHED PRODUCING AUDIO FOR: "<<key<<", SIZE: "<<levelsCopy->size();
QMetaObject::invokeMethod(m_object, "updateAudioThumbnail");
// Put into an image for caching.
int count = mltLevels.size();
QImage image((count + 3) / 4 / channels, channels, QImage::Format_ARGB32);
int n = image.width() * image.height();
for (int i = 0; i < n; i ++) {
QRgb p;
if ((4*i + 3) < count) {
p = qRgba(mltLevels.at(4*i), mltLevels.at(4*i+1), mltLevels.at(4*i+2), mltLevels.at(4*i+3));
} else {
int last = mltLevels.last();
int r = (4*i+0) < count? mltLevels.at(4*i+0) : last;
int g = (4*i+1) < count? mltLevels.at(4*i+1) : last;
int b = (4*i+2) < count? mltLevels.at(4*i+2) : last;
int a = last;
p = qRgba(r, g, b, a);
}
image.save(cachePath);
image.setPixel(i / 2, i % channels, p);
}
image.save(cachePath);
}
}
qDebug()<<"============= TASK WAS CANCELED: "<<m_isCanceled<<"\n\n===================";
pCore->taskManager.taskDone(m_owner.second, this);
QMetaObject::invokeMethod(m_object, "updateJobProgress");
}
......@@ -109,7 +109,7 @@ std::shared_ptr<Mlt::Producer> ClipLoadTask::loadResource(QString resource, cons
if (!resource.startsWith(type)) {
resource.prepend(type);
}
return std::make_shared<Mlt::Producer>(pCore->getCurrentProfile()->profile(), nullptr, resource.toUtf8().constData());
return std::make_shared<Mlt::Producer>(*pCore->getProjectProfile(), nullptr, resource.toUtf8().constData());
}
std::shared_ptr<Mlt::Producer> ClipLoadTask::loadPlaylist(QString &resource)
......@@ -133,15 +133,13 @@ std::shared_ptr<Mlt::Producer> ClipLoadTask::loadPlaylist(QString &resource)
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<Mlt::Producer>(pCore->getCurrentProfile()->profile(), loader.toUtf8().constData());
return std::make_shared<Mlt::Producer>(*pCore->getProjectProfile(), loader.toUtf8().constData());
} else {
m_errorMessage.append(i18n("No matching profile"));
return nullptr;
}
}
pCore->getCurrentProfile()->set_explicit(1);
return std::make_shared<Mlt::Producer>(pCore->getCurrentProfile()->profile(), "xml", resource.toUtf8().constData());
return std::make_shared<Mlt::Producer>(*pCore->getProjectProfile(), "xml", resource.toUtf8().constData());
}
// Read the properties of the xml and pass them to the producer. Note that some properties like resource are ignored
......@@ -180,13 +178,13 @@ void ClipLoadTask::processSlideShow(std::shared_ptr<Mlt::Producer> producer)
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");
auto *filter = new Mlt::Filter(*pCore->getProjectProfile(), "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");
auto *blur = new Mlt::Filter(*pCore->getProjectProfile(), "boxblur");
if ((blur != nullptr) && blur->is_valid()) {
producer->attach(*blur);
}
......@@ -200,7 +198,7 @@ void ClipLoadTask::processSlideShow(std::shared_ptr<Mlt::Producer> producer)
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");
auto *filter = new Mlt::Filter(*pCore->getProjectProfile(), "luma");
if ((filter != nullptr) && filter->is_valid()) {
if (ttl != 0) {
filter->set("cycle", ttl);
......@@ -224,7 +222,7 @@ void ClipLoadTask::processSlideShow(std::shared_ptr<Mlt::Producer> producer)
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");
auto *filter = new Mlt::Filter(*pCore->getProjectProfile(), "crop");
if ((filter != nullptr) && filter->is_valid()) {
filter->set("center", 1);