projectclip.cpp 51.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
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 "projectclip.h"
Nicolas Carion's avatar
Nicolas Carion committed
24
#include "core.h"
25
#include "doc/docundostack.hpp"
26
#include "bin.h"
27
#include "doc/kdenlivedoc.h"
28 29
#include "doc/kthumb.h"
#include "kdenlivesettings.h"
30
#include "lib/audio/audioStreamInfo.h"
Nicolas Carion's avatar
Nicolas Carion committed
31 32
#include "mltcontroller/bincontroller.h"
#include "mltcontroller/clipcontroller.h"
33
#include "mltcontroller/clip.h"
34
#include "mltcontroller/clippropertiescontroller.h"
35
#include "model/markerlistmodel.hpp"
36
#include "profiles/profilemodel.hpp"
Nicolas Carion's avatar
Nicolas Carion committed
37
#include "project/projectcommands.h"
38
#include "effects/effectstack/model/effectstackmodel.hpp"
39
#include "project/projectmanager.h"
Nicolas Carion's avatar
Nicolas Carion committed
40 41 42 43
#include "projectfolder.h"
#include "projectitemmodel.h"
#include "projectsubclip.h"
#include "timecode.h"
Nicolas Carion's avatar
style  
Nicolas Carion committed
44
#include "timeline2/model/snapmodel.hpp"
Nicolas Carion's avatar
Nicolas Carion committed
45
#include "utils/KoIconUtils.h"
46
#include "xml/xml.hpp"
47 48
#include <kimagecache.h>
#include <QPainter>
49

Laurent Montel's avatar
Laurent Montel committed
50
#include "kdenlive_debug.h"
51
#include <KLocalizedString>
52
#include <KMessageBox>
Nicolas Carion's avatar
Nicolas Carion committed
53 54 55 56 57
#include <QCryptographicHash>
#include <QDir>
#include <QDomElement>
#include <QFile>
#include <QtConcurrent>
Nicolas Carion's avatar
Nicolas Carion committed
58
#include <utility>
59

60 61
ProjectClip::ProjectClip(const QString &id, const QIcon &thumb, std::shared_ptr<ProjectItemModel> model, std::shared_ptr<Mlt::Producer> producer)
    : AbstractProjectItem(AbstractProjectItem::ClipItem, id, model)
62
    , ClipController(id, pCore->binController(), producer)
63 64
    , m_abortAudioThumb(false)
    , m_thumbsProducer(nullptr)
65
{
66
    m_markerModel = std::make_shared<MarkerListModel>(id, pCore->projectManager()->undoStack());
67
    m_clipStatus = StatusReady;
68 69 70 71
    m_name = clipName();
    m_duration = getStringDuration();
    m_date = date;
    m_description = ClipController::description();
72
    if (m_clipType == Audio) {
73
        m_thumbnail = KoIconUtils::themedIcon(QStringLiteral("audio-x-generic"));
74 75 76
    } else {
        m_thumbnail = thumb;
    }
77 78
    // Make sure we have a hash for this clip
    hash();
79
    connect(this, &ProjectClip::updateJobStatus, this, &ProjectClip::setJobStatus);
80
    connect(this, &ProjectClip::updateThumbProgress, model.get(), &ProjectItemModel::updateThumbProgress);
81 82 83
    connect(m_markerModel.get(), &MarkerListModel::modelChanged, [&](){
            setProducerProperty(QStringLiteral("kdenlive:markers"), m_markerModel->toJson());
        });
84
    connectEffectStack();
85 86
    QString markers = getProducerProperty(QStringLiteral("kdenlive:markers"));
    if (!markers.isEmpty()) {
87
        QMetaObject::invokeMethod(m_markerModel.get(), "importFromJson", Qt::QueuedConnection, Q_ARG(const QString &, markers), Q_ARG(bool, true), Q_ARG(bool, false));
88
    }
89 90
}

91 92
// static
std::shared_ptr<ProjectClip> ProjectClip::construct(const QString &id, const QIcon &thumb, std::shared_ptr<ProjectItemModel> model,
93
                                                    std::shared_ptr<Mlt::Producer> producer)
