clipcontroller.cpp 33.3 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
#include "bin/clipcreator.hpp"
32
#include "doc/kthumb.h"
33

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

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

Nicolas Carion's avatar
Nicolas Carion committed
42
ClipController::ClipController(const QString &clipId, const std::shared_ptr<Mlt::Producer> &producer)
43
44
45
    : selectedEffectIndex(1)
    , m_audioThumbCreated(false)
    , m_masterProducer(producer)
46
    , m_properties(producer ? new Mlt::Properties(producer->get_properties()) : nullptr)
47
48
49
    , m_usesProxy(false)
    , m_audioInfo(nullptr)
    , m_videoIndex(0)
50
    , m_clipType(ClipType::Unknown)
51
    , m_hasLimitedDuration(true)
52
    , m_effectStack(producer ? EffectStackModel::construct(producer, {ObjectType::BinClip, clipId.toInt()}, pCore->undoStack()) : nullptr)
53
    , m_hasAudio(false)
Vincent Pinon's avatar
Vincent Pinon committed
54
    , m_hasVideo(false)
55
    , m_thumbsProducer(nullptr)
56
    , m_producerLock(QReadWriteLock::Recursive)
Vincent Pinon's avatar
Vincent Pinon committed
57
    , m_controllerBinId(clipId)
58
{
59
    if (m_masterProducer && !m_masterProducer->is_valid()) {
Laurent Montel's avatar
Laurent Montel committed
60
        qCDebug(KDENLIVE_LOG) << "// WARNING, USING INVALID PRODUCER";
61
        return;
Nicolas Carion's avatar
Nicolas Carion committed
62
    }
63
    if (m_properties) {
64
        setProducerProperty(QStringLiteral("kdenlive:id"), m_controllerBinId);
65
        getInfoForProducer();
66
        checkAudioVideo();
67
    } else {
68
        m_producerLock.lockForWrite();
Nicolas Carion's avatar
Nicolas Carion committed
69
    }
70
71
}

72
ClipController::~ClipController()
73
{
74
75
    delete m_properties;
    m_masterProducer.reset();
76
77
}

78
const QString ClipController::binId() const
79
{
80
    return m_controllerBinId;
81
82
}

83
const std::unique_ptr<AudioStreamInfo> &ClipController::audioInfo() const
84
85
86
87
{
    return m_audioInfo;
}

Nicolas Carion's avatar
Nicolas Carion committed
88
void ClipController::addMasterProducer(const std::shared_ptr<Mlt::Producer> &producer)
89
{
90
    qDebug() << "################### ClipController::addmasterproducer";
91
    m_masterProducer = producer;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
92
    m_properties = new Mlt::Properties(m_masterProducer->get_properties());
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
    m_producerLock.unlock();
    // Pass temporary properties
    QMapIterator<QString, QVariant> i(m_tempProps);
    while (i.hasNext()) {
        i.next();
        switch(i.value().type()) {
            case QVariant::Int:
                setProducerProperty(i.key(), i.value().toInt());
                break;
            case QVariant::Double:
                setProducerProperty(i.key(), i.value().toDouble());
                break;
            default:
                setProducerProperty(i.key(), i.value().toString());
                break;
        }
    }
    m_tempProps.clear();
111
    int id = m_controllerBinId.toInt();
112
    m_effectStack = EffectStackModel::construct(producer, {ObjectType::BinClip, id}, pCore->undoStack());
Laurent Montel's avatar
Laurent Montel committed
113
    if (!m_masterProducer->is_valid()) {
114
        m_masterProducer = ClipController::mediaUnavailable;
Laurent Montel's avatar
Laurent Montel committed
115
116
        qCDebug(KDENLIVE_LOG) << "// WARNING, USING INVALID PRODUCER";
    } else {
117
        checkAudioVideo();
118
        setProducerProperty(QStringLiteral("kdenlive:id"), m_controllerBinId);
119
        getInfoForProducer();
120
        emitProducerChanged(m_controllerBinId, producer);
121
    }
122
    connectEffectStack();
123
124
}

125
namespace {
126
QString producerXml(const std::shared_ptr<Mlt::Producer> &producer, bool includeMeta, bool includeProfile)
127
{
Nicolas Carion's avatar
Nicolas Carion committed
128
    Mlt::Consumer c(*producer->profile(), "xml", "string");
129
130
131
132
133
134
135
136
137
138
139
140
    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);
    }
