projectitemmodel.cpp 40.5 KB
Newer Older
1 2 3
/*
Copyright (C) 2012  Till Theato <root@ttill.de>
Copyright (C) 2014  Jean-Baptiste Mardelle <jb@kdenlive.org>
4
Copyright (C) 2017  Nicolas Carion
5 6 7 8 9 10 11
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
12
by the membership of KDE e.V.), which shall act as a proxy
13 14 15 16 17 18 19 20 21 22 23 24 25
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 "projectitemmodel.h"
#include "abstractprojectitem.h"
26
#include "binplaylist.hpp"
27 28
#include "core.h"
#include "doc/kdenlivedoc.h"
29
#include "filewatcher.hpp"
30 31 32 33
#include "jobs/audiothumbjob.hpp"
#include "jobs/jobmanager.h"
#include "jobs/loadjob.hpp"
#include "jobs/thumbjob.hpp"
34
#include "jobs/cachejob.hpp"
35
#include "kdenlivesettings.h"
Nicolas Carion's avatar
Nicolas Carion committed
36
#include "macros.hpp"
Nicolas Carion's avatar
linting  
Nicolas Carion committed
37
#include "profiles/profilemodel.hpp"
38
#include "project/projectmanager.h"
39 40
#include "projectclip.h"
#include "projectfolder.h"
Nicolas Carion's avatar
Nicolas Carion committed
41
#include "projectsubclip.h"
42
#include "xml/xml.hpp"
43 44 45 46

#include <KLocalizedString>
#include <QIcon>
#include <QMimeData>
47
#include <QProgressDialog>
48
#include <mlt++/Mlt.h>
49
#include <queue>
Nicolas Carion's avatar
linting  
Nicolas Carion committed
50
#include <qvarlengtharray.h>
Nicolas Carion's avatar
Nicolas Carion committed
51
#include <utility>
52

53
ProjectItemModel::ProjectItemModel(QObject *parent)
54
    : AbstractTreeModel(parent)
Nicolas Carion's avatar
Nicolas Carion committed
55
    , m_lock(QReadWriteLock::Recursive)
56
    , m_binPlaylist(new BinPlaylist())
57
    , m_fileWatcher(new FileWatcher())
58
    , m_nextId(1)
59
    , m_blankThumb()
60
    , m_dragType(PlaylistState::Disabled)
61
{
62 63 64
    QPixmap pix(QSize(160, 90));
    pix.fill(Qt::lightGray);
    m_blankThumb.addPixmap(pix);
65 66
    connect(m_fileWatcher.get(), &FileWatcher::binClipModified, this, &ProjectItemModel::reloadClip);
    connect(m_fileWatcher.get(), &FileWatcher::binClipWaiting, this, &ProjectItemModel::setClipWaiting);
67
    connect(m_fileWatcher.get(), &FileWatcher::binClipMissing, this, &ProjectItemModel::setClipInvalid);
68 69
}

70
std::shared_ptr<ProjectItemModel> ProjectItemModel::construct(QObject *parent)
71
{
72
    std::shared_ptr<ProjectItemModel> self(new ProjectItemModel(parent));
73 74
    self->rootItem = ProjectFolder::construct(self);
    return self;
75 76
}

Nicolas Carion's avatar
Nicolas Carion committed
77
ProjectItemModel::~ProjectItemModel() = default;
78

79 80 81
int ProjectItemModel::mapToColumn(int column) const
{
    switch (column) {
82 83 84 85 86 87 88 89 90
    case 0:
        return AbstractProjectItem::DataName;
        break;
    case 1:
        return AbstractProjectItem::DataDate;
        break;
    case 2:
        return AbstractProjectItem::DataDescription;
        break;
91 92 93
    case 3:
        return AbstractProjectItem::ClipType;
        break;
94 95 96 97 98 99
    case 4:
        return AbstractProjectItem::DataTag;
        break;
    case 5:
        return AbstractProjectItem::DataDuration;
        break;
100 101 102
    case 6:
        return AbstractProjectItem::DataId;
        break;
103 104 105
    case 7:
        return AbstractProjectItem::DataRating;
        break;
106 107
    default:
        return AbstractProjectItem::DataName;
108 109 110
    }
}

111
QVariant ProjectItemModel::data(const QModelIndex &index, int role) const
112
{
Nicolas Carion's avatar
Nicolas Carion committed
113
    READ_LOCK();
114 115 116
    if (!index.isValid()) {
        return QVariant();
    }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
117
    if (role == Qt::DisplayRole || role == Qt::EditRole) {
118
        std::shared_ptr<AbstractProjectItem> item = getBinItemByIndex(index);
Nicolas Carion's avatar
Nicolas Carion committed
119 120 121
        auto type = static_cast<AbstractProjectItem::DataType>(mapToColumn(index.column()));
        QVariant ret = item->getData(type);
        return ret;
122
    }
123 124 125 126
    if (role == Qt::DecorationRole) {
        if (index.column() != 0) {
            return QVariant();
        }
127
        // Data has to be returned as icon to allow the view to scale it
128
        std::shared_ptr<AbstractProjectItem> item = getBinItemByIndex(index);
Nicolas Carion's avatar
Nicolas Carion committed
129 130 131 132 133 134 135
        QVariant thumb = item->getData(AbstractProjectItem::DataThumbnail);
        QIcon icon;
        if (thumb.canConvert<QIcon>()) {
            icon = thumb.value<QIcon>();
        } else {
            qDebug() << "ERROR: invalid icon found";
        }
136
        return icon;
Nicolas Carion's avatar
Nicolas Carion committed
137
    }
138
    std::shared_ptr<AbstractProjectItem> item = getBinItemByIndex(index);
139
    return item->getData(static_cast<AbstractProjectItem::DataType>(role));
140 141
}

142
bool ProjectItemModel::setData(const QModelIndex &index, const QVariant &value, int role)
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
143
{
Nicolas Carion's avatar
Nicolas Carion committed
144
    QWriteLocker locker(&m_lock);
145
    std::shared_ptr<AbstractProjectItem> item = getBinItemByIndex(index);
146
    if (item->rename(value.toString(), index.column())) {
147
        emit dataChanged(index, index, {role});
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
148 149 150 151 152 153
        return true;
    }
    // Item name was not changed
    return false;
}

154
Qt::ItemFlags ProjectItemModel::flags(const QModelIndex &index) const
155
{
156
    /*return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEditable;*/