94
{
95
    std::shared_ptr<ProjectClip> self(new ProjectClip(id, thumb, model, producer));
96
    baseFinishConstruct(self);
97
    model->loadSubClips(id, self->getPropertiesFromPrefix(QStringLiteral("kdenlive:clipzone.")));
98 99 100
    return self;
}

101 102 103
ProjectClip::ProjectClip(const QString &id, const QDomElement &description, const QIcon &thumb, std::shared_ptr<ProjectItemModel> model)
    : AbstractProjectItem(AbstractProjectItem::ClipItem, id, description, model)
    , ClipController(id, pCore->binController())
104 105
    , m_abortAudioThumb(false)
    , m_thumbsProducer(nullptr)
106
{
107
    m_clipStatus = StatusWaiting;
108
    m_thumbnail = thumb;
109
    m_markerModel = std::make_shared<MarkerListModel>(m_binId, pCore->projectManager()->undoStack());
110
    if (description.hasAttribute(QStringLiteral("type"))) {
111 112
        m_clipType = (ClipType)description.attribute(QStringLiteral("type")).toInt();
        if (m_clipType == Audio) {
113
            m_thumbnail = KoIconUtils::themedIcon(QStringLiteral("audio-x-generic"));
114
        }
115
    }
116
    m_temporaryUrl = getXmlProperty(description, QStringLiteral("resource"));
117
    QString clipName = getXmlProperty(description, QStringLiteral("kdenlive:clipname"));
118 119
    if (!clipName.isEmpty()) {
        m_name = clipName;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
120
    } else if (!m_temporaryUrl.isEmpty()) {
121
        m_name = QFileInfo(m_temporaryUrl).fileName();
122 123
    } else {
        m_name = i18n("Untitled");
124
    }
125
    connect(this, &ProjectClip::updateJobStatus, this, &ProjectClip::setJobStatus);
126
    connect(this, &ProjectClip::updateThumbProgress, model.get(), &ProjectItemModel::updateThumbProgress);
127 128 129
    connect(m_markerModel.get(), &MarkerListModel::modelChanged, [&](){
            setProducerProperty(QStringLiteral("kdenlive:markers"), m_markerModel->toJson());
        });
130
    connectEffectStack();
131 132
}

133
std::shared_ptr<ProjectClip> ProjectClip::construct(const QString &id, const QDomElement &description, const QIcon &thumb, std::shared_ptr<ProjectItemModel> model)
134
{
135
    std::shared_ptr<ProjectClip> self(new ProjectClip(id, description, thumb, model));
136 137 138 139
    baseFinishConstruct(self);
    return self;
}

140 141
ProjectClip::~ProjectClip()
{
142
    // controller is deleted in bincontroller
143
    abortAudioThumbs();
144 145
    QMutexLocker audioLock(&m_audioMutex);
    m_thumbMutex.lock();
146
    m_requestedThumbs.clear();
147
    m_thumbMutex.unlock();
148
    m_thumbThread.waitForFinished();
149
    delete m_thumbsProducer;
150
    audioFrameCache.clear();
151 152 153 154 155
    // delete all timeline producers
    std::map<int, std::shared_ptr<Mlt::Producer>>::iterator itr = m_timelineProducers.begin();
    while (itr != m_timelineProducers.end()) {
        itr = m_timelineProducers.erase(itr);
    }
156 157
}

158 159 160
void ProjectClip::connectEffectStack()
{
    connect(m_effectStack.get(), &EffectStackModel::dataChanged, [&](QModelIndex,QModelIndex,QVector<int>){
161
        updateChildProducers();
162 163 164 165
    });

}

166 167 168
void ProjectClip::abortAudioThumbs()
{
    m_abortAudioThumb = true;
169
    emit doAbortAudioThumbs();
170 171
}

172 173
QString ProjectClip::getToolTip() const
{
174
    return url();
175 176
}

