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