meltBuilder.cpp 18.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/***************************************************************************
 *   Copyright (C) 2017 by Nicolas Carion                                  *
 *   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  by the membership  *
 *   of KDE e.V.), which shall act as a proxy 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 "meltBuilder.hpp"
Nicolas Carion's avatar
Nicolas Carion committed
23
#include "../clipmodel.hpp"
24 25
#include "../timelineitemmodel.hpp"
#include "../timelinemodel.hpp"
26
#include "../trackmodel.hpp"
Nicolas Carion's avatar
Nicolas Carion committed
27
#include "../undohelper.hpp"
28
#include "bin/bin.h"
29
#include "bin/projectitemmodel.h"
Nicolas Carion's avatar
Nicolas Carion committed
30
#include "core.h"
31
#include "kdenlivesettings.h"
32 33

#include <KLocalizedString>
34
#include <KMessageBox>
Nicolas Carion's avatar
Nicolas Carion committed
35
#include <QDebug>
36
#include <QProgressDialog>
Nicolas Carion's avatar
Nicolas Carion committed
37
#include <QSet>
38 39
#include <mlt++/MltPlaylist.h>
#include <mlt++/MltProducer.h>
40
#include <mlt++/MltProfile.h>
41
#include <mlt++/MltFilter.h>
42
#include <mlt++/MltTransition.h>
Laurent Montel's avatar
Laurent Montel committed
43
#include <QApplication>
44

45
static QStringList m_errorMessage;
46

47
bool constructTrackFromMelt(const std::shared_ptr<TimelineItemModel> &timeline, int tid, Mlt::Tractor &track,
48
                            const std::unordered_map<QString, QString> &binIdCorresp, Fun &undo, Fun &redo, bool audioTrack, QString originalDecimalPoint, QProgressDialog *progressDialog = nullptr);
49
bool constructTrackFromMelt(const std::shared_ptr<TimelineItemModel> &timeline, int tid, Mlt::Playlist &track,
50
                            const std::unordered_map<QString, QString> &binIdCorresp, Fun &undo, Fun &redo, bool audioTrack, QString originalDecimalPoint, QProgressDialog *progressDialog = nullptr);
51

52
bool constructTimelineFromMelt(const std::shared_ptr<TimelineItemModel> &timeline, Mlt::Tractor tractor, QProgressDialog *progressDialog, QString originalDecimalPoint)
53
{
Nicolas Carion's avatar
Nicolas Carion committed
54 55 56
    Fun undo = []() { return true; };
    Fun redo = []() { return true; };
    // First, we destruct the previous tracks
57
    timeline->requestReset(undo, redo);
58
    m_errorMessage.clear();
59
    std::unordered_map<QString, QString> binIdCorresp;
60 61
    QStringList expandedFolders;
    pCore->projectItemModel()->loadBinPlaylist(&tractor, timeline->tractor(), binIdCorresp, expandedFolders, progressDialog);
62
    pCore->bin()->checkMissingProxies();
63 64 65 66 67 68 69 70
    QStringList foldersToExpand;
    // Find updated ids for expanded folders
    for (const QString &folderId : expandedFolders) {
        if (binIdCorresp.count(folderId) > 0) {
            foldersToExpand << binIdCorresp.at(folderId);
        }
    }
    pCore->bin()->loadFolderState(foldersToExpand);
71

72
    QSet<QString> reserved_names{QLatin1String("playlistmain"), QLatin1String("timeline_preview"), QLatin1String("timeline_overlay"), QLatin1String("black_track"), QLatin1String("overlay_track")};
73
    bool ok = true;
74
    qDebug() << "//////////////////////\nTrying to construct" << tractor.count() << "tracks.\n////////////////////////////////";
75

76
    // Import master track effects
77 78
    std::shared_ptr<Mlt::Service> serv = std::make_shared<Mlt::Service>(tractor.get_service());
    timeline->importMasterEffects(serv);
79

80
    QList <int> videoTracksIndexes;
81
    QList <int> lockedTracksIndexes;
82 83
    // Black track index
    videoTracksIndexes << 0;
84 85 86 87 88 89
    for (int i = 0; i < tractor.count() && ok; i++) {
        std::unique_ptr<Mlt::Producer> track(tractor.track(i));
        QString playlist_name = track->get("id");
        if (reserved_names.contains(playlist_name)) {
            continue;
        }
Nicolas Carion's avatar
Nicolas Carion committed
90
        switch (track->type()) {
91
        case producer_type:
Nicolas Carion's avatar
Nicolas Carion committed
92
            // TODO check that it is the black track, and otherwise log an error
93 94 95
            qDebug() << "SUSPICIOUS: we weren't expecting a producer when parsing the timeline";
            break;
        case tractor_type: {
Nicolas Carion's avatar
Nicolas Carion committed
96
            // that is a double track
97
            int tid;
98
            bool audioTrack = track->get_int("kdenlive:audio_track") == 1;
99 100 101
            if (!audioTrack) {
                videoTracksIndexes << i;
            }
102
            ok = timeline->requestTrackInsertion(-1, tid, QString(), audioTrack, undo, redo, false);
103 104 105
            if (track->get_int("kdenlive:locked_track") > 0) {
                lockedTracksIndexes << tid;
            }
106
            Mlt::Tractor local_tractor(*track);
107
            ok = ok && constructTrackFromMelt(timeline, tid, local_tractor, binIdCorresp, undo, redo, audioTrack, originalDecimalPoint, progressDialog);
108
            timeline->setTrackProperty(tid, QStringLiteral("kdenlive:thumbs_format"), track->get("kdenlive:thumbs_format"));
109
            timeline->setTrackProperty(tid, QStringLiteral("kdenlive:audio_rec"), track->get("kdenlive:audio_rec"));
110
            timeline->setTrackProperty(tid, QStringLiteral("kdenlive:timeline_active"), track->get("kdenlive:timeline_active"));
111 112 113
            break;
        }
        case playlist_type: {
Nicolas Carion's avatar
Nicolas Carion committed
114 115
            // that is a single track
            qDebug() << "Adding track: " << track->get("id");
116
            int tid;
Nicolas Carion's avatar
Nicolas Carion committed
117
            Mlt::Playlist local_playlist(*track);
118
            const QString trackName = local_playlist.get("kdenlive:track_name");
119
            bool audioTrack = local_playlist.get_int("kdenlive:audio_track") == 1;
120 121 122
            if (!audioTrack) {
                videoTracksIndexes << i;
            }
123
            ok = timeline->requestTrackInsertion(-1, tid, trackName, audioTrack, undo, redo, false);
124 125 126 127
            int muteState = track->get_int("hide");
            if (muteState > 0 && (!audioTrack || (audioTrack && muteState != 1))) {
                timeline->setTrackProperty(tid, QStringLiteral("hide"), QString::number(muteState));
            }
128

129
            ok = ok && constructTrackFromMelt(timeline, tid, local_playlist, binIdCorresp, undo, redo, audioTrack, originalDecimalPoint, progressDialog);
130 131 132
            if (local_playlist.get_int("kdenlive:locked_track") > 0) {
                lockedTracksIndexes << tid;
            }
133
            timeline->setTrackProperty(tid, QStringLiteral("kdenlive:thumbs_format"), local_playlist.get("kdenlive:thumbs_format"));
134
            timeline->setTrackProperty(tid, QStringLiteral("kdenlive:audio_rec"), track->get("kdenlive:audio_rec"));
135
            timeline->setTrackProperty(tid, QStringLiteral("kdenlive:timeline_active"), track->get("kdenlive:timeline_active"));
136 137 138 139 140 141
            break;
        }
        default:
            qDebug() << "ERROR: Unexpected item in the timeline";
        }
    }
142
    timeline->_resetView();
143

144
    // Loading compositions
145
    QScopedPointer<Mlt::Service> service(tractor.producer());
146
    QList<Mlt::Transition *> compositions;
Nicolas Carion's avatar
Nicolas Carion committed
147
    while ((service != nullptr) && service->is_valid()) {
148
        if (service->type() == transition_type) {
Nicolas Carion's avatar
Nicolas Carion committed
149
            Mlt::Transition t((mlt_transition)service->get_service());
150 151 152 153 154
            if (t.get_b_track() >= timeline->tractor()->count()) {
                // Composition outside of available track, maybe because of a preview track
                service.reset(service->producer());
                continue;
            }
155
            QString id(t.get("kdenlive_id"));
156 157
            QString internal(t.get("internal_added"));
            if (internal.isEmpty()) {
158
                compositions << new Mlt::Transition(t);
159
                if (id.isEmpty()) {
160
                    qDebug() << "// Warning, this should not happen, transition without id: " << t.get("id") << " = " << t.get("mlt_service")<<", ON TRACK: "<<t.get_b_track()<<", INVALIDS: "<<invalidTracks;
161
                    t.set("kdenlive_id", t.get("mlt_service"));
162
                }
163 164 165 166
            }
        }
        service.reset(service->producer());
    }
167
    // Sort compositions and insert
168
    bool compositionOk = true;
169 170 171 172 173 174 175
    while (!compositions.isEmpty()) {
        QScopedPointer<Mlt::Transition> t(compositions.takeFirst());
        QString id(t->get("kdenlive_id"));
        int compoId;
        int aTrack = t->get_a_track();
        if (aTrack > tractor.count()) {
            m_errorMessage << i18n("Invalid composition %1 found on track %2 at %3, compositing with track %4.", t->get("id"), t->get_b_track(),
Nicolas Carion's avatar
Nicolas Carion committed
176
                                       t->get_in(), t->get_a_track());
177 178
            continue;
        }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
179 180 181 182 183 184 185
        if (t->get_int("force_track") == 0) {
            // This is an automatic composition, check that we composite with lower track or warn
            int pos = videoTracksIndexes.indexOf(t->get_b_track());
            if (pos > 0 && videoTracksIndexes.at(pos - 1) != aTrack) {
                t->set("force_track", 1);
                m_errorMessage << i18n("Incorrect composition %1 found on track %2 at %3, compositing with track %4 was set to forced track.", t->get("id"), t->get_b_track(),
                                    t->get_in(), t->get_a_track());
186
            }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
187 188
        }
        auto transProps = std::make_unique<Mlt::Properties>(t->get_properties());
189
        compositionOk = timeline->requestCompositionInsertion(id, timeline->getTrackIndexFromPosition(t->get_b_track() - 1), t->get_a_track(), t->get_in(), t->get_length(), std::move(transProps), compoId, undo, redo, false, originalDecimalPoint);
190 191
        if (!compositionOk) {
            qDebug() << "ERROR : failed to insert composition in track " << t->get_b_track() << ", position" << t->get_in() << ", ID: " << id
192
                         << ", MLT ID: " << t->get("id");
193 194 195
            // timeline->requestItemDeletion(compoId, false);
            m_errorMessage << i18n("Invalid composition %1 found on track %2 at %3.", t->get("id"), t->get_b_track(), t->get_in());
            continue;
196
        }
197
        qDebug() << "Inserted composition in track " << t->get_b_track() << ", position" << t->get_in() << "/" << t->get_out();
198
    }
199

200 201
    // build internal track compositing
    timeline->buildTrackCompositing();
202 203

    // load locked state as last step
Vincent Pinon's avatar
Vincent Pinon committed
204
    for (int tid : qAsConst(lockedTracksIndexes)) {
205 206
        timeline->setTrackLockedState(tid, true);
    }
207

208
    if (!ok) {
Nicolas Carion's avatar
Nicolas Carion committed
209
        // TODO log error
210
        // Don't abort loading because of failed composition
211 212
        undo();
        return false;
213
    }
214
    if (!m_errorMessage.isEmpty()) {
215
        KMessageBox::sorry(qApp->activeWindow(), m_errorMessage.join("\n"), i18n("Problems found in your project file"));
216
    }
217 218 219
    return true;
}

220
bool constructTrackFromMelt(const std::shared_ptr<TimelineItemModel> &timeline, int tid, Mlt::Tractor &track,
221
                            const std::unordered_map<QString, QString> &binIdCorresp, Fun &undo, Fun &redo, bool audioTrack, QString originalDecimalPoint, QProgressDialog *progressDialog)
Nicolas Carion's avatar
Nicolas Carion committed
222
{
223
    if (track.count() != 2) {
Nicolas Carion's avatar
Nicolas Carion committed
224
        // we expect a tractor with two tracks (a "fake" track)
225 226 227 228 229 230 231 232 233
        qDebug() << "ERROR : wrong number of subtracks";
        return false;
    }
    for (int i = 0; i < track.count(); i++) {
        std::unique_ptr<Mlt::Producer> sub_track(track.track(i));
        if (sub_track->type() != playlist_type) {
            qDebug() << "ERROR : SubTracks must be MLT::Playlist";
            return false;
        }
Nicolas Carion's avatar
Nicolas Carion committed
234
        Mlt::Playlist playlist(*sub_track);
235
        constructTrackFromMelt(timeline, tid, playlist, binIdCorresp, undo, redo, audioTrack, originalDecimalPoint, progressDialog);
236 237 238 239
        if (i == 0) {
            // Pass track properties
            int height = track.get_int("kdenlive:trackheight");
            timeline->setTrackProperty(tid, "kdenlive:trackheight", height == 0 ? "100" : QString::number(height));
240
            timeline->setTrackProperty(tid, "kdenlive:collapsed", QString::number(track.get_int("kdenlive:collapsed")));
241 242 243 244
            QString trackName = track.get("kdenlive:track_name");
            if (!trackName.isEmpty()) {
                timeline->setTrackProperty(tid, QStringLiteral("kdenlive:track_name"), trackName.toUtf8().constData());
            }
245
            if (audioTrack) {
246 247
                // This is an audio track
                timeline->setTrackProperty(tid, QStringLiteral("kdenlive:audio_track"), QStringLiteral("1"));
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
248 249 250 251
                timeline->setTrackProperty(tid, QStringLiteral("hide"), QStringLiteral("1"));
            } else {
                // video track, hide audio
                timeline->setTrackProperty(tid, QStringLiteral("hide"), QStringLiteral("2"));
252
            }
253 254 255 256
            int muteState = playlist.get_int("hide");
            if (muteState > 0 && (!audioTrack || (audioTrack && muteState != 1))) {
                timeline->setTrackProperty(tid, QStringLiteral("hide"), QString::number(muteState));
            }
257
        }
258
    }
259 260
    std::shared_ptr<Mlt::Service> serv = std::make_shared<Mlt::Service>(track.get_service());
    timeline->importTrackEffects(tid, serv);
261 262 263
    return true;
}

264 265 266
namespace {

// This function tries to recover the state of the producer (audio or video or both)
Nicolas Carion's avatar
Nicolas Carion committed
267
PlaylistState::ClipState inferState(const std::shared_ptr<Mlt::Producer> &prod, bool audioTrack)
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284
{
    auto getProperty = [prod](const QString &name) {
        if (prod->parent().is_valid()) {
            return QString::fromUtf8(prod->parent().get(name.toUtf8().constData()));
        }
        return QString::fromUtf8(prod->get(name.toUtf8().constData()));
    };
    auto getIntProperty = [prod](const QString &name) {
        if (prod->parent().is_valid()) {
            return prod->parent().get_int(name.toUtf8().constData());
        }
        return prod->get_int(name.toUtf8().constData());
    };
    QString service = getProperty("mlt_service");
    std::pair<bool, bool> VidAud{true, true};
    VidAud.first = getIntProperty("set.test_image") == 0;
    VidAud.second = getIntProperty("set.test_audio") == 0;
285
    if (audioTrack || ((service.contains(QStringLiteral("avformat")) && getIntProperty(QStringLiteral("video_index")) == -1))) {
286 287
        VidAud.first = false;
    }
288
    if (!audioTrack || ((service.contains(QStringLiteral("avformat")) && getIntProperty(QStringLiteral("audio_index")) == -1))) {
289 290 291 292 293 294
        VidAud.second = false;
    }
    return stateFromBool(VidAud);
}
} // namespace

295
bool constructTrackFromMelt(const std::shared_ptr<TimelineItemModel> &timeline, int tid, Mlt::Playlist &track,
296
                            const std::unordered_map<QString, QString> &binIdCorresp, Fun &undo, Fun &redo, bool audioTrack, QString originalDecimalPoint, QProgressDialog *progressDialog)
Nicolas Carion's avatar
Nicolas Carion committed
297
{
298 299
    int max = track.count();
    for (int i = 0; i < max; i++) {
300 301 302
        if (track.is_blank(i)) {
            continue;
        }
303 304
        if (progressDialog) {
            progressDialog->setValue(progressDialog->value() + 1);
305
        } else {
Vincent Pinon's avatar
Vincent Pinon committed
306
            emit pCore->loadingMessageUpdated(QString(), 1);
307
        }
308 309
        std::shared_ptr<Mlt::Producer> clip(track.get_clip(i));
        int position = track.clip_start(i);
Nicolas Carion's avatar
Nicolas Carion committed
310
        switch (clip->type()) {
311 312
        case unknown_type:
        case producer_type: {
Simon Eugster's avatar
Simon Eugster committed
313
            qDebug() << "Looking for clip with ID " << clip->parent().get("kdenlive:id") << " and name " << clip->parent().get("kdenlive:clipname");
314 315 316 317 318
            QString binId;
            if (clip->parent().get_int("_kdenlive_processed") == 1) {
                // This is a bin clip, already processed no need to change id
                binId = QString(clip->parent().get("kdenlive:id"));
            } else {
319 320 321 322
                QString clipId = clip->parent().get("kdenlive:id");
                if (clipId.startsWith(QStringLiteral("slowmotion"))) {
                    clipId = clipId.section(QLatin1Char(':'), 1, 1);
                }
323 324 325
                if (clipId.isEmpty()) {
                    clipId = clip->get("kdenlive:id");
                }
326 327 328 329 330 331 332 333
                if (binIdCorresp.count(clipId) == 0) {
                    // Project was somehow corrupted
                    qDebug()<<"=== WARNING, CANNOT FIND CLIP WITH ID: "<<clipId<<" IN BIN PLAYLIST";
                    QStringList fixedId = pCore->projectItemModel()->getClipByUrl(QFileInfo(clip->parent().get("resource")));
                    if (!fixedId.isEmpty()) {
                        binId = fixedId.first();
                        m_errorMessage << i18n("Invalid clip %1 (%2) not found in project bin, recovered.", clip->parent().get("id"), clipId);
                    } else {
334
                        qWarning()<<"Warning, clip in timeline has no parent in bin: "<<clip->parent().get("id");
335 336 337 338 339 340
                        m_errorMessage << i18n("Project corrupted. Clip %1 (%2) not found in project bin.", clip->parent().get("id"), clipId);
                    }
                } else {
                    binId = binIdCorresp.at(clipId);
                }
                Q_ASSERT(!clipId.isEmpty() && !binId.isEmpty());
341 342
                clip->parent().set("kdenlive:id", binId.toUtf8().constData());
                clip->parent().set("_kdenlive_processed", 1);
343
            }
344
            bool ok = false;
345
            int cid = -1;
346
            if (pCore->bin()->getBinClip(binId)) {
347
                PlaylistState::ClipState st = inferState(clip, audioTrack);
348
                cid = ClipModel::construct(timeline, binId, clip, st, tid, originalDecimalPoint);
349
                ok = timeline->requestClipMove(cid, tid, position, true, true, false, true, undo, redo);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
350
            } else {
Nicolas Carion's avatar
Nicolas Carion committed
351
                qDebug() << "// Cannot find bin clip: " << binId << " - " << clip->get("id");
352
            }
353
            if (!ok && cid > -1) {
Nicolas Carion's avatar
Nicolas Carion committed
354
                qDebug() << "ERROR : failed to insert clip in track" << tid << "position" << position;
355
                timeline->requestItemDeletion(cid, false);
356
                m_errorMessage << i18n("Invalid clip %1 found on track %2 at %3.", clip->parent().get("id"), track.get("id"), position);
357
                break;
Nicolas Carion's avatar
Nicolas Carion committed
358 359
            }
            qDebug() << "Inserted clip in track" << tid << "at " << position;
360 361 362
            break;
        }
        case tractor_type: {
Nicolas Carion's avatar
Nicolas Carion committed
363
            // TODO This is a nested timeline
364 365 366 367 368 369 370 371 372
            qDebug() << "NOT_IMPLEMENTED: code for parsing nested timeline is not there yet.";
            break;
        }
        default:
            qDebug() << "ERROR : unexpected object found on playlist";
            return false;
            break;
        }
    }
373 374
    std::shared_ptr<Mlt::Service> serv = std::make_shared<Mlt::Service>(track.get_service());
    timeline->importTrackEffects(tid, serv);
375 376
    return true;
}