177
QString ProjectClip::getXmlProperty(const QDomElement &producer, const QString &propertyName, const QString &defaultValue)
178
{
179
    QString value = defaultValue;
180
    QDomNodeList props = producer.elementsByTagName(QStringLiteral("property"));
181
    for (int i = 0; i < props.count(); ++i) {
182
        if (props.at(i).toElement().attribute(QStringLiteral("name")) == propertyName) {
183 184 185 186 187 188 189
            value = props.at(i).firstChild().nodeValue();
            break;
        }
    }
    return value;
}

Laurent Montel's avatar
Laurent Montel committed
190
void ProjectClip::updateAudioThumbnail(const QVariantList &audioLevels)
191
{
192
    audioFrameCache = audioLevels;
193
    m_audioThumbCreated = true;
Nicolas Carion's avatar
Nicolas Carion committed
194
    if (auto ptr = m_model.lock()) emit std::static_pointer_cast<ProjectItemModel>(ptr)->refreshAudioThumbs(m_binId);
195 196 197 198 199
    emit gotAudioData();
}

bool ProjectClip::audioThumbCreated() const
{
200
    return (m_audioThumbCreated);
201 202
}

203 204
ClipType ProjectClip::clipType() const
{
205
    return m_clipType;
206 207
}

208 209
bool ProjectClip::hasParent(const QString &id) const
{
210 211
    std::shared_ptr<AbstractProjectItem> par = parent();
    while (par) {
212 213 214 215
        if (par->clipId() == id) {
            return true;
        }
        par = par->parent();
216 217 218 219
    }
    return false;
}

220
std::shared_ptr<ProjectClip> ProjectClip::clip(const QString &id)
221
{
Nicolas Carion's avatar
Nicolas Carion committed
222
    if (id == m_binId) {
223
        return std::static_pointer_cast<ProjectClip>(shared_from_this());
224
    }
225
    return std::shared_ptr<ProjectClip>();
226 227
}

228
std::shared_ptr<ProjectFolder> ProjectClip::folder(const QString &id)
229
{
230
    Q_UNUSED(id)
231
    return std::shared_ptr<ProjectFolder>();
232 233
}

234
std::shared_ptr<ProjectSubClip> ProjectClip::getSubClip(int in, int out)
235
{
236
    for (int i = 0; i < childCount(); ++i) {
237
        std::shared_ptr<ProjectSubClip> clip = std::static_pointer_cast<ProjectSubClip>(child(i))->subClip(in, out);
238 239 240 241
        if (clip) {
            return clip;
        }
    }
242
    return std::shared_ptr<ProjectSubClip>();
243 244
}

245 246 247
QStringList ProjectClip::subClipIds() const
{
    QStringList subIds;
248
    for (int i = 0; i < childCount(); ++i) {
249
        std::shared_ptr<AbstractProjectItem> clip = std::static_pointer_cast<AbstractProjectItem>(child(i));
250 251 252 253 254 255 256
        if (clip) {
            subIds << clip->clipId();
        }
    }
    return subIds;
}

257
std::shared_ptr<ProjectClip> ProjectClip::clipAt(int ix)
258
{
Nicolas Carion's avatar
Nicolas Carion committed
259
    if (ix == row()) {
260
        return std::static_pointer_cast<ProjectClip>(shared_from_this());
261
    }
262
    return std::shared_ptr<ProjectClip>();
263 264
}

265
/*bool ProjectClip::isValid() const
266
{
267 268
    return m_controller->isValid();
}*/
269

270 271
bool ProjectClip::hasUrl() const
{
272
    if ((m_clipType != Color) && (m_clipType != Unknown)) {
273
        return (!clipUrl().isEmpty());
274
    }
275 276 277
    return false;
}

278
const QString ProjectClip::url() const
279
{
280
    return clipUrl();
281 282
}

283
GenTime ProjectClip::duration() const
284
{
285
    return getPlaytime();
286 287
}

288 289 290
int ProjectClip::frameDuration() const
{
    GenTime d = duration();
291
    return d.frames(pCore->getCurrentFps());
292 293
}