157
    READ_LOCK();
158
    if (!index.isValid()) {
159
        return Qt::ItemIsDropEnabled;
160
    }
161
    std::shared_ptr<AbstractProjectItem> item = getBinItemByIndex(index);
162 163
    AbstractProjectItem::PROJECTITEMTYPE type = item->itemType();
    switch (type) {
164 165 166 167 168
    case AbstractProjectItem::FolderItem:
        return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEditable;
        break;
    case AbstractProjectItem::ClipItem:
        if (!item->statusReady()) {
169
            return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
170 171 172 173
        }
        return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEditable;
        break;
    case AbstractProjectItem::SubClipItem:
174
        return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
175 176 177
        break;
    default:
        return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
178
    }
179 180
}

181
// cppcheck-suppress unusedFunction
182 183 184 185
bool ProjectItemModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
    Q_UNUSED(row)
    Q_UNUSED(column)
186
    QWriteLocker locker(&m_lock);
187
    if (action == Qt::IgnoreAction) {
188
        return true;
189
    }
190 191 192 193 194 195 196

    if (data->hasUrls()) {
        emit itemDropped(data->urls(), parent);
        return true;
    }

    if (data->hasFormat(QStringLiteral("kdenlive/producerslist"))) {
197
        // Dropping an Bin item
Laurent Montel's avatar
Laurent Montel committed
198
        const QStringList ids = QString(data->data(QStringLiteral("kdenlive/producerslist"))).split(QLatin1Char(';'));
199
        if (ids.constFirst().contains(QLatin1Char('/'))) {
200
            // subclip zone
201
            QStringList clipData = ids.constFirst().split(QLatin1Char('/'));
202 203
            if (clipData.length() >= 3) {
                QString id;
204 205 206 207 208 209
                std::shared_ptr<ProjectClip> masterClip = getClipByBinID(clipData.at(0));
                std::shared_ptr<ProjectSubClip> sub = masterClip->getSubClip(clipData.at(1).toInt(), clipData.at(2).toInt());
                if (sub != nullptr) {
                    // This zone already exists
                    return false;
                }
210
                return requestAddBinSubClip(id, clipData.at(1).toInt(), clipData.at(2).toInt(), {}, clipData.at(0));
211 212 213 214
            } else {
                // error, malformed clip zone, abort
                return false;
            }
215
        } else {
216 217
            emit itemDropped(ids, parent);
        }
218 219 220
        return true;
    }

