File indexing completed on 2024-04-21 04:51:44

0001 /*
0002 SPDX-FileCopyrightText: 2012 Till Theato <root@ttill.de>
0003 SPDX-FileCopyrightText: 2014 Jean-Baptiste Mardelle <jb@kdenlive.org>
0004 This file is part of Kdenlive. See www.kdenlive.org.
0005 
0006 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0007 */
0008 
0009 #include "clipcontroller.h"
0010 #include "bin/clipcreator.hpp"
0011 #include "bin/model/markerlistmodel.hpp"
0012 #include "bin/model/markersortmodel.h"
0013 #include "doc/docundostack.hpp"
0014 #include "doc/kdenlivedoc.h"
0015 #include "doc/kthumb.h"
0016 #include "effects/effectstack/model/effectstackmodel.hpp"
0017 #include "kdenlivesettings.h"
0018 #include "lib/audio/audioStreamInfo.h"
0019 #include "profiles/profilemodel.hpp"
0020 
0021 #include "core.h"
0022 #include "kdenlive_debug.h"
0023 #include <KLocalizedString>
0024 #include <QApplication>
0025 #include <QFileInfo>
0026 #include <QPixmap>
0027 
0028 std::shared_ptr<Mlt::Producer> ClipController::mediaUnavailable;
0029 
0030 ClipController::ClipController(const QString &clipId, const std::shared_ptr<Mlt::Producer> &producer)
0031     : selectedEffectIndex(1)
0032     , m_audioThumbCreated(false)
0033     , m_producerLock(QReadWriteLock::Recursive)
0034     , m_masterProducer(std::move(producer))
0035     , m_properties(m_masterProducer ? new Mlt::Properties(m_masterProducer->get_properties()) : nullptr)
0036     , m_usesProxy(false)
0037     , m_audioInfo(nullptr)
0038     , m_videoIndex(0)
0039     , m_clipType(ClipType::Unknown)
0040     , m_forceLimitedDuration(false)
0041     , m_hasMultipleVideoStreams(false)
0042     , m_effectStack(m_masterProducer
0043                         ? EffectStackModel::construct(m_masterProducer, ObjectId(KdenliveObjectType::BinClip, clipId.toInt(), QUuid()), pCore->undoStack())
0044                         : nullptr)
0045     , m_hasAudio(false)
0046     , m_hasVideo(false)
0047     , m_controllerBinId(clipId)
0048 {
0049     if (m_masterProducer && !m_masterProducer->is_valid()) {
0050         qCDebug(KDENLIVE_LOG) << "// WARNING, USING INVALID PRODUCER";
0051         return;
0052     }
0053     if (m_properties) {
0054         m_hasMultipleVideoStreams = m_properties->property_exists("kdenlive:multistreams");
0055         setProducerProperty(QStringLiteral("kdenlive:id"), m_controllerBinId);
0056         getInfoForProducer();
0057         checkAudioVideo();
0058     } else {
0059         m_producerLock.lockForWrite();
0060     }
0061 }
0062 
0063 ClipController::~ClipController()
0064 {
0065     delete m_properties;
0066     Q_ASSERT(m_masterProducer.use_count() <= 1);
0067     m_masterProducer.reset();
0068 }
0069 
0070 const QString ClipController::binId() const
0071 {
0072     return m_controllerBinId;
0073 }
0074 
0075 const std::unique_ptr<AudioStreamInfo> &ClipController::audioInfo() const
0076 {
0077     return m_audioInfo;
0078 }
0079 
0080 void ClipController::addMasterProducer(const std::shared_ptr<Mlt::Producer> &producer)
0081 {
0082     qDebug() << "################### ClipController::addmasterproducer FOR: " << m_controllerBinId;
0083     if (QString(producer->get("mlt_service")).contains(QLatin1String("avformat")) && producer->type() == mlt_service_producer_type) {
0084         std::shared_ptr<Mlt::Chain> chain(new Mlt::Chain(pCore->getProjectProfile()));
0085         chain->set_source(*producer.get());
0086         m_masterProducer = std::move(chain);
0087     } else {
0088         m_masterProducer = std::move(producer);
0089     }
0090     m_properties = new Mlt::Properties(m_masterProducer->get_properties());
0091     m_producerLock.unlock();
0092     // Pass temporary properties
0093     QMapIterator<QString, QVariant> i(m_tempProps);
0094     while (i.hasNext()) {
0095         i.next();
0096 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0097         switch (i.value().type()) {
0098 #else
0099         switch (i.value().typeId()) {
0100 #endif
0101         case QVariant::Int:
0102             setProducerProperty(i.key(), i.value().toInt());
0103             break;
0104         case QVariant::Double:
0105             setProducerProperty(i.key(), i.value().toDouble());
0106             break;
0107         default:
0108             setProducerProperty(i.key(), i.value().toString());
0109             break;
0110         }
0111     }
0112     m_tempProps.clear();
0113     int id = m_controllerBinId.toInt();
0114     m_effectStack = EffectStackModel::construct(m_masterProducer, ObjectId(KdenliveObjectType::BinClip, id, QUuid()), pCore->undoStack());
0115     if (!m_masterProducer->is_valid()) {
0116         m_masterProducer = ClipController::mediaUnavailable;
0117         qCDebug(KDENLIVE_LOG) << "// WARNING, USING INVALID PRODUCER";
0118     } else {
0119         setProducerProperty(QStringLiteral("kdenlive:id"), m_controllerBinId);
0120         getInfoForProducer();
0121         checkAudioVideo();
0122         if (!m_hasMultipleVideoStreams && m_service.startsWith(QLatin1String("avformat")) && (m_clipType == ClipType::AV || m_clipType == ClipType::Video)) {
0123             // Check if clip has multiple video streams
0124             QList<int> videoStreams;
0125             QList<int> audioStreams;
0126             int aStreams = m_properties->get_int("meta.media.nb_streams");
0127             for (int ix = 0; ix < aStreams; ++ix) {
0128                 char property[200];
0129                 snprintf(property, sizeof(property), "meta.media.%d.stream.type", ix);
0130                 QString type = m_properties->get(property);
0131                 if (type == QLatin1String("video")) {
0132                     QString key = QString("meta.media.%1.codec.name").arg(ix);
0133                     QString codec_name = m_properties->get(key.toLatin1().constData());
0134                     if (codec_name == QLatin1String("png")) {
0135                         // This is a cover image, skip
0136                         qDebug() << "=== FOUND PNG COVER ART STREAM: " << ix;
0137                         continue;
0138                     }
0139                     if (codec_name == QLatin1String("mjpeg")) {
0140                         key = QString("meta.media.%1.stream.frame_rate").arg(ix);
0141                         QString fps = m_properties->get(key.toLatin1().constData());
0142                         if (fps.isEmpty()) {
0143                             key = QString("meta.media.%1.codec.frame_rate").arg(ix);
0144                             fps = m_properties->get(key.toLatin1().constData());
0145                         }
0146                         if (fps == QLatin1String("90000")) {
0147                             // This is a cover image, skip
0148                             qDebug() << "=== FOUND MJPEG COVER ART STREAM: " << ix;
0149                             continue;
0150                         }
0151                     }
0152                     videoStreams << ix;
0153                 } else if (type == QLatin1String("audio")) {
0154                     audioStreams << ix;
0155                 }
0156             }
0157             if (videoStreams.count() > 1) {
0158                 setProducerProperty(QStringLiteral("kdenlive:multistreams"), 1);
0159                 m_hasMultipleVideoStreams = true;
0160                 QMetaObject::invokeMethod(pCore->bin(), "processMultiStream", Qt::QueuedConnection, Q_ARG(const QString &, m_controllerBinId),
0161                                           Q_ARG(QList<int>, videoStreams), Q_ARG(QList<int>, audioStreams));
0162             }
0163         }
0164     }
0165     connectEffectStack();
0166 }
0167 
0168 const QString ClipController::producerXml(Mlt::Producer producer, bool includeMeta, bool includeProfile)
0169 {
0170     Mlt::Consumer c(*producer.profile(), "xml", "string");
0171     if (!producer.is_valid()) {
0172         return QString();
0173     }
0174     c.set("time_format", "frames");
0175     if (!includeMeta) {
0176         c.set("no_meta", 1);
0177     }
0178     if (!includeProfile) {
0179         c.set("no_profile", 1);
0180     }
0181     c.set("store", "kdenlive");
0182     c.set("no_root", 1);
0183     c.set("root", "/");
0184     c.connect(producer);
0185     c.run();
0186     return QString::fromUtf8(c.get("string"));
0187 }
0188 
0189 void ClipController::getProducerXML(QDomDocument &document, bool includeMeta, bool includeProfile)
0190 {
0191     // TODO refac this is a probable duplicate with Clip::xml
0192     if (m_masterProducer) {
0193         QString xml = producerXml(m_masterProducer->parent(), includeMeta, includeProfile);
0194         document.setContent(xml);
0195     } else {
0196         if (!m_temporaryUrl.isEmpty()) {
0197             document = ClipCreator::getXmlFromUrl(m_temporaryUrl);
0198         } else if (!m_path.isEmpty()) {
0199             document = ClipCreator::getXmlFromUrl(m_path);
0200         }
0201         qCDebug(KDENLIVE_LOG) << " + + ++ NO MASTER PROD";
0202     }
0203 }
0204 
0205 void ClipController::getInfoForProducer()
0206 {
0207     QReadLocker lock(&m_producerLock);
0208     m_service = m_properties->get("mlt_service");
0209     if (m_service == QLatin1String("qtext")) {
0210         // Placeholder clip, find real service
0211         QString originalService = m_properties->get("kdenlive:orig_service");
0212         if (!originalService.isEmpty()) {
0213             m_service = originalService;
0214         }
0215     }
0216     QString proxy = m_properties->get("kdenlive:proxy");
0217     QString path = m_properties->get("resource");
0218     if (!m_service.isEmpty() && proxy.length() > 2) {
0219         if (QFileInfo(path).isRelative() && path != QLatin1String("<tractor>")) {
0220             path.prepend(pCore->currentDoc()->documentRoot());
0221             m_properties->set("resource", path.toUtf8().constData());
0222         }
0223         if (QFileInfo(proxy).isRelative()) {
0224             proxy.prepend(pCore->currentDoc()->documentRoot());
0225             m_properties->set("kdenlive:proxy", proxy.toUtf8().constData());
0226         }
0227         if (proxy == path) {
0228             // This is a proxy producer, read original url from kdenlive property
0229             path = m_properties->get("kdenlive:originalurl");
0230             if (QFileInfo(path).isRelative()) {
0231                 path.prepend(pCore->currentDoc()->documentRoot());
0232             }
0233             m_usesProxy = true;
0234         }
0235     } else if (m_service != QLatin1String("color") && m_service != QLatin1String("colour") && !path.isEmpty() && QFileInfo(path).isRelative() &&
0236                path != QLatin1String("<producer>") && path != QLatin1String("<tractor>")) {
0237         path.prepend(pCore->currentDoc()->documentRoot());
0238         m_properties->set("resource", path.toUtf8().constData());
0239     }
0240     m_path = path.isEmpty() ? QString() : QFileInfo(path).absoluteFilePath();
0241     QString origurl = m_properties->get("kdenlive:originalurl");
0242     if (!origurl.isEmpty()) {
0243         m_properties->set("kdenlive:originalurl", m_path.toUtf8().constData());
0244     }
0245     date = QFileInfo(m_path).lastModified();
0246     m_videoIndex = -1;
0247     int audioIndex = -1;
0248     // special case: playlist with a proxy clip have to be detected separately
0249     if (m_usesProxy && m_path.endsWith(QStringLiteral(".mlt"))) {
0250         if (m_clipType != ClipType::Timeline) {
0251             m_clipType = ClipType::Playlist;
0252         }
0253     } else if (m_service == QLatin1String("avformat") || m_service == QLatin1String("avformat-novalidate")) {
0254         audioIndex = getProducerIntProperty(QStringLiteral("audio_index"));
0255         m_videoIndex = getProducerIntProperty(QStringLiteral("video_index"));
0256         if (m_videoIndex == -1) {
0257             m_clipType = ClipType::Audio;
0258         } else {
0259             if (audioIndex == -1) {
0260                 m_clipType = ClipType::Video;
0261             } else {
0262                 m_clipType = ClipType::AV;
0263             }
0264             if (m_service == QLatin1String("avformat")) {
0265                 m_properties->set("mlt_service", "avformat-novalidate");
0266                 m_properties->set("mute_on_pause", 0);
0267             }
0268         }
0269         /*} else if (m_service.isEmpty() && path == QLatin1String("<producer>")) {
0270                 m_clipType = ClipType::Timeline;
0271                 m_properties = new Mlt::Properties(m_masterProducer->parent().get_properties());
0272                 return getInfoForProducer();*/
0273     } else if (m_service == QLatin1String("qimage") || m_service == QLatin1String("pixbuf")) {
0274         if (m_path.contains(QLatin1Char('%')) || m_path.contains(QStringLiteral("/.all.")) || m_path.contains(QStringLiteral("\\.all."))) {
0275             m_clipType = ClipType::SlideShow;
0276         } else {
0277             m_clipType = ClipType::Image;
0278         }
0279     } else if (m_service == QLatin1String("colour") || m_service == QLatin1String("color")) {
0280         m_clipType = ClipType::Color;
0281         // Required for faster compositing
0282         m_masterProducer->set("mlt_image_format", "rgb");
0283     } else if (m_service == QLatin1String("kdenlivetitle")) {
0284         if (!m_path.isEmpty()) {
0285             m_clipType = ClipType::TextTemplate;
0286         } else {
0287             m_clipType = ClipType::Text;
0288         }
0289     } else if (m_service == QLatin1String("xml") || m_service == QLatin1String("consumer")) {
0290         if (m_clipType == ClipType::Timeline) {
0291             // Nothing to do, don't change existing type
0292         } else if (m_properties->property_exists("kdenlive:producer_type")) {
0293             m_clipType = (ClipType::ProducerType)m_properties->get_int("kdenlive:producer_type");
0294         } else {
0295             m_clipType = ClipType::Playlist;
0296         }
0297     } else if (m_service == QLatin1String("tractor") || m_service == QLatin1String("xml-string")) {
0298         if (m_properties->property_exists("kdenlive:producer_type")) {
0299             m_clipType = (ClipType::ProducerType)m_properties->get_int("kdenlive:producer_type");
0300         } else {
0301             m_clipType = ClipType::Timeline;
0302         }
0303     } else if (m_service == QLatin1String("webvfx")) {
0304         m_clipType = ClipType::WebVfx;
0305     } else if (m_service == QLatin1String("qtext")) {
0306         m_clipType = ClipType::QText;
0307     } else if (m_service == QLatin1String("qml")) {
0308         m_clipType = ClipType::Qml;
0309     } else if (m_service == QLatin1String("blipflash")) {
0310         // Mostly used for testing
0311         m_clipType = ClipType::AV;
0312     } else if (m_service == QLatin1String("glaxnimate")) {
0313         // Mostly used for testing
0314         m_clipType = ClipType::Animation;
0315     } else {
0316         if (m_properties->property_exists("kdenlive:producer_type")) {
0317             m_clipType = (ClipType::ProducerType)m_properties->get_int("kdenlive:producer_type");
0318         } else {
0319             m_clipType = ClipType::Unknown;
0320         }
0321     }
0322 
0323     if (audioIndex > -1) {
0324         buildAudioInfo(audioIndex);
0325     }
0326 
0327     if (!hasLimitedDuration()) {
0328         int playtime = m_masterProducer->time_to_frames(m_masterProducer->parent().get("kdenlive:duration"));
0329         if (playtime <= 0) {
0330             // Fix clips having missing kdenlive:duration
0331             m_masterProducer->parent().set("kdenlive:duration", m_masterProducer->frames_to_time(m_masterProducer->get_playtime(), mlt_time_clock));
0332             m_masterProducer->set("out", m_masterProducer->frames_to_time(m_masterProducer->get_length() - 1, mlt_time_clock));
0333         }
0334     }
0335 }
0336 
0337 void ClipController::buildAudioInfo(int audioIndex)
0338 {
0339     if (m_audioInfo) {
0340         m_audioInfo.reset();
0341     }
0342     m_audioInfo = std::make_unique<AudioStreamInfo>(m_masterProducer, audioIndex, m_clipType == ClipType::Playlist || m_clipType == ClipType::Timeline);
0343     // Load stream effects
0344     for (int stream : m_audioInfo->streams().keys()) {
0345         QString streamEffect = m_properties->get(QString("kdenlive:stream:%1").arg(stream).toUtf8().constData());
0346         if (!streamEffect.isEmpty()) {
0347             m_streamEffects.insert(stream, streamEffect.split(QChar('#')));
0348         }
0349     }
0350 }
0351 
0352 bool ClipController::hasLimitedDuration() const
0353 {
0354     if (m_forceLimitedDuration) {
0355         return true;
0356     }
0357 
0358     switch (m_clipType) {
0359         case ClipType::SlideShow:
0360             return getProducerIntProperty(QStringLiteral("loop")) == 1 ? false : true;
0361         case ClipType::Image:
0362         case ClipType::Color:
0363         case ClipType::TextTemplate:
0364         case ClipType::Text:
0365         case ClipType::QText:
0366         case ClipType::Qml:
0367             return false;
0368         case ClipType::AV:
0369         case ClipType::Animation:
0370         case ClipType::Playlist:
0371         case ClipType::Timeline:
0372             return true;
0373         default:
0374             return true;
0375     }
0376 }
0377 
0378 void ClipController::forceLimitedDuration()
0379 {
0380     m_forceLimitedDuration = true;
0381 }
0382 
0383 std::shared_ptr<Mlt::Producer> ClipController::originalProducer()
0384 {
0385     QReadLocker lock(&m_producerLock);
0386     return m_masterProducer;
0387 }
0388 
0389 Mlt::Producer *ClipController::masterProducer()
0390 {
0391     return new Mlt::Producer(*m_masterProducer);
0392 }
0393 
0394 bool ClipController::isValid()
0395 {
0396     if (m_masterProducer == nullptr) {
0397         return false;
0398     }
0399     return m_masterProducer->is_valid();
0400 }
0401 
0402 // static
0403 const char *ClipController::getPassPropertiesList(bool passLength)
0404 {
0405     if (!passLength) {
0406         return "kdenlive:proxy,kdenlive:originalurl,kdenlive:multistreams,rotate,force_aspect_num,force_aspect_den,force_aspect_ratio,force_fps,force_"
0407                "progressive,force_tff,threads,"
0408                "force_"
0409                "colorspace,set.force_full_luma,file_hash,autorotate,disable_exif,xmldata,vstream,astream,set.test_image,set.test_audio,ttl";
0410     }
0411     return "kdenlive:proxy,kdenlive:originalurl,kdenlive:multistreams,rotate,force_aspect_num,force_aspect_den,force_aspect_ratio,force_fps,force_progressive,"
0412            "force_tff,threads,"
0413            "force_"
0414            "colorspace,set.force_full_luma,templatetext,file_hash,autorotate,disable_exif,xmldata,length,vstream,astream,set.test_image,set.test_audio,"
0415            "ttl";
0416 }
0417 
0418 QMap<QString, QString> ClipController::getPropertiesFromPrefix(const QString &prefix, bool withPrefix)
0419 {
0420     QReadLocker lock(&m_producerLock);
0421     Mlt::Properties subProperties;
0422     subProperties.pass_values(*m_properties, prefix.toUtf8().constData());
0423     QMap<QString, QString> subclipsData;
0424     for (int i = 0; i < subProperties.count(); i++) {
0425         subclipsData.insert(withPrefix ? QString(prefix + subProperties.get_name(i)) : subProperties.get_name(i), subProperties.get(i));
0426     }
0427     return subclipsData;
0428 }
0429 
0430 void ClipController::updateProducer(const std::shared_ptr<Mlt::Producer> &producer)
0431 {
0432     qDebug() << "################### ClipController::updateProducer";
0433     if (!m_properties) {
0434         // producer has not been initialized
0435         return addMasterProducer(producer);
0436     }
0437     m_producerLock.lockForWrite();
0438     Mlt::Properties passProperties;
0439     // Keep track of necessary properties
0440     const QString proxy(producer->get("kdenlive:proxy"));
0441     if (proxy.length() > 2 && producer->get("resource") == proxy) {
0442         // This is a proxy producer, read original url from kdenlive property
0443         m_usesProxy = true;
0444     } else {
0445         m_usesProxy = false;
0446     }
0447     // When resetting profile, duration can change so we invalidate it to 0 in that case
0448     int length = m_properties->get_int("length");
0449     const char *passList = getPassPropertiesList(m_usesProxy && length > 0);
0450     // This is necessary as some properties like set.test_audio are reset on producer creation
0451     passProperties.pass_list(*m_properties, passList);
0452     delete m_properties;
0453 
0454     if (QString(producer->get("mlt_service")).contains(QLatin1String("avformat")) && producer->type() == mlt_service_producer_type) {
0455         std::shared_ptr<Mlt::Chain> chain(new Mlt::Chain(pCore->getProjectProfile()));
0456         chain->set_source(*producer.get());
0457         m_masterProducer = std::move(chain);
0458     } else {
0459         m_masterProducer = std::move(producer);
0460     }
0461     m_properties = new Mlt::Properties(m_masterProducer->get_properties());
0462     m_producerLock.unlock();
0463     if (!m_masterProducer->is_valid()) {
0464         qCDebug(KDENLIVE_LOG) << "// WARNING, USING INVALID PRODUCER";
0465     } else {
0466         // Pass properties from previous producer
0467         m_properties->pass_list(passProperties, passList);
0468         setProducerProperty(QStringLiteral("kdenlive:id"), m_controllerBinId);
0469         if (m_clipType != ClipType::Timeline) {
0470             // Timeline clips effect stack point to the tractor and are handled in ProjectClip::reloadTimeline
0471             m_effectStack->resetService(m_masterProducer);
0472         }
0473         if (m_clipType == ClipType::Unknown) {
0474             getInfoForProducer();
0475         }
0476         checkAudioVideo();
0477         // URL and name should not be updated otherwise when proxying a clip we cannot find back the original url
0478         /*m_url = QUrl::fromLocalFile(m_masterProducer->get("resource"));
0479         if (m_url.isValid()) {
0480             m_name = m_url.fileName();
0481         }
0482         */
0483     }
0484     qDebug() << "// replace finished: " << binId() << " : " << m_masterProducer->get("resource");
0485 }
0486 
0487 const QString ClipController::getStringDuration()
0488 {
0489     QReadLocker lock(&m_producerLock);
0490     if (m_masterProducer) {
0491         int playtime = m_masterProducer->time_to_frames(m_masterProducer->parent().get("kdenlive:duration"));
0492         if (playtime > 0) {
0493             return QString(m_properties->frames_to_time(playtime, mlt_time_smpte_df));
0494         }
0495         return m_properties->frames_to_time(m_masterProducer->parent().get_length(), mlt_time_smpte_df);
0496     }
0497     return i18n("Unknown");
0498 }
0499 
0500 int ClipController::getProducerDuration() const
0501 {
0502     QReadLocker lock(&m_producerLock);
0503     if (m_masterProducer) {
0504         int playtime = m_masterProducer->time_to_frames(m_masterProducer->parent().get("kdenlive:duration"));
0505         if (playtime <= 0) {
0506             return m_masterProducer->get_length();
0507         }
0508         return playtime;
0509     }
0510     return -1;
0511 }
0512 
0513 char *ClipController::framesToTime(int frames) const
0514 {
0515     QReadLocker lock(&m_producerLock);
0516     if (m_masterProducer) {
0517         return m_masterProducer->frames_to_time(frames, mlt_time_clock);
0518     }
0519     return nullptr;
0520 }
0521 
0522 GenTime ClipController::getPlaytime() const
0523 {
0524     QReadLocker lock(&m_producerLock);
0525     if (!m_masterProducer || !m_masterProducer->is_valid()) {
0526         return GenTime();
0527     }
0528     double fps = pCore->getCurrentFps();
0529     if (!hasLimitedDuration()) {
0530         int playtime = m_masterProducer->time_to_frames(m_masterProducer->parent().get("kdenlive:duration"));
0531         return GenTime(playtime == 0 ? m_masterProducer->get_playtime() : playtime, fps);
0532     }
0533     return {m_masterProducer->get_playtime(), fps};
0534 }
0535 
0536 int ClipController::getFramePlaytime() const
0537 {
0538     QReadLocker lock(&m_producerLock);
0539     if (!m_masterProducer || !m_masterProducer->is_valid()) {
0540         return 0;
0541     }
0542     if (!hasLimitedDuration() || m_clipType == ClipType::Playlist || m_clipType == ClipType::Timeline) {
0543         if (!m_masterProducer->parent().property_exists("kdenlive:duration")) {
0544             return m_masterProducer->get_length();
0545         }
0546         int playtime = m_masterProducer->time_to_frames(m_masterProducer->parent().get("kdenlive:duration"));
0547         return playtime == 0 ? m_masterProducer->get_length() : playtime;
0548     }
0549     return m_masterProducer->get_length();
0550 }
0551 
0552 bool ClipController::hasProducerProperty(const QString &name) const
0553 {
0554     QReadLocker lock(&m_producerLock);
0555     if (m_properties == nullptr) {
0556         return false;
0557     }
0558     return m_properties->property_exists(name.toUtf8().constData());
0559 }
0560 
0561 QString ClipController::getProducerProperty(const QString &name) const
0562 {
0563     QReadLocker lock(&m_producerLock);
0564     if (m_properties == nullptr) {
0565         return m_tempProps.value(name).toString();
0566     }
0567     if (m_usesProxy && name.startsWith(QLatin1String("meta."))) {
0568         QString correctedName = QStringLiteral("kdenlive:") + name;
0569         return m_properties->get(correctedName.toUtf8().constData());
0570     }
0571     return QString(m_properties->get(name.toUtf8().constData()));
0572 }
0573 
0574 int ClipController::getProducerIntProperty(const QString &name) const
0575 {
0576     QReadLocker lock(&m_producerLock);
0577     if (!m_properties) {
0578         return 0;
0579     }
0580     if (m_usesProxy && name.startsWith(QLatin1String("meta."))) {
0581         QString correctedName = QStringLiteral("kdenlive:") + name;
0582         return m_properties->get_int(correctedName.toUtf8().constData());
0583     }
0584     return m_properties->get_int(name.toUtf8().constData());
0585 }
0586 
0587 qint64 ClipController::getProducerInt64Property(const QString &name) const
0588 {
0589     QReadLocker lock(&m_producerLock);
0590     if (!m_properties) {
0591         return 0;
0592     }
0593     return m_properties->get_int64(name.toUtf8().constData());
0594 }
0595 
0596 double ClipController::getProducerDoubleProperty(const QString &name) const
0597 {
0598     QReadLocker lock(&m_producerLock);
0599     if (!m_properties) {
0600         return 0;
0601     }
0602     return m_properties->get_double(name.toUtf8().constData());
0603 }
0604 
0605 QColor ClipController::getProducerColorProperty(const QString &name) const
0606 {
0607     QReadLocker lock(&m_producerLock);
0608     if (!m_properties) {
0609         return {};
0610     }
0611     mlt_color color = m_properties->get_color(name.toUtf8().constData());
0612     return QColor::fromRgb(color.r, color.g, color.b);
0613 }
0614 
0615 QMap<QString, QString> ClipController::currentProperties(const QMap<QString, QString> &props)
0616 {
0617     QMap<QString, QString> currentProps;
0618     QMap<QString, QString>::const_iterator i = props.constBegin();
0619     while (i != props.constEnd()) {
0620         currentProps.insert(i.key(), getProducerProperty(i.key()));
0621         ++i;
0622     }
0623     return currentProps;
0624 }
0625 
0626 double ClipController::originalFps() const
0627 {
0628     QReadLocker lock(&m_producerLock);
0629     if (!m_properties) {
0630         return 0;
0631     }
0632     QString propertyName = QStringLiteral("meta.media.%1.stream.frame_rate").arg(m_videoIndex);
0633     return m_properties->get_double(propertyName.toUtf8().constData());
0634 }
0635 
0636 QString ClipController::videoCodecProperty(const QString &property) const
0637 {
0638     QReadLocker lock(&m_producerLock);
0639     if (!m_properties) {
0640         return QString();
0641     }
0642     QString propertyName = QStringLiteral("meta.media.%1.codec.%2").arg(m_videoIndex).arg(property);
0643     return m_properties->get(propertyName.toUtf8().constData());
0644 }
0645 
0646 const QString ClipController::codec(bool audioCodec) const
0647 {
0648     QReadLocker lock(&m_producerLock);
0649     if ((m_properties == nullptr) || (m_clipType != ClipType::AV && m_clipType != ClipType::Video && m_clipType != ClipType::Audio)) {
0650         return QString();
0651     }
0652     QString propertyName = QStringLiteral("meta.media.%1.codec.name").arg(audioCodec ? m_properties->get_int("audio_index") : m_videoIndex);
0653     return m_properties->get(propertyName.toUtf8().constData());
0654 }
0655 
0656 const QString ClipController::clipUrl() const
0657 {
0658     return m_path;
0659 }
0660 
0661 bool ClipController::sourceExists() const
0662 {
0663     if (m_clipType == ClipType::Color || m_clipType == ClipType::Text || m_clipType == ClipType::Timeline) {
0664         return true;
0665     }
0666     if (m_clipType == ClipType::SlideShow) {
0667         // TODO
0668         return true;
0669     }
0670     return QFile::exists(m_path);
0671 }
0672 
0673 QString ClipController::serviceName() const
0674 {
0675     return m_service;
0676 }
0677 
0678 void ClipController::setProducerProperty(const QString &name, int value)
0679 {
0680     if (!m_properties) {
0681         m_tempProps.insert(name, value);
0682         return;
0683     }
0684     QWriteLocker lock(&m_producerLock);
0685     m_masterProducer->parent().set(name.toUtf8().constData(), value);
0686 }
0687 
0688 void ClipController::setProducerProperty(const QString &name, double value)
0689 {
0690     if (!m_properties) {
0691         m_tempProps.insert(name, value);
0692         return;
0693     }
0694     QWriteLocker lock(&m_producerLock);
0695     m_masterProducer->parent().set(name.toUtf8().constData(), value);
0696 }
0697 
0698 void ClipController::setProducerProperty(const QString &name, const QString &value)
0699 {
0700     if (!m_properties) {
0701         m_tempProps.insert(name, value);
0702         return;
0703     }
0704 
0705     QWriteLocker lock(&m_producerLock);
0706     if (value.isEmpty()) {
0707         m_masterProducer->parent().set(name.toUtf8().constData(), nullptr);
0708     } else {
0709         m_masterProducer->parent().set(name.toUtf8().constData(), value.toUtf8().constData());
0710     }
0711 }
0712 
0713 void ClipController::resetProducerProperty(const QString &name)
0714 {
0715     if (!m_properties) {
0716         m_tempProps.insert(name, QString());
0717         return;
0718     }
0719 
0720     QWriteLocker lock(&m_producerLock);
0721     m_masterProducer->parent().set(name.toUtf8().constData(), nullptr);
0722 }
0723 
0724 ClipType::ProducerType ClipController::clipType() const
0725 {
0726     return m_clipType;
0727 }
0728 
0729 const QSize ClipController::getFrameSize() const
0730 {
0731     QReadLocker lock(&m_producerLock);
0732     if (m_masterProducer == nullptr) {
0733         return QSize();
0734     }
0735     if (m_usesProxy) {
0736         int width = m_properties->get_int("kdenlive:original.meta.media.width");
0737         int height = m_properties->get_int("kdenlive:original.meta.media.height");
0738         if (width == 0) {
0739             width = m_properties->get_int("kdenlive:original.width");
0740         }
0741         if (height == 0) {
0742             height = m_properties->get_int("kdenlive:original.height");
0743         }
0744         if (width > 0 && height > 0) {
0745             return QSize(width, height);
0746         }
0747     }
0748     int width = m_properties->get_int("meta.media.width");
0749     if (width == 0) {
0750         width = m_properties->get_int("width");
0751     }
0752     int height = m_properties->get_int("meta.media.height");
0753     if (height == 0) {
0754         height = m_properties->get_int("height");
0755     }
0756     return QSize(width, height);
0757 }
0758 
0759 bool ClipController::hasAudio() const
0760 {
0761     return m_hasAudio;
0762 }
0763 
0764 void ClipController::checkAudioVideo()
0765 {
0766     QReadLocker lock(&m_producerLock);
0767     if (m_masterProducer->get_int("_placeholder") == 1 || m_masterProducer->get_int("_missingsource") == 1) {
0768         // This is a placeholder file, try to guess from its properties
0769         QString orig_service = m_masterProducer->get("kdenlive:orig_service");
0770         if (orig_service.startsWith(QStringLiteral("avformat")) || (m_masterProducer->get_int("audio_index") + m_masterProducer->get_int("video_index") > 0)) {
0771             m_hasAudio = m_masterProducer->get_int("audio_index") >= 0;
0772             m_hasVideo = m_masterProducer->get_int("video_index") >= 0;
0773         } else if (orig_service == QStringLiteral("xml")) {
0774             // Playlist, assume we have audio and video
0775             m_hasAudio = true;
0776             m_hasVideo = true;
0777         } else {
0778             // Assume image or text producer
0779             m_hasAudio = false;
0780             m_hasVideo = true;
0781         }
0782         return;
0783     }
0784     if (m_masterProducer->property_exists("kdenlive:clip_type")) {
0785         int clipType = m_masterProducer->get_int("kdenlive:clip_type");
0786         qDebug() << "------------\nFOUND PRESET CTYPE: " << clipType << "\n------------------------";
0787         switch (clipType) {
0788         case 1:
0789             m_hasAudio = true;
0790             m_hasVideo = false;
0791             break;
0792         case 2:
0793             m_hasAudio = false;
0794             m_hasVideo = true;
0795             break;
0796         default:
0797             m_hasAudio = true;
0798             m_hasVideo = true;
0799             break;
0800         }
0801         if (m_clipType == ClipType::Timeline) {
0802             if (m_audioInfo == nullptr) {
0803                 if (m_hasAudio) {
0804                     buildAudioInfo(-1);
0805                 }
0806             } else if (!m_hasAudio) {
0807                 m_audioInfo.reset();
0808             }
0809         }
0810     } else {
0811         QString service = m_masterProducer->get("kdenlive:orig_service");
0812         if (service.isEmpty()) {
0813             service = m_masterProducer->get("mlt_service");
0814         }
0815         QList<ClipType::ProducerType> avTypes = {ClipType::Playlist, ClipType::AV, ClipType::Audio, ClipType::Unknown};
0816         if (m_clipType == ClipType::Timeline) {
0817             // use sequenceproperties to decide if clip has audio, video or both
0818             if (m_masterProducer->parent().get_int("kdenlive:sequenceproperties.hasAudio") == 1) {
0819                 m_hasAudio = true;
0820             }
0821             if (m_masterProducer->parent().get_int("kdenlive:sequenceproperties.hasVideo") == 1) {
0822                 m_hasVideo = true;
0823             }
0824             if (m_hasAudio) {
0825                 if (m_hasVideo) {
0826                     m_masterProducer->parent().set("kdenlive:clip_type", 0);
0827                 } else {
0828                     m_masterProducer->parent().set("kdenlive:clip_type", 1);
0829                 }
0830             } else if (!m_hasVideo) {
0831                 // sequence is incorrectly configured, enable both audio and video
0832                 m_hasAudio = true;
0833                 m_hasVideo = true;
0834                 m_masterProducer->parent().set("kdenlive:clip_type", 0);
0835             } else {
0836                 m_masterProducer->parent().set("kdenlive:clip_type", 2);
0837             }
0838             if (m_hasAudio) {
0839                 buildAudioInfo(-1);
0840             }
0841             return;
0842         }
0843         if (avTypes.contains(m_clipType)) {
0844             m_masterProducer->seek(0);
0845             QScopedPointer<Mlt::Frame> frame(m_masterProducer->get_frame());
0846             if (frame->is_valid()) {
0847                 // test_audio returns 1 if there is NO audio (strange but true at the time this code is written)
0848                 m_hasAudio = frame->get_int("test_audio") == 0;
0849                 m_hasVideo = frame->get_int("test_image") == 0;
0850                 if (m_hasAudio) {
0851                     if (m_hasVideo) {
0852                         m_masterProducer->set("kdenlive:clip_type", 0);
0853                     } else {
0854                         m_masterProducer->set("kdenlive:clip_type", 1);
0855                     }
0856                 } else if (m_hasVideo) {
0857                     m_masterProducer->set("kdenlive:clip_type", 2);
0858                 }
0859                 qDebug() << "------------\nFRAME HAS AUDIO: " << m_hasAudio << " / " << m_hasVideo << "\n------------------------";
0860                 m_masterProducer->seek(0);
0861             } else {
0862                 qDebug() << "* * * *ERROR INVALID FRAME On test";
0863             }
0864             if (m_clipType == ClipType::Playlist) {
0865                 if (m_audioInfo == nullptr) {
0866                     if (m_hasAudio) {
0867                         buildAudioInfo(-1);
0868                     }
0869                 } else if (!m_hasAudio) {
0870                     m_audioInfo.reset();
0871                 }
0872             }
0873         } else {
0874             // Assume video only producer
0875             m_hasAudio = false;
0876             m_hasVideo = true;
0877             m_masterProducer->set("kdenlive:clip_type", 2);
0878         }
0879     }
0880 }
0881 bool ClipController::hasVideo() const
0882 {
0883     return m_hasVideo;
0884 }
0885 PlaylistState::ClipState ClipController::defaultState() const
0886 {
0887     if (hasVideo()) {
0888         return PlaylistState::VideoOnly;
0889     }
0890     if (hasAudio()) {
0891         return PlaylistState::AudioOnly;
0892     }
0893     return PlaylistState::Disabled;
0894 }
0895 
0896 void ClipController::setZone(const QPoint &zone)
0897 {
0898     setProducerProperty(QStringLiteral("kdenlive:zone_in"), zone.x());
0899     setProducerProperty(QStringLiteral("kdenlive:zone_out"), zone.y());
0900 }
0901 
0902 const QString ClipController::getClipHash() const
0903 {
0904     return getProducerProperty(QStringLiteral("kdenlive:file_hash"));
0905 }
0906 
0907 Mlt::Properties &ClipController::properties()
0908 {
0909     QReadLocker lock(&m_producerLock);
0910     return *m_properties;
0911 }
0912 
0913 void ClipController::backupOriginalProperties()
0914 {
0915     QReadLocker lock(&m_producerLock);
0916     if (m_properties->get_int("kdenlive:original.backup") == 1) {
0917         return;
0918     }
0919     int propsCount = m_properties->count();
0920     // store original props
0921     QStringList doNotPass{QStringLiteral("kdenlive:proxy"), QStringLiteral("kdenlive:originalurl"), QStringLiteral("kdenlive:clipname")};
0922     for (int j = 0; j < propsCount; j++) {
0923         QString propName = m_properties->get_name(j);
0924         if (doNotPass.contains(propName)) {
0925             continue;
0926         }
0927         if (!propName.startsWith(QLatin1Char('_'))) {
0928             propName.prepend(QStringLiteral("kdenlive:original."));
0929             m_properties->set(propName.toUtf8().constData(), m_properties->get(j));
0930         }
0931     }
0932     m_properties->set("kdenlive:original.backup", 1);
0933 }
0934 
0935 void ClipController::clearBackupProperties()
0936 {
0937     QReadLocker lock(&m_producerLock);
0938     if (m_properties->get_int("kdenlive:original.backup") == 0) {
0939         return;
0940     }
0941     int propsCount = m_properties->count();
0942     // clear original props
0943     QStringList passProps;
0944     for (int j = 0; j < propsCount; j++) {
0945         QString propName = m_properties->get_name(j);
0946         if (propName.startsWith(QLatin1String("kdenlive:original."))) {
0947             passProps << propName;
0948         }
0949     }
0950     for (const QString &p : qAsConst(passProps)) {
0951         m_properties->clear(p.toUtf8().constData());
0952     }
0953     m_properties->clear("kdenlive:original.backup");
0954 }
0955 
0956 void ClipController::mirrorOriginalProperties(std::shared_ptr<Mlt::Properties> props)
0957 {
0958     QReadLocker lock(&m_producerLock);
0959     if (m_usesProxy && QFileInfo(m_properties->get("resource")).fileName() == QFileInfo(m_properties->get("kdenlive:proxy")).fileName()) {
0960         // This is a proxy, we need to use the real source properties
0961         if (m_properties->get_int("kdenlive:original.backup") == 0) {
0962             // We have a proxy clip, load original source producer
0963             std::shared_ptr<Mlt::Producer> prod = std::make_shared<Mlt::Producer>(pCore->getProjectProfile(), nullptr, m_path.toUtf8().constData());
0964             // Probe to retrieve all original props
0965             prod->probe();
0966             Mlt::Properties sourceProps(prod->get_properties());
0967             props->inherit(sourceProps);
0968             int propsCount = sourceProps.count();
0969             // store original props
0970             QStringList doNotPass{QStringLiteral("kdenlive:proxy"), QStringLiteral("kdenlive:originalurl"), QStringLiteral("kdenlive:clipname")};
0971             for (int i = 0; i < propsCount; i++) {
0972                 QString propName = sourceProps.get_name(i);
0973                 if (doNotPass.contains(propName)) {
0974                     continue;
0975                 }
0976                 if (!propName.startsWith(QLatin1Char('_'))) {
0977                     propName.prepend(QStringLiteral("kdenlive:original."));
0978                     m_properties->set(propName.toUtf8().constData(), sourceProps.get(i));
0979                 }
0980             }
0981             m_properties->set("kdenlive:original.backup", 1);
0982         }
0983         // Properties were fetched in the past, reuse
0984         Mlt::Properties sourceProps;
0985         sourceProps.pass_values(*m_properties, "kdenlive:original.");
0986         props->inherit(sourceProps);
0987     } else {
0988         if (m_clipType == ClipType::AV || m_clipType == ClipType::Video || m_clipType == ClipType::Audio) {
0989             // Make sure that a frame / image was fetched to initialize all meta properties
0990             m_masterProducer->probe();
0991         }
0992         props->inherit(*m_properties);
0993     }
0994 }
0995 
0996 int ClipController::effectsCount()
0997 {
0998     int count = 0;
0999     QReadLocker lock(&m_producerLock);
1000     Mlt::Service service(m_masterProducer->parent());
1001     for (int ix = 0; ix < service.filter_count(); ++ix) {
1002         QScopedPointer<Mlt::Filter> effect(service.filter(ix));
1003         QString id = effect->get("kdenlive_id");
1004         if (!id.isEmpty()) {
1005             count++;
1006         }
1007     }
1008     return count;
1009 }
1010 
1011 QStringList ClipController::filesUsedByEffects()
1012 {
1013     return m_effectStack->externalFiles();
1014 }
1015 
1016 bool ClipController::hasEffects() const
1017 {
1018     return m_effectStack->rowCount() > 0;
1019 }
1020 
1021 void ClipController::setBinEffectsEnabled(bool enabled)
1022 {
1023     m_effectStack->setEffectStackEnabled(enabled);
1024 }
1025 
1026 std::shared_ptr<EffectStackModel> ClipController::getEffectStack() const
1027 {
1028     return m_effectStack;
1029 }
1030 
1031 bool ClipController::addEffect(const QString &effectId, stringMap params)
1032 {
1033     return m_effectStack->appendEffect(effectId, true, params);
1034 }
1035 
1036 bool ClipController::copyEffect(const std::shared_ptr<EffectStackModel> &stackModel, int rowId)
1037 {
1038     m_effectStack->copyEffect(stackModel->getEffectStackRow(rowId),
1039                               !m_hasAudio ? PlaylistState::VideoOnly : !m_hasVideo ? PlaylistState::AudioOnly : PlaylistState::Disabled);
1040     return true;
1041 }
1042 
1043 std::shared_ptr<MarkerListModel> ClipController::getMarkerModel() const
1044 {
1045     return m_markerModel;
1046 }
1047 
1048 void ClipController::refreshAudioInfo()
1049 {
1050     if (m_audioInfo && m_masterProducer) {
1051         QReadLocker lock(&m_producerLock);
1052         m_audioInfo->setAudioIndex(m_masterProducer, m_properties->get_int("audio_index"));
1053     }
1054 }
1055 
1056 QMap<int, QString> ClipController::audioStreams() const
1057 {
1058     if (m_audioInfo) {
1059         return m_audioInfo->streams();
1060     }
1061     return {};
1062 }
1063 
1064 int ClipController::audioStreamIndex(int stream) const
1065 {
1066     QList<int> streams = audioStreams().keys();
1067     return streams.indexOf(stream);
1068 }
1069 
1070 QList<int> ClipController::activeStreamChannels() const
1071 {
1072     if (!audioInfo()) {
1073         return QList<int>();
1074     }
1075     return audioInfo()->activeStreamChannels();
1076 }
1077 
1078 QMap<int, QString> ClipController::activeStreams() const
1079 {
1080     if (m_audioInfo) {
1081         return m_audioInfo->activeStreams();
1082     }
1083     return {};
1084 }
1085 
1086 QVector<int> ClipController::activeFfmpegStreams() const
1087 {
1088     if (m_audioInfo) {
1089         QList<int> activeStreams = m_audioInfo->activeStreams().keys();
1090         QList<int> allStreams = m_audioInfo->streams().keys();
1091         QVector<int> ffmpegIndexes;
1092         for (auto &a : activeStreams) {
1093             ffmpegIndexes << allStreams.indexOf(a);
1094         }
1095         return ffmpegIndexes;
1096     }
1097     return {};
1098 }
1099 
1100 int ClipController::audioStreamsCount() const
1101 {
1102     if (m_audioInfo) {
1103         return m_audioInfo->streams().count();
1104     }
1105     return 0;
1106 }
1107 
1108 const QString ClipController::getOriginalUrl()
1109 {
1110     QString path = m_properties->get("kdenlive:originalurl");
1111     if (path.isEmpty()) {
1112         path = m_path;
1113     }
1114     if (!path.isEmpty() && QFileInfo(path).isRelative()) {
1115         path.prepend(pCore->currentDoc()->documentRoot());
1116     }
1117     return path;
1118 }
1119 
1120 bool ClipController::hasProxy() const
1121 {
1122     QString proxy = getProducerProperty(QStringLiteral("kdenlive:proxy"));
1123     // qDebug()<<"::: PROXY: "<<proxy<<" = "<<getProducerProperty(QStringLiteral("resource"));
1124     return proxy.size() > 2 && proxy == getProducerProperty(QStringLiteral("resource"));
1125 }
1126 
1127 std::shared_ptr<MarkerSortModel> ClipController::getFilteredMarkerModel() const
1128 {
1129     return m_markerFilterModel;
1130 }
1131 
1132 bool ClipController::isFullRange() const
1133 {
1134     bool full = !qstrcmp(m_masterProducer->get("meta.media.color_range"), "full");
1135     for (int i = 0; !full && i < m_masterProducer->get_int("meta.media.nb_streams"); i++) {
1136         QString key = QString("meta.media.%1.stream.type").arg(i);
1137         QString streamType(m_masterProducer->get(key.toLatin1().constData()));
1138         if (streamType == "video") {
1139             if (i == m_masterProducer->get_int("video_index")) {
1140                 key = QString("meta.media.%1.codec.pix_fmt").arg(i);
1141                 QString pix_fmt = QString::fromLatin1(m_masterProducer->get(key.toLatin1().constData()));
1142                 if (pix_fmt.startsWith("yuvj")) {
1143                     full = true;
1144                 } else if (pix_fmt.contains("gbr") || pix_fmt.contains("rgb")) {
1145                     full = true;
1146                 }
1147             }
1148         }
1149     }
1150     return full;
1151 }
1152 
1153 bool ClipController::removeMarkerCategories(QList<int> toRemove, const QMap<int, int> remapCategories, Fun &undo, Fun &redo)
1154 {
1155     bool found = false;
1156     if (m_markerModel->rowCount() == 0) {
1157         return false;
1158     }
1159     for (int i : toRemove) {
1160         QList<CommentedTime> toDelete = m_markerModel->getAllMarkers(i);
1161         if (!found && toDelete.count() > 0) {
1162             found = true;
1163         }
1164         if (remapCategories.contains(i)) {
1165             // Move markers to another category
1166             int newType = remapCategories.value(i);
1167             for (CommentedTime c : toDelete) {
1168                 m_markerModel->addMarker(c.time(), c.comment(), newType, undo, redo);
1169             }
1170         } else {
1171             // Delete markers
1172             for (CommentedTime c : toDelete) {
1173                 m_markerModel->removeMarker(c.time(), undo, redo);
1174             }
1175         }
1176     }
1177     return found;
1178 }