clipcontroller.cpp 34.4 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
/*
Copyright (C) 2012  Till Theato <root@ttill.de>
Copyright (C) 2014  Jean-Baptiste Mardelle <jb@kdenlive.org>
This file is part of Kdenlive. See www.kdenlive.org.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License or (at your option) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
Laurent Montel's avatar
Laurent Montel committed
11
by the membership of KDE e.V.), which shall act as a proxy
12
13
14
15
16
17
18
19
20
21
22
23
defined in Section 14 of version 3 of the license.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "clipcontroller.h"
24
#include "bin/model/markerlistmodel.hpp"
25
#include "doc/docundostack.hpp"
26
#include "doc/kdenlivedoc.h"
27
#include "effects/effectstack/model/effectstackmodel.hpp"
28
#include "kdenlivesettings.h"
Nicolas Carion's avatar
Nicolas Carion committed
29
#include "lib/audio/audioStreamInfo.h"
30
#include "profiles/profilemodel.hpp"
31

32
#include "core.h"
Nicolas Carion's avatar
Nicolas Carion committed
33
#include "kdenlive_debug.h"
34
#include <KLocalizedString>
Nicolas Carion's avatar
Nicolas Carion committed
35
36
#include <QFileInfo>
#include <QPixmap>
37

Nicolas Carion's avatar
Nicolas Carion committed
38
std::shared_ptr<Mlt::Producer> ClipController::mediaUnavailable;
39

Nicolas Carion's avatar
Nicolas Carion committed
40
ClipController::ClipController(const QString &clipId, const std::shared_ptr<Mlt::Producer> &producer)
41
42
43
    : selectedEffectIndex(1)
    , m_audioThumbCreated(false)
    , m_masterProducer(producer)
44
    , m_properties(producer ? new Mlt::Properties(producer->get_properties()) : nullptr)
45
46
47
    , m_usesProxy(false)
    , m_audioInfo(nullptr)
    , m_videoIndex(0)
48
    , m_clipType(ClipType::Unknown)
49
    , m_hasLimitedDuration(true)
50
    , m_effectStack(producer ? EffectStackModel::construct(producer, {ObjectType::BinClip, clipId.toInt()}, pCore->undoStack()) : nullptr)
51
    , m_hasAudio(false)
Vincent Pinon's avatar
Vincent Pinon committed
52
53
    , m_hasVideo(false)
    , m_controllerBinId(clipId)
54
{
55
    if (m_masterProducer && !m_masterProducer->is_valid()) {
Laurent Montel's avatar
Laurent Montel committed
56
        qCDebug(KDENLIVE_LOG) << "// WARNING, USING INVALID PRODUCER";
57
        return;
Nicolas Carion's avatar
Nicolas Carion committed
58
    }
59
    if (m_properties) {
60
        setProducerProperty(QStringLiteral("kdenlive:id"), m_controllerBinId);
61
62
63
64
        m_service = m_properties->get("mlt_service");
        QString proxy = m_properties->get("kdenlive:proxy");
        QString path = m_properties->get("resource");
        if (proxy.length() > 2) {
65
66
67
68
            if (QFileInfo(path).isRelative()) {
                path.prepend(pCore->currentDoc()->documentRoot());
                m_properties->set("resource", path.toUtf8().constData());
            }
69
70
71
            // This is a proxy producer, read original url from kdenlive property
            path = m_properties->get("kdenlive:originalurl");
            if (QFileInfo(path).isRelative()) {
72
                path.prepend(pCore->currentDoc()->documentRoot());
73
74
            }
            m_usesProxy = true;
75
76
        } else if (m_service != QLatin1String("color") && m_service != QLatin1String("colour") && !path.isEmpty() && QFileInfo(path).isRelative() &&
                   path != QLatin1String("<producer>")) {
77
            path.prepend(pCore->currentDoc()->documentRoot());
78
            m_properties->set("resource", path.toUtf8().constData());
79
        }
80
        m_path = path.isEmpty() ? QString() : QFileInfo(path).absoluteFilePath();
81
        getInfoForProducer();
82
        checkAudioVideo();
83
84
    } else {
        m_producerLock.lock();
Nicolas Carion's avatar
Nicolas Carion committed
85
    }
86
87
}

88
ClipController::~ClipController()
89
{
90
91
    delete m_properties;
    m_masterProducer.reset();
92
93
}

94
const QString ClipController::binId() const
95
{
96
    return m_controllerBinId;
97
98
}

99
const std::unique_ptr<AudioStreamInfo> &ClipController::audioInfo() const
100
101
102
103
{
    return m_audioInfo;
}

Nicolas Carion's avatar
Nicolas Carion committed
104
void ClipController::addMasterProducer(const std::shared_ptr<Mlt::Producer> &producer)
105
{
106
    qDebug() << "################### ClipController::addmasterproducer";
107
    QString documentRoot = pCore->currentDoc()->documentRoot();
108
    m_masterProducer = producer;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
109
    m_properties = new Mlt::Properties(m_masterProducer->get_properties());
110
    int id = m_controllerBinId.toInt();
111
    m_effectStack = EffectStackModel::construct(producer, {ObjectType::BinClip, id}, pCore->undoStack());
Laurent Montel's avatar
Laurent Montel committed
112
    if (!m_masterProducer->is_valid()) {
113
        m_masterProducer = ClipController::mediaUnavailable;
114
        m_producerLock.unlock();
Laurent Montel's avatar
Laurent Montel committed
115
116
        qCDebug(KDENLIVE_LOG) << "// WARNING, USING INVALID PRODUCER";
    } else {
117
        checkAudioVideo();
118
        m_producerLock.unlock();
119
        QString proxy = m_properties->get("kdenlive:proxy");
120
121
122
        m_service = m_properties->get("mlt_service");
        QString path = m_properties->get("resource");
        m_usesProxy = false;
123
124
        if (proxy.length() > 2) {
            // This is a proxy producer, read original url from kdenlive property
125
126
            path = m_properties->get("kdenlive:originalurl");
            if (QFileInfo(path).isRelative()) {
127
                path.prepend(documentRoot);
128
            }
129
            m_usesProxy = true;
130
        } else if (m_service != QLatin1String("color") && m_service != QLatin1String("colour") && !path.isEmpty() && QFileInfo(path).isRelative()) {
131
            path.prepend(documentRoot);
132
        }
133
        m_path = path.isEmpty() ? QString() : QFileInfo(path).absoluteFilePath();
134
        getInfoForProducer();
135
136
        emitProducerChanged(m_controllerBinId, producer);
        setProducerProperty(QStringLiteral("kdenlive:id"), m_controllerBinId);
137
    }
138
    connectEffectStack();
139
140
}

141
namespace {
142
QString producerXml(const std::shared_ptr<Mlt::Producer> &producer, bool includeMeta, bool includeProfile)
143
{
Nicolas Carion's avatar
Nicolas Carion committed
144
    Mlt::Consumer c(*producer->profile(), "xml", "string");
145
146
147
148
149
150
151
152
153
154
155
156
    Mlt::Service s(producer->get_service());
    if (!s.is_valid()) {
        return QString();
    }
    int ignore = s.get_int("ignore_points");
    if (ignore != 0) {
        s.set("ignore_points", 0);
    }
    c.set("time_format", "frames");
    if (!includeMeta) {
        c.set("no_meta", 1);
    }
157
158
159
    if (!includeProfile) {
        c.set("no_profile", 1);
    }
160
161
162
163
164
165
166
167
168
169
    c.set("store", "kdenlive");
    c.set("no_root", 1);
    c.set("root", "/");
    c.connect(s);
    c.start();
    if (ignore != 0) {
        s.set("ignore_points", ignore);
    }
    return QString::fromUtf8(c.get("string"));
}
Nicolas Carion's avatar
Nicolas Carion committed
170
} // namespace
171

172
void ClipController::getProducerXML(QDomDocument &document, bool includeMeta, bool includeProfile)
173
{
174
    // TODO refac this is a probable duplicate with Clip::xml
175
    if (m_masterProducer) {
176
        QString xml = producerXml(m_masterProducer, includeMeta, includeProfile);
177
        document.setContent(xml);
Laurent Montel's avatar
Laurent Montel committed
178
179
    } else {
        qCDebug(KDENLIVE_LOG) << " + + ++ NO MASTER PROD";
180
181
182
    }
}

183
184
void ClipController::getInfoForProducer()
{
185
    date = QFileInfo(m_path).lastModified();
186
    m_videoIndex = -1;
187
    int audioIndex = -1;
188
    // special case: playlist with a proxy clip have to be detected separately
189
    if (m_usesProxy && m_path.endsWith(QStringLiteral(".mlt"))) {
190
        m_clipType = ClipType::Playlist;
191
    } else if (m_service == QLatin1String("avformat") || m_service == QLatin1String("avformat-novalidate")) {
192
        audioIndex = getProducerIntProperty(QStringLiteral("audio_index"));
193
        m_videoIndex = getProducerIntProperty(QStringLiteral("video_index"));
194
        if (m_videoIndex == -1) {
195
            m_clipType = ClipType::Audio;
Laurent Montel's avatar
Laurent Montel committed
196
        } else {
197
198
199
200
201
202
203
204
            if (audioIndex == -1) {
                m_clipType = ClipType::Video;
            } else {
                m_clipType = ClipType::AV;
            }
            if (m_service == QLatin1String("avformat")) {
                m_properties->set("mlt_service", "avformat-novalidate");
            }
205
        }
Laurent Montel's avatar
Laurent Montel committed
206
    } else if (m_service == QLatin1String("qimage") || m_service == QLatin1String("pixbuf")) {
Laurent Montel's avatar
Laurent Montel committed
207
        if (m_path.contains(QLatin1Char('%')) || m_path.contains(QStringLiteral("/.all."))) {
208
            m_clipType = ClipType::SlideShow;
209
            m_hasLimitedDuration = true;
Laurent Montel's avatar
Laurent Montel committed
210
        } else {
211
            m_clipType = ClipType::Image;
212
            m_hasLimitedDuration = false;
213
        }
Laurent Montel's avatar
Laurent Montel committed
214
    } else if (m_service == QLatin1String("colour") || m_service == QLatin1String("color")) {
215
        m_clipType = ClipType::Color;
216
        m_hasLimitedDuration = false;
Laurent Montel's avatar
Laurent Montel committed
217
    } else if (m_service == QLatin1String("kdenlivetitle")) {
218
        if (!m_path.isEmpty()) {
219
            m_clipType = ClipType::TextTemplate;
220
        } else {
221
            m_clipType = ClipType::Text;
222
        }
223
        m_hasLimitedDuration = false;
Laurent Montel's avatar
Laurent Montel committed
224
    } else if (m_service == QLatin1String("xml") || m_service == QLatin1String("consumer")) {
225
        m_clipType = ClipType::Playlist;
Laurent Montel's avatar
Laurent Montel committed
226
    } else if (m_service == QLatin1String("webvfx")) {
227
        m_clipType = ClipType::WebVfx;
Laurent Montel's avatar
Laurent Montel committed
228
    } else if (m_service == QLatin1String("qtext")) {
229
        m_clipType = ClipType::QText;
230
231
232
233
    } else if (m_service == QLatin1String("blipflash")) {
        // Mostly used for testing
        m_clipType = ClipType::AV;
        m_hasLimitedDuration = true;
Laurent Montel's avatar
Laurent Montel committed
234
    } else {
235
        m_clipType = ClipType::Unknown;
Vincent Pinon's avatar
Vincent Pinon committed
236
    }
237
238
    if (audioIndex > -1 || m_clipType == ClipType::Playlist) {
        m_audioInfo = std::make_unique<AudioStreamInfo>(m_masterProducer, audioIndex);
239
    }
240
241

    if (!m_hasLimitedDuration) {
242
        int playtime = m_masterProducer->time_to_frames(m_masterProducer->get("kdenlive:duration"));
243
244
        if (playtime <= 0) {
            // Fix clips having missing kdenlive:duration
245
246
            m_masterProducer->set("kdenlive:duration", m_masterProducer->frames_to_time(m_masterProducer->get_playtime(), mlt_time_clock));
            m_masterProducer->set("out", m_masterProducer->frames_to_time(m_masterProducer->get_length() - 1, mlt_time_clock));
247
248
        }
    }
249
250
}

251
252
253
254
255
bool ClipController::hasLimitedDuration() const
{
    return m_hasLimitedDuration;
}

Nicolas Carion's avatar
Nicolas Carion committed
256
257
258
259
260
void ClipController::forceLimitedDuration()
{
    m_hasLimitedDuration = true;
}

261
std::shared_ptr<Mlt::Producer> ClipController::originalProducer()
262
{
263
    QMutexLocker lock(&m_producerLock);
264
    return m_masterProducer;
265
266
267
268
269
270
271
272
273
}

Mlt::Producer *ClipController::masterProducer()
{
    return new Mlt::Producer(*m_masterProducer);
}

bool ClipController::isValid()
{
Laurent Montel's avatar
Laurent Montel committed
274
    if (m_masterProducer == nullptr) {
Laurent Montel's avatar
Laurent Montel committed
275
276
        return false;
    }
277
278
279
    return m_masterProducer->is_valid();
}

280
// static
281
const char *ClipController::getPassPropertiesList(bool passLength)
282
{
283
    if (!passLength) {
284
285
        return "kdenlive:proxy,kdenlive:originalurl,force_aspect_num,force_aspect_den,force_aspect_ratio,force_fps,force_progressive,force_tff,threads,force_"
               "colorspace,set.force_full_luma,file_hash,autorotate,xmldata,video_index,audio_index,set.test_image,set.test_audio";
286
    }
287
288
    return "kdenlive:proxy,kdenlive:originalurl,force_aspect_num,force_aspect_den,force_aspect_ratio,force_fps,force_progressive,force_tff,threads,force_"
           "colorspace,set.force_full_luma,templatetext,file_hash,autorotate,xmldata,length,video_index,audio_index,set.test_image,set.test_audio";
289
290
}

Laurent Montel's avatar
Laurent Montel committed
291
QMap<QString, QString> ClipController::getPropertiesFromPrefix(const QString &prefix, bool withPrefix)
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
292
293
{
    Mlt::Properties subProperties;
294
    subProperties.pass_values(*m_properties, prefix.toUtf8().constData());
Laurent Montel's avatar
Laurent Montel committed
295
    QMap<QString, QString> subclipsData;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
296
    for (int i = 0; i < subProperties.count(); i++) {
297
        subclipsData.insert(withPrefix ? QString(prefix + subProperties.get_name(i)) : subProperties.get_name(i), subProperties.get(i));
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
298
299
300
301
    }
    return subclipsData;
}

Nicolas Carion's avatar
Nicolas Carion committed
302
void ClipController::updateProducer(const std::shared_ptr<Mlt::Producer> &producer)
303
{
304
    qDebug() << "################### ClipController::updateProducer";
Nicolas Carion's avatar
Nicolas Carion committed
305
    // TODO replace all track producers
306
307
308
309
    if (!m_properties) {
        // producer has not been initialized
        return addMasterProducer(producer);
    }
310
311
    Mlt::Properties passProperties;
    // Keep track of necessary properties
312
    QString proxy = producer->get("kdenlive:proxy");
313
314
315
    if (proxy.length() > 2) {
        // This is a proxy producer, read original url from kdenlive property
        m_usesProxy = true;
Laurent Montel's avatar
Laurent Montel committed
316
317
    } else {
        m_usesProxy = false;
318
    }
319
320
321
    // When resetting profile, duration can change so we invalidate it to 0 in that case
    int length = m_properties->get_int("length");
    const char *passList = getPassPropertiesList(m_usesProxy && length > 0);
322
323
    // This is necessary as some properties like set.test_audio are reset on producer creation
    passProperties.pass_list(*m_properties, passList);
324
    delete m_properties;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
325
    *m_masterProducer = producer.get();
326
    checkAudioVideo();
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
327
    m_properties = new Mlt::Properties(m_masterProducer->get_properties());
328
    // Pass properties from previous producer
329
    m_properties->pass_list(passProperties, passList);
Laurent Montel's avatar
Laurent Montel committed
330
331
332
    if (!m_masterProducer->is_valid()) {
        qCDebug(KDENLIVE_LOG) << "// WARNING, USING INVALID PRODUCER";
    } else {
333
        m_effectStack->resetService(m_masterProducer);
334
        emitProducerChanged(m_controllerBinId, producer);
335
        // URL and name should not be updated otherwise when proxying a clip we cannot find back the original url
336
337
338
339
340
341
        /*m_url = QUrl::fromLocalFile(m_masterProducer->get("resource"));
        if (m_url.isValid()) {
            m_name = m_url.fileName();
        }
        */
    }