221
    if (data->hasFormat(QStringLiteral("kdenlive/effect"))) {
222
        // Dropping effect on a Bin item
223 224 225 226 227
        QStringList effectData;
        effectData << QString::fromUtf8(data->data(QStringLiteral("kdenlive/effect")));
        QStringList source = QString::fromUtf8(data->data(QStringLiteral("kdenlive/effectsource"))).split(QLatin1Char('-'));
        effectData << source;
        emit effectDropped(effectData, parent);
228 229 230 231
        return true;
    }

    if (data->hasFormat(QStringLiteral("kdenlive/clip"))) {
Laurent Montel's avatar
Laurent Montel committed
232
        const QStringList list = QString(data->data(QStringLiteral("kdenlive/clip"))).split(QLatin1Char(';'));
233
        QString id;
234
        return requestAddBinSubClip(id, list.at(1).toInt(), list.at(2).toInt(), {}, list.at(0));
235
    }
236

237 238 239 240 241 242
    if (data->hasFormat(QStringLiteral("kdenlive/tag"))) {
        // Dropping effect on a Bin item
        QString tag = QString::fromUtf8(data->data(QStringLiteral("kdenlive/tag")));
        emit addTag(tag, parent);
        return true;
    }
243 244 245 246

    return false;
}

247 248
QVariant ProjectItemModel::headerData(int section, Qt::Orientation orientation, int role) const
{
249
    READ_LOCK();
250 251
    if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
        QVariant columnName;
252 253
        switch (section) {
        case 0:
254 255
            columnName = i18n("Name");
            break;
256
        case 1:
257 258
            columnName = i18n("Date");
            break;
259 260 261
        case 2:
            columnName = i18n("Description");
            break;
262 263 264
        case 3:
            columnName = i18n("Type");
            break;
265 266 267 268 269 270
        case 4:
            columnName = i18n("Tag");
            break;
        case 5:
            columnName = i18n("Duration");
            break;
271 272 273
        case 6:
            columnName = i18n("Id");
            break;
274 275 276
        case 7:
            columnName = i18n("Rating");
            break;
277 278 279 280 281 282 283 284 285
        default:
            columnName = i18n("Unknown");
            break;
        }
        return columnName;
    }
    return QAbstractItemModel::headerData(section, orientation, role);
}

286
int ProjectItemModel::columnCount(const QModelIndex &parent) const
287
{
288
    READ_LOCK();
289
    if (parent.isValid()) {
290
        return getBinItemByIndex(parent)->supportedDataCount();
Nicolas Carion's avatar
Nicolas Carion committed
291
    }
292
    return std::static_pointer_cast<ProjectFolder>(rootItem)->supportedDataCount();
293 294
}

295
// cppcheck-suppress unusedFunction
296 297 298 299 300
Qt::DropActions ProjectItemModel::supportedDropActions() const
{
    return Qt::CopyAction | Qt::MoveAction;
}

301 302
QStringList ProjectItemModel::mimeTypes() const
{
303
    QStringList types {QStringLiteral("kdenlive/producerslist"), QStringLiteral("text/uri-list"), QStringLiteral("kdenlive/clip"), QStringLiteral("kdenlive/effect"), QStringLiteral("kdenlive/tag")};
304 305 306
    return types;
}

307
QMimeData *ProjectItemModel::mimeData(const QModelIndexList &indices) const
308
{
309
    READ_LOCK();
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
310 311
    // Mime data is a list of id's separated by ';'.
    // Clip ids are represented like:  2 (where 2 is the clip's id)
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
312
    // Clip zone ids are represented like:  2/10/200 (where 2 is the clip's id, 10 and 200 are in and out points)
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
313
    // Folder ids are represented like:  #2 (where 2 is the folder's id)
Nicolas Carion's avatar
Nicolas Carion committed
314
    auto *mimeData = new QMimeData();
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
315
    QStringList list;
316
    size_t duration = 0;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
317 318
    for (int i = 0; i < indices.count(); i++) {
        QModelIndex ix = indices.at(i);
319 320 321
        if (!ix.isValid() || ix.column() != 0) {
            continue;
        }
322
        std::shared_ptr<AbstractProjectItem> item = getBinItemByIndex(ix);
323 324
        AbstractProjectItem::PROJECTITEMTYPE type = item->itemType();
        if (type == AbstractProjectItem::ClipItem) {
325 326 327 328
            ClipType::ProducerType cType = item->clipType();
            QString dragId = item->clipId();
            if ((cType == ClipType::AV || cType == ClipType::Playlist)) {
                switch (m_dragType) {
329 330 331 332 333 334 335 336
                case PlaylistState::AudioOnly:
                    dragId.prepend(QLatin1Char('A'));
                    break;
                case PlaylistState::VideoOnly:
                    dragId.prepend(QLatin1Char('V'));
                    break;
                default:
                    break;
337 338 339
                }
            }
            list << dragId;
340
            duration += (std::static_pointer_cast<ProjectClip>(item))->frameDuration();
341 342
        } else if (type == AbstractProjectItem::SubClipItem) {
            QPoint p = item->zone();
Nicolas Carion's avatar
Nicolas Carion committed
343 344
            list << std::static_pointer_cast<ProjectSubClip>(item)->getMasterClip()->clipId() + QLatin1Char('/') + QString::number(p.x()) + QLatin1Char('/') +
                        QString::number(p.y());
345
        } else if (type == AbstractProjectItem::FolderItem) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
346
            list << "#" + item->clipId();
347 348
        }
    }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