294
void ProjectClip::reloadProducer(bool refreshOnly)
295 296 297
{
    QDomDocument doc;
    QDomElement xml = toXml(doc);
298
    if (refreshOnly) {
299
        // set a special flag to request thumbnail only
300
        xml.setAttribute(QStringLiteral("refreshOnly"), QStringLiteral("1"));
301
    }
Nicolas Carion's avatar
Nicolas Carion committed
302
    if (auto ptr = m_model.lock()) emit std::static_pointer_cast<ProjectItemModel>(ptr)->reloadProducer(m_binId, xml);
303 304
}

305
QDomElement ProjectClip::toXml(QDomDocument &document, bool includeMeta)
306
{
307 308
    getProducerXML(document, includeMeta);
    QDomElement prod = document.documentElement().firstChildElement(QStringLiteral("producer"));
309 310
    if (m_clipType != Unknown) {
        prod.setAttribute(QStringLiteral("type"), (int)m_clipType);
311
    }
312
    return prod;
313 314
}

Laurent Montel's avatar
Laurent Montel committed
315
void ProjectClip::setThumbnail(const QImage &img)
316
{
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
317 318
    QPixmap thumb = roundedPixmap(QPixmap::fromImage(img));
    if (hasProxy() && !thumb.isNull()) {
319
        // Overlay proxy icon
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
320
        QPainter p(&thumb);
321
        QColor c(220, 220, 10, 200);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
322
        QRect r(0, 0, thumb.height() / 2.5, thumb.height() / 2.5);
323 324 325 326 327 328 329 330
        p.fillRect(r, c);
        QFont font = p.font();
        font.setPixelSize(r.height());
        font.setBold(true);
        p.setFont(font);
        p.setPen(Qt::black);
        p.drawText(r, Qt::AlignCenter, i18nc("The first letter of Proxy, used as abbreviation", "P"));
    }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
331
    m_thumbnail = QIcon(thumb);
332
    updateTimelineClips(QVector<int>() << TimelineModel::ReloadThumb);
333
    if (auto ptr = m_model.lock()) std::static_pointer_cast<ProjectItemModel>(ptr)->onItemUpdated(std::static_pointer_cast<ProjectClip>(shared_from_this()));
334 335
}

336 337 338 339 340
QPixmap ProjectClip::thumbnail(int width, int height)
{
    return m_thumbnail.pixmap(width, height);
}

341
bool ProjectClip::setProducer(std::shared_ptr<Mlt::Producer> producer, bool replaceProducer)
342
{
Nicolas Carion's avatar
Nicolas Carion committed
343
    updateProducer(std::move(producer));
344

345 346 347
    // Update info
    if (m_name.isEmpty()) {
        m_name = clipName();
348
    }
349 350 351
    m_date = date;
    m_description = ClipController::description();
    m_temporaryUrl.clear();
352 353
    if (m_clipType == Audio) {
        m_thumbnail = KoIconUtils::themedIcon(QStringLiteral("audio-x-generic"));
354 355 356 357
    } else if (m_clipType == Image) {
        if (getProducerIntProperty(QStringLiteral("meta.media.width")) < 8 || getProducerIntProperty(QStringLiteral("meta.media.height")) < 8) {
            KMessageBox::information(QApplication::activeWindow(), i18n("Image dimension smaller than 8 pixels.\nThis is not correctly supported by our video framework."));
        }
358
    }
359
    m_duration = getStringDuration();
360
    m_clipStatus = StatusReady;
361
    if (!hasProxy()) {
Nicolas Carion's avatar
Nicolas Carion committed
362
        if (auto ptr = m_model.lock()) emit std::static_pointer_cast<ProjectItemModel>(ptr)->refreshPanel(m_binId);
363
    }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
364 365 366
    if (auto ptr = m_model.lock()) {
        std::static_pointer_cast<ProjectItemModel>(ptr)->onItemUpdated(std::static_pointer_cast<ProjectClip>(shared_from_this()));
    }
367
    // Make sure we have a hash for this clip
368
    getFileHash();
369
    createAudioThumbs();
370 371 372 373
    if (replaceProducer) {
        // Recreate thumbnail
        
    }
374
    return true;
375 376 377 378
}