342
    qDebug() << "// replace finished: " << binId() << " : " << m_masterProducer->get("resource");
343
344
345
346
}

const QString ClipController::getStringDuration()
{
347
    if (m_masterProducer) {
348
        int playtime = m_masterProducer->time_to_frames(m_masterProducer->get("kdenlive:duration"));
349
        if (playtime > 0) {
350
            return QString(m_properties->frames_to_time(playtime, mlt_time_smpte_df));
351
        }
352
        return m_properties->frames_to_time(m_masterProducer->get_length(), mlt_time_smpte_df);
353
    }
Laurent Montel's avatar
Laurent Montel committed
354
    return i18n("Unknown");
355
356
}

357
358
359
int ClipController::getProducerDuration() const
{
    if (m_masterProducer) {
360
        int playtime = m_masterProducer->time_to_frames(m_masterProducer->get("kdenlive:duration"));
361
362
363
364
365
366
367
368
        if (playtime <= 0) {
            return playtime = m_masterProducer->get_length();
        }
        return playtime;
    }
    return -1;
}

369
370
371
372
373
374
375
376
char *ClipController::framesToTime(int frames) const
{
    if (m_masterProducer) {
        return m_masterProducer->frames_to_time(frames, mlt_time_clock);
    }
    return nullptr;
}