349 350
    if (!list.isEmpty()) {
        QByteArray data;
351
        data.append(list.join(QLatin1Char(';')).toUtf8());
Nicolas Carion's avatar
Nicolas Carion committed
352
        mimeData->setData(QStringLiteral("kdenlive/producerslist"), data);
353
        mimeData->setText(QString::number(duration));
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
354 355
    }
    return mimeData;
356 357
}

Nicolas Carion's avatar
Nicolas Carion committed
358
void ProjectItemModel::onItemUpdated(const std::shared_ptr<AbstractProjectItem> &item, int role)
359
{
360
    QWriteLocker locker(&m_lock);
361 362 363 364
    auto tItem = std::static_pointer_cast<TreeItem>(item);
    auto ptr = tItem->parentItem().lock();
    if (ptr) {
        auto index = getIndexFromItem(tItem);
365
        emit dataChanged(index, index, {role});
366
    }
367 368
}

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
369
void ProjectItemModel::onItemUpdated(const QString &binId, int role)
370
{
371
    QWriteLocker locker(&m_lock);
372 373
    std::shared_ptr<AbstractProjectItem> item = getItemByBinId(binId);
    if (item) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
374
        onItemUpdated(item, role);
375
    }
376
}
377

378
std::shared_ptr<ProjectClip> ProjectItemModel::getClipByBinID(const QString &binId)
379
{
380
    READ_LOCK();
381 382 383
    if (binId.contains(QLatin1Char('_'))) {
        return getClipByBinID(binId.section(QLatin1Char('_'), 0, 0));
    }
Nicolas Carion's avatar
Nicolas Carion committed
384
    for (const auto &clip : m_allItems) {
Nicolas Carion's avatar
Nicolas Carion committed
385
        auto c = std::static_pointer_cast<AbstractProjectItem>(clip.second.lock());
Nicolas Carion's avatar
Nicolas Carion committed
386 387 388
        if (c->itemType() == AbstractProjectItem::ClipItem && c->clipId() == binId) {
            return std::static_pointer_cast<ProjectClip>(c);
        }
389 390
    }
    return nullptr;
391 392
}

393
const QVector<uint8_t> ProjectItemModel::getAudioLevelsByBinID(const QString &binId, int stream)
394
{
395
    READ_LOCK();
396
    if (binId.contains(QLatin1Char('_'))) {
397
        return getAudioLevelsByBinID(binId.section(QLatin1Char('_'), 0, 0), stream);
398 399 400 401
    }
    for (const auto &clip : m_allItems) {
        auto c = std::static_pointer_cast<AbstractProjectItem>(clip.second.lock());
        if (c->itemType() == AbstractProjectItem::ClipItem && c->clipId() == binId) {
402
            return std::static_pointer_cast<ProjectClip>(c)->audioFrameCache(stream);
403 404
        }
    }
405
    return QVector<uint8_t>();
406 407
}

408 409
bool ProjectItemModel::hasClip(const QString &binId)
{
410
    READ_LOCK();
411 412 413
    return getClipByBinID(binId) != nullptr;
}

414
std::shared_ptr<ProjectFolder> ProjectItemModel::getFolderByBinId(const QString &binId)
415
{
416
    READ_LOCK();
Nicolas Carion's avatar
Nicolas Carion committed
417
    for (const auto &clip : m_allItems) {
Nicolas Carion's avatar
Nicolas Carion committed
418
        auto c = std::static_pointer_cast<AbstractProjectItem>(clip.second.lock());
Nicolas Carion's avatar
Nicolas Carion committed
419 420 421 422 423
        if (c->itemType() == AbstractProjectItem::FolderItem && c->clipId() == binId) {
            return std::static_pointer_cast<ProjectFolder>(c);
        }
    }
    return nullptr;
424 425
}

