clipcontroller.cpp 31 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_masterProducer) {
60
        checkAudioVideo();
61
    }
62
    if (m_properties) {
63
        setProducerProperty(QStringLiteral("kdenlive:id"), m_controllerBinId);
64 65 66 67 68 69 70
        m_service = m_properties->get("mlt_service");
        QString proxy = m_properties->get("kdenlive:proxy");
        QString path = m_properties->get("resource");
        if (proxy.length() > 2) {
            // This is a proxy producer, read original url from kdenlive property
            path = m_properties->get("kdenlive:originalurl");
            if (QFileInfo(path).isRelative()) {
71
                path.prepend(pCore->currentDoc()->documentRoot());
72 73
            }
            m_usesProxy = true;
74 75
        } else if (m_service != QLatin1String("color") && m_service != QLatin1String("colour") && !path.isEmpty() && QFileInfo(path).isRelative() &&
                   path != QLatin1String("<producer>")) {
76
            path.prepend(pCore->currentDoc()->documentRoot());
77
        }
78
        m_path = path.isEmpty() ? QString() : QFileInfo(path).absoluteFilePath();
79 80 81
        getInfoForProducer();
    } else {
        m_producerLock.lock();
Nicolas Carion's avatar
Nicolas Carion committed
82
    }
83 84
}

85
ClipController::~ClipController()
86
{
87 88
    delete m_properties;
    m_masterProducer.reset();
89 90
}

91
const QString ClipController::binId() const
92
{
93
    return m_controllerBinId;
94 95
}

96
const std::unique_ptr<AudioStreamInfo> &ClipController::audioInfo() const
97 98 99 100
{
    return m_audioInfo;
}

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

138
namespace {
139
QString producerXml(const std::shared_ptr<Mlt::Producer> &producer, bool includeMeta, bool includeProfile)
140
{
Nicolas Carion's avatar
Nicolas Carion committed
141
    Mlt::Consumer c(*producer->profile(), "xml", "string");
142 143 144 145 146 147 148 149 150 151 152 153
    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);
    }
154 155 156
    if (!includeProfile) {
        c.set("no_profile", 1);
    }
157 158 159 160 161 162 163 164 165 166
    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
167
} // namespace
168

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

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

    if (!m_hasLimitedDuration) {
239
        int playtime = m_masterProducer->time_to_frames(m_masterProducer->get("kdenlive:duration"));
240 241
        if (playtime <= 0) {
            // Fix clips having missing kdenlive:duration
242 243
            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));
244 245
        }
    }
246 247
}

248 249 250 251 252
bool ClipController::hasLimitedDuration() const
{
    return m_hasLimitedDuration;
}

Nicolas Carion's avatar
Nicolas Carion committed
253 254 255 256 257
void ClipController::forceLimitedDuration()
{
    m_hasLimitedDuration = true;
}

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

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

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

277
// static
278
const char *ClipController::getPassPropertiesList(bool passLength)
279
{
280
    if (!passLength) {
281 282
        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";
283
    }
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,templatetext,file_hash,autorotate,xmldata,length,video_index,audio_index,set.test_image,set.test_audio";
286 287
}

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

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

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

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

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

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

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

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

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

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

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

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

449 450 451 452 453 454 455 456 457 458 459
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;
}

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

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

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

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

QString ClipController::clipName() const
{
494
    QString name = getProducerProperty(QStringLiteral("kdenlive:clipname"));
Laurent Montel's avatar
Laurent Montel committed
495 496 497
    if (!name.isEmpty()) {
        return name;
    }
498
    return QFileInfo(m_path).fileName();
499 500
}

501 502
QString ClipController::description() const
{
503
    if (m_clipType == ClipType::TextTemplate) {
504
        QString name = getProducerProperty(QStringLiteral("templatetext"));
505 506
        return name;
    }
507
    QString name = getProducerProperty(QStringLiteral("kdenlive:description"));
Laurent Montel's avatar
Laurent Montel committed
508 509 510
    if (!name.isEmpty()) {
        return name;
    }
511
    return getProducerProperty(QStringLiteral("meta.attr.comment.markup"));
512 513
}

514 515 516 517 518
QString ClipController::serviceName() const
{
    return m_service;
}

Nicolas Carion's avatar
Nicolas Carion committed
519
void ClipController::setProducerProperty(const QString &name, int value)
520
{
521
    if (!m_masterProducer) return;
Nicolas Carion's avatar
Nicolas Carion committed
522
    // TODO: also set property on all track producers
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
523
    m_masterProducer->parent().set(name.toUtf8().constData(), value);
524 525
}