377
GenTime ClipController::getPlaytime() const
378
{
379
380
381
    if (!m_masterProducer || !m_masterProducer->is_valid()) {
        return GenTime();
    }
382
    double fps = pCore->getCurrentFps();
383
    if (!m_hasLimitedDuration) {
384
        int playtime = m_masterProducer->time_to_frames(m_masterProducer->get("kdenlive:duration"));
385
        return GenTime(playtime == 0 ? m_masterProducer->get_playtime() : playtime, fps);
386
    }
Nicolas Carion's avatar
Nicolas Carion committed
387
    return {m_masterProducer->get_playtime(), fps};
388
389
}

390
391
392
393
394
395
int ClipController::getFramePlaytime() const
{
    if (!m_masterProducer || !m_masterProducer->is_valid()) {
        return 0;
    }
    if (!m_hasLimitedDuration) {
396
        int playtime = m_masterProducer->time_to_frames(m_masterProducer->get("kdenlive:duration"));
397
398
399
400
401
        return playtime == 0 ? m_masterProducer->get_playtime() : playtime;
    }
    return m_masterProducer->get_playtime();
}

402
QString ClipController::getProducerProperty(const QString &name) const
403
{
Laurent Montel's avatar
Laurent Montel committed
404
405
406
    if (!m_properties) {
        return QString();
    }
Laurent Montel's avatar
Laurent Montel committed
407
    if (m_usesProxy && name.startsWith(QLatin1String("meta."))) {
408
409
410
        QString correctedName = QStringLiteral("kdenlive:") + name;
        return m_properties->get(correctedName.toUtf8().constData());
    }
411
    return QString(m_properties->get(name.toUtf8().constData()));
412
413
}