141
142
143
    if (!includeProfile) {
        c.set("no_profile", 1);
    }
144
145
146
147
148
149
150
151
152
153
    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
154
} // namespace
155

156
void ClipController::getProducerXML(QDomDocument &document, bool includeMeta, bool includeProfile)
157
{
158
    // TODO refac this is a probable duplicate with Clip::xml
159
    if (m_masterProducer) {
160
        QString xml = producerXml(m_masterProducer, includeMeta, includeProfile);
161
        document.setContent(xml);
Laurent Montel's avatar
Laurent Montel committed
162
    } else {
163
164
165
166
167
        if (!m_temporaryUrl.isEmpty()) {
            document = ClipCreator::getXmlFromUrl(m_temporaryUrl);
        } else if (!m_path.isEmpty()) {
            document = ClipCreator::getXmlFromUrl(m_path);
        }
Laurent Montel's avatar
Laurent Montel committed
168
        qCDebug(KDENLIVE_LOG) << " + + ++ NO MASTER PROD";
169
170
171
    }
}

172
173
void ClipController::getInfoForProducer()
{
174
    QReadLocker lock(&m_producerLock);
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
    m_service = m_properties->get("mlt_service");
    if (m_service == QLatin1String("qtext")) {
        // Placeholder clip, find real service
        QString originalService = m_properties->get("kdenlive:orig_service");
        if (!originalService.isEmpty()) {
            m_service = originalService;
        }
    }
    QString proxy = m_properties->get("kdenlive:proxy");
    QString path = m_properties->get("resource");
    if (proxy.length() > 2) {
        if (QFileInfo(path).isRelative()) {
            path.prepend(pCore->currentDoc()->documentRoot());
            m_properties->set("resource", path.toUtf8().constData());
        }
        // This is a proxy producer, read original url from kdenlive property
        path = m_properties->get("kdenlive:originalurl");
        if (QFileInfo(path).isRelative()) {
            path.prepend(pCore->currentDoc()->documentRoot());
        }
        m_usesProxy = true;
    } else if (m_service != QLatin1String("color") && m_service != QLatin1String("colour") && !path.isEmpty() && QFileInfo(path).isRelative() &&
                   path != QLatin1String("<producer>")) {
        path.prepend(pCore->currentDoc()->documentRoot());
        m_properties->set("resource", path.toUtf8().constData());
    }
    m_path = path.isEmpty() ? QString() : QFileInfo(path).absoluteFilePath();
    QString origurl = m_properties->get("kdenlive:originalurl");
    if (!origurl.isEmpty()) {
        m_properties->set("kdenlive:originalurl", m_path.toUtf8().constData());
    }
206
    date = QFileInfo(m_path).lastModified();
207
    m_videoIndex = -1;
208
    int audioIndex = -1;
209
    // special case: playlist with a proxy clip have to be detected separately
210
    if (m_usesProxy && m_path.endsWith(QStringLiteral(".mlt"))) {
211
        m_clipType = ClipType::Playlist;
212
    } else if (m_service == QLatin1String("avformat") || m_service == QLatin1String("avformat-novalidate")) {
213
        audioIndex = getProducerIntProperty(QStringLiteral("audio_index"));
214
        m_videoIndex = getProducerIntProperty(QStringLiteral("video_index"));
215
        if (m_videoIndex == -1) {
216
            m_clipType = ClipType::Audio;
Laurent Montel's avatar
Laurent Montel committed
217
        } else {
218
219
220
221
222
223
224
            if (audioIndex == -1) {
                m_clipType = ClipType::Video;
            } else {
                m_clipType = ClipType::AV;
            }
            if (m_service == QLatin1String("avformat")) {
                m_properties->set("mlt_service", "avformat-novalidate");
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
225
                m_properties->set("mute_on_pause", 0);
226
            }
227
        }
Laurent Montel's avatar
Laurent Montel committed
228
    } else if (m_service == QLatin1String("qimage") || m_service == QLatin1String("pixbuf")) {
Laurent Montel's avatar
Laurent Montel committed
229
        if (m_path.contains(QLatin1Char('%')) || m_path.contains(QStringLiteral("/.all."))) {
230
            m_clipType = ClipType::SlideShow;
231
            m_hasLimitedDuration = true;
Laurent Montel's avatar
Laurent Montel committed
232
        } else {
233
            m_clipType = ClipType::Image;
234
            m_hasLimitedDuration = false;
235
        }
Laurent Montel's avatar
Laurent Montel committed
236
    } else if (m_service == QLatin1String("colour") || m_service == QLatin1String("color")) {
237
        m_clipType = ClipType::Color;
238
239
        // Required for faster compositing
        m_masterProducer->set("mlt_image_format", "rgb24");
240
        m_hasLimitedDuration = false;
Laurent Montel's avatar
Laurent Montel committed
241
    } else if (m_service == QLatin1String("kdenlivetitle")) {
242
        if (!m_path.isEmpty()) {
243
            m_clipType = ClipType::TextTemplate;
244
        } else {
245
            m_clipType = ClipType::Text;
246
        }
247
        m_hasLimitedDuration = false;
Laurent Montel's avatar
Laurent Montel committed
248
    } else if (m_service == QLatin1String("xml") || m_service == QLatin1String("consumer")) {
249
        m_clipType = ClipType::Playlist;
Laurent Montel's avatar
Laurent Montel committed
250
    } else if (m_service == QLatin1String("webvfx")) {
251
        m_clipType = ClipType::WebVfx;
Laurent Montel's avatar
Laurent Montel committed
252
    } else if (m_service == QLatin1String("qtext")) {
253
        m_clipType = ClipType::QText;
254
255
256
    } else if (m_service == QLatin1String("qml")) {
        m_clipType = ClipType::Qml;
        m_hasLimitedDuration = false;
257
258
259
260
    } else if (m_service == QLatin1String("blipflash")) {
        // Mostly used for testing
        m_clipType = ClipType::AV;
        m_hasLimitedDuration = true;
Laurent Montel's avatar
Laurent Montel committed
261
    } else {
262
        m_clipType = ClipType::Unknown;
Vincent Pinon's avatar
Vincent Pinon committed
263
    }
264
    if (audioIndex > -1 || m_clipType == ClipType::Playlist) {
265
        m_audioInfo = std::make_unique<AudioStreamInfo>(m_masterProducer, audioIndex, m_clipType == ClipType::Playlist);
266
267
268
269
270
271
272
        // Load stream effects
        for (int stream : m_audioInfo->streams().keys()) {
            QString streamEffect = m_properties->get(QString("kdenlive:stream:%1").arg(stream).toUtf8().constData());
            if (!streamEffect.isEmpty()) {
                m_streamEffects.insert(stream, streamEffect.split(QChar('#')));
            }
        }
273
    }
274
275

    if (!m_hasLimitedDuration) {
276
        int playtime = m_masterProducer->time_to_frames(m_masterProducer->get("kdenlive:duration"));
277
278
        if (playtime <= 0) {
            // Fix clips having missing kdenlive:duration
279
280
            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));
281
282
        }
    }