426 427
const QString ProjectItemModel::getFolderIdByName(const QString &folderName)
{
428
    READ_LOCK();
429 430 431 432 433 434 435 436 437
    for (const auto &clip : m_allItems) {
        auto c = std::static_pointer_cast<AbstractProjectItem>(clip.second.lock());
        if (c->itemType() == AbstractProjectItem::FolderItem && c->name() == folderName) {
            return c->clipId();
        }
    }
    return QString();
}

438 439
std::shared_ptr<AbstractProjectItem> ProjectItemModel::getItemByBinId(const QString &binId)
{
440
    READ_LOCK();
441 442 443 444 445 446 447 448 449
    for (const auto &clip : m_allItems) {
        auto c = std::static_pointer_cast<AbstractProjectItem>(clip.second.lock());
        if (c->clipId() == binId) {
            return c;
        }
    }
    return nullptr;
}

450 451
void ProjectItemModel::setBinEffectsEnabled(bool enabled)
{
452
    QWriteLocker locker(&m_lock);
453
    return std::static_pointer_cast<AbstractProjectItem>(rootItem)->setBinEffectsEnabled(enabled);
454 455
}

Nicolas Carion's avatar
Nicolas Carion committed
456
QStringList ProjectItemModel::getEnclosingFolderInfo(const QModelIndex &index) const
457
{
458
    READ_LOCK();
459 460 461 462 463
    QStringList noInfo;
    noInfo << QString::number(-1);
    noInfo << QString();
    if (!index.isValid()) {
        return noInfo;
464
    }
465

466
    std::shared_ptr<AbstractProjectItem> currentItem = getBinItemByIndex(index);
467
    auto folder = currentItem->getEnclosingFolder(true);
Nicolas Carion's avatar
Nicolas Carion committed
468
    if ((folder == nullptr) || folder == rootItem) {
469
        return noInfo;
Nicolas Carion's avatar
Nicolas Carion committed
470 471 472 473 474
    }
    QStringList folderInfo;
    folderInfo << currentItem->clipId();
    folderInfo << currentItem->name();
    return folderInfo;
475 476
}

477
void ProjectItemModel::clean()
478
{
479
    QWriteLocker locker(&m_lock);
480
    std::vector<std::shared_ptr<AbstractProjectItem>> toDelete;
Nicolas Carion's avatar
Nicolas Carion committed
481
    toDelete.reserve((size_t)rootItem->childCount());
Nicolas Carion's avatar
Nicolas Carion committed
482
    for (int i = 0; i < rootItem->childCount(); ++i) {
483
        toDelete.push_back(std::static_pointer_cast<AbstractProjectItem>(rootItem->child(i)));
Nicolas Carion's avatar
Nicolas Carion committed
484
    }
485 486
    Fun undo = []() { return true; };
    Fun redo = []() { return true; };
Nicolas Carion's avatar
linting  
Nicolas Carion committed
487
    for (const auto &child : toDelete) {
488
        requestBinClipDeletion(child, undo, redo);
Nicolas Carion's avatar
Nicolas Carion committed
489 490
    }
    Q_ASSERT(rootItem->childCount() == 0);
491
    m_nextId = 1;
492
    m_fileWatcher->clear();
493 494
}

495
std::shared_ptr<ProjectFolder> ProjectItemModel::getRootFolder() const
496
{
497
    READ_LOCK();
498
    return std::static_pointer_cast<ProjectFolder>(rootItem);
499 500
}

501
void ProjectItemModel::loadSubClips(const QString &id, const QString &clipData)
502
{
503
    QWriteLocker locker(&m_lock);
504 505
    Fun undo = []() { return true; };
    Fun redo = []() { return true; };
506
    loadSubClips(id, clipData, undo, redo);
507 508
}