void ProjectClip::createAudioThumbs()
{
379
    if (KdenliveSettings::audiothumbnails() && (m_clipType == AV || m_clipType == Audio || m_clipType == Playlist)) {
Nicolas Carion's avatar
Nicolas Carion committed
380
        if (auto ptr = m_model.lock()) emit std::static_pointer_cast<ProjectItemModel>(ptr)->requestAudioThumbs(m_binId, duration().ms());
381
        emit updateJobStatus(AbstractClipJob::THUMBJOB, JobWaiting, 0);
382
    }
383 384
}

385 386 387 388 389 390
Mlt::Producer *ProjectClip::thumbProducer()
{
    QMutexLocker locker(&m_producerMutex);
    if (m_thumbsProducer) {
        return m_thumbsProducer;
    }
391
    if (clipType() == Unknown) {
Laurent Montel's avatar
Laurent Montel committed
392
        return nullptr;
393
    }
394 395
    std::shared_ptr<Mlt::Producer> prod = originalProducer();
    if (!prod->is_valid()) {
Laurent Montel's avatar
Laurent Montel committed
396
        return nullptr;
397
    }
398
    Clip clip(*prod.get());
399
    if (KdenliveSettings::gpu_accel()) {
400
        m_thumbsProducer = clip.softClone(ClipController::getPassPropertiesList());
401 402
        Mlt::Filter scaler(*prod->profile(), "swscale");
        Mlt::Filter converter(*prod->profile(), "avcolor_space");
403 404
        m_thumbsProducer->attach(scaler);
        m_thumbsProducer->attach(converter);
405 406
    } else {
        m_thumbsProducer = clip.clone();
407 408 409 410
    }
    return m_thumbsProducer;
}

411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442
std::shared_ptr<Mlt::Producer> ProjectClip::timelineProducer(PlaylistState::ClipState state, int track)
{
    if (!m_service.startsWith(QLatin1String("avformat"))) {
        return originalProducer();
    }
    if (state == PlaylistState::VideoOnly) {
        if (m_timelineProducers.count(0) > 0) {
            return m_timelineProducers.find(0)->second;
        }
        std::shared_ptr<Mlt::Producer> videoProd = cloneProducer();
        videoProd->set("audio_index", -1);
        m_timelineProducers[0] = videoProd;
        return videoProd;
    }
    if (state == PlaylistState::AudioOnly) {
        if (m_timelineProducers.count(-track) > 0) {
            return m_timelineProducers.find(-track)->second;
        }
        std::shared_ptr<Mlt::Producer> audioProd = cloneProducer();
        audioProd->set("video_index", -1);
        m_timelineProducers[-track] = audioProd;
        return audioProd;
    }
    if (m_timelineProducers.count(track) > 0) {
        return m_timelineProducers.find(track)->second;
    }
    std::shared_ptr<Mlt::Producer> normalProd = cloneProducer();
    m_timelineProducers[track] = normalProd;
    return normalProd;
}

std::shared_ptr<Mlt::Producer> ProjectClip::cloneProducer()
443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461
{
    Mlt::Consumer c(*m_masterProducer->profile(), "xml", "string");
    Mlt::Service s(m_masterProducer->get_service());
    int ignore = s.get_int("ignore_points");
    if (ignore) {
        s.set("ignore_points", 0);
    }
    c.connect(s);
    c.set("time_format", "frames");
    c.set("no_meta", 1);
    c.set("no_root", 1);
    c.set("no_profile", 1);
    c.set("root", "/");
    c.set("store", "kdenlive");
    c.start();
    if (ignore) {
        s.set("ignore_points", ignore);
    }
    const QByteArray clipXml = c.get("string");
462 463
    std::shared_ptr<Mlt::Producer> prod(new Mlt::Producer(*m_masterProducer->profile(), "xml-string", clipXml.constData()));
    return prod;
464 465
}

