diff --git a/src/bin/projectclip.cpp b/src/bin/projectclip.cpp index e1dd3a165..6912b359d 100644 --- a/src/bin/projectclip.cpp +++ b/src/bin/projectclip.cpp @@ -1,1441 +1,1441 @@ /* Copyright (C) 2012 Till Theato Copyright (C) 2014 Jean-Baptiste Mardelle 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 "projectclip.h" #include "bin.h" #include "core.h" #include "doc/docundostack.hpp" #include "doc/kdenlivedoc.h" #include "doc/kthumb.h" #include "effects/effectstack/model/effectstackmodel.hpp" #include "jobs/jobmanager.h" #include "jobs/loadjob.hpp" #include "jobs/thumbjob.hpp" #include "jobs/audiothumbjob.hpp" #include "kdenlivesettings.h" #include "lib/audio/audioStreamInfo.h" #include "mltcontroller/clipcontroller.h" #include "mltcontroller/clippropertiescontroller.h" #include "model/markerlistmodel.hpp" #include "profiles/profilemodel.hpp" #include "project/projectcommands.h" #include "project/projectmanager.h" #include "projectfolder.h" #include "projectitemmodel.h" #include "projectsubclip.h" #include "timecode.h" #include "timeline2/model/snapmodel.hpp" #include "utils/thumbnailcache.hpp" #include "xml/xml.hpp" #include #include #include #include "kdenlive_debug.h" #include "logger.hpp" #include #include #include #include #include #include #include #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" #pragma GCC diagnostic ignored "-Wsign-conversion" #pragma GCC diagnostic ignored "-Wfloat-equal" #pragma GCC diagnostic ignored "-Wshadow" #pragma GCC diagnostic ignored "-Wpedantic" #include #pragma GCC diagnostic pop RTTR_REGISTRATION { using namespace rttr; registration::class_("ProjectClip"); } ProjectClip::ProjectClip(const QString &id, const QIcon &thumb, const std::shared_ptr &model, std::shared_ptr producer) : AbstractProjectItem(AbstractProjectItem::ClipItem, id, model) , ClipController(id, std::move(producer)) , m_thumbsProducer(nullptr) { m_markerModel = std::make_shared(id, pCore->projectManager()->undoStack()); m_clipStatus = StatusReady; m_name = clipName(); m_duration = getStringDuration(); m_inPoint = 0; m_date = date; m_description = ClipController::description(); if (m_clipType == ClipType::Audio) { m_thumbnail = QIcon::fromTheme(QStringLiteral("audio-x-generic")); } else { m_thumbnail = thumb; } // Make sure we have a hash for this clip hash(); connect(m_markerModel.get(), &MarkerListModel::modelChanged, [&]() { setProducerProperty(QStringLiteral("kdenlive:markers"), m_markerModel->toJson()); }); QString markers = getProducerProperty(QStringLiteral("kdenlive:markers")); if (!markers.isEmpty()) { QMetaObject::invokeMethod(m_markerModel.get(), "importFromJson", Qt::QueuedConnection, Q_ARG(const QString &, markers), Q_ARG(bool, true), Q_ARG(bool, false)); } connectEffectStack(); } // static std::shared_ptr ProjectClip::construct(const QString &id, const QIcon &thumb, const std::shared_ptr &model, const std::shared_ptr &producer) { std::shared_ptr self(new ProjectClip(id, thumb, model, producer)); baseFinishConstruct(self); self->m_effectStack->importEffects(producer, PlaylistState::Disabled, true); model->loadSubClips(id, self->getPropertiesFromPrefix(QStringLiteral("kdenlive:clipzone."))); return self; } ProjectClip::ProjectClip(const QString &id, const QDomElement &description, const QIcon &thumb, const std::shared_ptr &model) : AbstractProjectItem(AbstractProjectItem::ClipItem, id, model) , ClipController(id) , m_thumbsProducer(nullptr) { m_clipStatus = StatusWaiting; m_thumbnail = thumb; m_markerModel = std::make_shared(m_binId, pCore->projectManager()->undoStack()); if (description.hasAttribute(QStringLiteral("type"))) { m_clipType = (ClipType::ProducerType)description.attribute(QStringLiteral("type")).toInt(); if (m_clipType == ClipType::Audio) { m_thumbnail = QIcon::fromTheme(QStringLiteral("audio-x-generic")); } } m_temporaryUrl = getXmlProperty(description, QStringLiteral("resource")); QString clipName = getXmlProperty(description, QStringLiteral("kdenlive:clipname")); if (!clipName.isEmpty()) { m_name = clipName; } else if (!m_temporaryUrl.isEmpty()) { m_name = QFileInfo(m_temporaryUrl).fileName(); } else { m_name = i18n("Untitled"); } connect(m_markerModel.get(), &MarkerListModel::modelChanged, [&]() { setProducerProperty(QStringLiteral("kdenlive:markers"), m_markerModel->toJson()); }); } std::shared_ptr ProjectClip::construct(const QString &id, const QDomElement &description, const QIcon &thumb, std::shared_ptr model) { std::shared_ptr self(new ProjectClip(id, description, thumb, std::move(model))); baseFinishConstruct(self); return self; } ProjectClip::~ProjectClip() { // controller is deleted in bincontroller m_thumbMutex.lock(); m_requestedThumbs.clear(); m_thumbMutex.unlock(); m_thumbThread.waitForFinished(); audioFrameCache.clear(); } void ProjectClip::connectEffectStack() { connect(m_effectStack.get(), &EffectStackModel::modelChanged, this, &ProjectClip::updateChildProducers); connect(m_effectStack.get(), &EffectStackModel::dataChanged, this, &ProjectClip::updateChildProducers); connect(m_effectStack.get(), &EffectStackModel::dataChanged, [&]() { if (auto ptr = m_model.lock()) { std::static_pointer_cast(ptr)->onItemUpdated(std::static_pointer_cast(shared_from_this()), AbstractProjectItem::IconOverlay); } }); /*connect(m_effectStack.get(), &EffectStackModel::modelChanged, [&](){ qDebug()<<"/ / / STACK CHANGED"; updateChildProducers(); });*/ } QString ProjectClip::getToolTip() const { return url(); } QString ProjectClip::getXmlProperty(const QDomElement &producer, const QString &propertyName, const QString &defaultValue) { QString value = defaultValue; QDomNodeList props = producer.elementsByTagName(QStringLiteral("property")); for (int i = 0; i < props.count(); ++i) { if (props.at(i).toElement().attribute(QStringLiteral("name")) == propertyName) { value = props.at(i).firstChild().nodeValue(); break; } } return value; } void ProjectClip::updateAudioThumbnail(QList audioLevels) { audioFrameCache = audioLevels; m_audioThumbCreated = true; if (auto ptr = m_model.lock()) { emit std::static_pointer_cast(ptr)->refreshAudioThumbs(m_binId); } qDebug()<<" * ** * YOP AUDIO LEF CHANGED"; updateTimelineClips({TimelineModel::AudioLevelsRole}); } bool ProjectClip::audioThumbCreated() const { return (m_audioThumbCreated); } ClipType::ProducerType ProjectClip::clipType() const { return m_clipType; } bool ProjectClip::hasParent(const QString &id) const { std::shared_ptr par = parent(); while (par) { if (par->clipId() == id) { return true; } par = par->parent(); } return false; } std::shared_ptr ProjectClip::clip(const QString &id) { if (id == m_binId) { return std::static_pointer_cast(shared_from_this()); } return std::shared_ptr(); } std::shared_ptr ProjectClip::folder(const QString &id) { Q_UNUSED(id) return std::shared_ptr(); } std::shared_ptr ProjectClip::getSubClip(int in, int out) { for (int i = 0; i < childCount(); ++i) { std::shared_ptr clip = std::static_pointer_cast(child(i))->subClip(in, out); if (clip) { return clip; } } return std::shared_ptr(); } QStringList ProjectClip::subClipIds() const { QStringList subIds; for (int i = 0; i < childCount(); ++i) { std::shared_ptr clip = std::static_pointer_cast(child(i)); if (clip) { subIds << clip->clipId(); } } return subIds; } std::shared_ptr ProjectClip::clipAt(int ix) { if (ix == row()) { return std::static_pointer_cast(shared_from_this()); } return std::shared_ptr(); } /*bool ProjectClip::isValid() const { return m_controller->isValid(); }*/ bool ProjectClip::hasUrl() const { if ((m_clipType != ClipType::Color) && (m_clipType != ClipType::Unknown)) { return (!clipUrl().isEmpty()); } return false; } const QString ProjectClip::url() const { return clipUrl(); } GenTime ProjectClip::duration() const { return getPlaytime(); } size_t ProjectClip::frameDuration() const { GenTime d = duration(); return (size_t)d.frames(pCore->getCurrentFps()); } void ProjectClip::reloadProducer(bool refreshOnly, bool audioStreamChanged) { // we find if there are some loading job on that clip int loadjobId = -1; pCore->jobManager()->hasPendingJob(clipId(), AbstractClipJob::LOADJOB, &loadjobId); QMutexLocker lock(&m_thumbMutex); if (refreshOnly) { // In that case, we only want a new thumbnail. // We thus set up a thumb job. We must make sure that there is no pending LOADJOB // Clear cache first ThumbnailCache::get()->invalidateThumbsForClip(clipId()); pCore->jobManager()->discardJobs(clipId(), AbstractClipJob::THUMBJOB); m_thumbsProducer.reset(); pCore->jobManager()->startJob({clipId()}, loadjobId, QString(), 150, -1, true, true); } else { // If another load job is running? if (loadjobId > -1) { pCore->jobManager()->discardJobs(clipId(), AbstractClipJob::LOADJOB); } QDomDocument doc; QDomElement xml = toXml(doc); if (!xml.isNull()) { pCore->jobManager()->discardJobs(clipId(), AbstractClipJob::THUMBJOB); m_thumbsProducer.reset(); ThumbnailCache::get()->invalidateThumbsForClip(clipId()); int loadJob = pCore->jobManager()->startJob({clipId()}, loadjobId, QString(), xml); pCore->jobManager()->startJob({clipId()}, loadJob, QString(), 150, -1, true, true); if (audioStreamChanged) { discardAudioThumb(); pCore->jobManager()->startJob({clipId()}, loadjobId, QString()); } } } } QDomElement ProjectClip::toXml(QDomDocument &document, bool includeMeta) { getProducerXML(document, includeMeta); QDomElement prod = document.documentElement().firstChildElement(QStringLiteral("producer")); if (m_clipType != ClipType::Unknown) { prod.setAttribute(QStringLiteral("type"), (int)m_clipType); } return prod; } void ProjectClip::setThumbnail(const QImage &img) { QPixmap thumb = roundedPixmap(QPixmap::fromImage(img)); if (hasProxy() && !thumb.isNull()) { // Overlay proxy icon QPainter p(&thumb); QColor c(220, 220, 10, 200); QRect r(0, 0, thumb.height() / 2.5, thumb.height() / 2.5); p.fillRect(r, c); QFont font = p.font(); font.setPixelSize(r.height()); 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")); } m_thumbnail = QIcon(thumb); if (auto ptr = m_model.lock()) { std::static_pointer_cast(ptr)->onItemUpdated(std::static_pointer_cast(shared_from_this()), AbstractProjectItem::DataThumbnail); } } bool ProjectClip::hasAudioAndVideo() const { return hasAudio() && hasVideo() && m_masterProducer->get_int("set.test_image") == 0 && m_masterProducer->get_int("set.test_audio") == 0; } bool ProjectClip::isCompatible(PlaylistState::ClipState state) const { switch (state) { case PlaylistState::AudioOnly: return hasAudio() && (m_masterProducer->get_int("set.test_audio") == 0); case PlaylistState::VideoOnly: return hasVideo() && (m_masterProducer->get_int("set.test_image") == 0); default: return true; } } QPixmap ProjectClip::thumbnail(int width, int height) { return m_thumbnail.pixmap(width, height); } bool ProjectClip::setProducer(std::shared_ptr producer, bool replaceProducer) { Q_UNUSED(replaceProducer) qDebug() << "################### ProjectClip::setproducer"; QMutexLocker locker(&m_producerMutex); updateProducer(producer); m_thumbsProducer.reset(); connectEffectStack(); // Update info if (m_name.isEmpty()) { m_name = clipName(); } m_date = date; m_description = ClipController::description(); m_temporaryUrl.clear(); if (m_clipType == ClipType::Audio) { m_thumbnail = QIcon::fromTheme(QStringLiteral("audio-x-generic")); } else if (m_clipType == ClipType::Image) { if (producer->get_int("meta.media.width") < 8 || producer->get_int("meta.media.height") < 8) { KMessageBox::information(QApplication::activeWindow(), i18n("Image dimension smaller than 8 pixels.\nThis is not correctly supported by our video framework.")); } } m_duration = getStringDuration(); m_clipStatus = StatusReady; if (!hasProxy()) { if (auto ptr = m_model.lock()) emit std::static_pointer_cast(ptr)->refreshPanel(m_binId); } if (auto ptr = m_model.lock()) { std::static_pointer_cast(ptr)->onItemUpdated(std::static_pointer_cast(shared_from_this()), AbstractProjectItem::DataDuration); std::static_pointer_cast(ptr)->updateWatcher(std::static_pointer_cast(shared_from_this())); } // Make sure we have a hash for this clip getFileHash(); // set parent again (some info need to be stored in producer) updateParent(parentItem().lock()); if (pCore->currentDoc()->getDocumentProperty(QStringLiteral("enableproxy")).toInt() == 1) { QList> clipList; // automatic proxy generation enabled if (m_clipType == ClipType::Image && pCore->currentDoc()->getDocumentProperty(QStringLiteral("generateimageproxy")).toInt() == 1) { if (getProducerIntProperty(QStringLiteral("meta.media.width")) >= KdenliveSettings::proxyimageminsize() && getProducerProperty(QStringLiteral("kdenlive:proxy")) == QStringLiteral()) { clipList << std::static_pointer_cast(shared_from_this()); } } else if (pCore->currentDoc()->getDocumentProperty(QStringLiteral("generateproxy")).toInt() == 1 && (m_clipType == ClipType::AV || m_clipType == ClipType::Video) && getProducerProperty(QStringLiteral("kdenlive:proxy")) == QStringLiteral()) { bool skipProducer = false; if (pCore->currentDoc()->getDocumentProperty(QStringLiteral("enableexternalproxy")).toInt() == 1) { QStringList externalParams = pCore->currentDoc()->getDocumentProperty(QStringLiteral("externalproxyparams")).split(QLatin1Char(';')); // We have a camcorder profile, check if we have opened a proxy clip if (externalParams.count() >= 6) { QFileInfo info(m_path); QDir dir = info.absoluteDir(); dir.cd(externalParams.at(3)); QString fileName = info.fileName(); if (!externalParams.at(2).isEmpty()) { fileName.chop(externalParams.at(2).size()); } fileName.append(externalParams.at(5)); if (dir.exists(fileName)) { setProducerProperty(QStringLiteral("kdenlive:proxy"), m_path); m_path = dir.absoluteFilePath(fileName); setProducerProperty(QStringLiteral("kdenlive:originalurl"), m_path); getFileHash(); skipProducer = true; } } } if (!skipProducer && getProducerIntProperty(QStringLiteral("meta.media.width")) >= KdenliveSettings::proxyminsize()) { clipList << std::static_pointer_cast(shared_from_this()); } } if (!clipList.isEmpty()) { pCore->currentDoc()->slotProxyCurrentItem(true, clipList, false); } } pCore->bin()->reloadMonitorIfActive(clipId()); for (auto &p : m_audioProducers) { m_effectStack->removeService(p.second); } for (auto &p : m_videoProducers) { m_effectStack->removeService(p.second); } for (auto &p : m_timewarpProducers) { m_effectStack->removeService(p.second); } // Release audio producers m_audioProducers.clear(); m_videoProducers.clear(); m_timewarpProducers.clear(); emit refreshPropertiesPanel(); replaceInTimeline(); return true; } std::shared_ptr ProjectClip::thumbProducer() { if (m_thumbsProducer) { return m_thumbsProducer; } if (clipType() == ClipType::Unknown) { return nullptr; } QMutexLocker lock(&m_thumbMutex); std::shared_ptr prod = originalProducer(); if (!prod->is_valid()) { return nullptr; } if (KdenliveSettings::gpu_accel()) { // TODO: when the original producer changes, we must reload this thumb producer m_thumbsProducer = softClone(ClipController::getPassPropertiesList()); Mlt::Filter converter(*prod->profile(), "avcolor_space"); m_thumbsProducer->attach(converter); } else { QString mltService = m_masterProducer->get("mlt_service"); const QString mltResource = m_masterProducer->get("resource"); if (mltService == QLatin1String("avformat")) { mltService = QStringLiteral("avformat-novalidate"); } m_thumbsProducer.reset(new Mlt::Producer(*pCore->thumbProfile(), mltService.toUtf8().constData(), mltResource.toUtf8().constData())); if (m_thumbsProducer->is_valid()) { Mlt::Properties original(m_masterProducer->get_properties()); Mlt::Properties cloneProps(m_thumbsProducer->get_properties()); cloneProps.pass_list(original, ClipController::getPassPropertiesList()); Mlt::Filter scaler(*pCore->thumbProfile(), "swscale"); Mlt::Filter padder(*pCore->thumbProfile(), "resize"); Mlt::Filter converter(*pCore->thumbProfile(), "avcolor_space"); m_thumbsProducer->set("audio_index", -1); m_thumbsProducer->attach(scaler); m_thumbsProducer->attach(padder); m_thumbsProducer->attach(converter); } } return m_thumbsProducer; } void ProjectClip::createDisabledMasterProducer() { if (!m_disabledProducer) { m_disabledProducer = cloneProducer(); m_disabledProducer->set("set.test_audio", 1); m_disabledProducer->set("set.test_image", 1); m_effectStack->addService(m_disabledProducer); } } std::shared_ptr ProjectClip::getTimelineProducer(int trackId, int clipId, PlaylistState::ClipState state, double speed) { if (!m_masterProducer) { return nullptr; } if (qFuzzyCompare(speed, 1.0)) { // we are requesting a normal speed producer if (trackId == -1 || (state == PlaylistState::VideoOnly && (m_clipType == ClipType::Color || m_clipType == ClipType::Image || m_clipType == ClipType::Text))) { // Temporary copy, return clone of master int duration = m_masterProducer->time_to_frames(m_masterProducer->get("kdenlive:duration")); return std::shared_ptr(m_masterProducer->cut(-1, duration > 0 ? duration : -1)); } if (m_timewarpProducers.count(clipId) > 0) { m_effectStack->removeService(m_timewarpProducers[clipId]); m_timewarpProducers.erase(clipId); } if (state == PlaylistState::AudioOnly) { // We need to get an audio producer, if none exists if (m_audioProducers.count(trackId) == 0) { m_audioProducers[trackId] = cloneProducer(true); m_audioProducers[trackId]->set("set.test_audio", 0); m_audioProducers[trackId]->set("set.test_image", 1); m_effectStack->addService(m_audioProducers[trackId]); } return std::shared_ptr(m_audioProducers[trackId]->cut()); } if (m_audioProducers.count(trackId) > 0) { m_effectStack->removeService(m_audioProducers[trackId]); m_audioProducers.erase(trackId); } if (state == PlaylistState::VideoOnly) { // we return the video producer // We need to get an audio producer, if none exists if (m_videoProducers.count(trackId) == 0) { m_videoProducers[trackId] = cloneProducer(true); m_videoProducers[trackId]->set("set.test_audio", 1); m_videoProducers[trackId]->set("set.test_image", 0); m_effectStack->addService(m_videoProducers[trackId]); } int duration = m_masterProducer->time_to_frames(m_masterProducer->get("kdenlive:duration")); return std::shared_ptr(m_videoProducers[trackId]->cut(-1, duration > 0 ? duration : -1)); } if (m_videoProducers.count(trackId) > 0) { m_effectStack->removeService(m_videoProducers[trackId]); m_videoProducers.erase(trackId); } Q_ASSERT(state == PlaylistState::Disabled); createDisabledMasterProducer(); int duration = m_masterProducer->time_to_frames(m_masterProducer->get("kdenlive:duration")); return std::shared_ptr(m_disabledProducer->cut(-1, duration > 0 ? duration : -1)); } // For timewarp clips, we keep one separate producer for each clip. std::shared_ptr warpProducer; if (m_timewarpProducers.count(clipId) > 0) { // remove in all cases, we add it unconditionally anyways m_effectStack->removeService(m_timewarpProducers[clipId]); if (qFuzzyCompare(m_timewarpProducers[clipId]->get_double("warp_speed"), speed)) { // the producer we have is good, use it ! warpProducer = m_timewarpProducers[clipId]; qDebug() << "Reusing producer!"; } else { m_timewarpProducers.erase(clipId); } } if (!warpProducer) { QLocale locale; QString resource(originalProducer()->get("resource")); if (resource.isEmpty() || resource == QLatin1String("")) { resource = m_service; } QString url = QString("timewarp:%1:%2").arg(locale.toString(speed)).arg(resource); - warpProducer.reset(new Mlt::Producer(originalProducer()->get_profile(), url.toUtf8().constData())); + warpProducer.reset(new Mlt::Producer(*originalProducer()->profile(), url.toUtf8().constData())); qDebug() << "new producer: " << url; qDebug() << "warp LENGTH before" << warpProducer->get_length(); int original_length = originalProducer()->get_length(); // this is a workaround to cope with Mlt erroneous rounding warpProducer->set("length", double(original_length) / std::abs(speed)); } qDebug() << "warp LENGTH" << warpProducer->get_length(); warpProducer->set("set.test_audio", 1); warpProducer->set("set.test_image", 1); if (state == PlaylistState::AudioOnly) { warpProducer->set("set.test_audio", 0); } if (state == PlaylistState::VideoOnly) { warpProducer->set("set.test_image", 0); } m_timewarpProducers[clipId] = warpProducer; m_effectStack->addService(m_timewarpProducers[clipId]); return std::shared_ptr(warpProducer->cut()); } std::pair, bool> ProjectClip::giveMasterAndGetTimelineProducer(int clipId, std::shared_ptr master, PlaylistState::ClipState state) { int in = master->get_in(); int out = master->get_out(); if (master->parent().is_valid()) { // in that case, we have a cut // check whether it's a timewarp double speed = 1.0; bool timeWarp = false; if (QString::fromUtf8(master->parent().get("mlt_service")) == QLatin1String("timewarp")) { speed = master->parent().get_double("warp_speed"); timeWarp = true; } if (master->parent().get_int("_loaded") == 1) { // we already have a clip that shares the same master if (state != PlaylistState::Disabled || timeWarp) { // In that case, we must create copies std::shared_ptr prod(getTimelineProducer(-1, clipId, state, speed)->cut(in, out)); return {prod, false}; } if (state == PlaylistState::Disabled && !m_disabledProducer) { qDebug() << "Warning: weird, we found a disabled clip whose master is already loaded but we don't have any yet"; createDisabledMasterProducer(); return {std::shared_ptr(m_disabledProducer->cut(in, out)), false}; } if (state == PlaylistState::Disabled && QString::fromUtf8(m_disabledProducer->get("id")) != QString::fromUtf8(master->parent().get("id"))) { qDebug() << "Warning: weird, we found a disabled clip whose master is already loaded but doesn't match ours"; return {std::shared_ptr(m_disabledProducer->cut(in, out)), false}; } // We have a good id, this clip can be used return {master, true}; } else { master->parent().set("_loaded", 1); if (timeWarp) { m_timewarpProducers[clipId] = std::make_shared(&master->parent()); m_effectStack->loadService(m_timewarpProducers[clipId]); return {master, true}; } if (state == PlaylistState::AudioOnly) { m_audioProducers[clipId] = std::make_shared(&master->parent()); m_effectStack->loadService(m_audioProducers[clipId]); return {master, true}; } if (state == PlaylistState::VideoOnly) { // good, we found a master video producer, and we didn't have any m_videoProducers[clipId] = std::make_shared(&master->parent()); m_effectStack->loadService(m_videoProducers[clipId]); return {master, true}; } if (state == PlaylistState::Disabled && !m_disabledProducer) { // good, we found a master disabled producer, and we didn't have any m_disabledProducer.reset(master->parent().cut()); m_effectStack->loadService(m_disabledProducer); return {master, true}; } qDebug() << "Warning: weird, we found a clip whose master is not loaded but we already have a master"; Q_ASSERT(false); } } else if (master->is_valid()) { // in that case, we have a master qDebug() << "Warning: weird, we received a master clip in lieue of a cut"; double speed = 1.0; if (QString::fromUtf8(master->parent().get("mlt_service")) == QLatin1String("timewarp")) { speed = master->get_double("warp_speed"); } return {getTimelineProducer(-1, clipId, state, speed), false}; } // we have a problem return {std::shared_ptr(ClipController::mediaUnavailable->cut()), false}; } /* std::shared_ptr ProjectClip::timelineProducer(PlaylistState::ClipState state, int track) { if (!m_service.startsWith(QLatin1String("avformat"))) { std::shared_ptr prod(originalProducer()->cut()); int length = getProducerIntProperty(QStringLiteral("kdenlive:duration")); if (length > 0) { prod->set_in_and_out(0, length); } return prod; } if (state == PlaylistState::VideoOnly) { if (m_timelineProducers.count(0) > 0) { return std::shared_ptr(m_timelineProducers.find(0)->second->cut()); } std::shared_ptr videoProd = cloneProducer(); videoProd->set("audio_index", -1); m_timelineProducers[0] = videoProd; return std::shared_ptr(videoProd->cut()); } if (state == PlaylistState::AudioOnly) { if (m_timelineProducers.count(-track) > 0) { return std::shared_ptr(m_timelineProducers.find(-track)->second->cut()); } std::shared_ptr audioProd = cloneProducer(); audioProd->set("video_index", -1); m_timelineProducers[-track] = audioProd; return std::shared_ptr(audioProd->cut()); } if (m_timelineProducers.count(track) > 0) { return std::shared_ptr(m_timelineProducers.find(track)->second->cut()); } std::shared_ptr normalProd = cloneProducer(); m_timelineProducers[track] = normalProd; return std::shared_ptr(normalProd->cut()); }*/ std::shared_ptr ProjectClip::cloneProducer(bool removeEffects) { Mlt::Consumer c(pCore->getCurrentProfile()->profile(), "xml", "string"); Mlt::Service s(m_masterProducer->get_service()); int ignore = s.get_int("ignore_points"); if (ignore) { s.set("ignore_points", 0); } c.connect(s); c.set("time_format", "frames"); c.set("no_meta", 1); c.set("no_root", 1); c.set("no_profile", 1); c.set("root", "/"); c.set("store", "kdenlive"); c.run(); if (ignore) { s.set("ignore_points", ignore); } const QByteArray clipXml = c.get("string"); std::shared_ptr prod; prod.reset(new Mlt::Producer(pCore->getCurrentProfile()->profile(), "xml-string", clipXml.constData())); if (strcmp(prod->get("mlt_service"), "avformat") == 0) { prod->set("mlt_service", "avformat-novalidate"); } // we pass some properties that wouldn't be passed because of the novalidate const char *prefix = "meta."; const size_t prefix_len = strlen(prefix); for (int i = 0; i < m_masterProducer->count(); ++i) { char *current = m_masterProducer->get_name(i); if (strlen(current) >= prefix_len && strncmp(current, prefix, prefix_len) == 0) { prod->set(current, m_masterProducer->get(i)); } } if (removeEffects) { int ct = 0; Mlt::Filter *filter = prod->filter(ct); while (filter) { qDebug() << "// EFFECT " << ct << " : " << filter->get("mlt_service"); QString ix = QString::fromLatin1(filter->get("kdenlive_id")); if (!ix.isEmpty()) { qDebug() << "/ + + DELTING"; if (prod->detach(*filter) == 0) { } else { ct++; } } else { ct++; } delete filter; filter = prod->filter(ct); } } prod->set("id", (char *)nullptr); return prod; } std::shared_ptr ProjectClip::cloneProducer(const std::shared_ptr &producer) { Mlt::Consumer c(*producer->profile(), "xml", "string"); Mlt::Service s(producer->get_service()); int ignore = s.get_int("ignore_points"); if (ignore) { s.set("ignore_points", 0); } c.connect(s); c.set("time_format", "frames"); c.set("no_meta", 1); c.set("no_root", 1); c.set("no_profile", 1); c.set("root", "/"); c.set("store", "kdenlive"); c.start(); if (ignore) { s.set("ignore_points", ignore); } const QByteArray clipXml = c.get("string"); std::shared_ptr prod(new Mlt::Producer(*producer->profile(), "xml-string", clipXml.constData())); if (strcmp(prod->get("mlt_service"), "avformat") == 0) { prod->set("mlt_service", "avformat-novalidate"); } return prod; } std::shared_ptr ProjectClip::softClone(const char *list) { QString service = QString::fromLatin1(m_masterProducer->get("mlt_service")); QString resource = QString::fromLatin1(m_masterProducer->get("resource")); std::shared_ptr clone(new Mlt::Producer(*m_masterProducer->profile(), service.toUtf8().constData(), resource.toUtf8().constData())); Mlt::Properties original(m_masterProducer->get_properties()); Mlt::Properties cloneProps(clone->get_properties()); cloneProps.pass_list(original, list); return clone; } bool ProjectClip::isReady() const { return m_clipStatus == StatusReady; } QPoint ProjectClip::zone() const { return ClipController::zone(); } const QString ProjectClip::hash() { QString clipHash = getProducerProperty(QStringLiteral("kdenlive:file_hash")); if (!clipHash.isEmpty()) { return clipHash; } return getFileHash(); } const QString ProjectClip::getFileHash() { QByteArray fileData; QByteArray fileHash; switch (m_clipType) { case ClipType::SlideShow: fileData = clipUrl().toUtf8(); fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5); break; case ClipType::Text: case ClipType::TextTemplate: fileData = getProducerProperty(QStringLiteral("xmldata")).toUtf8(); fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5); break; case ClipType::QText: fileData = getProducerProperty(QStringLiteral("text")).toUtf8(); fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5); break; case ClipType::Color: fileData = getProducerProperty(QStringLiteral("resource")).toUtf8(); fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5); break; default: QFile file(clipUrl()); if (file.open(QIODevice::ReadOnly)) { // write size and hash only if resource points to a file /* * 1 MB = 1 second per 450 files (or faster) * 10 MB = 9 seconds per 450 files (or faster) */ if (file.size() > 2000000) { fileData = file.read(1000000); if (file.seek(file.size() - 1000000)) { fileData.append(file.readAll()); } } else { fileData = file.readAll(); } file.close(); ClipController::setProducerProperty(QStringLiteral("kdenlive:file_size"), QString::number(file.size())); fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5); } break; } if (fileHash.isEmpty()) { qDebug() << "// WARNING EMPTY CLIP HASH: "; return QString(); } QString result = fileHash.toHex(); ClipController::setProducerProperty(QStringLiteral("kdenlive:file_hash"), result); return result; } double ProjectClip::getOriginalFps() const { return originalFps(); } bool ProjectClip::hasProxy() const { QString proxy = getProducerProperty(QStringLiteral("kdenlive:proxy")); return proxy.size() > 2; } void ProjectClip::setProperties(const QMap &properties, bool refreshPanel) { qDebug() << "// SETTING CLIP PROPERTIES: " << properties; QMapIterator i(properties); QMap passProperties; bool refreshAnalysis = false; bool reload = false; bool refreshOnly = true; if (properties.contains(QStringLiteral("templatetext"))) { m_description = properties.value(QStringLiteral("templatetext")); if (auto ptr = m_model.lock()) std::static_pointer_cast(ptr)->onItemUpdated(std::static_pointer_cast(shared_from_this()), AbstractProjectItem::ClipStatus); refreshPanel = true; } // Some properties also need to be passed to track producers QStringList timelineProperties{QStringLiteral("force_aspect_ratio"), QStringLiteral("set.force_full_luma"), QStringLiteral("full_luma"), QStringLiteral("threads"), QStringLiteral("force_colorspace"), QStringLiteral("force_tff"), QStringLiteral("force_progressive"), }; QStringList forceReloadProperties{QStringLiteral("autorotate"), QStringLiteral("templatetext"), QStringLiteral("resource"), QStringLiteral("force_fps"), QStringLiteral("set.test_image"), QStringLiteral("set.test_audio"), QStringLiteral("audio_index"), QStringLiteral("video_index")}; QStringList keys{QStringLiteral("luma_duration"), QStringLiteral("luma_file"), QStringLiteral("fade"), QStringLiteral("ttl"), QStringLiteral("softness"), QStringLiteral("crop"), QStringLiteral("animation") }; QVector updateRoles; while (i.hasNext()) { i.next(); setProducerProperty(i.key(), i.value()); if (m_clipType == ClipType::SlideShow && keys.contains(i.key())) { reload = true; refreshOnly = false; } if (i.key().startsWith(QLatin1String("kdenlive:clipanalysis"))) { refreshAnalysis = true; } if (timelineProperties.contains(i.key())) { passProperties.insert(i.key(), i.value()); } } if (properties.contains(QStringLiteral("kdenlive:proxy"))) { QString value = properties.value(QStringLiteral("kdenlive:proxy")); // If value is "-", that means user manually disabled proxy on this clip if (value.isEmpty() || value == QLatin1String("-")) { // reset proxy int id; if (pCore->jobManager()->hasPendingJob(clipId(), AbstractClipJob::PROXYJOB, &id)) { // The proxy clip is being created, abort pCore->jobManager()->discardJobs(clipId(), AbstractClipJob::PROXYJOB); } else { reload = true; refreshOnly = false; } } else { // A proxy was requested, make sure to keep original url setProducerProperty(QStringLiteral("kdenlive:originalurl"), url()); pCore->jobManager()->startJob({clipId()}, -1, QString()); } } else if (!reload) { const QList propKeys = properties.keys(); for (const QString &k : propKeys) { if (forceReloadProperties.contains(k)) { if (m_clipType != ClipType::Color) { reload = true; refreshOnly = false; } else { // Clip resource changed, update thumbnail reload = true; refreshPanel = true; updateRoles << TimelineModel::ResourceRole; } break; } } } if (!reload && (properties.contains(QStringLiteral("xmldata")) || !passProperties.isEmpty())) { reload = true; } if (refreshAnalysis) { emit refreshAnalysisPanel(); } if (properties.contains(QStringLiteral("length")) || properties.contains(QStringLiteral("kdenlive:duration"))) { m_duration = getStringDuration(); if (auto ptr = m_model.lock()) std::static_pointer_cast(ptr)->onItemUpdated(std::static_pointer_cast(shared_from_this()), AbstractProjectItem::DataDuration); refreshOnly = false; reload = true; } if (properties.contains(QStringLiteral("kdenlive:clipname"))) { m_name = properties.value(QStringLiteral("kdenlive:clipname")); refreshPanel = true; if (auto ptr = m_model.lock()) { std::static_pointer_cast(ptr)->onItemUpdated(std::static_pointer_cast(shared_from_this()), AbstractProjectItem::DataName); } // update timeline clips updateTimelineClips(QVector() << TimelineModel::NameRole); } if (refreshPanel) { // Some of the clip properties have changed through a command, update properties panel emit refreshPropertiesPanel(); } if (reload) { // producer has changed, refresh monitor and thumbnail if (hasProxy()) { pCore->jobManager()->discardJobs(clipId(), AbstractClipJob::PROXYJOB); setProducerProperty(QStringLiteral("_overwriteproxy"), 1); pCore->jobManager()->startJob({clipId()}, -1, QString()); } else { reloadProducer(refreshOnly, properties.contains(QStringLiteral("audio_index"))); } if (refreshOnly) { if (auto ptr = m_model.lock()) { emit std::static_pointer_cast(ptr)->refreshClip(m_binId); } } if (!updateRoles.isEmpty()) { updateTimelineClips(updateRoles); } } if (!passProperties.isEmpty()) { if (auto ptr = m_model.lock()) emit std::static_pointer_cast(ptr)->updateTimelineProducers(m_binId, passProperties); } } ClipPropertiesController *ProjectClip::buildProperties(QWidget *parent) { auto ptr = m_model.lock(); Q_ASSERT(ptr); auto *panel = new ClipPropertiesController(static_cast(this), parent); connect(this, &ProjectClip::refreshPropertiesPanel, panel, &ClipPropertiesController::slotReloadProperties); connect(this, &ProjectClip::refreshAnalysisPanel, panel, &ClipPropertiesController::slotFillAnalysisData); connect(panel, &ClipPropertiesController::requestProxy, [this](bool doProxy) { QList> clipList{std::static_pointer_cast(shared_from_this())}; pCore->currentDoc()->slotProxyCurrentItem(doProxy, clipList); }); connect(panel, &ClipPropertiesController::deleteProxy, this, &ProjectClip::deleteProxy); return panel; } void ProjectClip::deleteProxy() { // Disable proxy file QString proxy = getProducerProperty(QStringLiteral("kdenlive:proxy")); QList> clipList{std::static_pointer_cast(shared_from_this())}; pCore->currentDoc()->slotProxyCurrentItem(false, clipList); // Delete bool ok; QDir dir = pCore->currentDoc()->getCacheDir(CacheProxy, &ok); if (ok && proxy.length() > 2) { proxy = QFileInfo(proxy).fileName(); if (dir.exists(proxy)) { dir.remove(proxy); } } } void ProjectClip::updateParent(std::shared_ptr parent) { if (parent) { auto item = std::static_pointer_cast(parent); ClipController::setProducerProperty(QStringLiteral("kdenlive:folderid"), item->clipId()); } AbstractProjectItem::updateParent(parent); } bool ProjectClip::matches(const QString &condition) { // TODO Q_UNUSED(condition) return true; } bool ProjectClip::rename(const QString &name, int column) { QMap newProperites; QMap oldProperites; bool edited = false; switch (column) { case 0: if (m_name == name) { return false; } // Rename clip oldProperites.insert(QStringLiteral("kdenlive:clipname"), m_name); newProperites.insert(QStringLiteral("kdenlive:clipname"), name); m_name = name; edited = true; break; case 2: if (m_description == name) { return false; } // Rename clip if (m_clipType == ClipType::TextTemplate) { oldProperites.insert(QStringLiteral("templatetext"), m_description); newProperites.insert(QStringLiteral("templatetext"), name); } else { oldProperites.insert(QStringLiteral("kdenlive:description"), m_description); newProperites.insert(QStringLiteral("kdenlive:description"), name); } m_description = name; edited = true; break; } if (edited) { pCore->bin()->slotEditClipCommand(m_binId, oldProperites, newProperites); } return edited; } QVariant ProjectClip::getData(DataType type) const { switch (type) { case AbstractProjectItem::IconOverlay: return m_effectStack && m_effectStack->rowCount() > 0 ? QVariant("kdenlive-track_has_effect") : QVariant(); default: return AbstractProjectItem::getData(type); } } void ProjectClip::slotExtractImage(const QList &frames) { QMutexLocker lock(&m_thumbMutex); for (int i = 0; i < frames.count(); i++) { if (!m_requestedThumbs.contains(frames.at(i))) { m_requestedThumbs << frames.at(i); } } qSort(m_requestedThumbs); if (!m_thumbThread.isRunning()) { m_thumbThread = QtConcurrent::run(this, &ProjectClip::doExtractImage); } } void ProjectClip::doExtractImage() { // TODO refac: we can probably move that into a ThumbJob std::shared_ptr prod = thumbProducer(); if (prod == nullptr || !prod->is_valid()) { return; } int frameWidth = 150 * prod->profile()->dar() + 0.5; bool ok = false; auto ptr = m_model.lock(); Q_ASSERT(ptr); QDir thumbFolder = pCore->currentDoc()->getCacheDir(CacheThumbs, &ok); int max = prod->get_length(); while (!m_requestedThumbs.isEmpty()) { m_thumbMutex.lock(); int pos = m_requestedThumbs.takeFirst(); m_thumbMutex.unlock(); if (ok && thumbFolder.exists(hash() + QLatin1Char('#') + QString::number(pos) + QStringLiteral(".png"))) { emit thumbReady(pos, QImage(thumbFolder.absoluteFilePath(hash() + QLatin1Char('#') + QString::number(pos) + QStringLiteral(".png")))); continue; } if (pos >= max) { pos = max - 1; } const QString path = url() + QLatin1Char('_') + QString::number(pos); QImage img; if (ThumbnailCache::get()->hasThumbnail(clipId(), pos, true)) { img = ThumbnailCache::get()->getThumbnail(clipId(), pos, true); } if (!img.isNull()) { emit thumbReady(pos, img); continue; } prod->seek(pos); Mlt::Frame *frame = prod->get_frame(); frame->set("deinterlace_method", "onefield"); frame->set("top_field_first", -1); if (frame->is_valid()) { img = KThumb::getFrame(frame, frameWidth, 150, !qFuzzyCompare(prod->profile()->sar(), 1)); ThumbnailCache::get()->storeThumbnail(clipId(), pos, img, false); emit thumbReady(pos, img); } delete frame; } } int ProjectClip::audioChannels() const { if (!audioInfo()) { return 0; } return audioInfo()->channels(); } void ProjectClip::discardAudioThumb() { QString audioThumbPath = getAudioThumbPath(); if (!audioThumbPath.isEmpty()) { QFile::remove(audioThumbPath); } audioFrameCache.clear(); qCDebug(KDENLIVE_LOG) << "//////////////////// DISCARD AUIIO THUMBNS"; m_audioThumbCreated = false; refreshAudioInfo(); pCore->jobManager()->discardJobs(clipId(), AbstractClipJob::AUDIOTHUMBJOB); } const QString ProjectClip::getAudioThumbPath() { if (audioInfo() == nullptr) { return QString(); } int audioStream = audioInfo()->ffmpeg_audio_index(); QString clipHash = hash(); if (clipHash.isEmpty()) { return QString(); } bool ok = false; QDir thumbFolder = pCore->currentDoc()->getCacheDir(CacheAudio, &ok); if (!ok) { return QString(); } QString audioPath = thumbFolder.absoluteFilePath(clipHash); if (audioStream > 0) { audioPath.append(QLatin1Char('_') + QString::number(audioInfo()->audio_index())); } int roundedFps = (int)pCore->getCurrentFps(); audioPath.append(QStringLiteral("_%1_audio.png").arg(roundedFps)); return audioPath; } QStringList ProjectClip::updatedAnalysisData(const QString &name, const QString &data, int offset) { if (data.isEmpty()) { // Remove data return QStringList() << QString("kdenlive:clipanalysis." + name) << QString(); // m_controller->resetProperty("kdenlive:clipanalysis." + name); } QString current = getProducerProperty("kdenlive:clipanalysis." + name); if (!current.isEmpty()) { if (KMessageBox::questionYesNo(QApplication::activeWindow(), i18n("Clip already contains analysis data %1", name), QString(), KGuiItem(i18n("Merge")), KGuiItem(i18n("Add"))) == KMessageBox::Yes) { // Merge data auto &profile = pCore->getCurrentProfile(); Mlt::Geometry geometry(current.toUtf8().data(), duration().frames(profile->fps()), profile->width(), profile->height()); Mlt::Geometry newGeometry(data.toUtf8().data(), duration().frames(profile->fps()), profile->width(), profile->height()); Mlt::GeometryItem item; int pos = 0; while (newGeometry.next_key(&item, pos) == 0) { pos = item.frame(); item.frame(pos + offset); pos++; geometry.insert(item); } return QStringList() << QString("kdenlive:clipanalysis." + name) << geometry.serialise(); // m_controller->setProperty("kdenlive:clipanalysis." + name, geometry.serialise()); } // Add data with another name int i = 1; QString previous = getProducerProperty("kdenlive:clipanalysis." + name + QString::number(i)); while (!previous.isEmpty()) { ++i; previous = getProducerProperty("kdenlive:clipanalysis." + name + QString::number(i)); } return QStringList() << QString("kdenlive:clipanalysis." + name + QString::number(i)) << geometryWithOffset(data, offset); // m_controller->setProperty("kdenlive:clipanalysis." + name + QLatin1Char(' ') + QString::number(i), geometryWithOffset(data, offset)); } return QStringList() << QString("kdenlive:clipanalysis." + name) << geometryWithOffset(data, offset); // m_controller->setProperty("kdenlive:clipanalysis." + name, geometryWithOffset(data, offset)); } QMap ProjectClip::analysisData(bool withPrefix) { return getPropertiesFromPrefix(QStringLiteral("kdenlive:clipanalysis."), withPrefix); } const QString ProjectClip::geometryWithOffset(const QString &data, int offset) { if (offset == 0) { return data; } auto &profile = pCore->getCurrentProfile(); Mlt::Geometry geometry(data.toUtf8().data(), duration().frames(profile->fps()), profile->width(), profile->height()); Mlt::Geometry newgeometry(nullptr, duration().frames(profile->fps()), profile->width(), profile->height()); Mlt::GeometryItem item; int pos = 0; while (geometry.next_key(&item, pos) == 0) { pos = item.frame(); item.frame(pos + offset); pos++; newgeometry.insert(item); } return newgeometry.serialise(); } bool ProjectClip::isSplittable() const { return (m_clipType == ClipType::AV || m_clipType == ClipType::Playlist); } void ProjectClip::setBinEffectsEnabled(bool enabled) { ClipController::setBinEffectsEnabled(enabled); } void ProjectClip::registerService(std::weak_ptr timeline, int clipId, const std::shared_ptr &service, bool forceRegister) { if (!service->is_cut() || forceRegister) { int hasAudio = service->get_int("set.test_audio") == 0; int hasVideo = service->get_int("set.test_image") == 0; if (hasVideo && m_videoProducers.count(clipId) == 0) { // This is an undo producer, register it! m_videoProducers[clipId] = service; m_effectStack->addService(m_videoProducers[clipId]); } else if (hasAudio && m_audioProducers.count(clipId) == 0) { // This is an undo producer, register it! m_audioProducers[clipId] = service; m_effectStack->addService(m_audioProducers[clipId]); } } registerTimelineClip(std::move(timeline), clipId); } void ProjectClip::registerTimelineClip(std::weak_ptr timeline, int clipId) { Q_ASSERT(m_registeredClips.count(clipId) == 0); Q_ASSERT(!timeline.expired()); m_registeredClips[clipId] = std::move(timeline); setRefCount((uint)m_registeredClips.size()); } void ProjectClip::deregisterTimelineClip(int clipId) { qDebug() << " ** * DEREGISTERING TIMELINE CLIP: " << clipId; Q_ASSERT(m_registeredClips.count(clipId) > 0); m_registeredClips.erase(clipId); if (m_videoProducers.count(clipId) > 0) { m_effectStack->removeService(m_videoProducers[clipId]); m_videoProducers.erase(clipId); } if (m_audioProducers.count(clipId) > 0) { m_effectStack->removeService(m_audioProducers[clipId]); m_audioProducers.erase(clipId); } setRefCount((uint)m_registeredClips.size()); } QList ProjectClip::timelineInstances() const { QList ids; for (const auto &m_registeredClip : m_registeredClips) { ids.push_back(m_registeredClip.first); } return ids; } bool ProjectClip::selfSoftDelete(Fun &undo, Fun &redo) { 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) { // clip already deleted, was probably grouped with another one continue; } if (auto timeline = clip.second.lock()) { timeline->requestItemDeletion(clip.first, undo, redo); } else { qDebug() << "Error while deleting clip: timeline unavailable"; Q_ASSERT(false); return false; } } return AbstractProjectItem::selfSoftDelete(undo, redo); } bool ProjectClip::isIncludedInTimeline() { return m_registeredClips.size() > 0; } void ProjectClip::updateChildProducers() { // TODO refac: the effect should be managed by an effectstack on the master /* // pass effect stack on all child producers QMutexLocker locker(&m_producerMutex); for (const auto &clip : m_timelineProducers) { if (auto producer = clip.second) { Clip clp(producer->parent()); clp.deleteEffects(); clp.replaceEffects(*m_masterProducer); } } */ } void ProjectClip::replaceInTimeline() { for (const auto &clip : m_registeredClips) { if (auto timeline = clip.second.lock()) { timeline->requestClipReload(clip.first); } else { qDebug() << "Error while reloading clip: timeline unavailable"; Q_ASSERT(false); } } } void ProjectClip::updateTimelineClips(const QVector &roles) { for (const auto &clip : m_registeredClips) { if (auto timeline = clip.second.lock()) { timeline->requestClipUpdate(clip.first, roles); } else { qDebug() << "Error while reloading clip thumb: timeline unavailable"; Q_ASSERT(false); return; } } } diff --git a/src/mltcontroller/clipcontroller.cpp b/src/mltcontroller/clipcontroller.cpp index 91fc8b857..317304553 100644 --- a/src/mltcontroller/clipcontroller.cpp +++ b/src/mltcontroller/clipcontroller.cpp @@ -1,890 +1,890 @@ /* Copyright (C) 2012 Till Theato Copyright (C) 2014 Jean-Baptiste Mardelle 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 "clipcontroller.h" #include "bin/model/markerlistmodel.hpp" #include "doc/docundostack.hpp" #include "doc/kdenlivedoc.h" #include "effects/effectstack/model/effectstackmodel.hpp" #include "kdenlivesettings.h" #include "lib/audio/audioStreamInfo.h" #include "profiles/profilemodel.hpp" #include "core.h" #include "kdenlive_debug.h" #include #include #include std::shared_ptr ClipController::mediaUnavailable; ClipController::ClipController(const QString &clipId, const std::shared_ptr &producer) : selectedEffectIndex(1) , m_audioThumbCreated(false) , m_masterProducer(producer) , m_properties(producer ? new Mlt::Properties(producer->get_properties()) : nullptr) , m_usesProxy(false) , m_audioInfo(nullptr) , m_videoIndex(0) , m_clipType(ClipType::Unknown) , m_hasLimitedDuration(true) , m_effectStack(producer ? EffectStackModel::construct(producer, {ObjectType::BinClip, clipId.toInt()}, pCore->undoStack()) : nullptr) , m_hasAudio(false) , m_hasVideo(false) , m_controllerBinId(clipId) { if (m_masterProducer && !m_masterProducer->is_valid()) { qCDebug(KDENLIVE_LOG) << "// WARNING, USING INVALID PRODUCER"; return; } if (m_masterProducer) { checkAudioVideo(); } if (m_properties) { setProducerProperty(QStringLiteral("kdenlive:id"), m_controllerBinId); m_service = m_properties->get("mlt_service"); QString proxy = m_properties->get("kdenlive:proxy"); QString path = m_properties->get("resource"); if (proxy.length() > 2) { // This is a proxy producer, read original url from kdenlive property path = m_properties->get("kdenlive:originalurl"); if (QFileInfo(path).isRelative()) { path.prepend(pCore->currentDoc()->documentRoot()); } m_usesProxy = true; } else if (m_service != QLatin1String("color") && m_service != QLatin1String("colour") && !path.isEmpty() && QFileInfo(path).isRelative() && path != QLatin1String("")) { path.prepend(pCore->currentDoc()->documentRoot()); } m_path = path.isEmpty() ? QString() : QFileInfo(path).absoluteFilePath(); getInfoForProducer(); } else { m_producerLock.lock(); } } ClipController::~ClipController() { delete m_properties; m_masterProducer.reset(); } const QString ClipController::binId() const { return m_controllerBinId; } const std::unique_ptr &ClipController::audioInfo() const { return m_audioInfo; } void ClipController::addMasterProducer(const std::shared_ptr &producer) { qDebug() << "################### ClipController::addmasterproducer"; QString documentRoot = pCore->currentDoc()->documentRoot(); m_masterProducer = producer; m_properties = new Mlt::Properties(m_masterProducer->get_properties()); int id = m_controllerBinId.toInt(); m_effectStack = EffectStackModel::construct(producer, {ObjectType::BinClip, id}, pCore->undoStack()); if (!m_masterProducer->is_valid()) { m_masterProducer = ClipController::mediaUnavailable; m_producerLock.unlock(); qCDebug(KDENLIVE_LOG) << "// WARNING, USING INVALID PRODUCER"; } else { checkAudioVideo(); m_producerLock.unlock(); QString proxy = m_properties->get("kdenlive:proxy"); m_service = m_properties->get("mlt_service"); QString path = m_properties->get("resource"); m_usesProxy = false; if (proxy.length() > 2) { // This is a proxy producer, read original url from kdenlive property path = m_properties->get("kdenlive:originalurl"); if (QFileInfo(path).isRelative()) { path.prepend(documentRoot); } m_usesProxy = true; } else if (m_service != QLatin1String("color") && m_service != QLatin1String("colour") && !path.isEmpty() && QFileInfo(path).isRelative()) { path.prepend(documentRoot); } m_path = path.isEmpty() ? QString() : QFileInfo(path).absoluteFilePath(); getInfoForProducer(); emitProducerChanged(m_controllerBinId, producer); setProducerProperty(QStringLiteral("kdenlive:id"), m_controllerBinId); } connectEffectStack(); } namespace { QString producerXml(const std::shared_ptr &producer, bool includeMeta) { - Mlt::Consumer c(producer->get_profile(), "xml", "string"); + Mlt::Consumer c(*producer->profile(), "xml", "string"); Mlt::Service s(producer->get_service()); if (!s.is_valid()) { return QString(); } int ignore = s.get_int("ignore_points"); if (ignore != 0) { s.set("ignore_points", 0); } c.set("time_format", "frames"); if (!includeMeta) { c.set("no_meta", 1); } c.set("store", "kdenlive"); c.set("no_root", 1); c.set("root", "/"); c.connect(s); c.start(); if (ignore != 0) { s.set("ignore_points", ignore); } return QString::fromUtf8(c.get("string")); } } // namespace void ClipController::getProducerXML(QDomDocument &document, bool includeMeta) { // TODO refac this is a probable duplicate with Clip::xml if (m_masterProducer) { QString xml = producerXml(m_masterProducer, includeMeta); document.setContent(xml); } else { qCDebug(KDENLIVE_LOG) << " + + ++ NO MASTER PROD"; } } void ClipController::getInfoForProducer() { date = QFileInfo(m_path).lastModified(); m_videoIndex = -1; int audioIndex = -1; // special case: playlist with a proxy clip have to be detected separately if (m_usesProxy && m_path.endsWith(QStringLiteral(".mlt"))) { m_clipType = ClipType::Playlist; } else if (m_service == QLatin1String("avformat") || m_service == QLatin1String("avformat-novalidate")) { audioIndex = getProducerIntProperty(QStringLiteral("audio_index")); m_videoIndex = getProducerIntProperty(QStringLiteral("video_index")); if (m_videoIndex == -1) { m_clipType = ClipType::Audio; } else { if (audioIndex == -1) { m_clipType = ClipType::Video; } else { m_clipType = ClipType::AV; } if (m_service == QLatin1String("avformat")) { m_properties->set("mlt_service", "avformat-novalidate"); } } } else if (m_service == QLatin1String("qimage") || m_service == QLatin1String("pixbuf")) { if (m_path.contains(QLatin1Char('%')) || m_path.contains(QStringLiteral("/.all."))) { m_clipType = ClipType::SlideShow; m_hasLimitedDuration = true; } else { m_clipType = ClipType::Image; m_hasLimitedDuration = false; } } else if (m_service == QLatin1String("colour") || m_service == QLatin1String("color")) { m_clipType = ClipType::Color; m_hasLimitedDuration = false; } else if (m_service == QLatin1String("kdenlivetitle")) { if (!m_path.isEmpty()) { m_clipType = ClipType::TextTemplate; } else { m_clipType = ClipType::Text; } m_hasLimitedDuration = false; } else if (m_service == QLatin1String("xml") || m_service == QLatin1String("consumer")) { m_clipType = ClipType::Playlist; } else if (m_service == QLatin1String("webvfx")) { m_clipType = ClipType::WebVfx; } else if (m_service == QLatin1String("qtext")) { m_clipType = ClipType::QText; } else if (m_service == QLatin1String("blipflash")) { // Mostly used for testing m_clipType = ClipType::AV; m_hasLimitedDuration = true; } else { m_clipType = ClipType::Unknown; } if (audioIndex > -1 || m_clipType == ClipType::Playlist) { m_audioInfo = std::make_unique(m_masterProducer, audioIndex); } if (!m_hasLimitedDuration) { int playtime = m_masterProducer->time_to_frames(m_masterProducer->get("kdenlive:duration")); if (playtime <= 0) { // Fix clips having missing kdenlive:duration m_masterProducer->set("kdenlive:duration", m_masterProducer->frames_to_time(m_masterProducer->get_playtime(), mlt_time_clock)); m_masterProducer->set("out", m_masterProducer->frames_to_time(m_masterProducer->get_length() - 1, mlt_time_clock)); } } } bool ClipController::hasLimitedDuration() const { return m_hasLimitedDuration; } void ClipController::forceLimitedDuration() { m_hasLimitedDuration = true; } std::shared_ptr ClipController::originalProducer() { QMutexLocker lock(&m_producerLock); return m_masterProducer; } Mlt::Producer *ClipController::masterProducer() { return new Mlt::Producer(*m_masterProducer); } bool ClipController::isValid() { if (m_masterProducer == nullptr) { return false; } return m_masterProducer->is_valid(); } // static const char *ClipController::getPassPropertiesList(bool passLength) { if (!passLength) { return "kdenlive:proxy,kdenlive:originalurl,force_aspect_num,force_aspect_den,force_aspect_ratio,force_fps,force_progressive,force_tff,threads,force_" "colorspace,set.force_full_luma,file_hash,autorotate,xmldata,video_index,audio_index,set.test_image,set.test_audio"; } return "kdenlive:proxy,kdenlive:originalurl,force_aspect_num,force_aspect_den,force_aspect_ratio,force_fps,force_progressive,force_tff,threads,force_" "colorspace,set.force_full_luma,templatetext,file_hash,autorotate,xmldata,length,video_index,audio_index,set.test_image,set.test_audio"; } QMap ClipController::getPropertiesFromPrefix(const QString &prefix, bool withPrefix) { Mlt::Properties subProperties; subProperties.pass_values(*m_properties, prefix.toUtf8().constData()); QMap subclipsData; for (int i = 0; i < subProperties.count(); i++) { subclipsData.insert(withPrefix ? QString(prefix + subProperties.get_name(i)) : subProperties.get_name(i), subProperties.get(i)); } return subclipsData; } void ClipController::updateProducer(const std::shared_ptr &producer) { qDebug() << "################### ClipController::updateProducer"; // TODO replace all track producers if (!m_properties) { // producer has not been initialized return addMasterProducer(producer); } Mlt::Properties passProperties; // Keep track of necessary properties QString proxy = producer->get("kdenlive:proxy"); if (proxy.length() > 2) { // This is a proxy producer, read original url from kdenlive property m_usesProxy = true; } else { m_usesProxy = false; } // This is necessary as some properties like set.test_audio are reset on producer creation const char *passList = getPassPropertiesList(m_usesProxy); passProperties.pass_list(*m_properties, passList); delete m_properties; *m_masterProducer = producer.get(); checkAudioVideo(); m_properties = new Mlt::Properties(m_masterProducer->get_properties()); // Pass properties from previous producer m_properties->pass_list(passProperties, passList); if (!m_masterProducer->is_valid()) { qCDebug(KDENLIVE_LOG) << "// WARNING, USING INVALID PRODUCER"; } else { m_effectStack->resetService(m_masterProducer); emitProducerChanged(m_controllerBinId, producer); // URL and name should not be updated otherwise when proxying a clip we cannot find back the original url /*m_url = QUrl::fromLocalFile(m_masterProducer->get("resource")); if (m_url.isValid()) { m_name = m_url.fileName(); } */ } m_producerLock.unlock(); qDebug() << "// replace finished: " << binId() << " : " << m_masterProducer->get("resource"); } const QString ClipController::getStringDuration() { if (m_masterProducer) { int playtime = m_masterProducer->time_to_frames(m_masterProducer->get("kdenlive:duration")); if (playtime > 0) { return QString(m_properties->frames_to_time(playtime, mlt_time_smpte_df)); } return m_masterProducer->get_length_time(mlt_time_smpte_df); } return i18n("Unknown"); } int ClipController::getProducerDuration() const { if (m_masterProducer) { int playtime = m_masterProducer->time_to_frames(m_masterProducer->get("kdenlive:duration")); if (playtime <= 0) { return playtime = m_masterProducer->get_length(); } return playtime; } return -1; } char *ClipController::framesToTime(int frames) const { if (m_masterProducer) { return m_masterProducer->frames_to_time(frames, mlt_time_clock); } return nullptr; } GenTime ClipController::getPlaytime() const { if (!m_masterProducer || !m_masterProducer->is_valid()) { return GenTime(); } double fps = pCore->getCurrentFps(); if (!m_hasLimitedDuration) { int playtime = m_masterProducer->time_to_frames(m_masterProducer->get("kdenlive:duration")); return GenTime(playtime == 0 ? m_masterProducer->get_playtime() : playtime, fps); } return {m_masterProducer->get_playtime(), fps}; } int ClipController::getFramePlaytime() const { if (!m_masterProducer || !m_masterProducer->is_valid()) { return 0; } if (!m_hasLimitedDuration) { int playtime = m_masterProducer->time_to_frames(m_masterProducer->get("kdenlive:duration")); return playtime == 0 ? m_masterProducer->get_playtime() : playtime; } return m_masterProducer->get_playtime(); } QString ClipController::getProducerProperty(const QString &name) const { if (!m_properties) { return QString(); } if (m_usesProxy && name.startsWith(QLatin1String("meta."))) { QString correctedName = QStringLiteral("kdenlive:") + name; return m_properties->get(correctedName.toUtf8().constData()); } return QString(m_properties->get(name.toUtf8().constData())); } int ClipController::getProducerIntProperty(const QString &name) const { if (!m_properties) { return 0; } if (m_usesProxy && name.startsWith(QLatin1String("meta."))) { QString correctedName = QStringLiteral("kdenlive:") + name; return m_properties->get_int(correctedName.toUtf8().constData()); } return m_properties->get_int(name.toUtf8().constData()); } qint64 ClipController::getProducerInt64Property(const QString &name) const { if (!m_properties) { return 0; } return m_properties->get_int64(name.toUtf8().constData()); } double ClipController::getProducerDoubleProperty(const QString &name) const { if (!m_properties) { return 0; } return m_properties->get_double(name.toUtf8().constData()); } QColor ClipController::getProducerColorProperty(const QString &name) const { if (!m_properties) { return {}; } mlt_color color = m_properties->get_color(name.toUtf8().constData()); return QColor::fromRgb(color.r, color.g, color.b); } QMap ClipController::currentProperties(const QMap &props) { QMap currentProps; QMap::const_iterator i = props.constBegin(); while (i != props.constEnd()) { currentProps.insert(i.key(), getProducerProperty(i.key())); ++i; } return currentProps; } double ClipController::originalFps() const { if (!m_properties) { return 0; } QString propertyName = QStringLiteral("meta.media.%1.stream.frame_rate").arg(m_videoIndex); return m_properties->get_double(propertyName.toUtf8().constData()); } QString ClipController::videoCodecProperty(const QString &property) const { if (!m_properties) { return QString(); } QString propertyName = QStringLiteral("meta.media.%1.codec.%2").arg(m_videoIndex).arg(property); return m_properties->get(propertyName.toUtf8().constData()); } const QString ClipController::codec(bool audioCodec) const { if ((m_properties == nullptr) || (m_clipType != ClipType::AV && m_clipType != ClipType::Video && m_clipType != ClipType::Audio)) { return QString(); } QString propertyName = QStringLiteral("meta.media.%1.codec.name").arg(audioCodec ? m_properties->get_int("audio_index") : m_videoIndex); return m_properties->get(propertyName.toUtf8().constData()); } const QString ClipController::clipUrl() const { return m_path; } QString ClipController::clipName() const { QString name = getProducerProperty(QStringLiteral("kdenlive:clipname")); if (!name.isEmpty()) { return name; } return QFileInfo(m_path).fileName(); } QString ClipController::description() const { if (m_clipType == ClipType::TextTemplate) { QString name = getProducerProperty(QStringLiteral("templatetext")); return name; } QString name = getProducerProperty(QStringLiteral("kdenlive:description")); if (!name.isEmpty()) { return name; } return getProducerProperty(QStringLiteral("meta.attr.comment.markup")); } QString ClipController::serviceName() const { return m_service; } void ClipController::setProducerProperty(const QString &name, int value) { if (!m_masterProducer) return; // TODO: also set property on all track producers m_masterProducer->parent().set(name.toUtf8().constData(), value); } void ClipController::setProducerProperty(const QString &name, double value) { if (!m_masterProducer) return; // TODO: also set property on all track producers m_masterProducer->parent().set(name.toUtf8().constData(), value); } void ClipController::setProducerProperty(const QString &name, const QString &value) { if (!m_masterProducer) return; // TODO: also set property on all track producers if (value.isEmpty()) { m_masterProducer->parent().set(name.toUtf8().constData(), (char *)nullptr); } else { m_masterProducer->parent().set(name.toUtf8().constData(), value.toUtf8().constData()); } } void ClipController::resetProducerProperty(const QString &name) { // TODO: also set property on all track producers m_masterProducer->parent().set(name.toUtf8().constData(), (char *)nullptr); } ClipType::ProducerType ClipController::clipType() const { return m_clipType; } const QSize ClipController::getFrameSize() const { if (m_masterProducer == nullptr) { return QSize(); } int width = m_masterProducer->get_int("meta.media.width"); if (width == 0) { width = m_masterProducer->get_int("width"); } int height = m_masterProducer->get_int("meta.media.height"); if (height == 0) { height = m_masterProducer->get_int("height"); } return QSize(width, height); } bool ClipController::hasAudio() const { return m_hasAudio; } void ClipController::checkAudioVideo() { m_masterProducer->seek(0); if (m_masterProducer->get_int("_placeholder") == 1 || m_masterProducer->get("text") == QLatin1String("INVALID")) { // This is a placeholder file, try to guess from its properties QString orig_service = m_masterProducer->get("kdenlive:orig_service"); if (orig_service.startsWith(QStringLiteral("avformat")) || (m_masterProducer->get_int("audio_index") + m_masterProducer->get_int("video_index") > 0)) { m_hasAudio = m_masterProducer->get_int("audio_index") >= 0; m_hasVideo = m_masterProducer->get_int("video_index") >= 0; } else { // Assume image or text producer m_hasAudio = false; m_hasVideo = true; } return; } QScopedPointer frame(m_masterProducer->get_frame()); // test_audio returns 1 if there is NO audio (strange but true at the time this code is written) m_hasAudio = frame->get_int("test_audio") == 0; m_hasVideo = frame->get_int("test_image") == 0; } bool ClipController::hasVideo() const { return m_hasVideo; } PlaylistState::ClipState ClipController::defaultState() const { if (hasVideo()) { return PlaylistState::VideoOnly; } if (hasAudio()) { return PlaylistState::AudioOnly; } return PlaylistState::Disabled; } QPixmap ClipController::pixmap(int framePosition, int width, int height) { // TODO refac this should use the new thumb infrastructure m_masterProducer->seek(framePosition); Mlt::Frame *frame = m_masterProducer->get_frame(); if (frame == nullptr || !frame->is_valid()) { QPixmap p(width, height); p.fill(QColor(Qt::red).rgb()); return p; } frame->set("rescale.interp", "bilinear"); frame->set("deinterlace_method", "onefield"); frame->set("top_field_first", -1); if (width == 0) { width = m_masterProducer->get_int("meta.media.width"); if (width == 0) { width = m_masterProducer->get_int("width"); } } if (height == 0) { height = m_masterProducer->get_int("meta.media.height"); if (height == 0) { height = m_masterProducer->get_int("height"); } } // int ow = frameWidth; // int oh = height; mlt_image_format format = mlt_image_rgb24a; width += width % 2; height += height % 2; const uchar *imagedata = frame->get_image(format, width, height); QImage image(imagedata, width, height, QImage::Format_RGBA8888); QPixmap pixmap; pixmap.convertFromImage(image); delete frame; return pixmap; } void ClipController::setZone(const QPoint &zone) { setProducerProperty(QStringLiteral("kdenlive:zone_in"), zone.x()); setProducerProperty(QStringLiteral("kdenlive:zone_out"), zone.y()); } QPoint ClipController::zone() const { int in = getProducerIntProperty(QStringLiteral("kdenlive:zone_in")); int max = getFramePlaytime() - 1; int out = qMin(getProducerIntProperty(QStringLiteral("kdenlive:zone_out")), max); if (out <= in) { out = max; } QPoint zone(in, out); return zone; } const QString ClipController::getClipHash() const { return getProducerProperty(QStringLiteral("kdenlive:file_hash")); } Mlt::Properties &ClipController::properties() { return *m_properties; } void ClipController::mirrorOriginalProperties(Mlt::Properties &props) { if (m_usesProxy && QFileInfo(m_properties->get("resource")).fileName() == QFileInfo(m_properties->get("kdenlive:proxy")).fileName()) { // We have a proxy clip, load original source producer std::shared_ptr prod = std::make_shared(pCore->getCurrentProfile()->profile(), nullptr, m_path.toUtf8().constData()); // Get frame to make sure we retrieve all original props std::shared_ptr fr(prod->get_frame()); if (!prod->is_valid()) { return; } Mlt::Properties sourceProps(prod->get_properties()); props.inherit(sourceProps); } else { if (m_clipType == ClipType::AV || m_clipType == ClipType::Video || m_clipType == ClipType::Audio) { // Make sure that a frame / image was fetched to initialize all meta properties QString progressive = m_properties->get("meta.media.progressive"); if (progressive.isEmpty()) { // Fetch a frame to initialize required properties QScopedPointer tmpProd(nullptr); if (KdenliveSettings::gpu_accel()) { QString service = m_masterProducer->get("mlt_service"); tmpProd.reset(new Mlt::Producer(pCore->getCurrentProfile()->profile(), service.toUtf8().constData(), m_masterProducer->get("resource"))); } std::shared_ptr fr(tmpProd ? tmpProd->get_frame() : m_masterProducer->get_frame()); mlt_image_format format = mlt_image_none; int width = 0; int height = 0; fr->get_image(format, width, height); } } props.inherit(*m_properties); } } void ClipController::addEffect(QDomElement &xml) { Q_UNUSED(xml) // TODO refac: this must be rewritten /* QMutexLocker lock(&m_effectMutex); Mlt::Service service = m_masterProducer->parent(); ItemInfo info; info.cropStart = GenTime(); info.cropDuration = getPlaytime(); EffectsList eff = effectList(); EffectsController::initEffect(info, eff, getProducerProperty(QStringLiteral("kdenlive:proxy")), xml); // Add effect to list and setup a kdenlive_ix value int kdenlive_ix = 0; for (int i = 0; i < service.filter_count(); ++i) { QScopedPointer effect(service.filter(i)); int ix = effect->get_int("kdenlive_ix"); if (ix > kdenlive_ix) { kdenlive_ix = ix; } } kdenlive_ix++; xml.setAttribute(QStringLiteral("kdenlive_ix"), kdenlive_ix); EffectsParameterList params = EffectsController::getEffectArgs(xml); EffectManager effect(service); effect.addEffect(params, getPlaytime().frames(pCore->getCurrentFps())); if (auto ptr = m_binController.lock()) ptr->updateTrackProducer(m_controllerBinId); */ } void ClipController::removeEffect(int effectIndex, bool delayRefresh) { Q_UNUSED(effectIndex) Q_UNUSED(delayRefresh) // TODO refac: this must be rewritten /* QMutexLocker lock(&m_effectMutex); Mlt::Service service(m_masterProducer->parent()); EffectManager effect(service); effect.removeEffect(effectIndex, true); if (!delayRefresh) { if (auto ptr = m_binController.lock()) ptr->updateTrackProducer(m_controllerBinId); } */ } void ClipController::moveEffect(int oldPos, int newPos) { Q_UNUSED(oldPos) Q_UNUSED(newPos) // TODO refac: this must be rewritten /* QMutexLocker lock(&m_effectMutex); Mlt::Service service(m_masterProducer->parent()); EffectManager effect(service); effect.moveEffect(oldPos, newPos); */ } int ClipController::effectsCount() { int count = 0; Mlt::Service service(m_masterProducer->parent()); for (int ix = 0; ix < service.filter_count(); ++ix) { QScopedPointer effect(service.filter(ix)); QString id = effect->get("kdenlive_id"); if (!id.isEmpty()) { count++; } } return count; } void ClipController::changeEffectState(const QList &indexes, bool disable) { Q_UNUSED(indexes) Q_UNUSED(disable) // TODO refac : this must be rewritten /* Mlt::Service service = m_masterProducer->parent(); for (int i = 0; i < service.filter_count(); ++i) { QScopedPointer effect(service.filter(i)); if ((effect != nullptr) && effect->is_valid() && indexes.contains(effect->get_int("kdenlive_ix"))) { effect->set("disable", (int)disable); } } if (auto ptr = m_binController.lock()) ptr->updateTrackProducer(m_controllerBinId); */ } void ClipController::updateEffect(const QDomElement &e, int ix) { Q_UNUSED(e) Q_UNUSED(ix) // TODO refac : this must be rewritten /* QString tag = e.attribute(QStringLiteral("id")); if (tag == QLatin1String("autotrack_rectangle") || tag.startsWith(QLatin1String("ladspa")) || tag == QLatin1String("sox")) { // this filters cannot be edited, remove and re-add it removeEffect(ix, true); QDomElement clone = e.cloneNode().toElement(); addEffect(clone); return; } EffectsParameterList params = EffectsController::getEffectArgs(e); Mlt::Service service = m_masterProducer->parent(); for (int i = 0; i < service.filter_count(); ++i) { QScopedPointer effect(service.filter(i)); if (!effect || !effect->is_valid() || effect->get_int("kdenlive_ix") != ix) { continue; } service.lock(); QString prefix; QString ser = effect->get("mlt_service"); if (ser == QLatin1String("region")) { prefix = QStringLiteral("filter0."); } for (int j = 0; j < params.count(); ++j) { effect->set((prefix + params.at(j).name()).toUtf8().constData(), params.at(j).value().toUtf8().constData()); // qCDebug(KDENLIVE_LOG)<updateTrackProducer(m_controllerBinId); // slotRefreshTracks(); */ } bool ClipController::hasEffects() const { return m_effectStack->rowCount() > 0; } void ClipController::setBinEffectsEnabled(bool enabled) { m_effectStack->setEffectStackEnabled(enabled); } void ClipController::saveZone(QPoint zone, const QDir &dir) { QString path = QString(clipName() + QLatin1Char('_') + QString::number(zone.x()) + QStringLiteral(".mlt")); if (dir.exists(path)) { // TODO ask for overwrite } Mlt::Consumer xmlConsumer(pCore->getCurrentProfile()->profile(), ("xml:" + dir.absoluteFilePath(path)).toUtf8().constData()); xmlConsumer.set("terminate_on_pause", 1); Mlt::Producer prod(m_masterProducer->get_producer()); Mlt::Producer *prod2 = prod.cut(zone.x(), zone.y()); Mlt::Playlist list(pCore->getCurrentProfile()->profile()); list.insert_at(0, *prod2, 0); // list.set("title", desc.toUtf8().constData()); xmlConsumer.connect(list); xmlConsumer.run(); delete prod2; } std::shared_ptr ClipController::getEffectStack() const { return m_effectStack; } bool ClipController::addEffect(const QString &effectId) { return m_effectStack->appendEffect(effectId, true); } bool ClipController::copyEffect(const std::shared_ptr &stackModel, int rowId) { m_effectStack->copyEffect(stackModel->getEffectStackRow(rowId), !m_hasAudio ? PlaylistState::VideoOnly : !m_hasVideo ? PlaylistState::AudioOnly : PlaylistState::Disabled); return true; } std::shared_ptr ClipController::getMarkerModel() const { return m_markerModel; } void ClipController::refreshAudioInfo() { if (m_audioInfo && m_masterProducer) { m_audioInfo->setAudioIndex(m_masterProducer, m_properties->get_int("audio_index")); } }