509
void ProjectItemModel::loadSubClips(const QString &id, const QString &dataMap, Fun &undo, Fun &redo)
510
{
511 512 513
    if (dataMap.isEmpty()) {
        return;
    }
514
    QWriteLocker locker(&m_lock);
515
    std::shared_ptr<ProjectClip> clip = getClipByBinID(id);
516
    if (!clip) {
517
        qDebug()<<" = = = = = CLIP NOT LOADED";
518 519
        return;
    }
520 521 522 523 524
    auto json = QJsonDocument::fromJson(dataMap.toUtf8());
    if (!json.isArray()) {
        qDebug() << "Error loading zones : Json file should be an array";
        return;
    }
525
    int maxFrame = clip->duration().frames(pCore->getCurrentFps()) - 1;
526 527 528 529 530 531 532 533 534 535 536 537 538
    auto list = json.array();
    for (const auto &entry : list) {
        if (!entry.isObject()) {
            qDebug() << "Warning : Skipping invalid marker data";
            continue;
        }
        auto entryObj = entry.toObject();
        if (!entryObj.contains(QLatin1String("name"))) {
            qDebug() << "Warning : Skipping invalid zone(does not contain name)";
            continue;
        }
        int in = entryObj[QLatin1String("in")].toInt();
        int out = entryObj[QLatin1String("out")].toInt();
539 540 541 542
        QMap <QString, QString> zoneProperties;
        zoneProperties.insert(QStringLiteral("name"), entryObj[QLatin1String("name")].toString(i18n("Zone")));
        zoneProperties.insert(QStringLiteral("rating"), QString::number(entryObj[QLatin1String("rating")].toInt()));
        zoneProperties.insert(QStringLiteral("tags"), entryObj[QLatin1String("tags")].toString(QString()));
543
        if (in >= out) {
544
            qDebug() << "Warning : Invalid zone: "<<zoneProperties.value("name")<<", "<<in<<"-"<<out;
545 546 547 548 549
            continue;
        }
        if (maxFrame > 0) {
            out = qMin(out, maxFrame);
        }
550
        QString subId;
551
        requestAddBinSubClip(subId, in, out, zoneProperties, id, undo, redo);
552 553 554
    }
}

555 556
std::shared_ptr<AbstractProjectItem> ProjectItemModel::getBinItemByIndex(const QModelIndex &index) const
{
557
    READ_LOCK();
558 559
    return std::static_pointer_cast<AbstractProjectItem>(getItemById((int)index.internalId()));
}
Nicolas Carion's avatar
Nicolas Carion committed
560

Nicolas Carion's avatar
Nicolas Carion committed
561
bool ProjectItemModel::requestBinClipDeletion(const std::shared_ptr<AbstractProjectItem> &clip, Fun &undo, Fun &redo)
Nicolas Carion's avatar
Nicolas Carion committed
562 563 564 565 566
{
    QWriteLocker locker(&m_lock);
    Q_ASSERT(clip);
    if (!clip) return false;
    int parentId = -1;
567 568 569 570 571 572
    QString binId;
    if (auto ptr = clip->parent()) {
        parentId = ptr->getId();
        binId = ptr->clipId();
    }
    bool isSubClip = clip->itemType() == AbstractProjectItem::SubClipItem;
573
    clip->selfSoftDelete(undo, redo);
574
    int id = clip->getId();
575 576
    Fun operation = removeItem_lambda(id);
    Fun reverse = addItem_lambda(clip, parentId);
Nicolas Carion's avatar
Nicolas Carion committed
577 578
    bool res = operation();
    if (res) {
579 580 581 582 583 584 585 586 587 588 589 590 591
        if (isSubClip) {
            Fun update_doc = [this, binId]() {
                std::shared_ptr<AbstractProjectItem> parentItem = getItemByBinId(binId);
                if (parentItem && parentItem->itemType() == AbstractProjectItem::ClipItem) {
                    auto clipItem = std::static_pointer_cast<ProjectClip>(parentItem);
                    clipItem->updateZones();
                }
                return true;
            };
            update_doc();
            PUSH_LAMBDA(update_doc, operation);
            PUSH_LAMBDA(update_doc, reverse);
        }
Nicolas Carion's avatar
Nicolas Carion committed
592 593 594 595 596 597 598
        UPDATE_UNDO_REDO(operation, reverse, undo, redo);
    }
    return res;
}

void ProjectItemModel::registerItem(const std::shared_ptr<TreeItem> &item)
{
599
    QWriteLocker locker(&m_lock);
600
    auto clip = std::static_pointer_cast<AbstractProjectItem>(item);
601
    m_binPlaylist->manageBinItemInsertion(clip);
Nicolas Carion's avatar
Nicolas Carion committed
602
    AbstractTreeModel::registerItem(item);
603 604 605 606
    if (clip->itemType() == AbstractProjectItem::ClipItem) {
        auto clipItem = std::static_pointer_cast<ProjectClip>(clip);
        updateWatcher(clipItem);
    }
Nicolas Carion's avatar
Nicolas Carion committed
607
}
Nicolas Carion's avatar
Nicolas Carion committed
608
void ProjectItemModel::deregisterItem(int id, TreeItem *item)
Nicolas Carion's avatar
Nicolas Carion committed
609
{
610
    QWriteLocker locker(&m_lock);
Nicolas Carion's avatar
linting  
Nicolas Carion committed
611
    auto clip = static_cast<AbstractProjectItem *>(item);
612
    m_binPlaylist->manageBinItemDeletion(clip);
613
    // TODO : here, we should suspend jobs belonging to the item we delete. They can be restarted if the item is reinserted by undo
Nicolas Carion's avatar
Nicolas Carion committed
614
    AbstractTreeModel::deregisterItem(id, item);
615 616
    if (clip->itemType() == AbstractProjectItem::ClipItem) {
        auto clipItem = static_cast<ProjectClip *>(clip);
617
        m_fileWatcher->removeFile(clipItem->clipId());
618
    }
Nicolas Carion's avatar
Nicolas Carion committed
619
}
620

