File indexing completed on 2024-12-01 04:28:35
0001 /* 0002 SPDX-FileCopyrightText: 2021 Jean-Baptiste Mardelle <jb@kdenlive.org> 0003 This file is part of Kdenlive. See www.kdenlive.org. 0004 0005 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0006 */ 0007 0008 #include "cliploadtask.h" 0009 #include "audio/audioStreamInfo.h" 0010 #include "bin/projectclip.h" 0011 #include "bin/projectitemmodel.h" 0012 #include "core.h" 0013 #include "doc/kdenlivedoc.h" 0014 #include "doc/kthumb.h" 0015 #include "kdenlivesettings.h" 0016 #include "mltcontroller/clipcontroller.h" 0017 #include "project/dialogs/slideshowclip.h" 0018 #include "utils/thumbnailcache.hpp" 0019 0020 #include "xml/xml.hpp" 0021 #include <KLocalizedString> 0022 #include <KMessageWidget> 0023 #include <QAction> 0024 #include <QElapsedTimer> 0025 #include <QFile> 0026 #include <QImage> 0027 #include <QMimeDatabase> 0028 #include <QPainter> 0029 #include <QString> 0030 #include <QTime> 0031 #include <QUuid> 0032 #include <QVariantList> 0033 #include <audio/audioInfo.h> 0034 #include <monitor/monitor.h> 0035 #include <profiles/profilemodel.hpp> 0036 0037 ClipLoadTask::ClipLoadTask(const ObjectId &owner, const QDomElement &xml, bool thumbOnly, int in, int out, QObject *object) 0038 : AbstractTask(owner, AbstractTask::LOADJOB, object) 0039 , m_xml(xml) 0040 , m_in(in) 0041 , m_out(out) 0042 , m_thumbOnly(thumbOnly) 0043 { 0044 m_description = m_thumbOnly ? i18n("Video thumbs") : i18n("Loading clip"); 0045 } 0046 0047 ClipLoadTask::~ClipLoadTask() {} 0048 0049 void ClipLoadTask::start(const ObjectId &owner, const QDomElement &xml, bool thumbOnly, int in, int out, QObject *object, bool force, 0050 const std::function<void()> &readyCallBack) 0051 { 0052 ClipLoadTask *task = new ClipLoadTask(owner, xml, thumbOnly, in, out, object); 0053 if (!thumbOnly && pCore->taskManager.hasPendingJob(owner, AbstractTask::LOADJOB)) { 0054 delete task; 0055 task = nullptr; 0056 } 0057 if (task) { 0058 // Otherwise, start a new load task thread. 0059 task->m_isForce = force; 0060 connect(task, &ClipLoadTask::taskDone, [readyCallBack]() { QMetaObject::invokeMethod(qApp, [readyCallBack] { readyCallBack(); }); }); 0061 pCore->taskManager.startTask(owner.itemId, task); 0062 } 0063 } 0064 0065 ClipType::ProducerType ClipLoadTask::getTypeForService(const QString &id, const QString &path) 0066 { 0067 if (id.isEmpty()) { 0068 QString ext = path.section(QLatin1Char('.'), -1); 0069 if (ext == QLatin1String("mlt") || ext == QLatin1String("kdenlive")) { 0070 return ClipType::Playlist; 0071 } 0072 return ClipType::Unknown; 0073 } 0074 if (id == QLatin1String("color") || id == QLatin1String("colour")) { 0075 return ClipType::Color; 0076 } 0077 if (id == QLatin1String("kdenlivetitle")) { 0078 return ClipType::Text; 0079 } 0080 if (id == QLatin1String("qtext")) { 0081 return ClipType::QText; 0082 } 0083 if (id == QLatin1String("xml") || id == QLatin1String("consumer")) { 0084 return ClipType::Playlist; 0085 } 0086 if (id == QLatin1String("tractor")) { 0087 return ClipType::Timeline; 0088 } 0089 if (id == QLatin1String("webvfx")) { 0090 return ClipType::WebVfx; 0091 } 0092 if (id == QLatin1String("qml")) { 0093 return ClipType::Qml; 0094 } 0095 return ClipType::Unknown; 0096 } 0097 0098 // static 0099 std::shared_ptr<Mlt::Producer> ClipLoadTask::loadResource(QString resource, const QString &type) 0100 { 0101 if (!resource.startsWith(type)) { 0102 resource.prepend(type); 0103 } 0104 return std::make_shared<Mlt::Producer>(pCore->getProjectProfile(), nullptr, resource.toUtf8().constData()); 0105 } 0106 0107 std::shared_ptr<Mlt::Producer> ClipLoadTask::loadPlaylist(QString &resource) 0108 { 0109 // since MLT 7.14.0, playlists with different fps can be used in a project without corrupting the profile 0110 return std::make_shared<Mlt::Producer>(pCore->getProjectProfile(), nullptr, resource.toUtf8().constData()); 0111 } 0112 0113 // Read the properties of the xml and pass them to the producer. Note that some properties like resource are ignored 0114 void ClipLoadTask::processProducerProperties(const std::shared_ptr<Mlt::Producer> &prod, const QDomElement &xml) 0115 { 0116 // TODO: there is some duplication with clipcontroller > updateproducer that also copies properties 0117 QString value; 0118 QStringList internalProperties; 0119 internalProperties << QStringLiteral("bypassDuplicate") << QStringLiteral("resource") << QStringLiteral("mlt_service") << QStringLiteral("audio_index") 0120 << QStringLiteral("astream") << QStringLiteral("vstream") << QStringLiteral("video_index") << QStringLiteral("mlt_type") 0121 << QStringLiteral("length"); 0122 QDomNodeList props; 0123 0124 if (xml.tagName() == QLatin1String("producer") || xml.tagName() == QLatin1String("chain")) { 0125 props = xml.childNodes(); 0126 } else { 0127 QDomElement elem = xml.firstChildElement(QStringLiteral("chain")); 0128 if (elem.isNull()) { 0129 elem = xml.firstChildElement(QStringLiteral("producer")); 0130 } 0131 props = elem.childNodes(); 0132 } 0133 for (int i = 0; i < props.count(); ++i) { 0134 if (props.at(i).toElement().tagName() != QStringLiteral("property")) { 0135 continue; 0136 } 0137 QString propertyName = props.at(i).toElement().attribute(QStringLiteral("name")); 0138 if (!internalProperties.contains(propertyName) && !propertyName.startsWith(QLatin1Char('_'))) { 0139 value = props.at(i).firstChild().nodeValue(); 0140 if (propertyName.startsWith(QLatin1String("kdenlive-force."))) { 0141 // this is a special forced property, pass it 0142 propertyName.remove(0, 15); 0143 } 0144 prod->set(propertyName.toUtf8().constData(), value.toUtf8().constData()); 0145 } 0146 } 0147 } 0148 0149 void ClipLoadTask::processSlideShow(std::shared_ptr<Mlt::Producer> producer) 0150 { 0151 int ttl = Xml::getXmlProperty(m_xml, QStringLiteral("ttl")).toInt(); 0152 QString anim = Xml::getXmlProperty(m_xml, QStringLiteral("animation")); 0153 bool lowPass = Xml::getXmlProperty(m_xml, QStringLiteral("low-pass"), QStringLiteral("0")).toInt() == 1; 0154 if (lowPass) { 0155 auto *blur = new Mlt::Filter(pCore->getProjectProfile(), "avfilter.avgblur"); 0156 if ((blur == nullptr) || !blur->is_valid()) { 0157 delete blur; 0158 blur = new Mlt::Filter(pCore->getProjectProfile(), "boxblur"); 0159 } 0160 if ((blur != nullptr) && blur->is_valid()) { 0161 producer->attach(*blur); 0162 } 0163 } 0164 if (!anim.isEmpty()) { 0165 auto *filter = new Mlt::Filter(pCore->getProjectProfile(), "affine"); 0166 if ((filter != nullptr) && filter->is_valid()) { 0167 int cycle = ttl; 0168 QString geometry = SlideshowClip::animationToGeometry(anim, cycle); 0169 if (!geometry.isEmpty()) { 0170 filter->set("transition.rect", geometry.toUtf8().data()); 0171 filter->set("transition.cycle", cycle); 0172 filter->set("transition.mirror_off", 1); 0173 producer->attach(*filter); 0174 } 0175 } 0176 } 0177 QString fade = Xml::getXmlProperty(m_xml, QStringLiteral("fade")); 0178 if (fade == QLatin1String("1")) { 0179 // user wants a fade effect to slideshow 0180 auto *filter = new Mlt::Filter(pCore->getProjectProfile(), "luma"); 0181 if ((filter != nullptr) && filter->is_valid()) { 0182 if (ttl != 0) { 0183 filter->set("cycle", ttl); 0184 } 0185 QString luma_duration = Xml::getXmlProperty(m_xml, QStringLiteral("luma_duration")); 0186 QString luma_file = Xml::getXmlProperty(m_xml, QStringLiteral("luma_file")); 0187 if (!luma_duration.isEmpty()) { 0188 filter->set("duration", luma_duration.toInt()); 0189 } 0190 if (!luma_file.isEmpty()) { 0191 filter->set("luma.resource", luma_file.toUtf8().constData()); 0192 QString softness = Xml::getXmlProperty(m_xml, QStringLiteral("softness")); 0193 if (!softness.isEmpty()) { 0194 int soft = softness.toInt(); 0195 filter->set("luma.softness", double(soft) / 100.0); 0196 } 0197 } 0198 producer->attach(*filter); 0199 } 0200 } 0201 QString crop = Xml::getXmlProperty(m_xml, QStringLiteral("crop")); 0202 if (crop == QLatin1String("1")) { 0203 // user wants to center crop the slides 0204 auto *filter = new Mlt::Filter(pCore->getProjectProfile(), "crop"); 0205 if ((filter != nullptr) && filter->is_valid()) { 0206 filter->set("center", 1); 0207 producer->attach(*filter); 0208 } 0209 } 0210 } 0211 0212 void ClipLoadTask::generateThumbnail(std::shared_ptr<ProjectClip> binClip, std::shared_ptr<Mlt::Producer> producer) 0213 { 0214 // Fetch thumbnail 0215 if (m_isCanceled.loadAcquire() || pCore->taskManager.isBlocked()) { 0216 return; 0217 } 0218 int frameNumber = m_in; 0219 if (frameNumber < 0) { 0220 frameNumber = binClip->getThumbFrame(); 0221 } 0222 if (producer->get_int("video_index") > -1) { 0223 QImage thumb = ThumbnailCache::get()->getThumbnail(binClip->hashForThumbs(), QString::number(m_owner.itemId), frameNumber); 0224 if (!thumb.isNull()) { 0225 // Thumbnail found in cache 0226 qDebug() << "=== FOUND THUMB IN CACHe"; 0227 QMetaObject::invokeMethod(binClip.get(), "setThumbnail", Qt::QueuedConnection, Q_ARG(QImage, thumb), Q_ARG(int, m_in), Q_ARG(int, m_out), 0228 Q_ARG(bool, true)); 0229 } else { 0230 if (m_isCanceled.loadAcquire() || pCore->taskManager.isBlocked()) { 0231 return; 0232 } 0233 std::unique_ptr<Mlt::Producer> thumbProd = binClip->getThumbProducer(); 0234 if (thumbProd && thumbProd->is_valid()) { 0235 if (frameNumber > 0) { 0236 thumbProd->seek(frameNumber); 0237 } 0238 std::unique_ptr<Mlt::Frame> frame(thumbProd->get_frame()); 0239 if ((frame != nullptr) && frame->is_valid()) { 0240 frame->set("consumer.deinterlacer", "onefield"); 0241 frame->set("consumer.top_field_first", -1); 0242 frame->set("consumer.rescale", "nearest"); 0243 int imageHeight(pCore->thumbProfile().height()); 0244 int imageWidth(pCore->thumbProfile().width()); 0245 int fullWidth(qRound(imageHeight * pCore->getCurrentDar())); 0246 if (m_isCanceled.loadAcquire() || pCore->taskManager.isBlocked()) { 0247 return; 0248 } 0249 QImage result = KThumb::getFrame(frame.get(), imageWidth, imageHeight, fullWidth); 0250 if (result.isNull() && !m_isCanceled.loadAcquire()) { 0251 qDebug() << "+++++\nINVALID RESULT IMAGE\n++++++++++++++"; 0252 result = QImage(fullWidth, imageHeight, QImage::Format_ARGB32_Premultiplied); 0253 result.fill(Qt::red); 0254 QPainter p(&result); 0255 p.setPen(Qt::white); 0256 p.drawText(0, 0, fullWidth, imageHeight, Qt::AlignCenter, i18n("Invalid")); 0257 QMetaObject::invokeMethod(binClip.get(), "setThumbnail", Qt::QueuedConnection, Q_ARG(QImage, result), Q_ARG(int, m_in), 0258 Q_ARG(int, m_out), Q_ARG(bool, false)); 0259 } else if (binClip.get() && !m_isCanceled.loadAcquire()) { 0260 // We don't follow m_isCanceled there, 0261 qDebug() << "=== GOT THUMB FOR: " << m_in << "x" << m_out; 0262 QMetaObject::invokeMethod(binClip.get(), "setThumbnail", Qt::QueuedConnection, Q_ARG(QImage, result), Q_ARG(int, m_in), 0263 Q_ARG(int, m_out), Q_ARG(bool, false)); 0264 ThumbnailCache::get()->storeThumbnail(QString::number(m_owner.itemId), frameNumber, result, false); 0265 } 0266 } 0267 } 0268 } 0269 } 0270 } 0271 0272 void ClipLoadTask::run() 0273 { 0274 AbstractTaskDone whenFinished(m_owner.itemId, this); 0275 if (m_isCanceled.loadAcquire() == 1 || pCore->taskManager.isBlocked()) { 0276 abort(); 0277 return; 0278 } 0279 QMutexLocker lock(&m_runMutex); 0280 // QThread::currentThread()->setPriority(QThread::HighestPriority); 0281 if (m_thumbOnly) { 0282 auto binClip = pCore->projectItemModel()->getClipByBinID(QString::number(m_owner.itemId)); 0283 if (binClip && binClip->statusReady()) { 0284 if (m_isCanceled.loadAcquire() == 1 || pCore->taskManager.isBlocked()) { 0285 abort(); 0286 return; 0287 } 0288 generateThumbnail(binClip, binClip->originalProducer()); 0289 } 0290 if (m_isCanceled.loadAcquire() == 1 || pCore->taskManager.isBlocked()) { 0291 abort(); 0292 } 0293 return; 0294 } 0295 m_running = true; 0296 Q_EMIT pCore->projectItemModel()->resetPlayOrLoopZone(QString::number(m_owner.itemId)); 0297 QString resource = Xml::getXmlProperty(m_xml, QStringLiteral("resource")); 0298 qDebug() << "============STARTING LOAD TASK FOR: " << m_owner.itemId << " = " << resource << "\n\n:::::::::::::::::::"; 0299 int duration = 0; 0300 ClipType::ProducerType type = static_cast<ClipType::ProducerType>(m_xml.attribute(QStringLiteral("type")).toInt()); 0301 QString service = Xml::getXmlProperty(m_xml, QStringLiteral("mlt_service")); 0302 if (type == ClipType::Unknown) { 0303 type = getTypeForService(service, resource); 0304 } 0305 if (type == ClipType::Playlist && Xml::getXmlProperty(m_xml, QStringLiteral("kdenlive:proxy")).length() > 2) { 0306 // If this is a proxied playlist, load as AV 0307 type = ClipType::AV; 0308 service.clear(); 0309 } 0310 std::shared_ptr<Mlt::Producer> producer; 0311 switch (type) { 0312 case ClipType::Color: 0313 producer = loadResource(resource, QStringLiteral("color:")); 0314 break; 0315 case ClipType::Text: 0316 case ClipType::TextTemplate: { 0317 bool ok = false; 0318 int producerLength = 0; 0319 QString pLength = Xml::getXmlProperty(m_xml, QStringLiteral("length")); 0320 if (pLength.isEmpty()) { 0321 producerLength = m_xml.attribute(QStringLiteral("length")).toInt(); 0322 } else { 0323 producerLength = pLength.toInt(&ok); 0324 } 0325 producer = loadResource(resource, QStringLiteral("kdenlivetitle:")); 0326 0327 if (!resource.isEmpty()) { 0328 if (!ok) { 0329 producerLength = producer->time_to_frames(pLength.toUtf8().constData()); 0330 } 0331 // Title from .kdenlivetitle file 0332 QDomDocument txtdoc(QStringLiteral("titledocument")); 0333 if (Xml::docContentFromFile(txtdoc, resource, false)) { 0334 if (txtdoc.documentElement().hasAttribute(QStringLiteral("duration"))) { 0335 duration = txtdoc.documentElement().attribute(QStringLiteral("duration")).toInt(); 0336 } else if (txtdoc.documentElement().hasAttribute(QStringLiteral("out"))) { 0337 duration = txtdoc.documentElement().attribute(QStringLiteral("out")).toInt(); 0338 } 0339 } 0340 } else { 0341 QString xmlDuration = Xml::getXmlProperty(m_xml, QStringLiteral("kdenlive:duration")); 0342 duration = xmlDuration.toInt(&ok); 0343 if (!ok) { 0344 // timecode duration 0345 duration = producer->time_to_frames(xmlDuration.toUtf8().constData()); 0346 } 0347 } 0348 qDebug() << "===== GOT PRODUCER DURATION: " << duration << "; PROD: " << producerLength; 0349 if (duration <= 0) { 0350 if (producerLength > 0) { 0351 duration = producerLength; 0352 } else { 0353 duration = pCore->getDurationFromString(KdenliveSettings::title_duration()); 0354 } 0355 } 0356 if (producerLength <= 0) { 0357 producerLength = duration; 0358 } 0359 producer->set("length", producerLength); 0360 producer->set("kdenlive:duration", producer->frames_to_time(duration)); 0361 producer->set("out", producerLength - 1); 0362 } break; 0363 case ClipType::QText: 0364 producer = loadResource(resource, QStringLiteral("qtext:")); 0365 break; 0366 case ClipType::Qml: { 0367 bool ok; 0368 int producerLength = 0; 0369 QString pLength = Xml::getXmlProperty(m_xml, QStringLiteral("length")); 0370 if (pLength.isEmpty()) { 0371 producerLength = m_xml.attribute(QStringLiteral("length")).toInt(); 0372 } else { 0373 producerLength = pLength.toInt(&ok); 0374 } 0375 if (producerLength <= 0) { 0376 producerLength = pCore->getDurationFromString(KdenliveSettings::title_duration()); 0377 } 0378 producer = loadResource(resource, QStringLiteral("qml:")); 0379 producer->set("length", producerLength); 0380 producer->set("kdenlive:duration", producer->frames_to_time(producerLength)); 0381 producer->set("out", producerLength - 1); 0382 break; 0383 } 0384 case ClipType::Playlist: { 0385 producer = loadPlaylist(resource); 0386 if (producer) { 0387 QFile f(resource); 0388 QDomDocument doc; 0389 doc.setContent(&f, false); 0390 f.close(); 0391 if (resource.endsWith(QLatin1String(".kdenlive"))) { 0392 QDomElement pl = doc.documentElement().firstChildElement(QStringLiteral("playlist")); 0393 if (!pl.isNull()) { 0394 QString offsetData = Xml::getXmlProperty(pl, QStringLiteral("kdenlive:docproperties.seekOffset")); 0395 if (offsetData.isEmpty() && Xml::getXmlProperty(pl, QStringLiteral("kdenlive:docproperties.version")) == QLatin1String("0.98")) { 0396 offsetData = QStringLiteral("30000"); 0397 } 0398 if (!offsetData.isEmpty()) { 0399 bool ok = false; 0400 int offset = offsetData.toInt(&ok); 0401 if (ok) { 0402 qDebug() << " / / / FIXING OFFSET DATA: " << offset; 0403 offset = producer->get_playtime() - offset - 1; 0404 producer->set("out", offset - 1); 0405 producer->set("length", offset); 0406 producer->set("kdenlive:duration", producer->frames_to_time(offset)); 0407 } 0408 } else { 0409 qDebug() << "// NO OFFSET DAT FOUND\n\n"; 0410 } 0411 } else { 0412 qDebug() << ":_______\n______<nEMPTY PLAYLIST\n----"; 0413 } 0414 } else if (resource.endsWith(QLatin1String(".mlt"))) { 0415 // Find the last tractor and check if it has a duration 0416 QDomElement tractor = doc.documentElement().lastChildElement(QStringLiteral("tractor")); 0417 QString duration = Xml::getXmlProperty(tractor, QStringLiteral("kdenlive:duration")); 0418 if (!duration.isEmpty()) { 0419 int frames = producer->time_to_frames(duration.toUtf8().constData()); 0420 producer->set("out", frames - 1); 0421 producer->set("length", frames); 0422 producer->set("kdenlive:duration", duration.toUtf8().constData()); 0423 } 0424 } 0425 } 0426 break; 0427 } 0428 case ClipType::SlideShow: 0429 case ClipType::Image: { 0430 resource.prepend(QStringLiteral("qimage:")); 0431 producer = std::make_shared<Mlt::Producer>(pCore->getProjectProfile(), nullptr, resource.toUtf8().constData()); 0432 break; 0433 } 0434 default: 0435 if (!service.isEmpty()) { 0436 service.append(QChar(':')); 0437 if (service == QLatin1String("avformat-novalidate:")) { 0438 service = QStringLiteral("avformat:"); 0439 } 0440 producer = loadResource(resource, service); 0441 } else { 0442 producer = std::make_shared<Mlt::Producer>(pCore->getProjectProfile(), nullptr, resource.toUtf8().constData()); 0443 } 0444 break; 0445 } 0446 if (m_isCanceled.loadAcquire() == 1 || pCore->taskManager.isBlocked()) { 0447 abort(); 0448 return; 0449 } 0450 0451 if (!producer || producer->is_blank() || !producer->is_valid()) { 0452 qCDebug(KDENLIVE_LOG) << " / / / / / / / / ERROR / / / / // CANNOT LOAD PRODUCER: " << resource; 0453 if (producer) { 0454 producer.reset(); 0455 } 0456 auto binClip = pCore->projectItemModel()->getClipByBinID(QString::number(m_owner.itemId)); 0457 if (binClip && !binClip->isReloading) { 0458 QMetaObject::invokeMethod(pCore.get(), "displayBinMessage", Qt::QueuedConnection, 0459 Q_ARG(QString, m_errorMessage.isEmpty() ? i18n("Cannot open file %1", resource) : m_errorMessage), 0460 Q_ARG(int, int(KMessageWidget::Warning))); 0461 } 0462 Q_EMIT taskDone(); 0463 abort(); 0464 return; 0465 } 0466 const QString mltService = producer->get("mlt_service"); 0467 if (producer->get_length() == INT_MAX && producer->get("eof") == QLatin1String("loop")) { 0468 // This is a live source or broken clip 0469 // Check for AV 0470 ClipType::ProducerType cType = type; 0471 if (producer) { 0472 if (mltService.startsWith(QLatin1String("avformat")) && cType == ClipType::Unknown) { 0473 // Check if it is an audio or video only clip 0474 if (producer->get_int("video_index") == -1) { 0475 cType = ClipType::Audio; 0476 } else if (producer->get_int("audio_index") == -1) { 0477 cType = ClipType::Video; 0478 } else { 0479 cType = ClipType::AV; 0480 } 0481 } 0482 producer.reset(); 0483 } 0484 QMetaObject::invokeMethod(pCore->bin(), "requestTranscoding", Qt::QueuedConnection, Q_ARG(QString, resource), 0485 Q_ARG(QString, QString::number(m_owner.itemId)), Q_ARG(int, cType), Q_ARG(bool, pCore->bin()->shouldCheckProfile), 0486 Q_ARG(QString, QString()), 0487 Q_ARG(QString, i18n("Duration of file <b>%1</b> cannot be determined.", QFileInfo(resource).fileName()))); 0488 if (pCore->bin()->shouldCheckProfile) { 0489 pCore->bin()->shouldCheckProfile = false; 0490 } 0491 Q_EMIT taskDone(); 0492 abort(); 0493 return; 0494 } 0495 // Check external proxies 0496 if (pCore->currentDoc()->useExternalProxy() && mltService.contains(QLatin1String("avformat")) && !producer->property_exists("kdenlive:proxy")) { 0497 // We have a camcorder profile, check if we have opened a proxy clip 0498 QString path = producer->get("resource"); 0499 if (!path.isEmpty() && QFileInfo(path).isRelative() && path != QLatin1String("<tractor>")) { 0500 path.prepend(pCore->currentDoc()->documentRoot()); 0501 producer->set("resource", path.toUtf8().constData()); 0502 } 0503 QString original = ProjectClip::getOriginalFromProxy(path); 0504 if (!original.isEmpty()) { 0505 // Check that original and proxy have the same count of audio streams (for example Sony's FX6 proxy have only 1 audio stream) 0506 auto info = std::make_unique<AudioInfo>(producer); 0507 int proxyAudioStreams = info->size(); 0508 std::shared_ptr<Mlt::Producer> prod(new Mlt::Producer(pCore->getProjectProfile(), "avformat", original.toUtf8().constData())); 0509 // prod->set("video_index", -1); 0510 prod->probe(); 0511 auto info2 = std::make_unique<AudioInfo>(prod); 0512 int sourceStreams = info2->size(); 0513 if (proxyAudioStreams != sourceStreams) { 0514 // Rebuild a proxy with correct audio streams 0515 // Use source clip 0516 producer = std::move(prod); 0517 producer->set("kdenlive:originalurl", original.toUtf8().constData()); 0518 producer->set("kdenlive:camcorderproxy", path.toUtf8().constData()); 0519 const QString clipName = QFileInfo(original).fileName(); 0520 producer->set("kdenlive:clipname", clipName.toUtf8().constData()); 0521 // Trigger proxy rebuild 0522 producer->set("_replaceproxy", 1); 0523 producer->set("_reloadName", 1); 0524 } else { 0525 // Match, we opened a proxy clip 0526 producer->set("kdenlive:proxy", path.toUtf8().constData()); 0527 producer->set("kdenlive:originalurl", original.toUtf8().constData()); 0528 const QString clipName = QFileInfo(original).fileName(); 0529 producer->set("kdenlive:clipname", clipName.toUtf8().constData()); 0530 } 0531 } 0532 } 0533 processProducerProperties(producer, m_xml); 0534 QString clipName = Xml::getXmlProperty(m_xml, QStringLiteral("kdenlive:clipname")); 0535 if (clipName.isEmpty()) { 0536 clipName = QFileInfo(Xml::getXmlProperty(m_xml, QStringLiteral("kdenlive:originalurl"))).fileName(); 0537 } 0538 if (!clipName.isEmpty()) { 0539 producer->set("kdenlive:clipname", clipName.toUtf8().constData()); 0540 } 0541 QString groupId = Xml::getXmlProperty(m_xml, QStringLiteral("kdenlive:folderid")); 0542 if (!groupId.isEmpty()) { 0543 producer->set("kdenlive:folderid", groupId.toUtf8().constData()); 0544 } 0545 int clipOut = 0; 0546 if (m_xml.hasAttribute(QStringLiteral("out"))) { 0547 clipOut = m_xml.attribute(QStringLiteral("out")).toInt(); 0548 } 0549 // setup length here as otherwise default length (currently 15000 frames in MLT) will be taken even if outpoint is larger 0550 QMimeDatabase db; 0551 const QString mime = db.mimeTypeForFile(resource).name(); 0552 const bool isGif = mime.contains(QLatin1String("image/gif")); 0553 if ((duration == 0 && (type == ClipType::Text || type == ClipType::TextTemplate || type == ClipType::QText || type == ClipType::Color || 0554 type == ClipType::Image || type == ClipType::SlideShow)) || 0555 (isGif && mltService == QLatin1String("qimage"))) { 0556 int length; 0557 if (m_xml.hasAttribute(QStringLiteral("length"))) { 0558 length = m_xml.attribute(QStringLiteral("length")).toInt(); 0559 clipOut = qMax(1, length - 1); 0560 } else { 0561 if (isGif && mltService == QLatin1String("qimage")) { 0562 length = pCore->getDurationFromString(KdenliveSettings::image_duration()); 0563 clipOut = qMax(1, length - 1); 0564 } else { 0565 length = Xml::getXmlProperty(m_xml, QStringLiteral("length")).toInt(); 0566 clipOut -= m_xml.attribute(QStringLiteral("in")).toInt(); 0567 if (length < clipOut) { 0568 length = clipOut == 1 ? 1 : clipOut + 1; 0569 } 0570 } 0571 } 0572 // Pass duration if it was forced 0573 if (m_xml.hasAttribute(QStringLiteral("duration"))) { 0574 duration = m_xml.attribute(QStringLiteral("duration")).toInt(); 0575 if (length < duration) { 0576 length = duration; 0577 if (clipOut > 0) { 0578 clipOut = length - 1; 0579 } 0580 } 0581 } 0582 if (duration == 0) { 0583 duration = length; 0584 } 0585 producer->set("length", producer->frames_to_time(length, mlt_time_clock)); 0586 int kdenlive_duration = producer->time_to_frames(Xml::getXmlProperty(m_xml, QStringLiteral("kdenlive:duration")).toUtf8().constData()); 0587 if (kdenlive_duration > 0) { 0588 producer->set("kdenlive:duration", producer->frames_to_time(kdenlive_duration, mlt_time_clock)); 0589 } else { 0590 producer->set("kdenlive:duration", producer->frames_to_time(producer->get_int("length"))); 0591 } 0592 } 0593 if (clipOut > 0) { 0594 producer->set_in_and_out(m_xml.attribute(QStringLiteral("in")).toInt(), clipOut); 0595 } 0596 0597 if (m_xml.hasAttribute(QStringLiteral("templatetext"))) { 0598 producer->set("templatetext", m_xml.attribute(QStringLiteral("templatetext")).toUtf8().constData()); 0599 } 0600 if (type == ClipType::SlideShow) { 0601 processSlideShow(producer); 0602 } 0603 double fps = -1; 0604 bool isVariableFrameRate = false; 0605 bool seekable = true; 0606 bool checkProfile = pCore->bin()->shouldCheckProfile; 0607 if (mltService == QLatin1String("xml") || mltService == QLatin1String("consumer")) { 0608 // MLT playlist, create producer with blank profile to get real profile info 0609 QString tmpPath = resource; 0610 if (tmpPath.startsWith(QLatin1String("consumer:"))) { 0611 tmpPath = "xml:" + tmpPath.section(QLatin1Char(':'), 1); 0612 } 0613 Mlt::Profile original_profile; 0614 std::unique_ptr<Mlt::Producer> tmpProd(new Mlt::Producer(original_profile, nullptr, tmpPath.toUtf8().constData())); 0615 original_profile.set_explicit(1); 0616 double originalFps = original_profile.fps(); 0617 fps = originalFps; 0618 if (originalFps > 0 && !qFuzzyCompare(originalFps, pCore->getCurrentFps())) { 0619 int originalLength = tmpProd->get_length(); 0620 int fixedLength = int(originalLength * pCore->getCurrentFps() / originalFps); 0621 producer->set("length", fixedLength); 0622 producer->set("out", fixedLength - 1); 0623 } 0624 } else if (mltService.startsWith(QLatin1String("avformat"))) { 0625 // Start probe to init properties 0626 int vindex = producer->get_int("video_index"); 0627 bool hasAudio = false; 0628 bool hasVideo = false; 0629 // Work around MLT freeze on files with cover art 0630 if (vindex > -1) { 0631 QString key = QString("meta.media.%1.stream.frame_rate").arg(vindex); 0632 fps = producer->get_double(key.toLatin1().constData()); 0633 key = QString("meta.media.%1.codec.name").arg(vindex); 0634 QString codec_name = producer->get(key.toLatin1().constData()); 0635 key = QString("meta.media.%1.codec.frame_rate").arg(vindex); 0636 QString frame_rate = producer->get(key.toLatin1().constData()); 0637 if (codec_name == QLatin1String("png") || (codec_name == "mjpeg" && frame_rate == QLatin1String("90000"))) { 0638 // Cover art 0639 producer->set("video_index", -1); 0640 producer->set("set.test_image", 1); 0641 vindex = -1; 0642 } 0643 } 0644 // Check audio / video 0645 producer->probe(); 0646 hasAudio = producer->get_int("video_index") > -1; 0647 hasVideo = producer->get_int("audio_index") > -1; 0648 if (hasAudio) { 0649 if (hasVideo) { 0650 producer->set("kdenlive:clip_type", 0); 0651 } else { 0652 producer->set("kdenlive:clip_type", 1); 0653 } 0654 } else if (hasVideo) { 0655 producer->set("kdenlive:clip_type", 2); 0656 } 0657 // Check if file is seekable 0658 seekable = producer->get_int("seekable"); 0659 if (vindex <= -1) { 0660 checkProfile = false; 0661 } 0662 if (!seekable) { 0663 if (checkProfile) { 0664 pCore->bin()->shouldCheckProfile = false; 0665 } 0666 ClipType::ProducerType cType = type; 0667 if (cType == ClipType::Unknown) { 0668 // Check if it is an audio or video only clip 0669 if (!hasVideo) { 0670 cType = ClipType::Audio; 0671 } else if (!hasAudio) { 0672 cType = ClipType::Video; 0673 } else { 0674 cType = ClipType::AV; 0675 } 0676 } 0677 QMetaObject::invokeMethod(pCore->bin(), "requestTranscoding", Qt::QueuedConnection, Q_ARG(QString, resource), 0678 Q_ARG(QString, QString::number(m_owner.itemId)), Q_ARG(int, cType), Q_ARG(bool, checkProfile), Q_ARG(QString, QString()), 0679 Q_ARG(QString, i18n("File <b>%1</b> is not seekable.", QFileInfo(resource).fileName()))); 0680 } 0681 0682 // Check for variable frame rate 0683 isVariableFrameRate = producer->get_int("meta.media.variable_frame_rate"); 0684 if (isVariableFrameRate && seekable) { 0685 if (checkProfile) { 0686 pCore->bin()->shouldCheckProfile = false; 0687 } 0688 QString adjustedFpsString; 0689 if (fps > 0) { 0690 int integerFps = qRound(fps); 0691 adjustedFpsString = QString("-%1fps").arg(integerFps); 0692 } 0693 ClipType::ProducerType cType = type; 0694 if (cType == ClipType::Unknown) { 0695 // Check if it is an audio or video only clip 0696 if (!hasVideo) { 0697 cType = ClipType::Audio; 0698 } else if (!hasAudio) { 0699 cType = ClipType::Video; 0700 } else { 0701 cType = ClipType::AV; 0702 } 0703 } 0704 QMetaObject::invokeMethod(pCore->bin(), "requestTranscoding", Qt::QueuedConnection, Q_ARG(QString, resource), 0705 Q_ARG(QString, QString::number(m_owner.itemId)), Q_ARG(int, cType), Q_ARG(bool, checkProfile), 0706 Q_ARG(QString, adjustedFpsString), 0707 Q_ARG(QString, i18n("File <b>%1</b> has a variable frame rate.", QFileInfo(resource).fileName()))); 0708 } 0709 0710 if (fps <= 0 && !m_isCanceled.loadAcquire()) { 0711 if (producer->get_double("meta.media.frame_rate_den") > 0) { 0712 fps = producer->get_double("meta.media.frame_rate_num") / producer->get_double("meta.media.frame_rate_den"); 0713 } else { 0714 fps = producer->get_double("source_fps"); 0715 } 0716 } 0717 } 0718 if (fps <= 0 && type == ClipType::Unknown) { 0719 // something wrong, maybe audio file with embedded image 0720 if (mime.startsWith(QLatin1String("audio"))) { 0721 producer->set("video_index", -1); 0722 } 0723 } 0724 if (!m_isCanceled.loadAcquire()) { 0725 auto binClip = pCore->projectItemModel()->getClipByBinID(QString::number(m_owner.itemId)); 0726 if (binClip) { 0727 const QString xmlData = ClipController::producerXml(*producer.get(), true, false); 0728 bool replaceProxy = false; 0729 bool replaceName = false; 0730 if (producer->property_exists("_replaceproxy")) { 0731 replaceProxy = true; 0732 } 0733 if (producer->property_exists("_reloadName")) { 0734 replaceName = true; 0735 } 0736 // Reset produccer to get rid of cached frame 0737 producer.reset(new Mlt::Producer(pCore->getProjectProfile(), "xml-string", xmlData.toUtf8().constData())); 0738 if (replaceProxy) { 0739 producer->set("_replaceproxy", 1); 0740 } 0741 if (replaceName) { 0742 producer->set("_reloadName", 1); 0743 } 0744 QMetaObject::invokeMethod(binClip.get(), "setProducer", Qt::QueuedConnection, Q_ARG(std::shared_ptr<Mlt::Producer>, std::move(producer)), 0745 Q_ARG(bool, true)); 0746 if (checkProfile && !isVariableFrameRate && seekable) { 0747 pCore->bin()->shouldCheckProfile = false; 0748 QMetaObject::invokeMethod(pCore->bin(), "slotCheckProfile", Qt::QueuedConnection, Q_ARG(QString, QString::number(m_owner.itemId))); 0749 } 0750 Q_EMIT taskDone(); 0751 return; 0752 } 0753 } 0754 // Might be aborted by profile switch 0755 abort(); 0756 return; 0757 } 0758 0759 void ClipLoadTask::abort() 0760 { 0761 m_progress = 100; 0762 if (pCore->taskManager.isBlocked()) { 0763 return; 0764 } 0765 Fun undo = []() { return true; }; 0766 Fun redo = []() { return true; }; 0767 if (!m_softDelete && !m_thumbOnly) { 0768 auto binClip = pCore->projectItemModel()->getClipByBinID(QString::number(m_owner.itemId)); 0769 if (binClip) { 0770 QMetaObject::invokeMethod(binClip.get(), "setInvalid", Qt::QueuedConnection); 0771 if (!m_isCanceled.loadAcquire() && !binClip->isReloading) { 0772 // User tried to add an invalid clip, remove it. 0773 pCore->projectItemModel()->requestBinClipDeletion(binClip, undo, redo); 0774 } else { 0775 // An existing clip just became invalid, mark it as missing. 0776 binClip->setClipStatus(FileStatus::StatusMissing); 0777 } 0778 } 0779 } 0780 }