diff --git a/src/timeline2/model/builders/meltBuilder.cpp b/src/timeline2/model/builders/meltBuilder.cpp
index e342285ed..f8390b7c9 100644
--- a/src/timeline2/model/builders/meltBuilder.cpp
+++ b/src/timeline2/model/builders/meltBuilder.cpp
@@ -1,334 +1,346 @@
/***************************************************************************
* 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 . *
***************************************************************************/
#include "meltBuilder.hpp"
#include "../clipmodel.hpp"
#include "../timelineitemmodel.hpp"
#include "../timelinemodel.hpp"
#include "../trackmodel.hpp"
#include "../undohelper.hpp"
#include "bin/bin.h"
#include "bin/projectitemmodel.h"
#include "core.h"
#include "kdenlivesettings.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
static QStringList m_errorMessage;
bool constructTrackFromMelt(const std::shared_ptr &timeline, int tid, Mlt::Tractor &track,
const std::unordered_map &binIdCorresp, Fun &undo, Fun &redo, bool audioTrack, QProgressDialog *progressDialog = nullptr);
bool constructTrackFromMelt(const std::shared_ptr &timeline, int tid, Mlt::Playlist &track,
const std::unordered_map &binIdCorresp, Fun &undo, Fun &redo, bool audioTrack, QProgressDialog *progressDialog = nullptr);
bool constructTimelineFromMelt(const std::shared_ptr &timeline, Mlt::Tractor tractor, QProgressDialog *progressDialog)
{
Fun undo = []() { return true; };
Fun redo = []() { return true; };
// First, we destruct the previous tracks
timeline->requestReset(undo, redo);
m_errorMessage.clear();
std::unordered_map binIdCorresp;
pCore->projectItemModel()->loadBinPlaylist(&tractor, timeline->tractor(), binIdCorresp, progressDialog);
QSet reserved_names{QLatin1String("playlistmain"), QLatin1String("timeline_preview"), QLatin1String("timeline_overlay"),
QLatin1String("black_track")};
bool ok = true;
qDebug() << "//////////////////////\nTrying to construct" << tractor.count() << "tracks.\n////////////////////////////////";
QList videoTracksIndexes;
// Black track index
videoTracksIndexes << 0;
for (int i = 0; i < tractor.count() && ok; i++) {
std::unique_ptr track(tractor.track(i));
QString playlist_name = track->get("id");
if (reserved_names.contains(playlist_name)) {
continue;
}
switch (track->type()) {
case producer_type:
// TODO check that it is the black track, and otherwise log an error
qDebug() << "SUSPICIOUS: we weren't expecting a producer when parsing the timeline";
break;
case tractor_type: {
// that is a double track
int tid;
bool audioTrack = track->get_int("kdenlive:audio_track") == 1;
if (!audioTrack) {
videoTracksIndexes << i;
}
ok = timeline->requestTrackInsertion(-1, tid, QString(), audioTrack, undo, redo, false);
int lockState = track->get_int("kdenlive:locked_track");
Mlt::Tractor local_tractor(*track);
ok = ok && constructTrackFromMelt(timeline, tid, local_tractor, binIdCorresp, undo, redo, audioTrack, progressDialog);
timeline->setTrackProperty(tid, QStringLiteral("kdenlive:thumbs_format"), track->get("kdenlive:thumbs_format"));
timeline->setTrackProperty(tid, QStringLiteral("kdenlive:audio_rec"), track->get("kdenlive:audio_rec"));
if (lockState > 0) {
timeline->setTrackLockedState(tid, true);
}
break;
}
case playlist_type: {
// that is a single track
qDebug() << "Adding track: " << track->get("id");
int tid;
Mlt::Playlist local_playlist(*track);
const QString trackName = local_playlist.get("kdenlive:track_name");
bool audioTrack = local_playlist.get_int("kdenlive:audio_track") == 1;
if (!audioTrack) {
videoTracksIndexes << i;
}
ok = timeline->requestTrackInsertion(-1, tid, trackName, audioTrack, undo, redo, false);
int muteState = track->get_int("hide");
if (muteState > 0 && (!audioTrack || (audioTrack && muteState != 1))) {
timeline->setTrackProperty(tid, QStringLiteral("hide"), QString::number(muteState));
}
int lockState = local_playlist.get_int("kdenlive:locked_track");
ok = ok && constructTrackFromMelt(timeline, tid, local_playlist, binIdCorresp, undo, redo, audioTrack, progressDialog);
timeline->setTrackProperty(tid, QStringLiteral("kdenlive:thumbs_format"), local_playlist.get("kdenlive:thumbs_format"));
timeline->setTrackProperty(tid, QStringLiteral("kdenlive:audio_rec"), track->get("kdenlive:audio_rec"));
if (lockState > 0) {
timeline->setTrackLockedState(tid, true);
}
break;
}
default:
qDebug() << "ERROR: Unexpected item in the timeline";
}
}
timeline->_resetView();
// Loading compositions
QScopedPointer service(tractor.producer());
QList compositions;
while ((service != nullptr) && service->is_valid()) {
if (service->type() == transition_type) {
Mlt::Transition t((mlt_transition)service->get_service());
QString id(t.get("kdenlive_id"));
QString internal(t.get("internal_added"));
if (internal.isEmpty()) {
compositions << new Mlt::Transition(t);
if (id.isEmpty()) {
qDebug() << "// Warning, this should not happen, transition without id: " << t.get("id") << " = " << t.get("mlt_service");
t.set("kdenlive_id", t.get("mlt_service"));
}
}
}
service.reset(service->producer());
}
// Sort compositions and insert
bool compositionOk = true;
while (!compositions.isEmpty()) {
QScopedPointer 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(),
t->get_in(), t->get_a_track());
continue;
}
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());
}
}
auto transProps = std::make_unique(t->get_properties());
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);
if (!compositionOk) {
qDebug() << "ERROR : failed to insert composition in track " << t->get_b_track() << ", position" << t->get_in() << ", ID: " << id
<< ", MLT ID: " << t->get("id");
// 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;
}
qDebug() << "Inserted composition in track " << t->get_b_track() << ", position" << t->get_in() << "/" << t->get_out();
}
// build internal track compositing
timeline->buildTrackCompositing();
//timeline->updateDuration();
if (!ok) {
// TODO log error
// Don't abort loading because of failed composition
undo();
return false;
}
if (!m_errorMessage.isEmpty()) {
KMessageBox::sorry(qApp->activeWindow(), m_errorMessage.join("\n"), i18n("Problems found in your project file"));
}
return true;
}
bool constructTrackFromMelt(const std::shared_ptr &timeline, int tid, Mlt::Tractor &track,
const std::unordered_map &binIdCorresp, Fun &undo, Fun &redo, bool audioTrack, QProgressDialog *progressDialog)
{
if (track.count() != 2) {
// we expect a tractor with two tracks (a "fake" track)
qDebug() << "ERROR : wrong number of subtracks";
return false;
}
for (int i = 0; i < track.count(); i++) {
std::unique_ptr sub_track(track.track(i));
if (sub_track->type() != playlist_type) {
qDebug() << "ERROR : SubTracks must be MLT::Playlist";
return false;
}
Mlt::Playlist playlist(*sub_track);
constructTrackFromMelt(timeline, tid, playlist, binIdCorresp, undo, redo, audioTrack, progressDialog);
if (i == 0) {
// Pass track properties
int height = track.get_int("kdenlive:trackheight");
timeline->setTrackProperty(tid, "kdenlive:trackheight", height == 0 ? "100" : QString::number(height));
timeline->setTrackProperty(tid, "kdenlive:collapsed", QString::number(track.get_int("kdenlive:collapsed")));
QString trackName = track.get("kdenlive:track_name");
if (!trackName.isEmpty()) {
timeline->setTrackProperty(tid, QStringLiteral("kdenlive:track_name"), trackName.toUtf8().constData());
}
if (audioTrack) {
// This is an audio track
timeline->setTrackProperty(tid, QStringLiteral("kdenlive:audio_track"), QStringLiteral("1"));
timeline->setTrackProperty(tid, QStringLiteral("hide"), QStringLiteral("1"));
} else {
// video track, hide audio
timeline->setTrackProperty(tid, QStringLiteral("hide"), QStringLiteral("2"));
}
int muteState = playlist.get_int("hide");
if (muteState > 0 && (!audioTrack || (audioTrack && muteState != 1))) {
timeline->setTrackProperty(tid, QStringLiteral("hide"), QString::number(muteState));
}
}
}
std::shared_ptr serv = std::make_shared(track.get_service());
timeline->importTrackEffects(tid, serv);
return true;
}
namespace {
// This function tries to recover the state of the producer (audio or video or both)
PlaylistState::ClipState inferState(const std::shared_ptr &prod, bool audioTrack)
{
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 VidAud{true, true};
VidAud.first = getIntProperty("set.test_image") == 0;
VidAud.second = getIntProperty("set.test_audio") == 0;
if (audioTrack || ((service.contains(QStringLiteral("avformat")) && getIntProperty(QStringLiteral("video_index")) == -1))) {
VidAud.first = false;
}
if (!audioTrack || ((service.contains(QStringLiteral("avformat")) && getIntProperty(QStringLiteral("audio_index")) == -1))) {
VidAud.second = false;
}
return stateFromBool(VidAud);
}
} // namespace
bool constructTrackFromMelt(const std::shared_ptr &timeline, int tid, Mlt::Playlist &track,
const std::unordered_map &binIdCorresp, Fun &undo, Fun &redo, bool audioTrack, QProgressDialog *progressDialog)
{
for (int i = 0; i < track.count(); i++) {
if (track.is_blank(i)) {
continue;
}
if (progressDialog) {
progressDialog->setValue(progressDialog->value() + 1);
}
std::shared_ptr clip(track.get_clip(i));
int position = track.clip_start(i);
switch (clip->type()) {
case unknown_type:
case producer_type: {
qDebug() << "Looking for clip clip " << clip->parent().get("kdenlive:id") << " = " << clip->parent().get("kdenlive:clipname");
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 {
QString clipId = clip->parent().get("kdenlive:id");
if (clipId.startsWith(QStringLiteral("slowmotion"))) {
clipId = clipId.section(QLatin1Char(':'), 1, 1);
}
if (clipId.isEmpty()) {
clipId = clip->get("kdenlive:id");
}
- Q_ASSERT(!clipId.isEmpty() && binIdCorresp.count(clipId) > 0);
- binId = binIdCorresp.at(clipId);
+ if (binIdCorresp.count(clipId) == 0) {
+ // Project was somehow corrupted
+ qDebug()<<"=== WARNING, CANNOT FIND CLIP WITH ID: "<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 {
+ 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());
clip->parent().set("kdenlive:id", binId.toUtf8().constData());
clip->parent().set("_kdenlive_processed", 1);
}
bool ok = false;
int cid = -1;
if (pCore->bin()->getBinClip(binId)) {
PlaylistState::ClipState st = inferState(clip, audioTrack);
cid = ClipModel::construct(timeline, binId, clip, st);
ok = timeline->requestClipMove(cid, tid, position, true, false, true, undo, redo);
} else {
qDebug() << "// Cannot find bin clip: " << binId << " - " << clip->get("id");
}
if (!ok && cid > -1) {
qDebug() << "ERROR : failed to insert clip in track" << tid << "position" << position;
timeline->requestItemDeletion(cid, false);
m_errorMessage << i18n("Invalid clip %1 found on track %2 at %3.", clip->parent().get("id"), track.get("id"), position);
break;
}
qDebug() << "Inserted clip in track" << tid << "at " << position;
break;
}
case tractor_type: {
// TODO This is a nested timeline
qDebug() << "NOT_IMPLEMENTED: code for parsing nested timeline is not there yet.";
break;
}
default:
qDebug() << "ERROR : unexpected object found on playlist";
return false;
break;
}
}
std::shared_ptr serv = std::make_shared(track.get_service());
timeline->importTrackEffects(tid, serv);
return true;
}