414
int ClipController::getProducerIntProperty(const QString &name) const
415
{
Laurent Montel's avatar
Laurent Montel committed
416
417
418
    if (!m_properties) {
        return 0;
    }
Laurent Montel's avatar
Laurent Montel committed
419
    if (m_usesProxy && name.startsWith(QLatin1String("meta."))) {
420
421
422
        QString correctedName = QStringLiteral("kdenlive:") + name;
        return m_properties->get_int(correctedName.toUtf8().constData());
    }
423
    return m_properties->get_int(name.toUtf8().constData());
424
425
}

426
qint64 ClipController::getProducerInt64Property(const QString &name) const
427
{
Laurent Montel's avatar
Laurent Montel committed
428
429
430
    if (!m_properties) {
        return 0;
    }
431
432
433
    return m_properties->get_int64(name.toUtf8().constData());
}

434
double ClipController::getProducerDoubleProperty(const QString &name) const
435
{
Laurent Montel's avatar
Laurent Montel committed
436
437
438
    if (!m_properties) {
        return 0;
    }
439
440
441
    return m_properties->get_double(name.toUtf8().constData());
}

442
QColor ClipController::getProducerColorProperty(const QString &name) const
443
{
Laurent Montel's avatar
Laurent Montel committed
444
    if (!m_properties) {
Nicolas Carion's avatar
Nicolas Carion committed
445
        return {};
Laurent Montel's avatar
Laurent Montel committed
446
    }
447
448
449
450
    mlt_color color = m_properties->get_color(name.toUtf8().constData());
    return QColor::fromRgb(color.r, color.g, color.b);
}