283
284
}

285
286
287
288
289
bool ClipController::hasLimitedDuration() const
{
    return m_hasLimitedDuration;
}

Nicolas Carion's avatar
Nicolas Carion committed
290
291
292
293
294
void ClipController::forceLimitedDuration()
{
    m_hasLimitedDuration = true;
}

295
std::shared_ptr<Mlt::Producer> ClipController::originalProducer()
296
{
297
    QReadLocker lock(&m_producerLock);
298
    return m_masterProducer;
299
300
301
302
303
304
305
306
307
}

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

bool ClipController::isValid()
{
Laurent Montel's avatar
Laurent Montel committed
308
    if (m_masterProducer == nullptr) {
Laurent Montel's avatar
Laurent Montel committed
309
310
        return false;
    }
311
312
313
    return m_masterProducer->is_valid();
}

314
// static
315
const char *ClipController::getPassPropertiesList(bool passLength)
316
{
317
    if (!passLength) {
318
        return "kdenlive:proxy,kdenlive:originalurl,force_aspect_num,force_aspect_den,force_aspect_ratio,force_fps,force_progressive,force_tff,threads,force_"
319
               "colorspace,set.force_full_luma,file_hash,autorotate,disable_exif,xmldata,video_index,audio_index,set.test_image,set.test_audio";
320
    }
321
    return "kdenlive:proxy,kdenlive:originalurl,force_aspect_num,force_aspect_den,force_aspect_ratio,force_fps,force_progressive,force_tff,threads,force_"
322
           "colorspace,set.force_full_luma,templatetext,file_hash,autorotate,disable_exif,xmldata,length,video_index,audio_index,set.test_image,set.test_audio";
323
324
}

