File indexing completed on 2024-05-12 04:54:04
0001 /* 0002 SPDX-FileCopyrightText: 2017 Nicolas Carion 0003 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0004 */ 0005 0006 #include "meltBuilder.hpp" 0007 #include "../clipmodel.hpp" 0008 #include "../timelineitemmodel.hpp" 0009 #include "../timelinemodel.hpp" 0010 #include "../trackmodel.hpp" 0011 #include "../undohelper.hpp" 0012 #include "bin/bin.h" 0013 #include "bin/projectclip.h" 0014 #include "bin/projectitemmodel.h" 0015 #include "core.h" 0016 #include "kdenlivesettings.h" 0017 #include "transitions/transitionsrepository.hpp" 0018 0019 #include <KLocalizedString> 0020 #include <KMessageBox> 0021 #include <QApplication> 0022 #include <QDebug> 0023 #include <QSet> 0024 #include <mlt++/MltField.h> 0025 #include <mlt++/MltMultitrack.h> 0026 #include <mlt++/MltProfile.h> 0027 #include <mlt++/MltService.h> 0028 #include <mlt++/MltTractor.h> 0029 #include <mlt++/MltTransition.h> 0030 #include <project/projectmanager.h> 0031 0032 static QStringList m_errorMessage; 0033 static QStringList m_notesLog; 0034 std::unordered_map<QString, QString> binIdCorresp; 0035 0036 bool constructTrackFromMelt(const std::shared_ptr<TimelineItemModel> &timeline, int tid, bool useMappedIds, const QString trackTag, Mlt::Tractor &track, 0037 Fun &undo, Fun &redo, bool audioTrack, const QString &originalDecimalPoint); 0038 bool constructTrackFromMelt(const std::shared_ptr<TimelineItemModel> &timeline, int tid, bool useMappedIds, const QString trackTag, Mlt::Playlist &track, 0039 Fun &undo, Fun &redo, bool audioTrack, const QString &originalDecimalPoint, int playlist, 0040 const QList<Mlt::Transition *> &compositions); 0041 0042 bool loadProjectBin(Mlt::Tractor tractor, const QUuid &activeUuid) 0043 { 0044 Fun undo = []() { return true; }; 0045 Fun redo = []() { return true; }; 0046 // First, we destruct the previous tracks 0047 QStringList expandedFolders; 0048 int zoomLevel = -1; 0049 binIdCorresp.clear(); 0050 m_notesLog.clear(); 0051 QList<QUuid> brokenSequences = pCore->projectItemModel()->loadBinPlaylist(&tractor, binIdCorresp, expandedFolders, activeUuid, zoomLevel); 0052 if (!brokenSequences.isEmpty()) { 0053 KMessageBox::error(qApp->activeWindow(), i18n("Found an invalid sequence clip in Bin")); 0054 return false; 0055 } 0056 QStringList foldersToExpand; 0057 // Find updated ids for expanded folders 0058 for (const QString &folderId : expandedFolders) { 0059 if (binIdCorresp.count(folderId) > 0) { 0060 foldersToExpand << binIdCorresp.at(folderId); 0061 } 0062 } 0063 if (pCore->window()) { 0064 pCore->bin()->checkMissingProxies(); 0065 pCore->bin()->loadBinProperties(foldersToExpand, zoomLevel); 0066 } 0067 return true; 0068 } 0069 0070 bool constructTimelineFromTractor(const std::shared_ptr<TimelineItemModel> &timeline, const std::shared_ptr<ProjectItemModel> &projectModel, 0071 Mlt::Tractor tractor, const QString &originalDecimalPoint, const QString &chunks, const QString &dirty, bool enablePreview) 0072 { 0073 Fun undo = []() { return true; }; 0074 Fun redo = []() { return true; }; 0075 // First, we destruct the previous tracks 0076 timeline->requestReset(undo, redo); 0077 m_errorMessage.clear(); 0078 bool useMappedIds = true; 0079 0080 QStringList expandedFolders; 0081 if (projectModel) { 0082 // This is an old format project file 0083 int zoomLevel = -1; 0084 if (timeline->uuid() == pCore->currentTimelineId()) { 0085 binIdCorresp.clear(); 0086 projectModel->loadBinPlaylist(&tractor, binIdCorresp, expandedFolders, timeline->uuid(), zoomLevel); 0087 } else { 0088 projectModel->loadTractorPlaylist(tractor, binIdCorresp); 0089 } 0090 QStringList foldersToExpand; 0091 // Find updated ids for expanded folders 0092 for (const QString &folderId : expandedFolders) { 0093 if (binIdCorresp.count(folderId) > 0) { 0094 foldersToExpand << binIdCorresp.at(folderId); 0095 } 0096 } 0097 if (pCore->window()) { 0098 pCore->bin()->checkMissingProxies(); 0099 pCore->bin()->loadBinProperties(foldersToExpand, zoomLevel); 0100 } 0101 } else { 0102 // loading an extra timeline 0103 if (tractor.property_exists("_dontmapids")) { 0104 // We are reopening a closed sequence, don't use mapped ids! 0105 useMappedIds = false; 0106 } 0107 } 0108 0109 QSet<QString> reserved_names{QLatin1String("playlistmain"), QLatin1String("timeline_preview"), QLatin1String("timeline_overlay"), 0110 QLatin1String("black_track"), QLatin1String("overlay_track")}; 0111 bool ok = true; 0112 0113 // Import master track effects 0114 std::shared_ptr<Mlt::Service> serv = std::make_shared<Mlt::Service>(tractor.get_service()); 0115 timeline->importMasterEffects(serv); 0116 0117 QList<int> videoTracksIndexes; 0118 QList<int> lockedTracksIndexes; 0119 int vTracks = 0; 0120 int aTracks = 0; 0121 int aTracksCount = 1; 0122 // Black track index 0123 videoTracksIndexes << 0; 0124 QString playlist_name; 0125 for (int i = 0; i < tractor.count() && ok; i++) { 0126 std::unique_ptr<Mlt::Producer> track(tractor.track(i)); 0127 if (track->property_exists("kdenlive:playlistid")) { 0128 playlist_name = track->get("kdenlive:playlistid"); 0129 } else { 0130 playlist_name = track->get("id"); 0131 } 0132 if (reserved_names.contains(playlist_name)) { 0133 continue; 0134 } 0135 switch (track->type()) { 0136 case mlt_service_tractor_type: { 0137 // that is a double track 0138 if (track->get_int("kdenlive:audio_track") == 1) { 0139 aTracksCount++; 0140 } 0141 break; 0142 } 0143 case mlt_service_playlist_type: { 0144 // that is a single track 0145 Mlt::Playlist local_playlist(*track); 0146 if (local_playlist.get_int("kdenlive:audio_track") == 1) { 0147 aTracksCount++; 0148 } 0149 break; 0150 } 0151 default: 0152 break; 0153 } 0154 } 0155 0156 qDebug() << "=== OPENING FILE WITH TRACKS: " << tractor.count(); 0157 for (int i = 0; i < tractor.count() && ok; i++) { 0158 qApp->processEvents(); 0159 std::unique_ptr<Mlt::Producer> track(tractor.track(i)); 0160 if (track->property_exists("kdenlive:playlistid")) { 0161 playlist_name = track->get("kdenlive:playlistid"); 0162 } else { 0163 playlist_name = track->get("id"); 0164 } 0165 if (reserved_names.contains(playlist_name)) { 0166 if (playlist_name == QLatin1String("timeline_preview")) { 0167 Mlt::Playlist local_playlist(*track); 0168 pCore->loadTimelinePreview(timeline->uuid(), chunks, dirty, enablePreview, local_playlist); 0169 } 0170 continue; 0171 } 0172 switch (track->type()) { 0173 case mlt_service_producer_type: 0174 // TODO check that it is the black track, and otherwise log an error 0175 break; 0176 case mlt_service_tractor_type: { 0177 // that is a double track 0178 int tid; 0179 Mlt::Tractor local_tractor(*track.get()); 0180 qDebug() << ":::: FOUND TRACTOR WITH TRACKS: " << local_tractor.count() << "\n\n___________________________"; 0181 if (local_tractor.count() == 0) { 0182 // Invalid track 0183 break; 0184 } 0185 bool audioTrack = track->get_int("kdenlive:audio_track") == 1; 0186 if (!audioTrack) { 0187 videoTracksIndexes << i; 0188 vTracks++; 0189 } else { 0190 aTracks++; 0191 } 0192 ok = timeline->requestTrackInsertion(-1, tid, QString(), audioTrack, undo, redo, false); 0193 if (track->get_int("kdenlive:locked_track") > 0) { 0194 lockedTracksIndexes << tid; 0195 } 0196 const QString trackTag = audioTrack ? QStringLiteral("A%1").arg(aTracksCount - aTracks) : QStringLiteral("V%1").arg(vTracks); 0197 ok = ok && constructTrackFromMelt(timeline, tid, useMappedIds, trackTag, local_tractor, undo, redo, audioTrack, originalDecimalPoint); 0198 timeline->setTrackProperty(tid, QStringLiteral("kdenlive:thumbs_format"), track->get("kdenlive:thumbs_format")); 0199 timeline->setTrackProperty(tid, QStringLiteral("kdenlive:audio_rec"), track->get("kdenlive:audio_rec")); 0200 timeline->setTrackProperty(tid, QStringLiteral("kdenlive:timeline_active"), track->get("kdenlive:timeline_active")); 0201 break; 0202 } 0203 case mlt_service_playlist_type: { 0204 // that is a single track 0205 int tid; 0206 qDebug() << "=== LOADING PLAYLIST FROM SINGLE TRACK\n00000000000000000000000"; 0207 Mlt::Playlist local_playlist(*track); 0208 const QString trackName = local_playlist.get("kdenlive:track_name"); 0209 bool audioTrack = local_playlist.get_int("kdenlive:audio_track") == 1; 0210 if (!audioTrack) { 0211 videoTracksIndexes << i; 0212 } 0213 ok = timeline->requestTrackInsertion(-1, tid, trackName, audioTrack, undo, redo, false); 0214 int muteState = track->get_int("hide"); 0215 if (muteState > 0 && (!audioTrack || (audioTrack && muteState != 1))) { 0216 timeline->setTrackProperty(tid, QStringLiteral("hide"), QString::number(muteState)); 0217 } 0218 const QString trackTag = audioTrack ? QStringLiteral("A%1").arg(aTracksCount - aTracks) : QStringLiteral("V%1").arg(vTracks); 0219 ok = ok && constructTrackFromMelt(timeline, tid, useMappedIds, trackTag, local_playlist, undo, redo, audioTrack, originalDecimalPoint, 0, 0220 QList<Mlt::Transition *>()); 0221 if (local_playlist.get_int("kdenlive:locked_track") > 0) { 0222 lockedTracksIndexes << tid; 0223 } 0224 timeline->setTrackProperty(tid, QStringLiteral("kdenlive:thumbs_format"), local_playlist.get("kdenlive:thumbs_format")); 0225 timeline->setTrackProperty(tid, QStringLiteral("kdenlive:audio_rec"), track->get("kdenlive:audio_rec")); 0226 timeline->setTrackProperty(tid, QStringLiteral("kdenlive:timeline_active"), track->get("kdenlive:timeline_active")); 0227 break; 0228 } 0229 default: 0230 qWarning() << "Unexpected track type" << track->type(); 0231 } 0232 } 0233 timeline->_resetView(); 0234 0235 // Loading compositions 0236 Mlt::Service *prod = tractor.producer(); 0237 QList<Mlt::Transition *> compositions; 0238 if (prod && prod->is_valid()) { 0239 QScopedPointer<Mlt::Service> service(prod); 0240 while ((service != nullptr) && service->is_valid()) { 0241 if (service->type() == mlt_service_transition_type) { 0242 Mlt::Transition t(mlt_transition(service->get_service())); 0243 if (t.get_b_track() >= timeline->tractor()->count()) { 0244 // Composition outside of available track, maybe because of a preview track 0245 service.reset(service->producer()); 0246 continue; 0247 } 0248 QString id(t.get("kdenlive_id")); 0249 QString internal(t.get("internal_added")); 0250 QString isMix(t.get("kdenlive:mixcut")); 0251 if (internal.isEmpty() && isMix.isEmpty()) { 0252 compositions << new Mlt::Transition(t); 0253 if (id.isEmpty()) { 0254 qWarning() << "transition without id" << t.get("id") << t.get("mlt_service") << "on track" << t.get_b_track(); 0255 t.set("kdenlive_id", t.get("mlt_service")); 0256 } 0257 } 0258 } 0259 service.reset(service->producer()); 0260 } 0261 } 0262 // Sort compositions and insert 0263 bool compositionOk = true; 0264 QStringList validCompositions; 0265 while (!compositions.isEmpty()) { 0266 QScopedPointer<Mlt::Transition> t(compositions.takeFirst()); 0267 QString id(t->get("kdenlive_id")); 0268 int compoId; 0269 int aTrack = t->get_a_track(); 0270 if (aTrack > tractor.count()) { 0271 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(), 0272 t->get_a_track()); 0273 continue; 0274 } 0275 if (t->get_int("force_track") == 0) { 0276 // This is an automatic composition, check that we composite with lower track or warn 0277 int pos = videoTracksIndexes.indexOf(t->get_b_track()); 0278 if (pos > 0 && videoTracksIndexes.at(pos - 1) != aTrack) { 0279 t->set("force_track", 1); 0280 m_errorMessage << i18n("Incorrect composition %1 found on track %2 at %3, compositing with track %4 was set to forced track.", t->get("id"), 0281 t->get_b_track(), t->get_in(), t->get_a_track()); 0282 } 0283 } 0284 if (!validCompositions.contains(id) && !TransitionsRepository::get()->exists(id)) { 0285 m_errorMessage << i18n("Unknown composition %1 found on track %2 at %3, compositing with track %4.", id, t->get_b_track(), t->get_in(), 0286 t->get_a_track()); 0287 continue; 0288 } 0289 validCompositions << id; 0290 auto transProps = std::make_unique<Mlt::Properties>(t->get_properties()); 0291 compositionOk = timeline->requestCompositionInsertion(id, timeline->getTrackIndexFromPosition(t->get_b_track() - 1), t->get_a_track(), t->get_in(), 0292 t->get_length(), std::move(transProps), compoId, undo, redo, false, originalDecimalPoint); 0293 if (!compositionOk) { 0294 // timeline->requestItemDeletion(compoId, false); 0295 m_errorMessage << i18n("Invalid composition %1 found on track %2 at %3.", t->get("id"), t->get_b_track(), t->get_in()); 0296 continue; 0297 } 0298 } 0299 0300 // build internal track compositing 0301 timeline->buildTrackCompositing(); 0302 0303 // load locked state as last step 0304 for (int tid : qAsConst(lockedTracksIndexes)) { 0305 timeline->setTrackLockedState(tid, true); 0306 } 0307 0308 if (!ok) { 0309 // TODO log error 0310 // Don't abort loading because of failed composition 0311 undo(); 0312 return false; 0313 } 0314 timeline->isLoading = false; 0315 if (!m_errorMessage.isEmpty()) { 0316 if (pCore->window()) { 0317 KMessageBox::error(qApp->activeWindow(), m_errorMessage.join("\n"), i18n("Problems found in your project file")); 0318 } else { 0319 qWarning() << "++++++++++++++++\n++++++++++++++++\n\n" << m_errorMessage.join("\n") << "\n\n++++++++++++++++\n++++++++++++++++"; 0320 } 0321 } 0322 return true; 0323 } 0324 0325 bool constructTimelineFromMelt(const std::shared_ptr<TimelineItemModel> &timeline, Mlt::Tractor tractor, const QString &originalDecimalPoint, 0326 const QString &chunks, const QString &dirty, bool enablePreview, bool *projectErrors) 0327 { 0328 if (tractor.count() == 0) { 0329 // Trying to load invalid tractor, abort 0330 return false; 0331 } 0332 Fun undo = []() { return true; }; 0333 Fun redo = []() { return true; }; 0334 // First, we destruct the previous tracks 0335 timeline->requestReset(undo, redo); 0336 m_errorMessage.clear(); 0337 QStringList expandedFolders; 0338 int zoomLevel = -1; 0339 if (timeline->uuid() == pCore->currentTimelineId()) { 0340 pCore->projectItemModel()->loadBinPlaylist(&tractor, binIdCorresp, expandedFolders, timeline->uuid(), zoomLevel); 0341 } 0342 QStringList foldersToExpand; 0343 // Find updated ids for expanded folders 0344 for (const QString &folderId : expandedFolders) { 0345 if (binIdCorresp.count(folderId) > 0) { 0346 foldersToExpand << binIdCorresp.at(folderId); 0347 } 0348 } 0349 if (pCore->window()) { 0350 pCore->bin()->checkMissingProxies(); 0351 pCore->bin()->loadBinProperties(foldersToExpand, zoomLevel); 0352 } 0353 0354 QSet<QString> reserved_names{QLatin1String("playlistmain"), QLatin1String("timeline_preview"), QLatin1String("timeline_overlay"), 0355 QLatin1String("black_track"), QLatin1String("overlay_track")}; 0356 bool ok = true; 0357 0358 // Import master track effects 0359 std::shared_ptr<Mlt::Service> serv = std::make_shared<Mlt::Service>(tractor.get_service()); 0360 timeline->importMasterEffects(serv); 0361 0362 QList<int> videoTracksIndexes; 0363 QList<int> lockedTracksIndexes; 0364 // Black track index 0365 videoTracksIndexes << 0; 0366 // Check how many audio/video tracks we have 0367 int vTracks = 0; 0368 int aTracks = 0; 0369 int aTracksCount = 1; 0370 QString playlist_name; 0371 for (int i = 0; i < tractor.count() && ok; i++) { 0372 std::unique_ptr<Mlt::Producer> track(tractor.track(i)); 0373 if (track->property_exists("kdenlive:playlistid")) { 0374 playlist_name = track->get("kdenlive:playlistid"); 0375 } else { 0376 playlist_name = track->get("id"); 0377 } 0378 if (reserved_names.contains(playlist_name)) { 0379 continue; 0380 } 0381 switch (track->type()) { 0382 case mlt_service_tractor_type: { 0383 // that is a double track 0384 if (track->get_int("kdenlive:audio_track") == 1) { 0385 aTracksCount++; 0386 } 0387 break; 0388 } 0389 case mlt_service_playlist_type: { 0390 // that is a single track 0391 Mlt::Playlist local_playlist(*track); 0392 if (local_playlist.get_int("kdenlive:audio_track") == 1) { 0393 aTracksCount++; 0394 } 0395 break; 0396 } 0397 default: 0398 break; 0399 } 0400 } 0401 0402 for (int i = 0; i < tractor.count() && ok; i++) { 0403 qDebug() << "::: PROCESSING TK " << i; 0404 std::unique_ptr<Mlt::Producer> track(tractor.track(i)); 0405 if (track->property_exists("kdenlive:playlistid")) { 0406 playlist_name = track->get("kdenlive:playlistid"); 0407 } else { 0408 playlist_name = track->get("id"); 0409 } 0410 if (reserved_names.contains(playlist_name)) { 0411 if (playlist_name == QLatin1String("timeline_preview")) { 0412 Mlt::Playlist local_playlist(*track); 0413 pCore->loadTimelinePreview(timeline->uuid(), chunks, dirty, enablePreview, local_playlist); 0414 } 0415 continue; 0416 } 0417 switch (track->type()) { 0418 case mlt_service_producer_type: 0419 // TODO check that it is the black track, and otherwise log an error 0420 break; 0421 case mlt_service_tractor_type: { 0422 // that is a double track 0423 int tid; 0424 bool audioTrack = track->get_int("kdenlive:audio_track") == 1; 0425 if (!audioTrack) { 0426 videoTracksIndexes << i; 0427 vTracks++; 0428 } else { 0429 aTracks++; 0430 } 0431 ok = timeline->requestTrackInsertion(-1, tid, QString(), audioTrack, undo, redo, false); 0432 if (track->get_int("kdenlive:locked_track") > 0) { 0433 lockedTracksIndexes << tid; 0434 } 0435 Mlt::Tractor local_tractor(*track); 0436 const QString trackTag = audioTrack ? QStringLiteral("A%1").arg(aTracksCount - aTracks) : QStringLiteral("V%1").arg(vTracks); 0437 ok = ok && constructTrackFromMelt(timeline, tid, true, trackTag, local_tractor, undo, redo, audioTrack, originalDecimalPoint); 0438 timeline->setTrackProperty(tid, QStringLiteral("kdenlive:thumbs_format"), track->get("kdenlive:thumbs_format")); 0439 timeline->setTrackProperty(tid, QStringLiteral("kdenlive:audio_rec"), track->get("kdenlive:audio_rec")); 0440 timeline->setTrackProperty(tid, QStringLiteral("kdenlive:timeline_active"), track->get("kdenlive:timeline_active")); 0441 break; 0442 } 0443 case mlt_service_playlist_type: { 0444 // that is a single track 0445 int tid; 0446 Mlt::Playlist local_playlist(*track); 0447 const QString trackName = local_playlist.get("kdenlive:track_name"); 0448 bool audioTrack = local_playlist.get_int("kdenlive:audio_track") == 1; 0449 if (!audioTrack) { 0450 videoTracksIndexes << i; 0451 vTracks++; 0452 } else { 0453 aTracks++; 0454 } 0455 ok = timeline->requestTrackInsertion(-1, tid, trackName, audioTrack, undo, redo, false); 0456 int muteState = track->get_int("hide"); 0457 if (muteState > 0 && (!audioTrack || (audioTrack && muteState != 1))) { 0458 timeline->setTrackProperty(tid, QStringLiteral("hide"), QString::number(muteState)); 0459 } 0460 const QString trackTag = audioTrack ? QStringLiteral("A%1").arg(aTracksCount - aTracks) : QStringLiteral("V%1").arg(vTracks); 0461 ok = ok && constructTrackFromMelt(timeline, tid, true, trackTag, local_playlist, undo, redo, audioTrack, originalDecimalPoint, 0, 0462 QList<Mlt::Transition *>()); 0463 if (local_playlist.get_int("kdenlive:locked_track") > 0) { 0464 lockedTracksIndexes << tid; 0465 } 0466 timeline->setTrackProperty(tid, QStringLiteral("kdenlive:thumbs_format"), local_playlist.get("kdenlive:thumbs_format")); 0467 timeline->setTrackProperty(tid, QStringLiteral("kdenlive:audio_rec"), track->get("kdenlive:audio_rec")); 0468 timeline->setTrackProperty(tid, QStringLiteral("kdenlive:timeline_active"), track->get("kdenlive:timeline_active")); 0469 break; 0470 } 0471 default: 0472 qWarning() << "Unexpected track type" << track->type(); 0473 } 0474 } 0475 timeline->_resetView(); 0476 0477 // Loading compositions 0478 QScopedPointer<Mlt::Service> service(tractor.producer()); 0479 QList<Mlt::Transition *> compositions; 0480 while ((service != nullptr) && service->is_valid()) { 0481 if (service->type() == mlt_service_transition_type) { 0482 Mlt::Transition t(mlt_transition(service->get_service())); 0483 if (t.get_b_track() >= timeline->tractor()->count()) { 0484 // Composition outside of available track, maybe because of a preview track 0485 service.reset(service->producer()); 0486 continue; 0487 } 0488 QString id(t.get("kdenlive_id")); 0489 if (t.property_exists("internal_added") == false && t.property_exists("kdenlive:mixcut") == false) { 0490 compositions << new Mlt::Transition(t); 0491 if (id.isEmpty()) { 0492 qWarning() << "transition without id" << t.get("id") << t.get("mlt_service") << "on track" << t.get_b_track(); 0493 t.set("kdenlive_id", t.get("mlt_service")); 0494 } 0495 } 0496 } 0497 service.reset(service->producer()); 0498 } 0499 // Sort compositions and insert 0500 bool compositionOk = true; 0501 while (!compositions.isEmpty()) { 0502 QScopedPointer<Mlt::Transition> t(compositions.takeFirst()); 0503 QString id(t->get("kdenlive_id")); 0504 int compoId; 0505 int aTrack = t->get_a_track(); 0506 if (!timeline->isTrack(timeline->getTrackIndexFromPosition(t->get_b_track() - 1))) { 0507 QString tcInfo = QString("<a href=\"%1!%2\">%3</a>") 0508 .arg(timeline->uuid().toString(), QString::number(t->get_in()), pCore->timecode().getTimecodeFromFrames(t->get_in())); 0509 m_notesLog << i18n("%1 Composition (%2) with invalid track reference found and removed.", tcInfo, t->get("id")); 0510 m_errorMessage << i18n("Invalid composition %1 found on track %2 at %3, compositing with track %4.", t->get("id"), t->get_b_track(), 0511 pCore->timecode().getTimecodeFromFrames(t->get_in()), t->get_a_track()); 0512 continue; 0513 } 0514 if (aTrack > tractor.count()) { 0515 int tid = timeline->getTrackIndexFromPosition(t->get_b_track() - 1); 0516 const QString tcInfo = QString("<a href=\"%1!%2?%3\">%4 %5</a>") 0517 .arg(timeline->uuid().toString(), QString::number(t->get_in()), QString::number(timeline->getTrackPosition(tid) + 1), 0518 timeline->getTrackTagById(tid), pCore->timecode().getTimecodeFromFrames(t->get_in())); 0519 m_notesLog << i18n("%1 Composition (%2) with invalid track reference found and removed.", tcInfo, t->get("id")); 0520 m_errorMessage << i18n("Invalid composition %1 found on track %2 at %3, compositing with track %4.", t->get("id"), t->get_b_track(), 0521 pCore->timecode().getTimecodeFromFrames(t->get_in()), t->get_a_track()); 0522 continue; 0523 } 0524 if (t->get_int("force_track") == 0) { 0525 // This is an automatic composition, check that we composite with lower track or warn 0526 int pos = videoTracksIndexes.indexOf(t->get_b_track()); 0527 if (pos > 0 && videoTracksIndexes.at(pos - 1) != aTrack) { 0528 t->set("force_track", 1); 0529 int tid = timeline->getTrackIndexFromPosition(t->get_b_track() - 1); 0530 const QString tcInfo = QString("<a href=\"%1!%2?%3\">%4 %5</a>") 0531 .arg(timeline->uuid().toString(), QString::number(t->get_in()), QString::number(timeline->getTrackPosition(tid) + 1), 0532 timeline->getTrackTagById(tid), pCore->timecode().getTimecodeFromFrames(t->get_in())); 0533 m_notesLog << i18n("%1 Composition was not applied on expected track, manually enforce the track.", tcInfo); 0534 m_errorMessage << i18n("Incorrect composition %1 found on track %2 at %3, compositing with track %4 was set to forced track.", t->get("id"), 0535 t->get_b_track(), pCore->timecode().getTimecodeFromFrames(t->get_in()), t->get_a_track()); 0536 } 0537 } 0538 auto transProps = std::make_unique<Mlt::Properties>(t->get_properties()); 0539 compositionOk = timeline->requestCompositionInsertion(id, timeline->getTrackIndexFromPosition(t->get_b_track() - 1), t->get_a_track(), t->get_in(), 0540 t->get_length(), std::move(transProps), compoId, undo, redo, false, originalDecimalPoint); 0541 if (!compositionOk) { 0542 // timeline->requestItemDeletion(compoId, false); 0543 int tid = timeline->getTrackIndexFromPosition(t->get_b_track() - 1); 0544 const QString tcInfo = QString("<a href=\"%1!%2?%3\">%4 %5</a>") 0545 .arg(timeline->uuid().toString(), QString::number(t->get_in()), QString::number(timeline->getTrackPosition(tid) + 1), 0546 timeline->getTrackTagById(tid), pCore->timecode().getTimecodeFromFrames(t->get_in())); 0547 m_notesLog << i18n("%1 Invalid composition found and removed.", tcInfo); 0548 m_errorMessage << i18n("Invalid composition %1 found on track %2 at %3.", t->get("id"), t->get_b_track(), 0549 pCore->timecode().getTimecodeFromFrames(t->get_in())); 0550 continue; 0551 } 0552 } 0553 qDeleteAll(compositions); 0554 0555 // build internal track compositing 0556 timeline->buildTrackCompositing(); 0557 0558 // load locked state as last step 0559 for (int tid : qAsConst(lockedTracksIndexes)) { 0560 timeline->lockTrack(tid, true); 0561 } 0562 0563 if (!ok) { 0564 // Loading tracks failed, abort loading 0565 qDebug() << "IIIIIIIIIIIIIIIIII\nFAILED LOADING TIMELINE BBBBBBBBBBBBBBBBBBBB"; 0566 return false; 0567 } 0568 if (!qEnvironmentVariableIsSet("MLT_TESTS")) { 0569 if (!m_notesLog.isEmpty()) { 0570 if (projectErrors) { 0571 *projectErrors = true; 0572 } 0573 } 0574 } 0575 timeline->isLoading = false; 0576 return true; 0577 } 0578 0579 void checkProjectWarnings() 0580 { 0581 if (!qEnvironmentVariableIsSet("MLT_TESTS")) { 0582 if (!m_notesLog.isEmpty()) { 0583 m_notesLog.prepend(i18n("Errors found when opening project file (%1)", QDateTime::currentDateTime().toString())); 0584 pCore->projectManager()->slotAddTextNote(m_notesLog.join("<br/>")); 0585 KMessageBox::detailedError( 0586 qApp->activeWindow(), 0587 i18n("Some errors were detected in the project file.\nThe project was modified to fix the conflicts. Changes made to the " 0588 "project have been listed in the Project Notes tab,\nplease review them to ensure your project integrity."), 0589 m_errorMessage.join("\n"), i18n("Problems found in your project file")); 0590 } else if (!m_errorMessage.isEmpty()) { 0591 KMessageBox::error(qApp->activeWindow(), m_errorMessage.join("\n"), i18n("Problems found in your project file")); 0592 } 0593 } 0594 } 0595 0596 bool constructTrackFromMelt(const std::shared_ptr<TimelineItemModel> &timeline, int tid, bool useMappedIds, const QString trackTag, Mlt::Tractor &track, 0597 Fun &undo, Fun &redo, bool audioTrack, const QString &originalDecimalPoint) 0598 { 0599 if (track.count() != 2) { 0600 // we expect a tractor with two tracks (a "fake" track) 0601 qWarning() << "wrong number of subtracks"; 0602 return false; 0603 } 0604 // Check same track transitions 0605 QScopedPointer<Mlt::Service> service(track.field()); 0606 QList<Mlt::Transition *> compositions; 0607 while ((service != nullptr) && service->is_valid()) { 0608 if (service->type() == mlt_service_transition_type) { 0609 Mlt::Transition t(mlt_transition(service->get_service())); 0610 QString id(t.get("kdenlive_id")); 0611 compositions << new Mlt::Transition(t); 0612 if (id.isEmpty()) { 0613 qWarning() << "transition without id" << t.get("id") << t.get("mlt_service"); 0614 t.set("kdenlive_id", t.get("mlt_service")); 0615 } 0616 } 0617 service.reset(service->producer()); 0618 } 0619 for (int i = 0; i < track.count(); i++) { 0620 std::unique_ptr<Mlt::Producer> sub_track(track.track(i)); 0621 if (sub_track->type() != mlt_service_playlist_type) { 0622 qWarning() << "subtrack must be playlist"; 0623 return false; 0624 } 0625 Mlt::Playlist playlist(*sub_track); 0626 constructTrackFromMelt(timeline, tid, useMappedIds, trackTag, playlist, undo, redo, audioTrack, originalDecimalPoint, i, compositions); 0627 if (i == 0) { 0628 // Pass track properties 0629 int height = track.get_int("kdenlive:trackheight"); 0630 timeline->setTrackProperty(tid, "kdenlive:trackheight", height == 0 ? "100" : QString::number(height)); 0631 timeline->setTrackProperty(tid, "kdenlive:collapsed", QString::number(track.get_int("kdenlive:collapsed"))); 0632 QString trackName = track.get("kdenlive:track_name"); 0633 if (!trackName.isEmpty()) { 0634 timeline->setTrackProperty(tid, QStringLiteral("kdenlive:track_name"), trackName.toUtf8().constData()); 0635 } 0636 if (audioTrack) { 0637 // This is an audio track 0638 timeline->setTrackProperty(tid, QStringLiteral("kdenlive:audio_track"), QStringLiteral("1")); 0639 timeline->setTrackProperty(tid, QStringLiteral("hide"), QStringLiteral("1")); 0640 } else { 0641 // video track, hide audio 0642 timeline->setTrackProperty(tid, QStringLiteral("hide"), QStringLiteral("2")); 0643 } 0644 int muteState = playlist.get_int("hide"); 0645 if (muteState > 0 && (!audioTrack || (audioTrack && muteState != 1))) { 0646 timeline->setTrackProperty(tid, QStringLiteral("hide"), QString::number(muteState)); 0647 } 0648 } 0649 } 0650 // Load same track mixes 0651 for (auto compo : qAsConst(compositions)) { 0652 if (!timeline->plantMix(tid, compo)) { 0653 // There is an error with a mix, we have overlapping clips 0654 int in = compo->get_in(); 0655 int out = compo->get_out() - 1; 0656 bool reverse = compo->get_int("reverse") == 1; 0657 int cid1 = timeline->getClipByPosition(tid, in, reverse ? 1 : 0); 0658 int cid2 = timeline->getClipByPosition(tid, out, reverse ? 0 : 1); 0659 if (cid1 > 0 && cid2 > 0) { 0660 if (timeline->getClipSubPlaylistIndex(cid1) == 1) { 0661 // Delete clip 0662 const QString tcInfo = QString("<a href=\"%1!%2?%3\">%4 %5</a>") 0663 .arg(timeline->uuid().toString(), QString::number(in), QString::number(timeline->getTrackPosition(tid) + 1), 0664 timeline->getTrackTagById(tid), pCore->timecode().getTimecodeFromFrames(timeline->getClipPosition(cid1))); 0665 m_notesLog << i18n("Incorrect Mix, clip %1 was removed at %2.", timeline->getClipName(cid1), tcInfo); 0666 m_errorMessage << i18n("Incorrect mix found on track %1 at %2, clip %3 removed.", timeline->getTrackTagById(tid), 0667 pCore->timecode().getTimecodeFromFrames(timeline->getClipPosition(cid1)), timeline->getClipName(cid1)); 0668 timeline->requestItemDeletion(cid1, false); 0669 0670 } else if (timeline->getClipSubPlaylistIndex(cid2) == 1) { 0671 // Delete clip 0672 const QString tcInfo = QString("<a href=\"%1!%2?%3\">%4 %5</a>") 0673 .arg(timeline->uuid().toString(), QString::number(in), QString::number(timeline->getTrackPosition(tid) + 1), 0674 timeline->getTrackTagById(tid), pCore->timecode().getTimecodeFromFrames(timeline->getClipPosition(cid2))); 0675 m_notesLog << i18n("Incorrect Mix, clip %1 was removed at %2.", timeline->getClipName(cid2), tcInfo); 0676 m_errorMessage << i18n("Incorrect mix found at track %1 at %2, clip %3 removed.", timeline->getTrackTagById(tid), 0677 pCore->timecode().getTimecodeFromFrames(timeline->getClipPosition(cid2)), timeline->getClipName(cid2)); 0678 timeline->requestItemDeletion(cid2, false); 0679 } 0680 } 0681 } 0682 } 0683 std::shared_ptr<Mlt::Service> serv = std::make_shared<Mlt::Service>(track.get_service()); 0684 timeline->importTrackEffects(tid, serv); 0685 return true; 0686 } 0687 0688 namespace { 0689 0690 // This function tries to recover the state of the producer (audio or video or both) 0691 PlaylistState::ClipState inferState(const std::shared_ptr<Mlt::Producer> &prod, bool audioTrack) 0692 { 0693 auto getProperty = [prod](const QString &name) { 0694 if (prod->parent().is_valid()) { 0695 return QString::fromUtf8(prod->parent().get(name.toUtf8().constData())); 0696 } 0697 return QString::fromUtf8(prod->get(name.toUtf8().constData())); 0698 }; 0699 auto getIntProperty = [prod](const QString &name) { 0700 if (prod->parent().is_valid()) { 0701 return prod->parent().get_int(name.toUtf8().constData()); 0702 } 0703 return prod->get_int(name.toUtf8().constData()); 0704 }; 0705 QString service = getProperty("mlt_service"); 0706 std::pair<bool, bool> VidAud{true, true}; 0707 VidAud.first = getIntProperty("set.test_image") == 0; 0708 VidAud.second = getIntProperty("set.test_audio") == 0; 0709 if (audioTrack || ((service.contains(QStringLiteral("avformat")) && getIntProperty(QStringLiteral("video_index")) == -1))) { 0710 VidAud.first = false; 0711 } 0712 if (!audioTrack || ((service.contains(QStringLiteral("avformat")) && getIntProperty(QStringLiteral("audio_index")) == -1))) { 0713 VidAud.second = false; 0714 } 0715 return stateFromBool(VidAud); 0716 } 0717 } // namespace 0718 0719 bool constructTrackFromMelt(const std::shared_ptr<TimelineItemModel> &timeline, int tid, bool useMappedIds, const QString trackTag, Mlt::Playlist &track, 0720 Fun &undo, Fun &redo, bool audioTrack, const QString &originalDecimalPoint, int playlist, 0721 const QList<Mlt::Transition *> &compositions) 0722 { 0723 int max = track.count(); 0724 const QString sequenceBinId = pCore->projectItemModel()->getSequenceId(timeline->uuid()); 0725 for (int i = 0; i < max; i++) { 0726 if (track.is_blank(i)) { 0727 continue; 0728 } 0729 std::shared_ptr<Mlt::Producer> clip(track.get_clip(i)); 0730 int position = track.clip_start(i); 0731 switch (clip->type()) { 0732 case mlt_service_unknown_type: 0733 case mlt_service_chain_type: 0734 case mlt_service_producer_type: { 0735 QString binId; 0736 if (clip->parent().get_int("_kdenlive_processed") == 1) { 0737 // This is a bin clip, already processed no need to change id 0738 binId = QString(clip->parent().get("kdenlive:id")); 0739 } else { 0740 QString clipId = clip->parent().get("kdenlive:id"); 0741 if (clipId.startsWith(QStringLiteral("slowmotion"))) { 0742 clipId = clipId.section(QLatin1Char(':'), 1, 1); 0743 } 0744 if (clipId.isEmpty()) { 0745 clipId = clip->get("kdenlive:id"); 0746 } 0747 const QString service = clip->parent().get("mlt_service"); 0748 QString resource = service == QLatin1String("timewarp") ? clip->parent().get("warp_resource") : clip->parent().get("resource"); 0749 if (!useMappedIds || binIdCorresp.size() == 0 || (clip->parent().get_int("kdenlive:producer_type") == ClipType::Timeline)) { 0750 // Currently "sequence" clips inserted in timeline are cuts of the bin clip, so it's kdenlive id is changed in loadBinPlaylist 0751 if (clip->parent().get_int("kdenlive:producer_type") == ClipType::Timeline && binIdCorresp.count(clipId) > 0 && 0752 !clip->parent().property_exists("_kdenlive_processed")) { 0753 binId = binIdCorresp.at(clipId); 0754 } else { 0755 binId = clipId; 0756 } 0757 // Ensure we don't try to embed a sequence into itself 0758 if (binId == sequenceBinId) { 0759 // Trying to embed a sequence into itself, drop 0760 const QString tcInfo = 0761 QString("<a href=\"%1!%2?%3\">%4 %5</a>") 0762 .arg(timeline->uuid().toString(), QString::number(position), QString::number(timeline->getTrackPosition(tid) + 1), trackTag, 0763 pCore->timecode().getTimecodeFromFrames(position)); 0764 m_notesLog << i18n("%1 Timeline clip (%2) without bin reference found and removed.", tcInfo, clip->parent().get("id")); 0765 m_errorMessage << i18n("Project corrupted. Clip %1 (%2) not found in project bin.", clip->parent().get("id"), clipId); 0766 continue; 0767 } 0768 // Check that the sequence clip exists 0769 auto binClip = pCore->projectItemModel()->getClipByBinID(binId); 0770 if (binClip == nullptr || binClip->getProducerIntProperty("kdenlive:producer_type") != clip->parent().get_int("kdenlive:producer_type")) { 0771 // Sequence clip not found, error out 0772 const QString tcInfo = 0773 QString("<a href=\"%1!%2?%3\">%4 %5</a>") 0774 .arg(timeline->uuid().toString(), QString::number(position), QString::number(timeline->getTrackPosition(tid) + 1), trackTag, 0775 pCore->timecode().getTimecodeFromFrames(position)); 0776 m_notesLog << i18n("%1 Timeline clip (%2) without bin reference found and removed.", tcInfo, clip->parent().get("id")); 0777 m_errorMessage << i18n("Project corrupted. Clip %1 (%2) not found in project bin.", clip->parent().get("id"), clipId); 0778 continue; 0779 } 0780 } else if (binIdCorresp.count(clipId) == 0) { 0781 if (clip->property_exists("kdenlive:remove")) { 0782 // Clip was marked for deletion 0783 continue; 0784 } 0785 // Project was somehow corrupted 0786 qWarning() << "can't find clip with id: " << clipId << "in bin playlist"; 0787 QStringList fixedId = pCore->projectItemModel()->getClipByUrl(QFileInfo(resource)); 0788 const QString tcInfo = 0789 QString("<a href=\"%1!%2?%3\">%4 %5</a>") 0790 .arg(timeline->uuid().toString(), QString::number(position), QString::number(timeline->getTrackPosition(tid) + 1), trackTag, 0791 pCore->timecode().getTimecodeFromFrames(position)); 0792 if (!fixedId.isEmpty()) { 0793 binId = fixedId.first(); 0794 m_notesLog << i18n("%1 Timeline clip (%2) with incorrect bin reference found and recovered.", tcInfo, clip->parent().get("id")); 0795 m_errorMessage << i18n("Invalid clip %1 (%2) not found in project bin, recovered.", clip->parent().get("id"), clipId); 0796 } else { 0797 m_notesLog << i18n("%1 Timeline clip (%2) without bin reference found and removed.", tcInfo, clip->parent().get("id")); 0798 m_errorMessage << i18n("Project corrupted. Clip %1 (%2) not found in project bin.", clip->parent().get("id"), clipId); 0799 // Do not try to insert clip 0800 continue; 0801 } 0802 } else { 0803 binId = binIdCorresp.at(clipId); 0804 } 0805 Q_ASSERT(!clipId.isEmpty() && !binId.isEmpty()); 0806 clip->parent().set("kdenlive:id", binId.toUtf8().constData()); 0807 clip->parent().set("_kdenlive_processed", 1); 0808 } 0809 bool ok = false; 0810 int cid = -1; 0811 if (pCore->projectItemModel()->getClipByBinID(binId) == nullptr) { 0812 // Trying to recover clip by its resource 0813 const QString service = clip->parent().get("mlt_service"); 0814 const QString resource = service == QLatin1String("timewarp") ? clip->parent().get("warp_resource") : clip->parent().get("resource"); 0815 const QStringList possibleIds = pCore->projectItemModel()->getClipByUrl(QFileInfo(resource)); 0816 qWarning() << "Incorred clip id, trying to recover " << binId << "/" << clip->get("id") << " = " << resource; 0817 if (!possibleIds.isEmpty()) { 0818 binId = possibleIds.first(); 0819 clip->parent().set("kdenlive:id", binId.toUtf8().constData()); 0820 qDebug() << "=== FOUND POSSIBLE MATCHES: " << possibleIds; 0821 } 0822 } 0823 if (pCore->projectItemModel()->getClipByBinID(binId)) { 0824 PlaylistState::ClipState st = inferState(clip, audioTrack); 0825 bool enforceTopPlaylist = false; 0826 if (playlist > 0) { 0827 // Clips on playlist > 0 must have a mix or something is wrong 0828 bool hasStartMix = !timeline->trackIsBlankAt(tid, position, 0); 0829 int duration = clip->get_playtime() - 1; 0830 bool hasEndMix = !timeline->trackIsBlankAt(tid, position + duration, 0); 0831 bool startMixToFind = hasStartMix; 0832 bool endMixToFind = hasEndMix; 0833 if (startMixToFind || endMixToFind) { 0834 for (auto &compo : compositions) { 0835 int in = compo->get_in(); 0836 int out = compo->get_out(); 0837 if (startMixToFind && out > position && in <= position) { 0838 startMixToFind = false; 0839 } 0840 if (endMixToFind && in < position + duration && out >= position + duration) { 0841 endMixToFind = false; 0842 } 0843 if (!startMixToFind && !endMixToFind) { 0844 break; 0845 } 0846 } 0847 if (startMixToFind || endMixToFind) { 0848 // A mix for this clip is missing 0849 const QString tcInfo = 0850 QString("<a href=\"%1!%2?%3\">%4 %5</a>") 0851 .arg(timeline->uuid().toString(), QString::number(position), QString::number(timeline->getTrackPosition(tid) + 1), trackTag, 0852 pCore->timecode().getTimecodeFromFrames(position)); 0853 // Try to resize clip and put it on playlist 0 0854 if (hasEndMix && endMixToFind) { 0855 // Find last empty frame on playlist 0 0856 int lastAvailableFrame = timeline->getClipStartAt(tid, position + duration, 0); 0857 if (lastAvailableFrame > position) { 0858 // Resize clip to fit. 0859 int updatedDuration = lastAvailableFrame - position - 1; 0860 int currentIn = clip->get_in(); 0861 clip->set_in_and_out(currentIn, currentIn + updatedDuration); 0862 hasEndMix = false; 0863 if (!startMixToFind) { 0864 // Move to top playlist 0865 cid = ClipModel::construct(timeline, binId, clip, st, tid, originalDecimalPoint, hasStartMix ? playlist : 0); 0866 timeline->requestClipMove(cid, tid, position, true, true, false, true, undo, redo); 0867 m_notesLog << i18n("%1 Clip (%2) with missing mix found and resized", tcInfo, clip->parent().get("id")); 0868 m_errorMessage << i18n("Clip without mix %1 found and resized on track %2 at %3.", clip->parent().get("id"), trackTag, 0869 pCore->timecode().getTimecodeFromFrames(position)); 0870 continue; 0871 } 0872 } else { 0873 m_errorMessage << i18n("Invalid clip without mix %1 found and removed on track %2 at %3.", clip->parent().get("id"), 0874 trackTag, pCore->timecode().getTimecodeFromFrames(position)); 0875 m_notesLog << i18n("%1 Clip (%2) with missing mix found and removed", tcInfo, clip->parent().get("id")); 0876 continue; 0877 } 0878 } 0879 if (hasStartMix && startMixToFind) { 0880 int firstAvailableFrame = timeline->getClipEndAt(tid, position, 0); 0881 if (firstAvailableFrame < position + duration) { 0882 int delta = firstAvailableFrame - position; 0883 int currentIn = clip->get_in(); 0884 currentIn += delta; 0885 position += delta; 0886 int currentOut = clip->get_out(); 0887 clip->set_in_and_out(currentIn, currentOut); 0888 // Move to top playlist 0889 cid = ClipModel::construct(timeline, binId, clip, st, tid, originalDecimalPoint, hasEndMix ? playlist : 0); 0890 ok = timeline->requestClipMove(cid, tid, position, true, true, false, true, undo, redo); 0891 if (!ok && cid > -1) { 0892 timeline->requestItemDeletion(cid, false); 0893 m_errorMessage << i18n("Invalid clip %1 found on track %2 at %3.", clip->parent().get("id"), track.get("id"), 0894 pCore->timecode().getTimecodeFromFrames(position)); 0895 m_notesLog << i18n("%1 Invalid clip (%2) found and removed", tcInfo, clip->parent().get("id")); 0896 continue; 0897 } 0898 m_errorMessage << i18n("Clip without mix %1 found and resized on track %2 at %3.", clip->parent().get("id"), trackTag, 0899 pCore->timecode().getTimecodeFromFrames(position)); 0900 m_notesLog << i18n("%1 Clip (%2) with missing mix found and resized", tcInfo, clip->parent().get("id")); 0901 continue; 0902 } 0903 } 0904 m_errorMessage << i18n("Invalid clip without mix %1 found and removed on track %2 at %3.", clip->parent().get("id"), trackTag, 0905 pCore->timecode().getTimecodeFromFrames(position)); 0906 m_notesLog << i18n("%1 Clip (%2) with missing mix found and removed", tcInfo, clip->parent().get("id")); 0907 continue; 0908 } 0909 } else { 0910 // Check if playlist 0 is available 0911 enforceTopPlaylist = timeline->trackIsAvailable(tid, position, duration, 0); 0912 const QString tcInfo = 0913 QString("<a href=\"%1!%2?%3\">%4 %5</a>") 0914 .arg(timeline->uuid().toString(), QString::number(position), QString::number(timeline->getTrackPosition(tid) + 1), trackTag, 0915 pCore->timecode().getTimecodeFromFrames(position)); 0916 if (enforceTopPlaylist) { 0917 m_errorMessage << i18n("Clip %1 on incorrect subtrack found and fixed on track %2 at %3.", clip->parent().get("id"), trackTag, 0918 pCore->timecode().getTimecodeFromFrames(position)); 0919 m_notesLog << i18n("%1 Clip (%2) on incorrect subtrack found and fixed / %3", tcInfo, clip->parent().get("id"), 0920 QString::number(tid)); 0921 } else { 0922 m_errorMessage << i18n("Clip %1 on incorrect subtrack found on track %2 at %3.", clip->parent().get("id"), trackTag, 0923 pCore->timecode().getTimecodeFromFrames(position)); 0924 m_notesLog << i18n("%1 Clip (%2) with incorrect subplaylist found", tcInfo, clip->parent().get("id")); 0925 } 0926 } 0927 } 0928 cid = ClipModel::construct(timeline, binId, clip, st, tid, originalDecimalPoint, enforceTopPlaylist ? 0 : playlist); 0929 ok = timeline->requestClipMove(cid, tid, position, true, true, false, true, undo, redo); 0930 } else { 0931 qWarning() << "Really can't find bin clip" << binId << clip->get("id"); 0932 } 0933 if (!ok && cid > -1) { 0934 timeline->requestItemDeletion(cid, false); 0935 m_errorMessage << i18n("Invalid clip %1 found on track %2 at %3.", clip->parent().get("id"), track.get("id"), 0936 pCore->timecode().getTimecodeFromFrames(position)); 0937 const QString tcInfo = QString("<a href=\"%1!%2?%3\">%4 %5</a>") 0938 .arg(timeline->uuid().toString(), QString::number(position), QString::number(timeline->getTrackPosition(tid) + 1), 0939 trackTag, pCore->timecode().getTimecodeFromFrames(position)); 0940 m_notesLog << i18n("%1 Invalid clip (%2) found and removed", tcInfo, clip->parent().get("id")); 0941 continue; 0942 } 0943 break; 0944 } 0945 case mlt_service_tractor_type: { 0946 // TODO This is a nested timeline 0947 qWarning() << "nested timelines not yet implemented"; 0948 Q_ASSERT(false); 0949 break; 0950 } 0951 default: 0952 qWarning() << "unexpected object found in playlist"; 0953 return false; 0954 break; 0955 } 0956 } 0957 std::shared_ptr<Mlt::Service> serv = std::make_shared<Mlt::Service>(track.get_service()); 0958 timeline->importTrackEffects(tid, serv); 0959 return true; 0960 }