Nicolas Carion's avatar
Nicolas Carion committed
526
void ClipController::setProducerProperty(const QString &name, double value)
527
{
528
    if (!m_masterProducer) return;
Nicolas Carion's avatar
Nicolas Carion committed
529
    // TODO: also set property on all track producers
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
530
    m_masterProducer->parent().set(name.toUtf8().constData(), value);
531 532
}

Nicolas Carion's avatar
Nicolas Carion committed
533
void ClipController::setProducerProperty(const QString &name, const QString &value)
534
{
535
    if (!m_masterProducer) return;
Nicolas Carion's avatar
Nicolas Carion committed
536
    // TODO: also set property on all track producers
537
    if (value.isEmpty()) {
Laurent Montel's avatar
Laurent Montel committed
538
        m_masterProducer->parent().set(name.toUtf8().constData(), (char *)nullptr);
Laurent Montel's avatar
Laurent Montel committed
539 540
    } else {
        m_masterProducer->parent().set(name.toUtf8().constData(), value.toUtf8().constData());
541
    }
542 543
}

544
void ClipController::resetProducerProperty(const QString &name)
545
{
Nicolas Carion's avatar
Nicolas Carion committed
546
    // TODO: also set property on all track producers
Laurent Montel's avatar
Laurent Montel committed
547
    m_masterProducer->parent().set(name.toUtf8().constData(), (char *)nullptr);
548 549
}

550
ClipType::ProducerType ClipController::clipType() const
551
{
552
    return m_clipType;
553 554
}

555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570
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);
}

571 572 573 574
bool ClipController::hasAudio() const
{
    return m_hasAudio;
}
575
void ClipController::checkAudioVideo()
576 577
{
    m_masterProducer->seek(0);
578
    if (m_masterProducer->get_int("_placeholder") == 1 || m_masterProducer->get("text") == QLatin1String("INVALID")) {
579 580
        // This is a placeholder file, try to guess from its properties
        QString orig_service = m_masterProducer->get("kdenlive:orig_service");
581
        if (orig_service.startsWith(QStringLiteral("avformat")) || (m_masterProducer->get_int("audio_index") + m_masterProducer->get_int("video_index") > 0)) {
582 583 584 585 586 587 588 589 590
            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;
    }
591
    QScopedPointer<Mlt::Frame> frame(m_masterProducer->get_frame());
592
    // test_audio returns 1 if there is NO audio (strange but true at the time this code is written)
593
    m_hasAudio = frame->get_int("test_audio") == 0;
594 595 596 597 598 599
    m_hasVideo = frame->get_int("test_image") == 0;
}
bool ClipController::hasVideo() const
{
    return m_hasVideo;
}
600
PlaylistState::ClipState ClipController::defaultState() const
601 602 603 604 605 606 607 608
{
    if (hasVideo()) {
        return PlaylistState::VideoOnly;
    }
    if (hasAudio()) {
        return PlaylistState::AudioOnly;
    }
    return PlaylistState::Disabled;
609 610
}

611 612
QPixmap ClipController::pixmap(int framePosition, int width, int height)
{
613
    // TODO refac this should use the new thumb infrastructure
614 615
    m_masterProducer->seek(framePosition);
    Mlt::Frame *frame = m_masterProducer->get_frame();
Laurent Montel's avatar
Laurent Montel committed
616
    if (frame == nullptr || !frame->is_valid()) {
617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640
        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;
641 642
    width += width % 2;
    height += height % 2;
Laurent Montel's avatar
Laurent Montel committed
643
    const uchar *imagedata = frame->get_image(format, width, height);
644 645
    QImage image(imagedata, width, height, QImage::Format_RGBA8888);
    QPixmap pixmap;
646
    pixmap.convertFromImage(image);
647
    delete frame;
648 649
    return pixmap;
}
650 651 652

void ClipController::setZone(const QPoint &zone)
{
Nicolas Carion's avatar
Nicolas Carion committed
653 654
    setProducerProperty(QStringLiteral("kdenlive:zone_in"), zone.x());
    setProducerProperty(QStringLiteral("kdenlive:zone_out"), zone.y());
655 656 657 658
}

QPoint ClipController::zone() const
{
659
    int in = getProducerIntProperty(QStringLiteral("kdenlive:zone_in"));
660
    int max = getFramePlaytime() - 1;
661
    int out = qMin(getProducerIntProperty(QStringLiteral("kdenlive:zone_out")), max);
Laurent Montel's avatar
Laurent Montel committed
662 663 664
    if (out <= in) {
        out = max;
    }
665 666 667 668 669 670
    QPoint zone(in, out);
    return zone;
}

const QString ClipController::getClipHash() const
{
671
    return getProducerProperty(QStringLiteral("kdenlive:file_hash"));
672 673
}

674 675 676 677
Mlt::Properties &ClipController::properties()
{
    return *m_properties;
}
678

679 680 681 682 683 684 685 686 687 688 689 690 691
void ClipController::mirrorOriginalProperties(Mlt::Properties &props)
{
    if (m_usesProxy && QFileInfo(m_properties->get("resource")).fileName() == QFileInfo(m_properties->get("kdenlive:proxy")).fileName()) {
        // 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;
        }
        Mlt::Properties sourceProps(prod->get_properties());
        props.inherit(sourceProps);
    } else {
692 693 694 695 696
        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
697 698 699 700 701 702 703
                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;
704 705
                int width = 0;
                int height = 0;
706
                fr->get_image(format, width, height);
707 708
            }
        }
709 710 711 712
        props.inherit(*m_properties);
    }
}