Laurent Montel's avatar
Laurent Montel committed
325
QMap<QString, QString> ClipController::getPropertiesFromPrefix(const QString &prefix, bool withPrefix)
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
326
{
327
    QReadLocker lock(&m_producerLock);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
328
    Mlt::Properties subProperties;
329
    subProperties.pass_values(*m_properties, prefix.toUtf8().constData());
Laurent Montel's avatar
Laurent Montel committed
330
    QMap<QString, QString> subclipsData;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
331
    for (int i = 0; i < subProperties.count(); i++) {
332
        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
333
334
335
336
    }
    return subclipsData;
}

Nicolas Carion's avatar
Nicolas Carion committed
337
void ClipController::updateProducer(const std::shared_ptr<Mlt::Producer> &producer)
338
{
339
    qDebug() << "################### ClipController::updateProducer";
Nicolas Carion's avatar
Nicolas Carion committed
340
    // TODO replace all track producers
341
342
343
344
    if (!m_properties) {
        // producer has not been initialized
        return addMasterProducer(producer);
    }
345
    m_producerLock.lockForWrite();
346
347
    Mlt::Properties passProperties;
    // Keep track of necessary properties
348
    QString proxy = producer->get("kdenlive:proxy");
349
350
351
    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
352
353
    } else {
        m_usesProxy = false;
354
    }
355
356
357
    // 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);
358
359
    // This is necessary as some properties like set.test_audio are reset on producer creation
    passProperties.pass_list(*m_properties, passList);
360
    delete m_properties;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
361
362
    *m_masterProducer = producer.get();
    m_properties = new Mlt::Properties(m_masterProducer->get_properties());
363
    m_producerLock.unlock();
Laurent Montel's avatar
Laurent Montel committed
364
365
366
    if (!m_masterProducer->is_valid()) {
        qCDebug(KDENLIVE_LOG) << "// WARNING, USING INVALID PRODUCER";
    } else {
367
368
369
370
        // Pass properties from previous producer
        m_properties->pass_list(passProperties, passList);
        checkAudioVideo();
        setProducerProperty(QStringLiteral("kdenlive:id"), m_controllerBinId);
371
        m_effectStack->resetService(m_masterProducer);
372
        emitProducerChanged(m_controllerBinId, producer);
373
        // URL and name should not be updated otherwise when proxying a clip we cannot find back the original url
374
375
376
377
378
379
        /*m_url = QUrl::fromLocalFile(m_masterProducer->get("resource"));
        if (m_url.isValid()) {
            m_name = m_url.fileName();
        }
        */
    }
380
    qDebug() << "// replace finished: " << binId() << " : " << m_masterProducer->get("resource");
381
382
383
384
}

const QString ClipController::getStringDuration()
{
385
    QReadLocker lock(&m_producerLock);
386
    if (m_masterProducer) {
387
        int playtime = m_masterProducer->time_to_frames(m_masterProducer->get("kdenlive:duration"));
388
        if (playtime > 0) {
389
            return QString(m_properties->frames_to_time(playtime, mlt_time_smpte_df));
390
        }
391
        return m_properties->frames_to_time(m_masterProducer->get_length(), mlt_time_smpte_df);
392
    }
Laurent Montel's avatar
Laurent Montel committed
393
    return i18n("Unknown");
394
395
}

396
397
int ClipController::getProducerDuration() const
{
398
    QReadLocker lock(&m_producerLock);
399
    if (m_masterProducer) {
400
        int playtime = m_masterProducer->time_to_frames(m_masterProducer->get("kdenlive:duration"));
401
402
403
404
405
406
407
408
        if (playtime <= 0) {
            return playtime = m_masterProducer->get_length();
        }
        return playtime;
    }
    return -1;
}