451
452
453
454
455
456
457
458
459
460
461
QMap<QString, QString> ClipController::currentProperties(const QMap<QString, QString> &props)
{
    QMap<QString, QString> currentProps;
    QMap<QString, QString>::const_iterator i = props.constBegin();
    while (i != props.constEnd()) {
        currentProps.insert(i.key(), getProducerProperty(i.key()));
        ++i;
    }
    return currentProps;
}

462
463
double ClipController::originalFps() const
{
Laurent Montel's avatar
Laurent Montel committed
464
465
466
    if (!m_properties) {
        return 0;
    }
467
    QString propertyName = QStringLiteral("meta.media.%1.stream.frame_rate").arg(m_videoIndex);
468
469
470
    return m_properties->get_double(propertyName.toUtf8().constData());
}

471
472
QString ClipController::videoCodecProperty(const QString &property) const
{
Laurent Montel's avatar
Laurent Montel committed
473
474
475
    if (!m_properties) {
        return QString();
    }
476
    QString propertyName = QStringLiteral("meta.media.%1.codec.%2").arg(m_videoIndex).arg(property);
477
478
479
    return m_properties->get(propertyName.toUtf8().constData());
}

480
481
const QString ClipController::codec(bool audioCodec) const
{
482
    if ((m_properties == nullptr) || (m_clipType != ClipType::AV && m_clipType != ClipType::Video && m_clipType != ClipType::Audio)) {
Laurent Montel's avatar
Laurent Montel committed
483
484
        return QString();
    }
485
    QString propertyName = QStringLiteral("meta.media.%1.codec.name").arg(audioCodec ? m_properties->get_int("audio_index") : m_videoIndex);
486
487
488
    return m_properties->get(propertyName.toUtf8().constData());
}

489
const QString ClipController::clipUrl() const
490
{
491
    return m_path;
492
493
}

494
495
496
497
498
499
500
501
502
503
504
505
bool ClipController::sourceExists() const
{
    if (m_clipType == ClipType::Color || m_clipType == ClipType::Text) {
        return true;
    }
    if (m_clipType == ClipType::SlideShow) {
        //TODO
        return true;
    }
    return QFile::exists(m_path);
}

506
507
QString ClipController::clipName() const
{
508
    QString name = getProducerProperty(QStringLiteral("kdenlive:clipname"));
Laurent Montel's avatar
Laurent Montel committed
509
510
511
    if (!name.isEmpty()) {
        return name;
    }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
512
    return QFileInfo(m_path).fileName();
513
514
}

515
516
QString ClipController::description() const
{
517
    if (m_clipType == ClipType::TextTemplate) {
518
        QString name = getProducerProperty(QStringLiteral("templatetext"));
519
520
        return name;
    }
521
    QString name = getProducerProperty(QStringLiteral("kdenlive:description"));
Laurent Montel's avatar
Laurent Montel committed
522
523
524
    if (!name.isEmpty()) {
        return name;
    }
525
    return getProducerProperty(QStringLiteral("meta.attr.comment.markup"));
526
527
}

528
529
530
531
532
QString ClipController::serviceName() const
{
    return m_service;
}

Nicolas Carion's avatar
Nicolas Carion committed
533
void ClipController::setProducerProperty(const QString &name, int value)
534
{
535
    if (!m_masterProducer) return;
Nicolas Carion's avatar
Nicolas Carion committed
536
    // TODO: also set property on all track producers
Jean-Baptiste Mardelle's avatar
cleanup    
Jean-Baptiste Mardelle committed
537
    m_masterProducer->parent().set(name.toUtf8().constData(), value);
538
539
}