466
bool ProjectClip::isReady() const
467
{
468
    return m_clipStatus == StatusReady;
469 470
}

471
/*void ProjectClip::setZone(const QPoint &zone)
472 473
{
    m_zone = zone;
474
}*/
475 476 477

QPoint ProjectClip::zone() const
{
478 479
    int x = getProducerIntProperty(QStringLiteral("kdenlive:zone_in"));
    int y = getProducerIntProperty(QStringLiteral("kdenlive:zone_out"));
480
    return QPoint(x, y);
481 482 483 484
}

const QString ProjectClip::hash()
{
485
    QString clipHash = getProducerProperty(QStringLiteral("kdenlive:file_hash"));
486 487
    if (!clipHash.isEmpty()) {
        return clipHash;
488
    }
489
    return getFileHash();
490 491
}

492
const QString ProjectClip::getFileHash()
493
{
494 495
    QByteArray fileData;
    QByteArray fileHash;
496
    switch (m_clipType) {
497
    case SlideShow:
498
        fileData = clipUrl().toUtf8();
499 500 501
        fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5);
        break;
    case Text:
502
        fileData = getProducerProperty(QStringLiteral("xmldata")).toUtf8();
503 504 505
        fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5);
        break;
    case QText:
506
        fileData = getProducerProperty(QStringLiteral("text")).toUtf8();
507 508 509
        fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5);
        break;
    case Color:
510
        fileData = getProducerProperty(QStringLiteral("resource")).toUtf8();
511 512 513
        fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5);
        break;
    default:
514
        QFile file(clipUrl());
515 516 517 518 519
        if (file.open(QIODevice::ReadOnly)) { // write size and hash only if resource points to a file
            /*
             * 1 MB = 1 second per 450 files (or faster)
             * 10 MB = 9 seconds per 450 files (or faster)
             */
520 521
            if (file.size() > 2000000) {
                fileData = file.read(1000000);
522
                if (file.seek(file.size() - 1000000)) {
523
                    fileData.append(file.readAll());
524 525
                }
            } else {
526
                fileData = file.readAll();
527
            }
528
            file.close();
Nicolas Carion's avatar
Nicolas Carion committed
529
            ClipController::setProducerProperty(QStringLiteral("kdenlive:file_size"), QString::number(file.size()));
530
            fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5);
531 532 533 534 535
        }
        break;
    }
    if (fileHash.isEmpty()) {
        return QString();
536 537
    }
    QString result = fileHash.toHex();
Nicolas Carion's avatar
Nicolas Carion committed
538
    ClipController::setProducerProperty(QStringLiteral("kdenlive:file_hash"), result);
539
    return result;
540
}
541 542 543

double ProjectClip::getOriginalFps() const
{
544
    return originalFps();
545
}
546

547

548 549
bool ProjectClip::hasProxy() const
{
550
    QString proxy = getProducerProperty(QStringLiteral("kdenlive:proxy"));
551
    return proxy.size() > 2;
552 553
}