409
410
char *ClipController::framesToTime(int frames) const
{
411
    QReadLocker lock(&m_producerLock);
412
413
414
415
416
417
    if (m_masterProducer) {
        return m_masterProducer->frames_to_time(frames, mlt_time_clock);
    }
    return nullptr;
}

418
GenTime ClipController::getPlaytime() const
419
{
420
    QReadLocker lock(&m_producerLock);
421
422
423
    if (!m_masterProducer || !m_masterProducer->is_valid()) {
        return GenTime();
    }
424
    double fps = pCore->getCurrentFps();
425
    if (!m_hasLimitedDuration) {
426
        int playtime = m_masterProducer->time_to_frames(m_masterProducer->get("kdenlive:duration"));
427
        return GenTime(playtime == 0 ? m_masterProducer->get_playtime() : playtime, fps);
428
    }
Nicolas Carion's avatar
Nicolas Carion committed
429
    return {m_masterProducer->get_playtime(), fps};
430
431
}

432
433
int ClipController::getFramePlaytime() const
{
434
    QReadLocker lock(&m_producerLock);
435
436
437
438
    if (!m_masterProducer || !m_masterProducer->is_valid()) {
        return 0;
    }
    if (!m_hasLimitedDuration) {
439
        int playtime = m_masterProducer->time_to_frames(m_masterProducer->get("kdenlive:duration"));
440
441
442
443
444
        return playtime == 0 ? m_masterProducer->get_playtime() : playtime;
    }
    return m_masterProducer->get_playtime();
}

445
QString ClipController::getProducerProperty(const QString &name) const
446
{
447
448
    QReadLocker lock(&m_producerLock);
    if (m_properties == nullptr) {
Laurent Montel's avatar
Laurent Montel committed
449
450
        return QString();
    }
Laurent Montel's avatar
Laurent Montel committed
451
    if (m_usesProxy && name.startsWith(QLatin1String("meta."))) {
452
453
454
        QString correctedName = QStringLiteral("kdenlive:") + name;
        return m_properties->get(correctedName.toUtf8().constData());
    }
455
    return QString(m_properties->get(name.toUtf8().constData()));
456
457
}

458
int ClipController::getProducerIntProperty(const QString &name) const
459
{
460
    QReadLocker lock(&m_producerLock);
Laurent Montel's avatar
Laurent Montel committed
461
462
463
    if (!m_properties) {
        return 0;
    }
Laurent Montel's avatar
Laurent Montel committed
464
    if (m_usesProxy && name.startsWith(QLatin1String("meta."))) {
465
466
467
        QString correctedName = QStringLiteral("kdenlive:") + name;
        return m_properties->get_int(correctedName.toUtf8().constData());
    }
468
    return m_properties->get_int(name.toUtf8().constData());
469
470
}

471
qint64 ClipController::getProducerInt64Property(const QString &name) const
472
{
473
    QReadLocker lock(&m_producerLock);
Laurent Montel's avatar
Laurent Montel committed
474
475
476
    if (!m_properties) {
        return 0;
    }
477
478
479
    return m_properties->get_int64(name.toUtf8().constData());
}

480
double ClipController::getProducerDoubleProperty(const QString &name) const
481
{
482
    QReadLocker lock(&m_producerLock);
Laurent Montel's avatar
Laurent Montel committed
483
484
485
    if (!m_properties) {
        return 0;
    }
486
487
488
    return m_properties->get_double(name.toUtf8().constData());
}

489
QColor ClipController::getProducerColorProperty(const QString &name) const
490
{
491
    QReadLocker lock(&m_producerLock);
Laurent Montel's avatar
Laurent Montel committed
492
    if (!m_properties) {
Nicolas Carion's avatar
Nicolas Carion committed
493
        return {};
Laurent Montel's avatar
Laurent Montel committed
494
    }
495
496
497
498
    mlt_color color = m_properties->get_color(name.toUtf8().constData());
    return QColor::fromRgb(color.r, color.g, color.b);
}

499
500
501
502
503
504
505
506
507
508
509
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;
}

510
511
double ClipController::originalFps() const
{
512
    QReadLocker lock(&m_producerLock);
Laurent Montel's avatar
Laurent Montel committed
513
514
515
    if (!m_properties) {
        return 0;
    }
516
    QString propertyName = QStringLiteral("meta.media.%1.stream.frame_rate").arg(m_videoIndex);
517
518
519
    return m_properties->get_double(propertyName.toUtf8().constData());
}