Nicolas Carion's avatar
Nicolas Carion committed
621 622
int ProjectItemModel::getFreeFolderId()
{
623 624 625
    while (!isIdFree(QString::number(++m_nextId))) {
    };
    return m_nextId;
Nicolas Carion's avatar
Nicolas Carion committed
626 627 628 629
}

int ProjectItemModel::getFreeClipId()
{
630 631 632
    while (!isIdFree(QString::number(++m_nextId))) {
    };
    return m_nextId;
Nicolas Carion's avatar
Nicolas Carion committed
633
}
Nicolas Carion's avatar
Nicolas Carion committed
634

Nicolas Carion's avatar
Nicolas Carion committed
635
bool ProjectItemModel::addItem(const std::shared_ptr<AbstractProjectItem> &item, const QString &parentId, Fun &undo, Fun &redo)
Nicolas Carion's avatar
Nicolas Carion committed
636 637
{
    QWriteLocker locker(&m_lock);
638 639
    std::shared_ptr<AbstractProjectItem> parentItem = getItemByBinId(parentId);
    if (!parentItem) {
Nicolas Carion's avatar
Nicolas Carion committed
640 641 642
        qCDebug(KDENLIVE_LOG) << "  / / ERROR IN PARENT FOLDER";
        return false;
    }
643 644 645 646 647 648 649 650 651
    if (item->itemType() == AbstractProjectItem::ClipItem && parentItem->itemType() != AbstractProjectItem::FolderItem) {
        qCDebug(KDENLIVE_LOG) << "  / / ERROR when inserting clip: a clip should be inserted in a folder";
        return false;
    }
    if (item->itemType() == AbstractProjectItem::SubClipItem && parentItem->itemType() != AbstractProjectItem::ClipItem) {
        qCDebug(KDENLIVE_LOG) << "  / / ERROR when inserting subclip: a subclip should be inserted in a clip";
        return false;
    }
    Fun operation = addItem_lambda(item, parentItem->getId());
652

653 654 655 656 657 658 659 660 661 662 663 664 665
    int itemId = item->getId();
    Fun reverse = removeItem_lambda(itemId);
    bool res = operation();
    Q_ASSERT(item->isInModel());
    if (res) {
        UPDATE_UNDO_REDO(operation, reverse, undo, redo);
    }
    return res;
}

bool ProjectItemModel::requestAddFolder(QString &id, const QString &name, const QString &parentId, Fun &undo, Fun &redo)
{
    QWriteLocker locker(&m_lock);
666
    if (!id.isEmpty() && !isIdFree(id)) {
667
        id.clear();
668
    }
Nicolas Carion's avatar
Nicolas Carion committed
669 670 671
    if (id.isEmpty()) {
        id = QString::number(getFreeFolderId());
    }
672
    std::shared_ptr<ProjectFolder> new_folder = ProjectFolder::construct(id, name, std::static_pointer_cast<ProjectItemModel>(shared_from_this()));
673 674 675
    return addItem(new_folder, parentId, undo, redo);
}

676 677
bool ProjectItemModel::requestAddBinClip(QString &id, const QDomElement &description, const QString &parentId, Fun &undo, Fun &redo,
                                         const std::function<void(const QString &)> &readyCallBack)