Laurent Montel's avatar
Laurent Montel committed
554
void ProjectClip::setProperties(const QMap<QString, QString> &properties, bool refreshPanel)
555 556
{
    QMapIterator<QString, QString> i(properties);
Laurent Montel's avatar
Laurent Montel committed
557
    QMap<QString, QString> passProperties;
558
    bool refreshAnalysis = false;
559
    bool reload = false;
560
    bool refreshOnly = true;
561 562
    // Some properties also need to be passed to track producers
    QStringList timelineProperties;
Laurent Montel's avatar
Laurent Montel committed
563 564
    if (properties.contains(QStringLiteral("templatetext"))) {
        m_description = properties.value(QStringLiteral("templatetext"));
565 566
        if (auto ptr = m_model.lock())
            std::static_pointer_cast<ProjectItemModel>(ptr)->onItemUpdated(std::static_pointer_cast<ProjectClip>(shared_from_this()));
567 568
        refreshPanel = true;
    }
Nicolas Carion's avatar
Nicolas Carion committed
569 570 571 572
    timelineProperties << QStringLiteral("force_aspect_ratio") << QStringLiteral("video_index") << QStringLiteral("audio_index")
                       << QStringLiteral("set.force_full_luma") << QStringLiteral("full_luma") << QStringLiteral("threads")
                       << QStringLiteral("force_colorspace") << QStringLiteral("force_tff") << QStringLiteral("force_progressive")
                       << QStringLiteral("force_fps");
573
    QStringList keys;
Nicolas Carion's avatar
Nicolas Carion committed
574 575
    keys << QStringLiteral("luma_duration") << QStringLiteral("luma_file") << QStringLiteral("fade") << QStringLiteral("ttl") << QStringLiteral("softness")
         << QStringLiteral("crop") << QStringLiteral("animation");
576 577
    while (i.hasNext()) {
        i.next();
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
578
        setProducerProperty(i.key(), i.value());
579
        if (m_clipType == SlideShow && keys.contains(i.key())) {
580
            reload = true;
581
            refreshOnly = false;
582
        }
583 584 585
        if (i.key().startsWith(QLatin1String("kdenlive:clipanalysis"))) {
            refreshAnalysis = true;
        }
586 587 588
        if (timelineProperties.contains(i.key())) {
            passProperties.insert(i.key(), i.value());
        }
589
    }
590 591
    if (properties.contains(QStringLiteral("kdenlive:proxy"))) {
        QString value = properties.value(QStringLiteral("kdenlive:proxy"));
592
        // If value is "-", that means user manually disabled proxy on this clip
593
        if (value.isEmpty() || value == QLatin1String("-")) {
594
            // reset proxy
595
            if (auto ptr = m_model.lock()) {
596 597
                emit std::static_pointer_cast<ProjectItemModel>(ptr)->discardJobs(m_binId, AbstractClipJob::PROXYJOB);
                reloadProducer();
598
            }
599
        } else {
600
            // A proxy was requested, make sure to keep original url
601
            setProducerProperty(QStringLiteral("kdenlive:originalurl"), url());
602 603 604
            if (auto ptr = m_model.lock()) {
                emit std::static_pointer_cast<ProjectItemModel>(ptr)->startJob(m_binId, AbstractClipJob::PROXYJOB);
            }
605
        }
Nicolas Carion's avatar
Nicolas Carion committed
606 607
    } else if (properties.contains(QStringLiteral("resource")) || properties.contains(QStringLiteral("templatetext")) ||
               properties.contains(QStringLiteral("autorotate"))) {
608
        // Clip resource changed, update thumbnail
609
        if (m_clipType != Color) {
610
            reloadProducer();
611 612
        } else {
            reload = true;
613
        }
614
    }
615
    if (properties.contains(QStringLiteral("xmldata")) || !passProperties.isEmpty()) {
616
        reload = true;
617
    }
618 619 620
    if (refreshAnalysis) {
        emit refreshAnalysisPanel();
    }
621
    if (properties.contains(QStringLiteral("length")) || properties.contains(QStringLiteral("kdenlive:duration"))) {
622
        m_duration = getStringDuration();
623 624
        if (auto ptr = m_model.lock())
            std::static_pointer_cast<ProjectItemModel>(ptr)->onItemUpdated(std::static_pointer_cast<ProjectClip>(shared_from_this()));
625 626
        refreshOnly = false;
        reload = true;
627 628
    }

629 630
    if (properties.contains(QStringLiteral("kdenlive:clipname"))) {
        m_name = properties.value(QStringLiteral("kdenlive:clipname"));
631
        refreshPanel = true;
632
        if (auto ptr = m_model.lock()) {
633
            std::static_pointer_cast<ProjectItemModel>(ptr)->onItemUpdated(std::static_pointer_cast<ProjectClip>(shared_from_this()));
634 635 636
        }
        // update timeline clips
        updateTimelineClips(QVector<int>() << TimelineModel::NameRole);
637
    }
638 639 640 641
    if (refreshPanel) {
        // Some of the clip properties have changed through a command, update properties panel
        emit refreshPropertiesPanel();
    }
642
    if (reload) {
643
        // producer has changed, refresh monitor and thumbnail
644
        reloadProducer(refreshOnly);
645
        if (auto ptr = m_model.lock()) emit std::static_pointer_cast<ProjectItemModel>(ptr)->refreshClip(m_binId);
646
    }
647
    if (!passProperties.isEmpty()) {
648
        if (auto ptr = m_model.lock()) emit std::static_pointer_cast<ProjectItemModel>(ptr)->updateTimelineProducers(m_binId, passProperties);
649
    }
650 651
}