520
521
QString ClipController::videoCodecProperty(const QString &property) const
{
522
    QReadLocker lock(&m_producerLock);
Laurent Montel's avatar
Laurent Montel committed
523
524
525
    if (!m_properties) {
        return QString();
    }
526
    QString propertyName = QStringLiteral("meta.media.%1.codec.%2").arg(m_videoIndex).arg(property);
527
528
529
    return m_properties->get(propertyName.toUtf8().constData());
}

530
531
const QString ClipController::codec(bool audioCodec) const
{
532
    QReadLocker lock(&m_producerLock);
533
    if ((m_properties == nullptr) || (m_clipType != ClipType::AV && m_clipType != ClipType::Video && m_clipType != ClipType::Audio)) {
Laurent Montel's avatar
Laurent Montel committed
534
535
        return QString();
    }
536
    QString propertyName = QStringLiteral("meta.media.%1.codec.name").arg(audioCodec ? m_properties->get_int("audio_index") : m_videoIndex);
537
538
539
    return m_properties->get(propertyName.toUtf8().constData());
}

540
const QString ClipController::clipUrl() const
541
{
542
    return m_path;
543
544
}

545
546
547
548
549
550
551
552
553
554
555
556
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);
}

557
558
QString ClipController::clipName() const
{
559
    QString name = getProducerProperty(QStringLiteral("kdenlive:clipname"));
Laurent Montel's avatar
Laurent Montel committed
560
561
562
    if (!name.isEmpty()) {
        return name;
    }
563
    return m_path.isEmpty() ? i18n("Unnamed") : QFileInfo(m_path).fileName();
564
565
}

566
567
QString ClipController::description() const
{
568
    if (m_clipType == ClipType::TextTemplate) {
569
        QString name = getProducerProperty(QStringLiteral("templatetext"));
570
571
        return name;
    }
572
    QString name = getProducerProperty(QStringLiteral("kdenlive:description"));
Laurent Montel's avatar
Laurent Montel committed
573
574
575
    if (!name.isEmpty()) {
        return name;
    }
576
    return getProducerProperty(QStringLiteral("meta.attr.comment.markup"));
577
578
}

579
580
581
582
583
QString ClipController::serviceName() const
{
    return m_service;
}

Nicolas Carion's avatar
Nicolas Carion committed
584
void ClipController::setProducerProperty(const QString &name, int value)
585
{
586
587
588
589
590
    if (!m_masterProducer) {
        m_tempProps.insert(name, value);
        return;
    }
    QWriteLocker lock(&m_producerLock);
Jean-Baptiste Mardelle's avatar
cleanup    
Jean-Baptiste Mardelle committed
591
    m_masterProducer->parent().set(name.toUtf8().constData(), value);
592
593
}

Nicolas Carion's avatar
Nicolas Carion committed
594
void ClipController::setProducerProperty(const QString &name, double value)
595
{
596
597
598
599
600
    if (!m_masterProducer) {
        m_tempProps.insert(name, value);
        return;
    }
    QWriteLocker lock(&m_producerLock);
Jean-Baptiste Mardelle's avatar
cleanup    
Jean-Baptiste Mardelle committed
601
    m_masterProducer->parent().set(name.toUtf8().constData(), value);
602
603
}

Nicolas Carion's avatar
Nicolas Carion committed
604
void ClipController::setProducerProperty(const QString &name, const QString &value)
605
{
606
607
608
609
610
611
    if (!m_masterProducer) {
        m_tempProps.insert(name, value);
        return;
    }

    QWriteLocker lock(&m_producerLock);
612
    if (value.isEmpty()) {
Laurent Montel's avatar
Laurent Montel committed
613
        m_masterProducer->parent().set(name.toUtf8().constData(), (char *)nullptr);
Laurent Montel's avatar
Laurent Montel committed
614
615
    } else {
        m_masterProducer->parent().set(name.toUtf8().constData(), value.toUtf8().constData());
616
    }
617
618
}

