1 /*
2 SPDX-FileCopyrightText: 2012 Till Theato <root@ttill.de>
3 SPDX-FileCopyrightText: 2014 Jean-Baptiste Mardelle <jb@kdenlive.org>
4 This file is part of Kdenlive. See www.kdenlive.org.
5 
6 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
7 */
8 
9 #include "clipcontroller.h"
10 #include "bin/model/markerlistmodel.hpp"
11 #include "doc/docundostack.hpp"
12 #include "doc/kdenlivedoc.h"
13 #include "effects/effectstack/model/effectstackmodel.hpp"
14 #include "kdenlivesettings.h"
15 #include "lib/audio/audioStreamInfo.h"
16 #include "profiles/profilemodel.hpp"
17 #include "bin/clipcreator.hpp"
18 #include "doc/kthumb.h"
19 
20 #include "core.h"
21 #include "kdenlive_debug.h"
22 #include <KLocalizedString>
23 #include <QFileInfo>
24 #include <QPixmap>
25 
26 std::shared_ptr<Mlt::Producer> ClipController::mediaUnavailable;
27 
ClipController(const QString & clipId,const std::shared_ptr<Mlt::Producer> & producer)28 ClipController::ClipController(const QString &clipId, const std::shared_ptr<Mlt::Producer> &producer)
29     : selectedEffectIndex(1)
30     , m_audioThumbCreated(false)
31     , m_producerLock(QReadWriteLock::Recursive)
32     , m_masterProducer(producer)
33     , m_properties(producer ? new Mlt::Properties(producer->get_properties()) : nullptr)
34     , m_usesProxy(false)
35     , m_audioInfo(nullptr)
36     , m_videoIndex(0)
37     , m_clipType(ClipType::Unknown)
38     , m_hasLimitedDuration(true)
39     , m_effectStack(producer ? EffectStackModel::construct(producer, {ObjectType::BinClip, clipId.toInt()}, pCore->undoStack()) : nullptr)
40     , m_hasAudio(false)
41     , m_hasVideo(false)
42     , m_thumbsProducer(nullptr)
43     , m_controllerBinId(clipId)
44 {
45     if (m_masterProducer && !m_masterProducer->is_valid()) {
46         qCDebug(KDENLIVE_LOG) << "// WARNING, USING INVALID PRODUCER";
47         return;
48     }
49     if (m_properties) {
50         setProducerProperty(QStringLiteral("kdenlive:id"), m_controllerBinId);
51         getInfoForProducer();
52         checkAudioVideo();
53     } else {
54         m_producerLock.lockForWrite();
55     }
56 }
57 
~ClipController()58 ClipController::~ClipController()
59 {
60     delete m_properties;
61     m_thumbsProducer.reset();
62     m_masterProducer.reset();
63 }
64 
binId() const65 const QString ClipController::binId() const
66 {
67     return m_controllerBinId;
68 }
69 
audioInfo() const70 const std::unique_ptr<AudioStreamInfo> &ClipController::audioInfo() const
71 {
72     return m_audioInfo;
73 }
74 
addMasterProducer(const std::shared_ptr<Mlt::Producer> & producer)75 void ClipController::addMasterProducer(const std::shared_ptr<Mlt::Producer> &producer)
76 {
77     qDebug() << "################### ClipController::addmasterproducer";
78     m_masterProducer = producer;
79     m_properties = new Mlt::Properties(m_masterProducer->get_properties());
80     m_producerLock.unlock();
81     // Pass temporary properties
82     QMapIterator<QString, QVariant> i(m_tempProps);
83     while (i.hasNext()) {
84         i.next();
85         switch(i.value().type()) {
86             case QVariant::Int:
87                 setProducerProperty(i.key(), i.value().toInt());
88                 break;
89             case QVariant::Double:
90                 setProducerProperty(i.key(), i.value().toDouble());
91                 break;
92             default:
93                 setProducerProperty(i.key(), i.value().toString());
94                 break;
95         }
96     }
97     m_tempProps.clear();
98     int id = m_controllerBinId.toInt();
99     m_effectStack = EffectStackModel::construct(producer, {ObjectType::BinClip, id}, pCore->undoStack());
100     if (!m_masterProducer->is_valid()) {
101         m_masterProducer = ClipController::mediaUnavailable;
102         qCDebug(KDENLIVE_LOG) << "// WARNING, USING INVALID PRODUCER";
103     } else {
104         checkAudioVideo();
105         setProducerProperty(QStringLiteral("kdenlive:id"), m_controllerBinId);
106         getInfoForProducer();
107         emitProducerChanged(m_controllerBinId, producer);
108     }
109     connectEffectStack();
110 }
111 
112 namespace {
producerXml(const std::shared_ptr<Mlt::Producer> & producer,bool includeMeta,bool includeProfile)113 QString producerXml(const std::shared_ptr<Mlt::Producer> &producer, bool includeMeta, bool includeProfile)
114 {
115     Mlt::Consumer c(*producer->profile(), "xml", "string");
116     Mlt::Service s(producer->get_service());
117     if (!s.is_valid()) {
118         return QString();
119     }
120     int ignore = s.get_int("ignore_points");
121     if (ignore != 0) {
122         s.set("ignore_points", 0);
123     }
124     c.set("time_format", "frames");
125     if (!includeMeta) {
126         c.set("no_meta", 1);
127     }
128     if (!includeProfile) {
129         c.set("no_profile", 1);
130     }
131     c.set("store", "kdenlive");
132     c.set("no_root", 1);
133     c.set("root", "/");
134     c.connect(s);
135     c.start();
136     if (ignore != 0) {
137         s.set("ignore_points", ignore);
138     }
139     return QString::fromUtf8(c.get("string"));
140 }
141 } // namespace
142 
getProducerXML(QDomDocument & document,bool includeMeta,bool includeProfile)143 void ClipController::getProducerXML(QDomDocument &document, bool includeMeta, bool includeProfile)
144 {
145     // TODO refac this is a probable duplicate with Clip::xml
146     if (m_masterProducer) {
147         QString xml = producerXml(m_masterProducer, includeMeta, includeProfile);
148         document.setContent(xml);
149     } else {
150         if (!m_temporaryUrl.isEmpty()) {
151             document = ClipCreator::getXmlFromUrl(m_temporaryUrl);
152         } else if (!m_path.isEmpty()) {
153             document = ClipCreator::getXmlFromUrl(m_path);
154         }
155         qCDebug(KDENLIVE_LOG) << " + + ++ NO MASTER PROD";
156     }
157 }
158 
getInfoForProducer()159 void ClipController::getInfoForProducer()
160 {
161     QReadLocker lock(&m_producerLock);
162     m_service = m_properties->get("mlt_service");
163     if (m_service == QLatin1String("qtext")) {
164         // Placeholder clip, find real service
165         QString originalService = m_properties->get("kdenlive:orig_service");
166         if (!originalService.isEmpty()) {
167             m_service = originalService;
168         }
169     }
170     QString proxy = m_properties->get("kdenlive:proxy");
171     QString path = m_properties->get("resource");
172     if (proxy.length() > 2) {
173         if (QFileInfo(path).isRelative()) {
174             path.prepend(pCore->currentDoc()->documentRoot());
175             m_properties->set("resource", path.toUtf8().constData());
176         }
177         if (QFileInfo(proxy).isRelative()) {
178             proxy.prepend(pCore->currentDoc()->documentRoot());
179             m_properties->set("kdenlive:proxy", proxy.toUtf8().constData());
180         }
181         // This is a proxy producer, read original url from kdenlive property
182         path = m_properties->get("kdenlive:originalurl");
183         if (QFileInfo(path).isRelative()) {
184             path.prepend(pCore->currentDoc()->documentRoot());
185         }
186         m_usesProxy = true;
187     } else if (m_service != QLatin1String("color") && m_service != QLatin1String("colour") && !path.isEmpty() && QFileInfo(path).isRelative() &&
188                    path != QLatin1String("<producer>")) {
189         path.prepend(pCore->currentDoc()->documentRoot());
190         m_properties->set("resource", path.toUtf8().constData());
191     }
192     m_path = path.isEmpty() ? QString() : QFileInfo(path).absoluteFilePath();
193     QString origurl = m_properties->get("kdenlive:originalurl");
194     if (!origurl.isEmpty()) {
195         m_properties->set("kdenlive:originalurl", m_path.toUtf8().constData());
196     }
197     date = QFileInfo(m_path).lastModified();
198     m_videoIndex = -1;
199     int audioIndex = -1;
200     // special case: playlist with a proxy clip have to be detected separately
201     if (m_usesProxy && m_path.endsWith(QStringLiteral(".mlt"))) {
202         m_clipType = ClipType::Playlist;
203     } else if (m_service == QLatin1String("avformat") || m_service == QLatin1String("avformat-novalidate")) {
204         audioIndex = getProducerIntProperty(QStringLiteral("audio_index"));
205         m_videoIndex = getProducerIntProperty(QStringLiteral("video_index"));
206         if (m_videoIndex == -1) {
207             m_clipType = ClipType::Audio;
208         } else {
209             if (audioIndex == -1) {
210                 m_clipType = ClipType::Video;
211             } else {
212                 m_clipType = ClipType::AV;
213             }
214             if (m_service == QLatin1String("avformat")) {
215                 m_properties->set("mlt_service", "avformat-novalidate");
216                 m_properties->set("mute_on_pause", 0);
217             }
218         }
219     } else if (m_service == QLatin1String("qimage") || m_service == QLatin1String("pixbuf")) {
220         if (m_path.contains(QLatin1Char('%')) || m_path.contains(QStringLiteral("/.all.")) || m_path.contains(QStringLiteral("\\.all."))) {
221             m_clipType = ClipType::SlideShow;
222             m_hasLimitedDuration = true;
223         } else {
224             m_clipType = ClipType::Image;
225             m_hasLimitedDuration = false;
226         }
227     } else if (m_service == QLatin1String("colour") || m_service == QLatin1String("color")) {
228         m_clipType = ClipType::Color;
229         // Required for faster compositing
230         m_masterProducer->set("mlt_image_format", "rgb");
231         m_hasLimitedDuration = false;
232     } else if (m_service == QLatin1String("kdenlivetitle")) {
233         if (!m_path.isEmpty()) {
234             m_clipType = ClipType::TextTemplate;
235         } else {
236             m_clipType = ClipType::Text;
237         }
238         m_hasLimitedDuration = false;
239     } else if (m_service == QLatin1String("xml") || m_service == QLatin1String("consumer")) {
240         m_clipType = ClipType::Playlist;
241     } else if (m_service == QLatin1String("webvfx")) {
242         m_clipType = ClipType::WebVfx;
243     } else if (m_service == QLatin1String("qtext")) {
244         m_clipType = ClipType::QText;
245     } else if (m_service == QLatin1String("qml")) {
246         m_clipType = ClipType::Qml;
247         m_hasLimitedDuration = false;
248     } else if (m_service == QLatin1String("blipflash")) {
249         // Mostly used for testing
250         m_clipType = ClipType::AV;
251         m_hasLimitedDuration = true;
252     } else {
253         m_clipType = ClipType::Unknown;
254     }
255     if (audioIndex > -1 || m_clipType == ClipType::Playlist) {
256         m_audioInfo = std::make_unique<AudioStreamInfo>(m_masterProducer, audioIndex, m_clipType == ClipType::Playlist);
257         // Load stream effects
258         for (int stream : m_audioInfo->streams().keys()) {
259             QString streamEffect = m_properties->get(QString("kdenlive:stream:%1").arg(stream).toUtf8().constData());
260             if (!streamEffect.isEmpty()) {
261                 m_streamEffects.insert(stream, streamEffect.split(QChar('#')));
262             }
263         }
264     }
265 
266     if (!m_hasLimitedDuration) {
267         int playtime = m_masterProducer->time_to_frames(m_masterProducer->get("kdenlive:duration"));
268         if (playtime <= 0) {
269             // Fix clips having missing kdenlive:duration
270             m_masterProducer->set("kdenlive:duration", m_masterProducer->frames_to_time(m_masterProducer->get_playtime(), mlt_time_clock));
271             m_masterProducer->set("out", m_masterProducer->frames_to_time(m_masterProducer->get_length() - 1, mlt_time_clock));
272         }
273     }
274 }
275 
hasLimitedDuration() const276 bool ClipController::hasLimitedDuration() const
277 {
278     return m_hasLimitedDuration;
279 }
280 
forceLimitedDuration()281 void ClipController::forceLimitedDuration()
282 {
283     m_hasLimitedDuration = true;
284 }
285 
originalProducer()286 std::shared_ptr<Mlt::Producer> ClipController::originalProducer()
287 {
288     QReadLocker lock(&m_producerLock);
289     return m_masterProducer;
290 }
291 
masterProducer()292 Mlt::Producer *ClipController::masterProducer()
293 {
294     return new Mlt::Producer(*m_masterProducer);
295 }
296 
isValid()297 bool ClipController::isValid()
298 {
299     if (m_masterProducer == nullptr) {
300         return false;
301     }
302     return m_masterProducer->is_valid();
303 }
304 
305 // static
getPassPropertiesList(bool passLength)306 const char *ClipController::getPassPropertiesList(bool passLength)
307 {
308     if (!passLength) {
309         return "kdenlive:proxy,kdenlive:originalurl,force_aspect_num,force_aspect_den,force_aspect_ratio,force_fps,force_progressive,force_tff,threads,force_"
310                "colorspace,set.force_full_luma,file_hash,autorotate,disable_exif,xmldata,video_index,audio_index,set.test_image,set.test_audio";
311     }
312     return "kdenlive:proxy,kdenlive:originalurl,force_aspect_num,force_aspect_den,force_aspect_ratio,force_fps,force_progressive,force_tff,threads,force_"
313            "colorspace,set.force_full_luma,templatetext,file_hash,autorotate,disable_exif,xmldata,length,video_index,audio_index,set.test_image,set.test_audio";
314 }
315 
getPropertiesFromPrefix(const QString & prefix,bool withPrefix)316 QMap<QString, QString> ClipController::getPropertiesFromPrefix(const QString &prefix, bool withPrefix)
317 {
318     QReadLocker lock(&m_producerLock);
319     Mlt::Properties subProperties;
320     subProperties.pass_values(*m_properties, prefix.toUtf8().constData());
321     QMap<QString, QString> subclipsData;
322     for (int i = 0; i < subProperties.count(); i++) {
323         subclipsData.insert(withPrefix ? QString(prefix + subProperties.get_name(i)) : subProperties.get_name(i), subProperties.get(i));
324     }
325     return subclipsData;
326 }
327 
updateProducer(const std::shared_ptr<Mlt::Producer> & producer)328 void ClipController::updateProducer(const std::shared_ptr<Mlt::Producer> &producer)
329 {
330     qDebug() << "################### ClipController::updateProducer";
331     if (!m_properties) {
332         // producer has not been initialized
333         return addMasterProducer(producer);
334     }
335     m_producerLock.lockForWrite();
336     Mlt::Properties passProperties;
337     // Keep track of necessary properties
338     QString proxy = producer->get("kdenlive:proxy");
339     if (proxy.length() > 2) {
340         // This is a proxy producer, read original url from kdenlive property
341         m_usesProxy = true;
342     } else {
343         m_usesProxy = false;
344     }
345     // When resetting profile, duration can change so we invalidate it to 0 in that case
346     int length = m_properties->get_int("length");
347     const char *passList = getPassPropertiesList(m_usesProxy && length > 0);
348     // This is necessary as some properties like set.test_audio are reset on producer creation
349     passProperties.pass_list(*m_properties, passList);
350     delete m_properties;
351     *m_masterProducer = producer.get();
352     m_properties = new Mlt::Properties(m_masterProducer->get_properties());
353     m_producerLock.unlock();
354     if (!m_masterProducer->is_valid()) {
355         qCDebug(KDENLIVE_LOG) << "// WARNING, USING INVALID PRODUCER";
356     } else {
357         // Pass properties from previous producer
358         m_properties->pass_list(passProperties, passList);
359         checkAudioVideo();
360         setProducerProperty(QStringLiteral("kdenlive:id"), m_controllerBinId);
361         m_effectStack->resetService(m_masterProducer);
362         emitProducerChanged(m_controllerBinId, producer);
363         if (m_clipType == ClipType::Unknown) {
364             getInfoForProducer();
365         }
366         // URL and name should not be updated otherwise when proxying a clip we cannot find back the original url
367         /*m_url = QUrl::fromLocalFile(m_masterProducer->get("resource"));
368         if (m_url.isValid()) {
369             m_name = m_url.fileName();
370         }
371         */
372     }
373     qDebug() << "// replace finished: " << binId() << " : " << m_masterProducer->get("resource");
374 }
375 
getStringDuration()376 const QString ClipController::getStringDuration()
377 {
378     QReadLocker lock(&m_producerLock);
379     if (m_masterProducer) {
380         int playtime = m_masterProducer->time_to_frames(m_masterProducer->get("kdenlive:duration"));
381         if (playtime > 0) {
382             return QString(m_properties->frames_to_time(playtime, mlt_time_smpte_df));
383         }
384         return m_properties->frames_to_time(m_masterProducer->get_length(), mlt_time_smpte_df);
385     }
386     return i18n("Unknown");
387 }
388 
getProducerDuration() const389 int ClipController::getProducerDuration() const
390 {
391     QReadLocker lock(&m_producerLock);
392     if (m_masterProducer) {
393         int playtime = m_masterProducer->time_to_frames(m_masterProducer->get("kdenlive:duration"));
394         if (playtime <= 0) {
395             return m_masterProducer->get_length();
396         }
397         return playtime;
398     }
399     return -1;
400 }
401 
framesToTime(int frames) const402 char *ClipController::framesToTime(int frames) const
403 {
404     QReadLocker lock(&m_producerLock);
405     if (m_masterProducer) {
406         return m_masterProducer->frames_to_time(frames, mlt_time_clock);
407     }
408     return nullptr;
409 }
410 
getPlaytime() const411 GenTime ClipController::getPlaytime() const
412 {
413     QReadLocker lock(&m_producerLock);
414     if (!m_masterProducer || !m_masterProducer->is_valid()) {
415         return GenTime();
416     }
417     double fps = pCore->getCurrentFps();
418     if (!m_hasLimitedDuration) {
419         int playtime = m_masterProducer->time_to_frames(m_masterProducer->get("kdenlive:duration"));
420         return GenTime(playtime == 0 ? m_masterProducer->get_playtime() : playtime, fps);
421     }
422     return {m_masterProducer->get_playtime(), fps};
423 }
424 
getFramePlaytime() const425 int ClipController::getFramePlaytime() const
426 {
427     QReadLocker lock(&m_producerLock);
428     if (!m_masterProducer || !m_masterProducer->is_valid()) {
429         return 0;
430     }
431     if (!m_hasLimitedDuration) {
432         int playtime = m_masterProducer->time_to_frames(m_masterProducer->get("kdenlive:duration"));
433         return playtime == 0 ? m_masterProducer->get_length() : playtime;
434     }
435     return m_masterProducer->get_length();
436 }
437 
hasProducerProperty(const QString & name) const438 bool ClipController::hasProducerProperty(const QString &name) const
439 {
440     QReadLocker lock(&m_producerLock);
441     if (m_properties == nullptr) {
442         return false;
443     }
444     return m_properties->property_exists(name.toUtf8().constData());
445 }
446 
getProducerProperty(const QString & name) const447 QString ClipController::getProducerProperty(const QString &name) const
448 {
449     QReadLocker lock(&m_producerLock);
450     if (m_properties == nullptr) {
451         return QString();
452     }
453     if (m_usesProxy && name.startsWith(QLatin1String("meta."))) {
454         QString correctedName = QStringLiteral("kdenlive:") + name;
455         return m_properties->get(correctedName.toUtf8().constData());
456     }
457     return QString(m_properties->get(name.toUtf8().constData()));
458 }
459 
getProducerIntProperty(const QString & name) const460 int ClipController::getProducerIntProperty(const QString &name) const
461 {
462     QReadLocker lock(&m_producerLock);
463     if (!m_properties) {
464         return 0;
465     }
466     if (m_usesProxy && name.startsWith(QLatin1String("meta."))) {
467         QString correctedName = QStringLiteral("kdenlive:") + name;
468         return m_properties->get_int(correctedName.toUtf8().constData());
469     }
470     return m_properties->get_int(name.toUtf8().constData());
471 }
472 
getProducerInt64Property(const QString & name) const473 qint64 ClipController::getProducerInt64Property(const QString &name) const
474 {
475     QReadLocker lock(&m_producerLock);
476     if (!m_properties) {
477         return 0;
478     }
479     return m_properties->get_int64(name.toUtf8().constData());
480 }
481 
getProducerDoubleProperty(const QString & name) const482 double ClipController::getProducerDoubleProperty(const QString &name) const
483 {
484     QReadLocker lock(&m_producerLock);
485     if (!m_properties) {
486         return 0;
487     }
488     return m_properties->get_double(name.toUtf8().constData());
489 }
490 
getProducerColorProperty(const QString & name) const491 QColor ClipController::getProducerColorProperty(const QString &name) const
492 {
493     QReadLocker lock(&m_producerLock);
494     if (!m_properties) {
495         return {};
496     }
497     mlt_color color = m_properties->get_color(name.toUtf8().constData());
498     return QColor::fromRgb(color.r, color.g, color.b);
499 }
500 
currentProperties(const QMap<QString,QString> & props)501 QMap<QString, QString> ClipController::currentProperties(const QMap<QString, QString> &props)
502 {
503     QMap<QString, QString> currentProps;
504     QMap<QString, QString>::const_iterator i = props.constBegin();
505     while (i != props.constEnd()) {
506         currentProps.insert(i.key(), getProducerProperty(i.key()));
507         ++i;
508     }
509     return currentProps;
510 }
511 
originalFps() const512 double ClipController::originalFps() const
513 {
514     QReadLocker lock(&m_producerLock);
515     if (!m_properties) {
516         return 0;
517     }
518     QString propertyName = QStringLiteral("meta.media.%1.stream.frame_rate").arg(m_videoIndex);
519     return m_properties->get_double(propertyName.toUtf8().constData());
520 }
521 
videoCodecProperty(const QString & property) const522 QString ClipController::videoCodecProperty(const QString &property) const
523 {
524     QReadLocker lock(&m_producerLock);
525     if (!m_properties) {
526         return QString();
527     }
528     QString propertyName = QStringLiteral("meta.media.%1.codec.%2").arg(m_videoIndex).arg(property);
529     return m_properties->get(propertyName.toUtf8().constData());
530 }
531 
codec(bool audioCodec) const532 const QString ClipController::codec(bool audioCodec) const
533 {
534     QReadLocker lock(&m_producerLock);
535     if ((m_properties == nullptr) || (m_clipType != ClipType::AV && m_clipType != ClipType::Video && m_clipType != ClipType::Audio)) {
536         return QString();
537     }
538     QString propertyName = QStringLiteral("meta.media.%1.codec.name").arg(audioCodec ? m_properties->get_int("audio_index") : m_videoIndex);
539     return m_properties->get(propertyName.toUtf8().constData());
540 }
541 
clipUrl() const542 const QString ClipController::clipUrl() const
543 {
544     return m_path;
545 }
546 
sourceExists() const547 bool ClipController::sourceExists() const
548 {
549     if (m_clipType == ClipType::Color || m_clipType == ClipType::Text) {
550         return true;
551     }
552     if (m_clipType == ClipType::SlideShow) {
553         //TODO
554         return true;
555     }
556     return QFile::exists(m_path);
557 }
558 
clipName() const559 QString ClipController::clipName() const
560 {
561     QString name = getProducerProperty(QStringLiteral("kdenlive:clipname"));
562     if (!name.isEmpty()) {
563         return name;
564     }
565     return m_path.isEmpty() ? i18n("Unnamed") : QFileInfo(m_path).fileName();
566 }
567 
description() const568 QString ClipController::description() const
569 {
570     if (m_clipType == ClipType::TextTemplate) {
571         QString name = getProducerProperty(QStringLiteral("templatetext"));
572         return name;
573     }
574     QString name = getProducerProperty(QStringLiteral("kdenlive:description"));
575     if (!name.isEmpty()) {
576         return name;
577     }
578     return getProducerProperty(QStringLiteral("meta.attr.comment.markup"));
579 }
580 
serviceName() const581 QString ClipController::serviceName() const
582 {
583     return m_service;
584 }
585 
setProducerProperty(const QString & name,int value)586 void ClipController::setProducerProperty(const QString &name, int value)
587 {
588     if (!m_masterProducer) {
589         m_tempProps.insert(name, value);
590         return;
591     }
592     QWriteLocker lock(&m_producerLock);
593     m_masterProducer->parent().set(name.toUtf8().constData(), value);
594 }
595 
setProducerProperty(const QString & name,double value)596 void ClipController::setProducerProperty(const QString &name, double value)
597 {
598     if (!m_masterProducer) {
599         m_tempProps.insert(name, value);
600         return;
601     }
602     QWriteLocker lock(&m_producerLock);
603     m_masterProducer->parent().set(name.toUtf8().constData(), value);
604 }
605 
setProducerProperty(const QString & name,const QString & value)606 void ClipController::setProducerProperty(const QString &name, const QString &value)
607 {
608     if (!m_masterProducer) {
609         m_tempProps.insert(name, value);
610         return;
611     }
612 
613     QWriteLocker lock(&m_producerLock);
614     if (value.isEmpty()) {
615         m_masterProducer->parent().set(name.toUtf8().constData(), nullptr);
616     } else {
617         m_masterProducer->parent().set(name.toUtf8().constData(), value.toUtf8().constData());
618     }
619 }
620 
resetProducerProperty(const QString & name)621 void ClipController::resetProducerProperty(const QString &name)
622 {
623     if (!m_masterProducer) {
624         m_tempProps.insert(name, QString());
625         return;
626     }
627 
628     QWriteLocker lock(&m_producerLock);
629     m_masterProducer->parent().set(name.toUtf8().constData(), nullptr);
630 }
631 
clipType() const632 ClipType::ProducerType ClipController::clipType() const
633 {
634     return m_clipType;
635 }
636 
getFrameSize() const637 const QSize ClipController::getFrameSize() const
638 {
639     QReadLocker lock(&m_producerLock);
640     if (m_masterProducer == nullptr) {
641         return QSize();
642     }
643     if (m_usesProxy) {
644         int width = m_masterProducer->get_int("kdenlive:original.meta.media.width");
645         int height = m_masterProducer->get_int("kdenlive:original.meta.media.height");
646         if (width == 0) {
647             width = m_masterProducer->get_int("kdenlive:original.width");
648         }
649         if (height == 0) {
650             width = m_masterProducer->get_int("kdenlive:original.height");
651         }
652         if (width > 0 && height > 0) {
653             return QSize(width, height);
654         }
655     }
656     int width = m_masterProducer->get_int("meta.media.width");
657     if (width == 0) {
658         width = m_masterProducer->get_int("width");
659     }
660     int height = m_masterProducer->get_int("meta.media.height");
661     if (height == 0) {
662         height = m_masterProducer->get_int("height");
663     }
664     return QSize(width, height);
665 }
666 
hasAudio() const667 bool ClipController::hasAudio() const
668 {
669     return m_hasAudio;
670 }
checkAudioVideo()671 void ClipController::checkAudioVideo()
672 {
673     QReadLocker lock(&m_producerLock);
674     m_masterProducer->seek(0);
675     if (m_masterProducer->get_int("_placeholder") == 1 || m_masterProducer->get_int("_missingsource") == 1) {
676         // This is a placeholder file, try to guess from its properties
677         QString orig_service = m_masterProducer->get("kdenlive:orig_service");
678         if (orig_service.startsWith(QStringLiteral("avformat")) || (m_masterProducer->get_int("audio_index") + m_masterProducer->get_int("video_index") > 0)) {
679             m_hasAudio = m_masterProducer->get_int("audio_index") >= 0;
680             m_hasVideo = m_masterProducer->get_int("video_index") >= 0;
681         } else if (orig_service == QStringLiteral("xml")) {
682             // Playlist, assume we have audio and video
683             m_hasAudio = true;
684             m_hasVideo = true;
685         } else {
686             // Assume image or text producer
687             m_hasAudio = false;
688             m_hasVideo = true;
689         }
690         return;
691     }
692     QScopedPointer<Mlt::Frame> frame(m_masterProducer->get_frame());
693     if (frame->is_valid()) {
694         // test_audio returns 1 if there is NO audio (strange but true at the time this code is written)
695         m_hasAudio = frame->get_int("test_audio") == 0;
696         m_hasVideo = frame->get_int("test_image") == 0;
697         m_masterProducer->seek(0);
698     } else {
699         qDebug()<<"* * * *ERROR INVALID FRAME On test";
700     }
701 }
hasVideo() const702 bool ClipController::hasVideo() const
703 {
704     return m_hasVideo;
705 }
defaultState() const706 PlaylistState::ClipState ClipController::defaultState() const
707 {
708     if (hasVideo()) {
709         return PlaylistState::VideoOnly;
710     }
711     if (hasAudio()) {
712         return PlaylistState::AudioOnly;
713     }
714     return PlaylistState::Disabled;
715 }
716 
pixmap(int framePosition,int width,int height)717 QPixmap ClipController::pixmap(int framePosition, int width, int height)
718 {
719     // TODO refac this should use the new thumb infrastructure
720     QReadLocker lock(&m_producerLock);
721     if (thumbProducer() == nullptr) {
722         return QPixmap();
723     }
724     m_thumbsProducer->seek(framePosition);
725     QScopedPointer<Mlt::Frame> frame(m_thumbsProducer->get_frame());
726     if (frame == nullptr || !frame->is_valid()) {
727         QPixmap p(width, height);
728         p.fill(QColor(Qt::red).rgb());
729         return p;
730     }
731     frame->set("deinterlace_method", "onefield");
732     frame->set("top_field_first", -1);
733     frame->set("rescale.interp", "nearest");
734     QImage img = KThumb::getFrame(frame.data());
735     return QPixmap::fromImage(img/*.scaled(height, width, Qt::KeepAspectRatio)*/);
736 }
737 
setZone(const QPoint & zone)738 void ClipController::setZone(const QPoint &zone)
739 {
740     setProducerProperty(QStringLiteral("kdenlive:zone_in"), zone.x());
741     setProducerProperty(QStringLiteral("kdenlive:zone_out"), zone.y());
742 }
743 
zone() const744 QPoint ClipController::zone() const
745 {
746     int in = getProducerIntProperty(QStringLiteral("kdenlive:zone_in"));
747     int max = getFramePlaytime();
748     int out = qMin(getProducerIntProperty(QStringLiteral("kdenlive:zone_out")), max);
749     if (out <= in) {
750         out = max;
751     }
752     QPoint zone(in, out);
753     return zone;
754 }
755 
getClipHash() const756 const QString ClipController::getClipHash() const
757 {
758     return getProducerProperty(QStringLiteral("kdenlive:file_hash"));
759 }
760 
properties()761 Mlt::Properties &ClipController::properties()
762 {
763     QReadLocker lock(&m_producerLock);
764     return *m_properties;
765 }
766 
767 
backupOriginalProperties()768 void ClipController::backupOriginalProperties()
769 {
770     QReadLocker lock(&m_producerLock);
771     if (m_properties->get_int("kdenlive:original.backup") == 1) {
772         return;
773     }
774     int propsCount = m_properties->count();
775     // store original props
776     QStringList doNotPass {QStringLiteral("kdenlive:proxy"),QStringLiteral("kdenlive:originalurl"),QStringLiteral("kdenlive:clipname")};
777     for (int j = 0; j < propsCount; j++) {
778         QString propName = m_properties->get_name(j);
779         if (doNotPass.contains(propName)) {
780             continue;
781         }
782         if (!propName.startsWith(QLatin1Char('_'))) {
783             propName.prepend(QStringLiteral("kdenlive:original."));
784             m_properties->set(propName.toUtf8().constData(), m_properties->get(j));
785         }
786     }
787     m_properties->set("kdenlive:original.backup", 1);
788 }
789 
clearBackupProperties()790 void ClipController::clearBackupProperties()
791 {
792     QReadLocker lock(&m_producerLock);
793     if (m_properties->get_int("kdenlive:original.backup") == 0) {
794         return;
795     }
796     int propsCount = m_properties->count();
797     // clear original props
798     QStringList passProps;
799     for (int j = 0; j < propsCount; j++) {
800         QString propName = m_properties->get_name(j);
801         if (propName.startsWith(QLatin1String("kdenlive:original."))) {
802             passProps << propName;
803         }
804     }
805     for (const QString &p : qAsConst(passProps)) {
806         m_properties->set(p.toUtf8().constData(), nullptr);
807     }
808     m_properties->set("kdenlive:original.backup", nullptr);
809 }
810 
mirrorOriginalProperties(Mlt::Properties & props)811 void ClipController::mirrorOriginalProperties(Mlt::Properties &props)
812 {
813     QReadLocker lock(&m_producerLock);
814     if (m_usesProxy && QFileInfo(m_properties->get("resource")).fileName() == QFileInfo(m_properties->get("kdenlive:proxy")).fileName()) {
815         // This is a proxy, we need to use the real source properties
816         if (m_properties->get_int("kdenlive:original.backup") == 0) {
817             // We have a proxy clip, load original source producer
818             std::shared_ptr<Mlt::Producer> prod = std::make_shared<Mlt::Producer>(pCore->getCurrentProfile()->profile(), nullptr, m_path.toUtf8().constData());
819             // Get frame to make sure we retrieve all original props
820             std::shared_ptr<Mlt::Frame> fr(prod->get_frame());
821             if (!prod->is_valid()) {
822                 return;
823             }
824             int width = 0;
825             int height = 0;
826             mlt_image_format format = mlt_image_none;
827             fr->get_image(format, width, height);
828             Mlt::Properties sourceProps(prod->get_properties());
829             props.inherit(sourceProps);
830             int propsCount = sourceProps.count();
831             // store original props
832             QStringList doNotPass {QStringLiteral("kdenlive:proxy"),QStringLiteral("kdenlive:originalurl"),QStringLiteral("kdenlive:clipname")};
833             for (int i = 0; i < propsCount; i++) {
834                 QString propName = sourceProps.get_name(i);
835                 if (doNotPass.contains(propName)) {
836                     continue;
837                 }
838                 if (!propName.startsWith(QLatin1Char('_'))) {
839                     propName.prepend(QStringLiteral("kdenlive:original."));
840                     m_properties->set(propName.toUtf8().constData(), sourceProps.get(i));
841                 }
842             }
843             m_properties->set("kdenlive:original.backup", 1);
844         }
845         // Properties were fetched in the past, reuse
846         Mlt::Properties sourceProps;
847         sourceProps.pass_values(*m_properties, "kdenlive:original.");
848         props.inherit(sourceProps);
849     } else {
850         if (m_clipType == ClipType::AV || m_clipType == ClipType::Video || m_clipType == ClipType::Audio) {
851             // Make sure that a frame / image was fetched to initialize all meta properties
852             QString progressive = m_properties->get("meta.media.progressive");
853             if (progressive.isEmpty()) {
854                 // Fetch a frame to initialize required properties
855                 QScopedPointer<Mlt::Producer> tmpProd(nullptr);
856                 if (KdenliveSettings::gpu_accel()) {
857                     QString service = m_masterProducer->get("mlt_service");
858                     tmpProd.reset(new Mlt::Producer(pCore->getCurrentProfile()->profile(), service.toUtf8().constData(), m_masterProducer->get("resource")));
859                 }
860                 std::shared_ptr<Mlt::Frame> fr(tmpProd ? tmpProd->get_frame() : m_masterProducer->get_frame());
861                 mlt_image_format format = mlt_image_none;
862                 int width = 0;
863                 int height = 0;
864                 fr->get_image(format, width, height);
865             }
866         }
867         props.inherit(*m_properties);
868     }
869 }
870 
effectsCount()871 int ClipController::effectsCount()
872 {
873     int count = 0;
874     QReadLocker lock(&m_producerLock);
875     Mlt::Service service(m_masterProducer->parent());
876     for (int ix = 0; ix < service.filter_count(); ++ix) {
877         QScopedPointer<Mlt::Filter> effect(service.filter(ix));
878         QString id = effect->get("kdenlive_id");
879         if (!id.isEmpty()) {
880             count++;
881         }
882     }
883     return count;
884 }
885 
filesUsedByEffects()886 QStringList ClipController::filesUsedByEffects()
887 {
888     return m_effectStack->externalFiles();
889 }
890 
hasEffects() const891 bool ClipController::hasEffects() const
892 {
893     return m_effectStack->rowCount() > 0;
894 }
895 
setBinEffectsEnabled(bool enabled)896 void ClipController::setBinEffectsEnabled(bool enabled)
897 {
898     m_effectStack->setEffectStackEnabled(enabled);
899 }
900 
saveZone(QPoint zone,const QDir & dir)901 void ClipController::saveZone(QPoint zone, const QDir &dir)
902 {
903     QString path = QString(clipName() + QLatin1Char('_') + QString::number(zone.x()) + QStringLiteral(".mlt"));
904     if (dir.exists(path)) {
905         // TODO ask for overwrite
906     }
907     Mlt::Consumer xmlConsumer(pCore->getCurrentProfile()->profile(), ("xml:" + dir.absoluteFilePath(path)).toUtf8().constData());
908     xmlConsumer.set("terminate_on_pause", 1);
909     QReadLocker lock(&m_producerLock);
910     Mlt::Producer prod(m_masterProducer->get_producer());
911     Mlt::Producer *prod2 = prod.cut(zone.x(), zone.y());
912     Mlt::Playlist list(pCore->getCurrentProfile()->profile());
913     list.insert_at(0, *prod2, 0);
914     // list.set("title", desc.toUtf8().constData());
915     xmlConsumer.connect(list);
916     xmlConsumer.run();
917     delete prod2;
918 }
919 
getEffectStack() const920 std::shared_ptr<EffectStackModel> ClipController::getEffectStack() const
921 {
922     return m_effectStack;
923 }
924 
addEffect(const QString & effectId)925 bool ClipController::addEffect(const QString &effectId)
926 {
927     return m_effectStack->appendEffect(effectId, true);
928 }
929 
copyEffect(const std::shared_ptr<EffectStackModel> & stackModel,int rowId)930 bool ClipController::copyEffect(const std::shared_ptr<EffectStackModel> &stackModel, int rowId)
931 {
932     m_effectStack->copyEffect(stackModel->getEffectStackRow(rowId),
933                               !m_hasAudio ? PlaylistState::VideoOnly : !m_hasVideo ? PlaylistState::AudioOnly : PlaylistState::Disabled);
934     return true;
935 }
936 
getMarkerModel() const937 std::shared_ptr<MarkerListModel> ClipController::getMarkerModel() const
938 {
939     return m_markerModel;
940 }
941 
refreshAudioInfo()942 void ClipController::refreshAudioInfo()
943 {
944     if (m_audioInfo && m_masterProducer) {
945         QReadLocker lock(&m_producerLock);
946         m_audioInfo->setAudioIndex(m_masterProducer, m_properties->get_int("audio_index"));
947     }
948 }
949 
audioStreams() const950 QMap <int, QString> ClipController::audioStreams() const
951 {
952     if (m_audioInfo) {
953         return m_audioInfo->streams();
954     }
955     return {};
956 }
957 
activeStreamChannels() const958 QList <int> ClipController::activeStreamChannels() const
959 {
960     if (!audioInfo()) {
961         return QList <int>();
962     }
963     return audioInfo()->activeStreamChannels();
964 }
965 
activeStreams() const966 QMap <int, QString> ClipController::activeStreams() const
967 {
968     if (m_audioInfo) {
969         return m_audioInfo->activeStreams();
970     }
971     return {};
972 }
973 
audioStreamsCount() const974 int ClipController::audioStreamsCount() const
975 {
976     if (m_audioInfo) {
977         return m_audioInfo->streams().count();
978     }
979     return 0;
980 }
981 
getOriginalUrl()982 const QString ClipController::getOriginalUrl()
983 {
984     QString path = m_properties->get("kdenlive:originalurl");
985     if (path.isEmpty()) {
986         path = m_path;
987     }
988     if (QFileInfo(path).isRelative()) {
989         path.prepend(pCore->currentDoc()->documentRoot());
990     }
991     return path;
992 }
993