678
{
Nicolas Carion's avatar
Nicolas Carion committed
679
    qDebug() << "/////////// requestAddBinClip" << parentId;
680 681
    QWriteLocker locker(&m_lock);
    if (id.isEmpty()) {
682
        id =
683
            Xml::getXmlProperty(description, QStringLiteral("kdenlive:id"), QStringLiteral("-1"));
684 685 686
        if (id == QStringLiteral("-1") || !isIdFree(id)) {
            id = QString::number(getFreeClipId());
        }
687
    }
688
    Q_ASSERT(isIdFree(id));
Nicolas Carion's avatar
Nicolas Carion committed
689
    qDebug() << "/////////// found id" << id;
690 691 692
    std::shared_ptr<ProjectClip> new_clip =
        ProjectClip::construct(id, description, m_blankThumb, std::static_pointer_cast<ProjectItemModel>(shared_from_this()));
    qDebug() << "/////////// constructed ";
693
    bool res = addItem(new_clip, parentId, undo, redo);
Nicolas Carion's avatar
Nicolas Carion committed
694
    qDebug() << "/////////// added " << res;
Nicolas Carion's avatar
Nicolas Carion committed
695
    if (res) {
696
        int loadJob = pCore->jobManager()->startJob<LoadJob>({id}, -1, QString(), description, std::bind(readyCallBack, id));
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
697
        pCore->jobManager()->startJob<ThumbJob>({id}, loadJob, QString(), 0, true);
698
        ClipType::ProducerType type = new_clip->clipType();
699
        if (type == ClipType::AV || type == ClipType::Audio || type == ClipType::Playlist || type == ClipType::Unknown) {
700 701
            pCore->jobManager()->startJob<AudioThumbJob>({id}, loadJob, QString());
        }
702 703 704 705 706 707
    }
    return res;
}

bool ProjectItemModel::requestAddBinClip(QString &id, const QDomElement &description, const QString &parentId, const QString &undoText)
{
708
    QWriteLocker locker(&m_lock);
709 710 711 712
    Fun undo = []() { return true; };
    Fun redo = []() { return true; };
    bool res = requestAddBinClip(id, description, parentId, undo, redo);
    if (res) {
713 714 715 716 717
        pCore->pushUndo(undo, redo, undoText.isEmpty() ? i18n("Add bin clip") : undoText);
    }
    return res;
}

Nicolas Carion's avatar
Nicolas Carion committed
718
bool ProjectItemModel::requestAddBinClip(QString &id, const std::shared_ptr<Mlt::Producer> &producer, const QString &parentId, Fun &undo, Fun &redo)
719 720 721 722 723 724 725 726
{
    QWriteLocker locker(&m_lock);
    if (id.isEmpty()) {
        id = QString::number(producer->get_int("kdenlive:id"));
        if (!isIdFree(id)) {
            id = QString::number(getFreeClipId());
        }
    }
727
    Q_ASSERT(isIdFree(id));
728 729
    std::shared_ptr<ProjectClip> new_clip = ProjectClip::construct(id, m_blankThumb, std::static_pointer_cast<ProjectItemModel>(shared_from_this()), producer);
    bool res = addItem(new_clip, parentId, undo, redo);
730
    if (res) {
731
        new_clip->importEffects(producer);
732
        if (new_clip->isReady() || new_clip->sourceExists()) {
733
            int blocking = pCore->jobManager()->getBlockingJobId(id, AbstractClipJob::LOADJOB);
734
            pCore->jobManager()->startJob<ThumbJob>({id}, blocking, QString(), -1, true);
735 736
            pCore->jobManager()->startJob<AudioThumbJob>({id}, blocking, QString());
        }
737
    }
738 739 740
    return res;
}

741
bool ProjectItemModel::requestAddBinSubClip(QString &id, int in, int out, const QMap<QString, QString> zoneProperties, const QString &parentId, Fun &undo, Fun &redo)
742 743 744 745 746
{
    QWriteLocker locker(&m_lock);
    if (id.isEmpty()) {
        id = QString::number(getFreeClipId());
    }
747
    Q_ASSERT(isIdFree(id));
748 749 750 751 752
    QString subId = parentId;
    if (subId.startsWith(QLatin1Char('A')) || subId.startsWith(QLatin1Char('V'))) {
        subId.remove(0, 1);
    }
    auto clip = getClipByBinID(subId);
753 754
    Q_ASSERT(clip->itemType() == AbstractProjectItem::ClipItem);
    auto tc = pCore->currentDoc()->timecode().getDisplayTimecodeFromFrames(in, KdenliveSettings::frametimecode());
755
    std::shared_ptr<ProjectSubClip> new_clip =
756
        ProjectSubClip::construct(id, clip, std::static_pointer_cast<ProjectItemModel>(shared_from_this()), in, out, tc, zoneProperties);
757
    bool res = addItem(new_clip, subId, undo, redo);
758
    if (res) {
759
        int parentJob = pCore->jobManager()->getBlockingJobId(subId, AbstractClipJob::LOADJOB);
760
        pCore-&