619
void ClipController::resetProducerProperty(const QString &name)
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
620
{
621
622
623
624
625
626
    if (!m_masterProducer) {
        m_tempProps.insert(name, QString());
        return;
    }

    QWriteLocker lock(&m_producerLock);
Laurent Montel's avatar
Laurent Montel committed
627
    m_masterProducer->parent().set(name.toUtf8().constData(), (char *)nullptr);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
628
629
}

630
ClipType::ProducerType ClipController::clipType() const
631
{
632
    return m_clipType;
633
634
}

635
636
const QSize ClipController::getFrameSize() const
{
637
    QReadLocker lock(&m_producerLock);
638
639
640
641
642
643
644
645
646
647
648
649
650
651
    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);
}

652
653
654
655
bool ClipController::hasAudio() const
{
    return m_hasAudio;
}
656
void ClipController::checkAudioVideo()
657
{
658
    QReadLocker lock(&m_producerLock);
659
    m_masterProducer->seek(0);
660
    if (m_masterProducer->get_int("_placeholder") == 1 || m_masterProducer->get_int("_missingsource") == 1) {
661
662
        // This is a placeholder file, try to guess from its properties
        QString orig_service = m_masterProducer->get("kdenlive:orig_service");
663
        if (orig_service.startsWith(QStringLiteral("avformat")) || (m_masterProducer->get_int("audio_index") + m_masterProducer->get_int("video_index") > 0)) {
664
665
            m_hasAudio = m_masterProducer->get_int("audio_index") >= 0;
            m_hasVideo = m_masterProducer->get_int("video_index") >= 0;
666
667
668
669
        } else if (orig_service == QStringLiteral("xml")) {
            // Playlist, assume we have audio and video
            m_hasAudio = true;
            m_hasVideo = true;
670
671
672
673
674
675
676
        } else {
            // Assume image or text producer
            m_hasAudio = false;
            m_hasVideo = true;
        }
        return;
    }
677
    QScopedPointer<Mlt::Frame> frame(m_masterProducer->get_frame());
678
679
680
681
    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;
682
        m_masterProducer->seek(0);
683
684
685
    } else {
        qDebug()<<"* * * *ERROR INVALID FRAME On test";
    }
686
687
688
689
690
}
bool ClipController::hasVideo() const
{
    return m_hasVideo;
}
691
PlaylistState::ClipState ClipController::defaultState() const
692
693
694
695
696
697
698
699
{
    if (hasVideo()) {
        return PlaylistState::VideoOnly;
    }
    if (hasAudio()) {
        return PlaylistState::AudioOnly;
    }
    return PlaylistState::Disabled;
700
701
}

702
703
QPixmap ClipController::pixmap(int framePosition, int width, int height)
{
704
    // TODO refac this should use the new thumb infrastructure
705
    QReadLocker lock(&m_producerLock);
706
707
708
709
710
    if (thumbProducer() == nullptr) {
        return QPixmap();
    }
    m_thumbsProducer->seek(framePosition);
    QScopedPointer<Mlt::Frame> frame(m_thumbsProducer->get_frame());
Laurent Montel's avatar
Laurent Montel committed
711
    if (frame == nullptr || !frame->is_valid()) {
712
713
714
715
716
717
        QPixmap p(width, height);
        p.fill(QColor(Qt::red).rgb());
        return p;
    }
    frame->set("deinterlace_method", "onefield");
    frame->set("top_field_first", -1);
718
719
720
    frame->set("rescale.interp", "nearest");
    QImage img = KThumb::getFrame(frame.data());
    return QPixmap::fromImage(img/*.scaled(height, width, Qt::KeepAspectRatio)*/);
721
}
722
723
724

void ClipController::setZone(const QPoint &zone)
{
Nicolas Carion's avatar
Nicolas Carion committed
725
726
    setProducerProperty(QStringLiteral("kdenlive:zone_in"), zone.x());
    setProducerProperty(QStringLiteral("kdenlive:zone_out"), zone.y());
727
728
729
730
}

QPoint ClipController::zone() const
{
731
    int in = getProducerIntProperty(QStringLiteral("kdenlive:zone_in"));
732
    int max = getFramePlaytime();
733
    int out = qMin(getProducerIntProperty(QStringLiteral("kdenlive:zone_out")), max);
Laurent Montel's avatar
Laurent Montel committed
734
735
736
    if (out <= in) {
        out = max;
    }
737
738
739
740
741
742
    QPoint zone(in, out);
    return zone;
}