713
void ClipController::addEffect(QDomElement &xml)
714
{
715
    Q_UNUSED(xml)
716 717
    // TODO refac: this must be rewritten
    /*
718
    QMutexLocker lock(&m_effectMutex);
719
    Mlt::Service service = m_masterProducer->parent();
720 721 722
    ItemInfo info;
    info.cropStart = GenTime();
    info.cropDuration = getPlaytime();
723
    EffectsList eff = effectList();
724
    EffectsController::initEffect(info, eff, getProducerProperty(QStringLiteral("kdenlive:proxy")), xml);
725
    // Add effect to list and setup a kdenlive_ix value
726 727
    int kdenlive_ix = 0;
    for (int i = 0; i < service.filter_count(); ++i) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
728
        QScopedPointer<Mlt::Filter> effect(service.filter(i));
729
        int ix = effect->get_int("kdenlive_ix");
Laurent Montel's avatar
Laurent Montel committed
730 731 732
        if (ix > kdenlive_ix) {
            kdenlive_ix = ix;
        }
733 734
    }
    kdenlive_ix++;
735
    xml.setAttribute(QStringLiteral("kdenlive_ix"), kdenlive_ix);
736
    EffectsParameterList params = EffectsController::getEffectArgs(xml);
737
    EffectManager effect(service);
738
    effect.addEffect(params, getPlaytime().frames(pCore->getCurrentFps()));
739
    if (auto ptr = m_binController.lock()) ptr->updateTrackProducer(m_controllerBinId);
740
    */
741 742
}

743 744
void ClipController::removeEffect(int effectIndex, bool delayRefresh)
{
Nicolas Carion's avatar
Nicolas Carion committed
745
    Q_UNUSED(effectIndex) Q_UNUSED(delayRefresh)
746 747
    // TODO refac: this must be rewritten
    /*
748
    QMutexLocker lock(&m_effectMutex);
749
    Mlt::Service service(m_masterProducer->parent());
750 751
    EffectManager effect(service);
    effect.removeEffect(effectIndex, true);
Laurent Montel's avatar
Laurent Montel committed
752
    if (!delayRefresh) {
753
        if (auto ptr = m_binController.lock()) ptr->updateTrackProducer(m_controllerBinId);
Laurent Montel's avatar
Laurent Montel committed
754
    }
755
    */
756 757
}

758 759
void ClipController::moveEffect(int oldPos, int newPos)
{
760 761
    Q_UNUSED(oldPos)
    Q_UNUSED(newPos)
762 763
    // TODO refac: this must be rewritten
    /*
764 765 766 767
    QMutexLocker lock(&m_effectMutex);
    Mlt::Service service(m_masterProducer->parent());
    EffectManager effect(service);
    effect.moveEffect(oldPos, newPos);
768
    */
769 770 771 772 773 774 775
}

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
776
        QScopedPointer<Mlt::Filter> effect(service.filter(ix));
777 778 779 780 781 782 783 784
        QString id = effect->get("kdenlive_id");
        if (!id.isEmpty()) {
            count++;
        }
    }
    return count;
}

Laurent Montel's avatar
Laurent Montel committed
785
void ClipController::changeEffectState(const QList<int> &indexes, bool disable)
786
{
787 788
    Q_UNUSED(indexes)
    Q_UNUSED(disable)
789 790
    // TODO refac : this must be rewritten
    /*
791 792
    Mlt::Service service = m_masterProducer->parent();
    for (int i = 0; i < service.filter_count(); ++i) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
793
        QScopedPointer<Mlt::Filter> effect(service.filter(i));
Nicolas Carion's avatar
Nicolas Carion committed
794
        if ((effect != nullptr) && effect->is_valid() && indexes.contains(effect->get_int("kdenlive_ix"))) {
Nicolas Carion's avatar
Nicolas Carion committed
795
            effect->set("disable", (int)disable);
796 797
        }
    }
798
    if (auto ptr = m_binController.lock()) ptr->updateTrackProducer(m_controllerBinId);
799
    */
800 801
}

