File indexing completed on 2024-04-28 08:44:05
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 }