const QString ClipController::getClipHash() const
{
743
    return getProducerProperty(QStringLiteral("kdenlive:file_hash"));
744
745
}

746
747
Mlt::Properties &ClipController::properties()
{
748
    QReadLocker lock(&m_producerLock);
749
750
    return *m_properties;
}
751

752
753
754

void ClipController::backupOriginalProperties()
{
755
    QReadLocker lock(&m_producerLock);
756
757
758
759
760
    if (m_properties->get_int("kdenlive:original.backup") == 1) {
        return;
    }
    int propsCount = m_properties->count();
    // store original props
761
    QStringList doNotPass {QStringLiteral("kdenlive:proxy"),QStringLiteral("kdenlive:originalurl"),QStringLiteral("kdenlive:clipname")};
762
763
    for (int j = 0; j < propsCount; j++) {
        QString propName = m_properties->get_name(j);
764
765
766
        if (doNotPass.contains(propName)) {
            continue;
        } 
767
768
769
770
771
772
773
774
775
776
        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()
{
777
    QReadLocker lock(&m_producerLock);
778
779
780
781
782
783
784
785
786
787
788
789
    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;
        }
    }
Vincent Pinon's avatar
Vincent Pinon committed
790
    for (const QString &p : qAsConst(passProps)) {
791
792
793
794
795
        m_properties->set(p.toUtf8().constData(), (char *)nullptr);
    }
    m_properties->set("kdenlive:original.backup", (char *)nullptr);
}

796
797
void ClipController::mirrorOriginalProperties(Mlt::Properties &props)
{
798
    QReadLocker lock(&m_producerLock);
799
    if (m_usesProxy && QFileInfo(m_properties->get("resource")).fileName() == QFileInfo(m_properties->get("kdenlive:proxy")).fileName()) {
800
801
802
        // 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
803
            std::shared_ptr<Mlt::Producer> prod = std::make_shared<Mlt::Producer>(pCore->getCurrentProfile()->profile(), nullptr, m_path.toUtf8().constData());
804
805
806
807
808
809
810
811
812
813
814
815
816
            // 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
817
            QStringList doNotPass {QStringLiteral("kdenlive:proxy"),QStringLiteral("kdenlive:originalurl"),QStringLiteral("kdenlive:clipname")};
818
819
            for (int i = 0; i < propsCount; i++) {
                QString propName = sourceProps.get_name(i);
820
821
822
                if (doNotPass.contains(propName)) {
                    continue;
                }
823
824
825
826
827
828
                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);
829
        }
830
831
832
        // Properties were fetched in the past, reuse
        Mlt::Properties sourceProps;
        sourceProps.pass_values(*m_properties, "kdenlive:original.");
833
834
        props.inherit(sourceProps);
    } else {
835
836
837
838
839
        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
840
841
842
                QScopedPointer<Mlt::Producer> tmpProd(nullptr);
                if (KdenliveSettings::gpu_accel()) {
                    QString service = m_masterProducer->get("mlt_service");
843
                    tmpProd.reset(new Mlt::Producer(pCore->getCurrentProfile()->profile(), service.toUtf8().constData(), m_masterProducer->get("resource")));
844
845
846
                }
                std::shared_ptr<Mlt::Frame> fr(tmpProd ? tmpProd->get_frame() : m_masterProducer->get_frame());
                mlt_image_format format = mlt_image_none;
847
848
                int width = 0;
                int height = 0;
849
                fr->get_image(format, width, height);
850
851
            }
        }
852
853
854
855
        props.inherit(*m_properties);
    }
}

856
857
858
int ClipController::effectsCount()
{
    int count = 0;
859
    QReadLocker lock(&m_producerLock);
860
861
    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;
}

871
872
bool ClipController::hasEffects() const
{
873
    return m_effectStack->rowCount() > 0;
874
875
}

876
void ClipController::setBinEffectsEnabled(bool enabled)
877
{
878
    m_effectStack->setEffectStackEnabled(enabled);
879
880
}

Laurent Montel's avatar
Laurent Montel committed
881
void ClipController::saveZone(QPoint zone, const QDir &dir)