Nicolas Carion's avatar
Nicolas Carion committed
540
void ClipController::setProducerProperty(const QString &name, double value)
541
{
542
    if (!m_masterProducer) return;
Nicolas Carion's avatar
Nicolas Carion committed
543
    // TODO: also set property on all track producers
Jean-Baptiste Mardelle's avatar
cleanup    
Jean-Baptiste Mardelle committed
544
    m_masterProducer->parent().set(name.toUtf8().constData(), value);
545
546
}

Nicolas Carion's avatar
Nicolas Carion committed
547
void ClipController::setProducerProperty(const QString &name, const QString &value)
548
{
549
    if (!m_masterProducer) return;
Nicolas Carion's avatar
Nicolas Carion committed
550
    // TODO: also set property on all track producers
551
    if (value.isEmpty()) {
Laurent Montel's avatar
Laurent Montel committed
552
        m_masterProducer->parent().set(name.toUtf8().constData(), (char *)nullptr);
Laurent Montel's avatar
Laurent Montel committed
553
554
    } else {
        m_masterProducer->parent().set(name.toUtf8().constData(), value.toUtf8().constData());
555
    }
556
557
}

558
void ClipController::resetProducerProperty(const QString &name)
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
559
{
Nicolas Carion's avatar
Nicolas Carion committed
560
    // TODO: also set property on all track producers
561
    if (!m_masterProducer) return;
Laurent Montel's avatar
Laurent Montel committed
562
    m_masterProducer->parent().set(name.toUtf8().constData(), (char *)nullptr);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
563
564
}

565
ClipType::ProducerType ClipController::clipType() const
566
{
567
    return m_clipType;
568
569
}

570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
const QSize ClipController::getFrameSize() const
{
    if (m_masterProducer == nullptr) {
        return QSize();
    }
    int width = m_masterProducer->get_int("meta.media.width");
    if (width == 0) {
        width = m_masterProducer->get_int("width");
    }
    int height = m_masterProducer->get_int("meta.media.height");
    if (height == 0) {
        height = m_masterProducer->get_int("height");
    }
    return QSize(width, height);
}

586
587
588
589
bool ClipController::hasAudio() const
{
    return m_hasAudio;
}
590
void ClipController::checkAudioVideo()
591
592
{
    m_masterProducer->seek(0);
593
    if (m_masterProducer->get_int("_placeholder") == 1 || m_masterProducer->get("text") == QLatin1String("INVALID")) {
594
595
        // This is a placeholder file, try to guess from its properties
        QString orig_service = m_masterProducer->get("kdenlive:orig_service");
596
        if (orig_service.startsWith(QStringLiteral("avformat")) || (m_masterProducer->get_int("audio_index") + m_masterProducer->get_int("video_index") > 0)) {
597
598
599
600
601
602
603
604
605
            m_hasAudio = m_masterProducer->get_int("audio_index") >= 0;
            m_hasVideo = m_masterProducer->get_int("video_index") >= 0;
        } else {
            // Assume image or text producer
            m_hasAudio = false;
            m_hasVideo = true;
        }
        return;
    }
606
    QScopedPointer<Mlt::Frame> frame(m_masterProducer->get_frame());
607
608
609
610
611
612
613
    if (frame->is_valid()) {
        // test_audio returns 1 if there is NO audio (strange but true at the time this code is written)
        m_hasAudio = frame->get_int("test_audio") == 0;
        m_hasVideo = frame->get_int("test_image") == 0;
    } else {
        qDebug()<<"* * * *ERROR INVALID FRAME On test";
    }
614
615
616
617
618
}
bool ClipController::hasVideo() const
{
    return m_hasVideo;
}
619
PlaylistState::ClipState ClipController::defaultState() const
620
621
622
623
624
625
626
627
{
    if (hasVideo()) {
        return PlaylistState::VideoOnly;
    }
    if (hasAudio()) {
        return PlaylistState::AudioOnly;
    }
    return PlaylistState::Disabled;
628
629
}

630
631
QPixmap ClipController::pixmap(int framePosition, int width, int height)
{
632
    // TODO refac this should use the new thumb infrastructure
633
634
    m_masterProducer->seek(framePosition);
    Mlt::Frame *frame = m_masterProducer->get_frame();
Laurent Montel's avatar
Laurent Montel committed
635
    if (frame == nullptr || !frame->is_valid()) {
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
        QPixmap p(width, height);
        p.fill(QColor(Qt::red).rgb());
        return p;
    }

    frame->set("rescale.interp", "bilinear");
    frame->set("deinterlace_method", "onefield");
    frame->set("top_field_first", -1);

    if (width == 0) {
        width = m_masterProducer->get_int("meta.media.width");
        if (width == 0) {
            width = m_masterProducer->get_int("width");
        }
    }
    if (height == 0) {
        height = m_masterProducer->get_int("meta.media.height");
        if (height == 0) {
            height = m_masterProducer->get_int("height");
        }
    }
    //     int ow = frameWidth;
    //     int oh = height;
    mlt_image_format format = mlt_image_rgb24a;
660
661
    width += width % 2;
    height += height % 2;
Laurent Montel's avatar
Laurent Montel committed
662
    const uchar *imagedata = frame->get_image(format, width, height);
663
664
    QImage image(imagedata, width, height, QImage::Format_RGBA8888);
    QPixmap pixmap;
665
    pixmap.convertFromImage(image);
666
    delete frame;
667
668
    return pixmap;
}
669
670
671

