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 }