802
void ClipController::updateEffect(const QDomElement &e, int ix)
803
{
804 805
    Q_UNUSED(e)
    Q_UNUSED(ix)
806 807
    // TODO refac : this must be rewritten
    /*
Laurent Montel's avatar
Laurent Montel committed
808
    QString tag = e.attribute(QStringLiteral("id"));
809 810 811 812
    if (tag == QLatin1String("autotrack_rectangle") || tag.startsWith(QLatin1String("ladspa")) || tag == QLatin1String("sox")) {
        // this filters cannot be edited, remove and re-add it
        removeEffect(ix, true);
        QDomElement clone = e.cloneNode().toElement();
813
        addEffect(clone);
814 815
        return;
    }
816
    EffectsParameterList params = EffectsController::getEffectArgs(e);
817 818
    Mlt::Service service = m_masterProducer->parent();
    for (int i = 0; i < service.filter_count(); ++i) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
819 820 821 822
        QScopedPointer<Mlt::Filter> effect(service.filter(i));
        if (!effect || !effect->is_valid() || effect->get_int("kdenlive_ix") != ix) {
            continue;
        }
823 824 825
        service.lock();
        QString prefix;
        QString ser = effect->get("mlt_service");
Laurent Montel's avatar
Laurent Montel committed
826 827 828
        if (ser == QLatin1String("region")) {
            prefix = QStringLiteral("filter0.");
        }
829 830
        for (int j = 0; j < params.count(); ++j) {
            effect->set((prefix + params.at(j).name()).toUtf8().constData(), params.at(j).value().toUtf8().constData());
Nicolas Carion's avatar
Nicolas Carion committed
831
            // qCDebug(KDENLIVE_LOG)<<params.at(j).name()<<" = "<<params.at(j).value();
832
        }
833
        service.unlock();
834
    }
835
    if (auto ptr = m_binController.lock()) ptr->updateTrackProducer(m_controllerBinId);
Nicolas Carion's avatar
Nicolas Carion committed
836
    // slotRefreshTracks();
837
    */
838 839 840 841
}

bool ClipController::hasEffects() const
{
842
    return m_effectStack->rowCount() > 0;
843 844
}

845
void ClipController::setBinEffectsEnabled(bool enabled)
846
{
847
    m_effectStack->setEffectStackEnabled(enabled);
848 849
}

Laurent Montel's avatar
Laurent Montel committed
850
void ClipController::saveZone(QPoint zone, const QDir &dir)
851
{
852
    QString path = QString(clipName() + QLatin1Char('_') + QString::number(zone.x()) + QStringLiteral(".mlt"));
853
    if (dir.exists(path)) {
Nicolas Carion's avatar
Nicolas Carion committed
854
        // TODO ask for overwrite
855
    }
856
    Mlt::Consumer xmlConsumer(pCore->getCurrentProfile()->profile(), ("xml:" + dir.absoluteFilePath(path)).toUtf8().constData());
857 858 859
    xmlConsumer.set("terminate_on_pause", 1);
    Mlt::Producer prod(m_masterProducer->get_producer());
    Mlt::Producer *prod2 = prod.cut(zone.x(), zone.y());
860
    Mlt::Playlist list(pCore->getCurrentProfile()->profile());
861
    list.insert_at(0, *prod2, 0);
Nicolas Carion's avatar
Nicolas Carion committed
862
    // list.set("title", desc.toUtf8().constData());
863 864 865 866
    xmlConsumer.connect(list);
    xmlConsumer.run();
    delete prod2;
}
867 868 869 870 871

std::shared_ptr<EffectStackModel> ClipController::getEffectStack() const
{
    return m_effectStack;
}
872 873

bool ClipController::addEffect(const QString &effectId)
874
{
875
    return m_effectStack->appendEffect(effectId, true);
876
}
877

Nicolas Carion's avatar
Nicolas Carion committed
878
bool ClipController::copyEffect(const std::shared_ptr<EffectStackModel> &stackModel, int rowId)
879
{
880 881
    m_effectStack->copyEffect(stackModel->getEffectStackRow(rowId),
                              !m_hasAudio ? PlaylistState::VideoOnly : !m_hasVideo ? PlaylistState::AudioOnly : PlaylistState::Disabled);
882 883 884
    return true;
}

885 886 887 888
std::shared_ptr<MarkerListModel> ClipController::getMarkerModel() const
{
    return m_markerModel;
}