void ClipController::setZone(const QPoint &zone)
{
Nicolas Carion's avatar
Nicolas Carion committed
672
673
    setProducerProperty(QStringLiteral("kdenlive:zone_in"), zone.x());
    setProducerProperty(QStringLiteral("kdenlive:zone_out"), zone.y());
674
675
676
677
}

QPoint ClipController::zone() const
{
678
    int in = getProducerIntProperty(QStringLiteral("kdenlive:zone_in"));
679
    int max = getFramePlaytime() - 1;
680
    int out = qMin(getProducerIntProperty(QStringLiteral("kdenlive:zone_out")), max);
Laurent Montel's avatar
Laurent Montel committed
681
682
683
    if (out <= in) {
        out = max;
    }
684
685
686
687
688
689
    QPoint zone(in, out);
    return zone;
}

const QString ClipController::getClipHash() const
{
690
    return getProducerProperty(QStringLiteral("kdenlive:file_hash"));
691
692
}

693
694
695
696
Mlt::Properties &ClipController::properties()
{
    return *m_properties;
}
697

698
699
700
701
702
703
704
705

void ClipController::backupOriginalProperties()
{
    if (m_properties->get_int("kdenlive:original.backup") == 1) {
        return;
    }
    int propsCount = m_properties->count();
    // store original props
706
    QStringList doNotPass {QStringLiteral("kdenlive:proxy"),QStringLiteral("kdenlive:originalurl"),QStringLiteral("kdenlive:clipname")};
707
708
    for (int j = 0; j < propsCount; j++) {
        QString propName = m_properties->get_name(j);
709
710
711
        if (doNotPass.contains(propName)) {
            continue;
        } 
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
        if (!propName.startsWith(QLatin1Char('_'))) {
            propName.prepend(QStringLiteral("kdenlive:original."));
            m_properties->set(propName.toUtf8().constData(), m_properties->get(j));
        }
    }
    m_properties->set("kdenlive:original.backup", 1);
}

void ClipController::clearBackupProperties()
{
    if (m_properties->get_int("kdenlive:original.backup") == 0) {
        return;
    }
    int propsCount = m_properties->count();
    // clear original props
    QStringList passProps;
    for (int j = 0; j < propsCount; j++) {
        QString propName = m_properties->get_name(j);
        if (propName.startsWith(QLatin1String("kdenlive:original."))) {
            passProps << propName;
        }
    }
    for (const QString &p : passProps) {
        m_properties->set(p.toUtf8().constData(), (char *)nullptr);
    }
    m_properties->set("kdenlive:original.backup", (char *)nullptr);
}

740
741
742
void ClipController::mirrorOriginalProperties(Mlt::Properties &props)
{
    if (m_usesProxy && QFileInfo(m_properties->get("resource")).fileName() == QFileInfo(m_properties->get("kdenlive:proxy")).fileName()) {
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
        // This is a proxy, we need to use the real source properties
        if (m_properties->get_int("kdenlive:original.backup") == 0) {
            // We have a proxy clip, load original source producer
            std::shared_ptr<Mlt::Producer> prod = std::make_shared<Mlt::Producer>(pCore->getCurrentProfile()->profile(), nullptr, m_path.toUtf8().constData());
            // Get frame to make sure we retrieve all original props
            std::shared_ptr<Mlt::Frame> fr(prod->get_frame());
            if (!prod->is_valid()) {
                return;
            }
            int width = 0;
            int height = 0;
            mlt_image_format format = mlt_image_none;
            fr->get_image(format, width, height);
            Mlt::Properties sourceProps(prod->get_properties());
            props.inherit(sourceProps);
            int propsCount = sourceProps.count();
            // store original props
760
            QStringList doNotPass {QStringLiteral("kdenlive:proxy"),QStringLiteral("kdenlive:originalurl"),QStringLiteral("kdenlive:clipname")};
761
762
            for (int i = 0; i < propsCount; i++) {
                QString propName = sourceProps.get_name(i);
763
764
765
                if (doNotPass.contains(propName)) {
                    continue;
                }
766
767
768
769
770
771
                if (!propName.startsWith(QLatin1Char('_'))) {
                    propName.prepend(QStringLiteral("kdenlive:original."));
                    m_properties->set(propName.toUtf8().constData(), sourceProps.get(i));
                }
            }
            m_properties->set("kdenlive:original.backup", 1);
772
        }
773
774
775
        // Properties were fetched in the past, reuse
        Mlt::Properties sourceProps;
        sourceProps.pass_values(*m_properties, "kdenlive:original.");
776
777
        props.inherit(sourceProps);
    } else {
778
779
780
781
782
        if (m_clipType == ClipType::AV || m_clipType == ClipType::Video || m_clipType == ClipType::Audio) {
            // Make sure that a frame / image was fetched to initialize all meta properties
            QString progressive = m_properties->get("meta.media.progressive");
            if (progressive.isEmpty()) {
                // Fetch a frame to initialize required properties
783
784
785
786
787
788
789
                QScopedPointer<Mlt::Producer> tmpProd(nullptr);
                if (KdenliveSettings::gpu_accel()) {
                    QString service = m_masterProducer->get("mlt_service");
                    tmpProd.reset(new Mlt::Producer(pCore->getCurrentProfile()->profile(), service.toUtf8().constData(), m_masterProducer->get("resource")));
                }
                std::shared_ptr<Mlt::Frame> fr(tmpProd ? tmpProd->get_frame() : m_masterProducer->get_frame());
                mlt_image_format format = mlt_image_none;
790
791
                int width = 0;
                int height = 0;
792
                fr->get_image(format, width, height);
793
794
            }
        }
795
796
797
798
        props.inherit(*m_properties);
    }
}