652
void ProjectClip::setJobStatus(int jobType, int status, int progress, const QString &statusMessage)
653
{
Nicolas Carion's avatar
Nicolas Carion committed
654
    m_jobType = (AbstractClipJob::JOBTYPE)jobType;
655
    if (progress > 0) {
656 657 658
        if (m_jobProgress == progress) {
            return;
        }
659
        m_jobProgress = progress;
660
    } else {
661
        m_jobProgress = status;
662 663 664
        if (m_jobType == AbstractClipJob::PROXYJOB && (status == JobAborted || status == JobCrashed)) {
            setProducerProperty(QStringLiteral("kdenlive:proxy"), QStringLiteral("-"));
        }
Nicolas Carion's avatar
Nicolas Carion committed
665
        if ((status == JobAborted || status == JobCrashed || status == JobDone) && !statusMessage.isEmpty()) {
666
            m_jobMessage = statusMessage;
667
            if (auto ptr = m_model.lock()) emit std::static_pointer_cast<ProjectItemModel>(ptr)->emitMessage(statusMessage, 100, OperationCompletedMessage);
668
        }
669
    }
670
    if (auto ptr = m_model.lock()) std::static_pointer_cast<ProjectItemModel>(ptr)->onItemUpdated(std::static_pointer_cast<ProjectClip>(shared_from_this()));
671
}
672

673 674
ClipPropertiesController *ProjectClip::buildProperties(QWidget *parent)
{
675 676
    auto ptr = m_model.lock();
    Q_ASSERT(ptr);
Nicolas Carion's avatar
Nicolas Carion committed
677
    ClipPropertiesController *panel =
678
        new ClipPropertiesController(static_cast<ClipController *>(this), parent);
Laurent Montel's avatar
Laurent Montel committed
679 680
    connect(this, &ProjectClip::refreshPropertiesPanel, panel, &ClipPropertiesController::slotReloadProperties);
    connect(this, &ProjectClip::refreshAnalysisPanel, panel, &ClipPropertiesController::slotFillAnalysisData);
681 682 683
    return panel;
}

684 685
void ProjectClip::updateParentInfo(const QString &folderid, const QString &foldername)
{
686
    Q_UNUSED(foldername);
Nicolas Carion's avatar
Nicolas Carion committed
687
    ClipController::setProducerProperty(QStringLiteral("kdenlive:folderid"), folderid);
688 689
}

Laurent Montel's avatar
Laurent Montel committed
690
bool ProjectClip::matches(const QString &condition)
691
{
Nicolas Carion's avatar
Nicolas Carion committed
692
    // TODO
693
    Q_UNUSED(condition)
694 695 696
    return true;
}

697
bool ProjectClip::rename(const QString &name, int column)
698
{
Laurent Montel's avatar
Laurent Montel committed
699 700
    QMap<QString, QString> newProperites;
    QMap<QString, QString> oldProperites;
701 702
    bool edited = false;
    switch (column) {
703 704 705 706
    case 0:
        if (m_name == name) {
            return false;
        }
707
        // Rename clip
708 709
        oldProperites.insert(QStringLiteral("kdenlive:clipname"), m_name);
        newProperites.insert(QStringLiteral("kdenlive:clipname"), name);
710 711 712
        m_name = name;
        edited = true;
        break;
713 714 715 716
    case 2:
        if (m_description == name) {
            return false;
        }