799
void ClipController::addEffect(QDomElement &xml)
800
{
801
    Q_UNUSED(xml)
802
803
    // TODO refac: this must be rewritten
    /*
804
    QMutexLocker lock(&m_effectMutex);
805
    Mlt::Service service = m_masterProducer->parent();
806
807
808
    ItemInfo info;
    info.cropStart = GenTime();
    info.cropDuration = getPlaytime();
809
    EffectsList eff = effectList();
810
    EffectsController::initEffect(info, eff, getProducerProperty(QStringLiteral("kdenlive:proxy")), xml);
811
    // Add effect to list and setup a kdenlive_ix value
812
813
    int kdenlive_ix = 0;
    for (int i = 0; i < service.filter_count(); ++i) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
814
        QScopedPointer<Mlt::Filter> effect(service.filter(i));
815
        int ix = effect->get_int("kdenlive_ix");
Laurent Montel's avatar
Laurent Montel committed
816
817
818
        if (ix > kdenlive_ix) {
            kdenlive_ix = ix;
        }
819
820
    }
    kdenlive_ix++;
821
    xml.setAttribute(QStringLiteral("kdenlive_ix"), kdenlive_ix);
822
    EffectsParameterList params = EffectsController::getEffectArgs(xml);
823
    EffectManager effect(service);
824
    effect.addEffect(params, getPlaytime().frames(pCore->getCurrentFps()));
825
    if (auto ptr = m_binController.lock()) ptr->updateTrackProducer(m_controllerBinId);
826
    */
827
828
}

829
830
void ClipController::removeEffect(int effectIndex, bool delayRefresh)
{
Nicolas Carion's avatar
Nicolas Carion committed
831
    Q_UNUSED(effectIndex) Q_UNUSED(delayRefresh)
832
833
    // TODO refac: this must be rewritten
    /*
834
    QMutexLocker lock(&m_effectMutex);
835
    Mlt::Service service(m_masterProducer->parent());
836
837
    EffectManager effect(service);
    effect.removeEffect(effectIndex, true);
Laurent Montel's avatar
Laurent Montel committed
838
    if (!delayRefresh) {
839
        if (auto ptr = m_binController.lock()) ptr->updateTrackProducer(m_controllerBinId);
Laurent Montel's avatar
Laurent Montel committed
840
    }
841
    */
842
843
}

844
845
void ClipController::moveEffect(int oldPos, int newPos)
{
846
847
    Q_UNUSED(oldPos)
    Q_UNUSED(newPos)
848
849
    // TODO refac: this must be rewritten
    /*
850
851
852
853
    QMutexLocker lock(&m_effectMutex);
    Mlt::Service service(m_masterProducer->parent());
    EffectManager effect(service);
    effect.moveEffect(oldPos, newPos);
854
    */
855
856
857
858
859
860
861
}

int ClipController::effectsCount()
{
    int count = 0;
    Mlt::Service service(m_masterProducer->parent());
    for (int ix = 0; ix < service.filter_count(); ++ix) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
862
        QScopedPointer<Mlt::Filter> effect(service.filter(ix));
863
864
865
866
867
868
869
870
        QString id = effect->get("kdenlive_id");
        if (!id.isEmpty()) {
            count++;
        }
    }
    return count;
}

Laurent Montel's avatar
Laurent Montel committed
871
void ClipController::changeEffectState(const QList<int> &indexes, bool disable)
872
{
873
874
    Q_UNUSED(indexes)
    Q_UNUSED(disable)
875
876
    // TODO refac : this must be rewritten
    /*
877
878
    Mlt::Service service = m_masterProducer->parent();
    for (int i = 0; i < service.filter_count(); ++i) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
879
        QScopedPointer<Mlt::Filter> effect(service.filter(i));
Nicolas Carion's avatar
Nicolas Carion committed
880
        if ((effect != nullptr) && effect->is_valid() && indexes.contains(effect->get_int("kdenlive_ix"))) {
Nicolas Carion's avatar
Nicolas Carion committed
881
            effect->set("disable", (int)disable);
882
883
        }
    }
884
    if (auto ptr = m_binController.lock()) ptr->updateTrackProducer(m_controllerBinId);
Nicolas Carion's avatar