File indexing completed on 2024-04-28 04:52:30
0001 /* 0002 SPDX-FileCopyrightText: 2017 Nicolas Carion 0003 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0004 */ 0005 0006 #include "timelineitemmodel.hpp" 0007 #include "assets/keyframes/model/keyframemodel.hpp" 0008 #include "audiomixer/mixermanager.hpp" 0009 #include "bin/model/markerlistmodel.hpp" 0010 #include "bin/model/subtitlemodel.hpp" 0011 #include "clipmodel.hpp" 0012 #include "compositionmodel.hpp" 0013 #include "core.h" 0014 #include "doc/docundostack.hpp" 0015 #include "doc/kdenlivedoc.h" 0016 #include "groupsmodel.hpp" 0017 #include "kdenlivesettings.h" 0018 #include "macros.hpp" 0019 #include "snapmodel.hpp" 0020 #include "timeline2/view/previewmanager.h" 0021 #include "trackmodel.hpp" 0022 #include "transitions/transitionsrepository.hpp" 0023 #include <QDebug> 0024 #include <QFileInfo> 0025 #include <mlt++/MltField.h> 0026 #include <mlt++/MltProfile.h> 0027 #include <mlt++/MltTractor.h> 0028 #include <mlt++/MltTransition.h> 0029 0030 #ifdef CRASH_AUTO_TEST 0031 #pragma GCC diagnostic push 0032 #pragma GCC diagnostic ignored "-Wunused-parameter" 0033 #pragma GCC diagnostic ignored "-Wsign-conversion" 0034 #pragma GCC diagnostic ignored "-Wfloat-equal" 0035 #pragma GCC diagnostic ignored "-Wshadow" 0036 #pragma GCC diagnostic ignored "-Wpedantic" 0037 #include <rttr/registration> 0038 #pragma GCC diagnostic pop 0039 RTTR_REGISTRATION 0040 { 0041 using namespace rttr; 0042 registration::class_<TimelineItemModel>("TimelineItemModel"); 0043 } 0044 #endif 0045 0046 TimelineItemModel::TimelineItemModel(const QUuid &uuid, std::weak_ptr<DocUndoStack> undo_stack) 0047 : TimelineModel(uuid, std::move(undo_stack)) 0048 { 0049 } 0050 0051 void TimelineItemModel::finishConstruct(const std::shared_ptr<TimelineItemModel> &ptr) 0052 { 0053 ptr->weak_this_ = ptr; 0054 ptr->m_groups = std::make_unique<GroupsModel>(ptr); 0055 } 0056 0057 std::shared_ptr<TimelineItemModel> TimelineItemModel::construct(const QUuid &uuid, std::weak_ptr<DocUndoStack> undo_stack) 0058 { 0059 std::shared_ptr<TimelineItemModel> ptr(new TimelineItemModel(uuid, std::move(undo_stack))); 0060 finishConstruct(ptr); 0061 return ptr; 0062 } 0063 0064 TimelineItemModel::~TimelineItemModel() = default; 0065 0066 QModelIndex TimelineItemModel::index(int row, int column, const QModelIndex &parent) const 0067 { 0068 READ_LOCK(); 0069 QModelIndex result; 0070 if (parent.isValid()) { 0071 auto trackId = int(parent.internalId()); 0072 Q_ASSERT(isTrack(trackId)); 0073 int clipId = getTrackById_const(trackId)->getClipByRow(row); 0074 if (clipId != -1) { 0075 result = createIndex(row, 0, quintptr(clipId)); 0076 } else if (row < getTrackClipsCount(trackId) + getTrackCompositionsCount(trackId)) { 0077 int compoId = getTrackById_const(trackId)->getCompositionByRow(row); 0078 if (compoId != -1) { 0079 result = createIndex(row, 0, quintptr(compoId)); 0080 } 0081 } else { 0082 // Invalid index requested 0083 Q_ASSERT(false); 0084 } 0085 } else if (row < int(m_allTracks.size()) && row >= 0) { 0086 // Get sort order 0087 // row = getTracksCount() - 1 - row; 0088 auto it = m_allTracks.cbegin(); 0089 std::advance(it, row); 0090 int trackId = (*it)->getId(); 0091 result = createIndex(row, column, quintptr(trackId)); 0092 } 0093 return result; 0094 } 0095 0096 /*QModelIndex TimelineItemModel::makeIndex(int trackIndex, int clipIndex) const 0097 { 0098 return index(clipIndex, 0, index(trackIndex)); 0099 }*/ 0100 0101 bool TimelineItemModel::addTracksAtPosition(int position, int tracksCount, QString &trackName, bool addAudioTrack, bool addAVTrack, bool addRecTrack) 0102 { 0103 Fun undo = []() { return true; }; 0104 Fun redo = []() { return true; }; 0105 bool result = true; 0106 0107 int insertionIndex = position; 0108 0109 for (int ix = 0; ix < tracksCount; ++ix) { 0110 int newTid; 0111 result = requestTrackInsertion(insertionIndex, newTid, trackName, addAudioTrack, undo, redo); 0112 // bump up insertion index so that the next new track goes after this one 0113 insertionIndex++; 0114 if (result) { 0115 if (addAVTrack) { 0116 int newTid2; 0117 int mirrorPos = 0; 0118 int mirrorId = getMirrorAudioTrackId(newTid); 0119 if (mirrorId > -1) { 0120 mirrorPos = getTrackMltIndex(mirrorId); 0121 } 0122 result = requestTrackInsertion(mirrorPos, newTid2, trackName, true, undo, redo); 0123 // because we also added an audio track, we need to put the next 0124 // new track's index is 1 further 0125 insertionIndex++; 0126 } 0127 if (addRecTrack) { 0128 setTrackProperty(newTid, "kdenlive:audio_rec", QStringLiteral("1")); 0129 } 0130 } else { 0131 break; 0132 } 0133 } 0134 if (result) { 0135 pCore->pushUndo(undo, redo, addAVTrack || tracksCount > 1 ? i18nc("@action", "Insert Tracks") : i18nc("@action", "Insert Track")); 0136 return true; 0137 } else { 0138 undo(); 0139 return false; 0140 } 0141 } 0142 0143 QModelIndex TimelineItemModel::makeClipIndexFromID(int clipId) const 0144 { 0145 Q_ASSERT(m_allClips.count(clipId) > 0); 0146 int trackId = m_allClips.at(clipId)->getCurrentTrackId(); 0147 if (trackId == -1) { 0148 // Clip is not inserted in a track 0149 qDebug() << "/// WARNING; INVALID CLIP INDEX REQUESTED: " << clipId << "\n________________"; 0150 return {}; 0151 } 0152 int row = getTrackById_const(trackId)->getRowfromClip(clipId); 0153 return index(row, 0, makeTrackIndexFromID(trackId)); 0154 } 0155 0156 QModelIndex TimelineItemModel::makeCompositionIndexFromID(int compoId) const 0157 { 0158 Q_ASSERT(m_allCompositions.count(compoId) > 0); 0159 int trackId = m_allCompositions.at(compoId)->getCurrentTrackId(); 0160 return index(getTrackById_const(trackId)->getRowfromComposition(compoId), 0, makeTrackIndexFromID(trackId)); 0161 } 0162 0163 void TimelineItemModel::subtitleChanged(int subId, const QVector<int> &roles) 0164 { 0165 if (m_closing) { 0166 return; 0167 } 0168 Q_ASSERT(m_subtitleModel != nullptr); 0169 Q_ASSERT(m_allSubtitles.count(subId) > 0); 0170 m_subtitleModel->updateSub(subId, roles); 0171 } 0172 0173 QModelIndex TimelineItemModel::makeTrackIndexFromID(int trackId) const 0174 { 0175 // we retrieve iterator 0176 Q_ASSERT(m_iteratorTable.count(trackId) > 0); 0177 auto it = m_iteratorTable.at(trackId); 0178 int ind = int(std::distance<decltype(m_allTracks.cbegin())>(m_allTracks.begin(), it)); 0179 // Get sort order 0180 // ind = getTracksCount() - 1 - ind; 0181 return index(ind); 0182 } 0183 0184 QModelIndex TimelineItemModel::parent(const QModelIndex &index) const 0185 { 0186 READ_LOCK(); 0187 // qDebug() << "TimelineItemModel::parent"<< index; 0188 if (index == QModelIndex()) { 0189 return index; 0190 } 0191 const int id = static_cast<int>(index.internalId()); 0192 if (!index.isValid() || isTrack(id)) { 0193 return QModelIndex(); 0194 } 0195 if (isClip(id)) { 0196 const int trackId = getClipTrackId(id); 0197 return makeTrackIndexFromID(trackId); 0198 } 0199 if (isComposition(id)) { 0200 const int trackId = getCompositionTrackId(id); 0201 return makeTrackIndexFromID(trackId); 0202 } 0203 return {}; 0204 } 0205 0206 int TimelineItemModel::rowCount(const QModelIndex &parent) const 0207 { 0208 READ_LOCK(); 0209 if (parent.isValid()) { 0210 const int id = int(parent.internalId()); 0211 if (!isTrack(id)) { 0212 // clips don't have children 0213 // if it is not a track, it is something invalid 0214 return 0; 0215 } 0216 return getTrackClipsCount(id) + getTrackCompositionsCount(id); 0217 } 0218 return int(m_allTracks.size()); 0219 } 0220 0221 int TimelineItemModel::columnCount(const QModelIndex &parent) const 0222 { 0223 Q_UNUSED(parent); 0224 return 1; 0225 } 0226 0227 QHash<int, QByteArray> TimelineItemModel::roleNames() const 0228 { 0229 QHash<int, QByteArray> roles; 0230 roles[NameRole] = "name"; 0231 roles[ResourceRole] = "resource"; 0232 roles[ServiceRole] = "mlt_service"; 0233 roles[BinIdRole] = "binId"; 0234 roles[TrackIdRole] = "trackId"; 0235 roles[TagRole] = "tag"; 0236 roles[FakeTrackIdRole] = "fakeTrackId"; 0237 roles[FakePositionRole] = "fakePosition"; 0238 roles[StartRole] = "start"; 0239 roles[MixRole] = "mixDuration"; 0240 roles[MixCutRole] = "mixCut"; 0241 roles[DurationRole] = "duration"; 0242 roles[MaxDurationRole] = "maxDuration"; 0243 roles[MarkersRole] = "markers"; 0244 roles[KeyframesRole] = "keyframeModel"; 0245 roles[ShowKeyframesRole] = "showKeyframes"; 0246 roles[PlaylistStateRole] = "clipState"; 0247 roles[StatusRole] = "clipStatus"; 0248 roles[TypeRole] = "clipType"; 0249 roles[InPointRole] = "in"; 0250 roles[OutPointRole] = "out"; 0251 roles[FramerateRole] = "fps"; 0252 roles[GroupedRole] = "grouped"; 0253 roles[IsDisabledRole] = "disabled"; 0254 roles[IsAudioRole] = "audio"; 0255 roles[AudioChannelsRole] = "audioChannels"; 0256 roles[AudioStreamRole] = "audioStream"; 0257 roles[AudioMultiStreamRole] = "multiStream"; 0258 roles[AudioStreamIndexRole] = "audioStreamIndex"; 0259 roles[IsCompositeRole] = "composite"; 0260 roles[IsLockedRole] = "locked"; 0261 roles[FadeInRole] = "fadeIn"; 0262 roles[FadeOutRole] = "fadeOut"; 0263 roles[FileHashRole] = "hash"; 0264 roles[SpeedRole] = "speed"; 0265 roles[TimeRemapRole] = "timeremap"; 0266 roles[HeightRole] = "trackHeight"; 0267 roles[TrackTagRole] = "trackTag"; 0268 roles[ItemIdRole] = "item"; 0269 roles[ItemATrack] = "a_track"; 0270 roles[HasAudio] = "hasAudio"; 0271 roles[CanBeAudioRole] = "canBeAudio"; 0272 roles[CanBeVideoRole] = "canBeVideo"; 0273 roles[ClipThumbRole] = "clipThumbId"; 0274 roles[ReloadAudioThumbRole] = "reloadAudioThumb"; 0275 roles[PositionOffsetRole] = "positionOffset"; 0276 roles[ThumbsFormatRole] = "thumbsFormat"; 0277 roles[AudioRecordRole] = "audioRecord"; 0278 roles[TrackActiveRole] = "trackActive"; 0279 roles[EffectNamesRole] = "effectNames"; 0280 roles[EffectsEnabledRole] = "isStackEnabled"; 0281 roles[EffectZonesRole] = "effectZones"; 0282 roles[GrabbedRole] = "isGrabbed"; 0283 roles[SelectedRole] = "selected"; 0284 return roles; 0285 } 0286 0287 QVariant TimelineItemModel::data(const QModelIndex &index, int role) const 0288 { 0289 READ_LOCK(); 0290 if (!m_tractor || !index.isValid()) { 0291 // qDebug() << "DATA abort. Index validity="<<index.isValid(); 0292 return QVariant(); 0293 } 0294 const int id = int(index.internalId()); 0295 if (role == ItemIdRole) { 0296 return id; 0297 } 0298 if (role == SortRole) { 0299 if (isTrack(id)) { 0300 return getTrackSortValue(id, KdenliveSettings::audiotracksbelow()); 0301 } 0302 return QVariant(); 0303 } 0304 if (isClip(id)) { 0305 // qDebug() << "REQUESTING DATA "<<roleNames()[role]<<index; 0306 std::shared_ptr<ClipModel> clip = m_allClips.at(id); 0307 // Get data for a clip 0308 switch (role) { 0309 // TODO 0310 case NameRole: 0311 case Qt::DisplayRole: { 0312 return clip->clipName(); 0313 } 0314 case ResourceRole: { 0315 QString result = clip->getProperty("resource"); 0316 if (result == QLatin1String("<producer>")) { 0317 result = clip->getProperty("mlt_service"); 0318 } 0319 return result; 0320 } 0321 case StatusRole: { 0322 return clip->clipStatus(); 0323 } 0324 case FakeTrackIdRole: 0325 return clip->getFakeTrackId(); 0326 case FakePositionRole: 0327 return clip->getFakePosition(); 0328 case BinIdRole: 0329 return clip->binId(); 0330 case TrackIdRole: 0331 return clip->getCurrentTrackId(); 0332 case ServiceRole: 0333 return clip->getProperty("mlt_service"); 0334 break; 0335 case AudioChannelsRole: 0336 return clip->audioChannels(); 0337 case AudioStreamRole: 0338 return clip->audioStream(); 0339 case AudioMultiStreamRole: 0340 return clip->audioMultiStream(); 0341 case AudioStreamIndexRole: 0342 return clip->audioStreamIndex(); 0343 case HasAudio: 0344 return clip->audioEnabled(); 0345 case IsAudioRole: 0346 return clip->isAudioOnly(); 0347 case CanBeAudioRole: 0348 return clip->canBeAudio(); 0349 case CanBeVideoRole: 0350 return clip->canBeVideo(); 0351 case MarkersRole: { 0352 return QVariant::fromValue<MarkerListModel *>(clip->getMarkerModel().get()); 0353 } 0354 case KeyframesRole: { 0355 return QVariant::fromValue<KeyframeModel *>(clip->getKeyframeModel()); 0356 } 0357 case PlaylistStateRole: 0358 return QVariant::fromValue(clip->clipState()); 0359 case TypeRole: 0360 return QVariant::fromValue(clip->clipType()); 0361 case StartRole: 0362 return clip->getPosition(); 0363 case DurationRole: 0364 return clip->getPlaytime(); 0365 case MaxDurationRole: 0366 return clip->getMaxDuration(); 0367 case GroupedRole: 0368 return m_groups->isInGroup(id); 0369 case EffectNamesRole: 0370 return clip->effectNames(); 0371 case EffectsEnabledRole: 0372 return clip->stackEnabled(); 0373 case InPointRole: 0374 return clip->getIn(); 0375 case OutPointRole: 0376 return clip->getOut(); 0377 case ShowKeyframesRole: 0378 return clip->showKeyframes(); 0379 case FadeInRole: 0380 return clip->fadeIn(); 0381 case FadeOutRole: 0382 return clip->fadeOut(); 0383 case MixRole: 0384 return clip->getMixDuration(); 0385 case MixCutRole: 0386 return clip->getMixCutPosition(); 0387 case ClipThumbRole: 0388 return clip->clipThumbPath(); 0389 case ReloadAudioThumbRole: 0390 return clip->forceThumbReload; 0391 case PositionOffsetRole: 0392 return clip->getOffset(); 0393 case SpeedRole: 0394 return clip->getSpeed(); 0395 case GrabbedRole: 0396 return clip->isGrabbed(); 0397 case SelectedRole: 0398 return clip->selected; 0399 case TagRole: 0400 return clip->clipTag(); 0401 case TimeRemapRole: 0402 return clip->hasTimeRemap(); 0403 default: 0404 break; 0405 } 0406 } else if (isTrack(id)) { 0407 // qDebug() << "DATA REQUESTED FOR TRACK "<< id; 0408 switch (role) { 0409 case NameRole: 0410 case Qt::DisplayRole: { 0411 return getTrackById_const(id)->getProperty("kdenlive:track_name").toString(); 0412 } 0413 case TypeRole: 0414 return QVariant::fromValue(ClipType::ProducerType::Track); 0415 case DurationRole: 0416 // qDebug() << "DATA yielding duration" << m_tractor->get_playtime(); 0417 return getTrackById_const(id)->trackDuration(); 0418 case IsDisabledRole: 0419 // qDebug() << "DATA yielding mute" << 0; 0420 return getTrackById_const(id)->isAudioTrack() ? getTrackById_const(id)->isMute() : getTrackById_const(id)->isHidden(); 0421 case IsAudioRole: 0422 return getTrackById_const(id)->isAudioTrack(); 0423 case TrackTagRole: 0424 return getTrackTagById(id); 0425 case IsLockedRole: 0426 return getTrackById_const(id)->isLocked(); 0427 case HeightRole: { 0428 int collapsed = getTrackById_const(id)->getProperty("kdenlive:collapsed").toInt(); 0429 if (collapsed > 0) { 0430 return collapsed; 0431 } 0432 int height = getTrackById_const(id)->getProperty("kdenlive:trackheight").toInt(); 0433 // qDebug() << "DATA yielding height" << height; 0434 return (height > 0 ? height : KdenliveSettings::trackheight()); 0435 } 0436 case ThumbsFormatRole: 0437 return getTrackById_const(id)->getProperty("kdenlive:thumbs_format").toInt(); 0438 case IsCompositeRole: { 0439 case AudioRecordRole: 0440 return getTrackById_const(id)->getProperty("kdenlive:audio_rec").toInt(); 0441 } 0442 case TrackActiveRole: { 0443 return getTrackById_const(id)->isTimelineActive(); 0444 } 0445 case EffectNamesRole: { 0446 return getTrackById_const(id)->effectNames(); 0447 } 0448 case EffectsEnabledRole: { 0449 return getTrackById_const(id)->stackEnabled(); 0450 } 0451 case EffectZonesRole: { 0452 return getTrackById_const(id)->stackZones(); 0453 } 0454 default: 0455 break; 0456 } 0457 } else if (isComposition(id)) { 0458 std::shared_ptr<CompositionModel> compo = m_allCompositions.at(id); 0459 switch (role) { 0460 case NameRole: 0461 case Qt::DisplayRole: 0462 case ResourceRole: 0463 case ServiceRole: 0464 return compo->displayName(); 0465 break; 0466 case TypeRole: 0467 return QVariant::fromValue(ClipType::ProducerType::Composition); 0468 case StartRole: 0469 return compo->getPosition(); 0470 case TrackIdRole: 0471 return compo->getCurrentTrackId(); 0472 case DurationRole: 0473 return compo->getPlaytime(); 0474 case GroupedRole: 0475 return m_groups->isInGroup(id); 0476 case InPointRole: 0477 return 0; 0478 case OutPointRole: 0479 return 100; 0480 case BinIdRole: 0481 return 5; 0482 case KeyframesRole: { 0483 return QVariant::fromValue<KeyframeModel *>(compo->getEffectKeyframeModel()); 0484 } 0485 case ShowKeyframesRole: 0486 return compo->showKeyframes(); 0487 case ItemATrack: 0488 return compo->getForcedTrack(); 0489 case MarkersRole: { 0490 QVariantList markersList; 0491 return markersList; 0492 } 0493 case GrabbedRole: 0494 return compo->isGrabbed(); 0495 case SelectedRole: 0496 return compo->selected; 0497 default: 0498 break; 0499 } 0500 } else { 0501 qDebug() << "UNKNOWN DATA requested " << index << roleNames()[role]; 0502 } 0503 return QVariant(); 0504 } 0505 0506 void TimelineItemModel::setTrackName(int trackId, const QString &text) 0507 { 0508 QWriteLocker locker(&m_lock); 0509 const QString ¤tName = getTrackProperty(trackId, QStringLiteral("kdenlive:track_name")).toString(); 0510 if (text == currentName) { 0511 return; 0512 } 0513 Fun undo_lambda = [this, trackId, currentName]() { 0514 setTrackProperty(trackId, QStringLiteral("kdenlive:track_name"), currentName); 0515 return true; 0516 }; 0517 Fun redo_lambda = [this, trackId, text]() { 0518 setTrackProperty(trackId, QStringLiteral("kdenlive:track_name"), text); 0519 return true; 0520 }; 0521 redo_lambda(); 0522 PUSH_UNDO(undo_lambda, redo_lambda, i18n("Rename Track")); 0523 } 0524 0525 void TimelineItemModel::setTrackProperty(int trackId, const QString &name, const QString &value) 0526 { 0527 std::shared_ptr<TrackModel> track = getTrackById(trackId); 0528 track->setProperty(name, value); 0529 QVector<int> roles; 0530 bool updateMultiTrack = false; 0531 if (name == QLatin1String("kdenlive:track_name")) { 0532 roles.push_back(NameRole); 0533 if (!track->isAudioTrack()) { 0534 updateMultiTrack = true; 0535 } 0536 } else if (name == QLatin1String("kdenlive:locked_track")) { 0537 roles.push_back(IsLockedRole); 0538 } else if (name == QLatin1String("hide")) { 0539 roles.push_back(IsDisabledRole); 0540 if (!track->isAudioTrack() && !isLoading) { 0541 pCore->invalidateItem(ObjectId(KdenliveObjectType::TimelineTrack, trackId, m_uuid)); 0542 pCore->refreshProjectMonitorOnce(); 0543 updateMultiTrack = true; 0544 } 0545 } else if (name == QLatin1String("kdenlive:timeline_active")) { 0546 roles.push_back(TrackActiveRole); 0547 } else if (name == QLatin1String("kdenlive:thumbs_format")) { 0548 roles.push_back(ThumbsFormatRole); 0549 } else if (name == QLatin1String("kdenlive:collapsed")) { 0550 roles.push_back(HeightRole); 0551 } else if (name == QLatin1String("kdenlive:audio_rec")) { 0552 roles.push_back(AudioRecordRole); 0553 } 0554 if (!roles.isEmpty()) { 0555 QModelIndex ix = makeTrackIndexFromID(trackId); 0556 Q_EMIT dataChanged(ix, ix, roles); 0557 if (updateMultiTrack) { 0558 Q_EMIT trackVisibilityChanged(); 0559 } 0560 } 0561 } 0562 0563 void TimelineItemModel::setTrackStackEnabled(int tid, bool enable) 0564 { 0565 std::shared_ptr<TrackModel> track = getTrackById(tid); 0566 track->setEffectStackEnabled(enable); 0567 QModelIndex ix = makeTrackIndexFromID(tid); 0568 Q_EMIT dataChanged(ix, ix, {TimelineModel::EffectsEnabledRole}); 0569 } 0570 0571 void TimelineItemModel::importTrackEffects(int tid, std::weak_ptr<Mlt::Service> service) 0572 { 0573 std::shared_ptr<TrackModel> track = getTrackById(tid); 0574 Mlt::Tractor *destination = track->getTrackService(); 0575 // Audio mixer effects are attached to the Tractor service, while track effects are attached to first playlist service 0576 if (auto ptr = service.lock()) { 0577 for (int i = 0; i < ptr->filter_count(); i++) { 0578 std::unique_ptr<Mlt::Filter> filter(ptr->filter(i)); 0579 if (filter->get_int("internal_added") > 0) { 0580 destination->attach(*filter.get()); 0581 } 0582 } 0583 } 0584 0585 track->importEffects(std::move(service)); 0586 } 0587 0588 QVariant TimelineItemModel::getTrackProperty(int tid, const QString &name) const 0589 { 0590 return getTrackById_const(tid)->getProperty(name); 0591 } 0592 0593 int TimelineItemModel::getFirstVideoTrackIndex() const 0594 { 0595 int trackId = -1; 0596 auto it = m_allTracks.cbegin(); 0597 while (it != m_allTracks.cend()) { 0598 trackId = (*it)->getId(); 0599 if (!(*it)->isAudioTrack()) { 0600 break; 0601 } 0602 ++it; 0603 } 0604 return trackId; 0605 } 0606 0607 int TimelineItemModel::getFirstAudioTrackIndex() const 0608 { 0609 int trackId = -1; 0610 auto it = m_allTracks.cbegin(); 0611 while (it != m_allTracks.cend()) { 0612 if ((*it)->isAudioTrack()) { 0613 trackId = (*it)->getId(); 0614 } 0615 ++it; 0616 } 0617 return trackId; 0618 } 0619 0620 std::shared_ptr<SubtitleModel> TimelineItemModel::createSubtitleModel() 0621 { 0622 if (m_subtitleModel == nullptr) { 0623 // Initialize the subtitle model and load file if any 0624 m_subtitleModel.reset( 0625 new SubtitleModel(std::static_pointer_cast<TimelineItemModel>(shared_from_this()), std::static_pointer_cast<SnapInterface>(m_snaps), this)); 0626 Q_EMIT subtitleModelInitialized(); 0627 } 0628 return m_subtitleModel; 0629 } 0630 0631 const QString TimelineItemModel::getTrackFullName(int tid) const 0632 { 0633 QString tag = getTrackTagById(tid); 0634 QString trackName = getTrackById_const(tid)->getProperty(QStringLiteral("kdenlive:track_name")).toString(); 0635 return trackName.isEmpty() ? tag : tag + QStringLiteral(" - ") + trackName; 0636 } 0637 0638 const QString TimelineItemModel::groupsData() 0639 { 0640 return m_groups->toJson(); 0641 } 0642 0643 bool TimelineItemModel::loadGroups(const QString &groupsData) 0644 { 0645 return m_groups->fromJson(groupsData); 0646 } 0647 0648 void TimelineItemModel::notifyChange(const QModelIndex &topleft, const QModelIndex &bottomright, bool start, bool duration, bool updateThumb) 0649 { 0650 QVector<int> roles; 0651 if (start) { 0652 roles.push_back(TimelineModel::StartRole); 0653 if (updateThumb) { 0654 roles.push_back(TimelineModel::InPointRole); 0655 } 0656 } 0657 if (duration) { 0658 roles.push_back(TimelineModel::DurationRole); 0659 if (updateThumb) { 0660 roles.push_back(TimelineModel::OutPointRole); 0661 } 0662 } 0663 Q_EMIT dataChanged(topleft, bottomright, roles); 0664 } 0665 0666 void TimelineItemModel::notifyChange(const QModelIndex &topleft, const QModelIndex &bottomright, const QVector<int> &roles) 0667 { 0668 Q_EMIT dataChanged(topleft, bottomright, roles); 0669 } 0670 0671 void TimelineItemModel::rebuildMixer() 0672 { 0673 if (pCore->mixer() == nullptr) { 0674 return; 0675 } 0676 pCore->mixer()->cleanup(); 0677 pCore->mixer()->setModel(std::static_pointer_cast<TimelineItemModel>(shared_from_this())); 0678 auto it = m_allTracks.cbegin(); 0679 while (it != m_allTracks.cend()) { 0680 if ((*it)->isAudioTrack()) { 0681 pCore->mixer()->registerTrack((*it)->getId(), (*it)->getTrackService(), getTrackTagById((*it)->getId()), 0682 (*it)->getProperty(QStringLiteral("kdenlive:track_name")).toString()); 0683 connect(pCore->mixer(), &MixerManager::showEffectStack, this, &TimelineItemModel::showTrackEffectStack); 0684 } 0685 ++it; 0686 } 0687 } 0688 0689 bool TimelineItemModel::copyClipEffect(int clipId, const QString sourceId) 0690 { 0691 QStringList source = sourceId.split(QLatin1Char(',')); 0692 Q_ASSERT(m_allClips.count(clipId) && source.count() == 4); 0693 int itemType = source.at(0).toInt(); 0694 int itemId = source.at(1).toInt(); 0695 int itemRow = source.at(2).toInt(); 0696 const QUuid uuid(source.at(3)); 0697 std::shared_ptr<EffectStackModel> effectStack = pCore->getItemEffectStack(uuid, itemType, itemId); 0698 if (m_singleSelectionMode && m_currentSelection.count(clipId)) { 0699 // only operate on the selected item 0700 Fun undo = []() { return true; }; 0701 Fun redo = []() { return true; }; 0702 for (auto &s : m_currentSelection) { 0703 if (isClip(s)) { 0704 m_allClips.at(s)->copyEffectWithUndo(uuid, effectStack, itemRow, undo, redo); 0705 } 0706 } 0707 pCore->pushUndo(undo, redo, i18n("Copy effect")); 0708 return true; 0709 } else if (m_groups->isInGroup(clipId)) { 0710 int parentGroup = m_groups->getRootId(clipId); 0711 if (parentGroup > -1) { 0712 Fun undo = []() { return true; }; 0713 Fun redo = []() { return true; }; 0714 std::unordered_set<int> sub = m_groups->getLeaves(parentGroup); 0715 for (auto &s : sub) { 0716 if (isClip(s)) { 0717 m_allClips.at(s)->copyEffectWithUndo(uuid, effectStack, itemRow, undo, redo); 0718 } 0719 } 0720 pCore->pushUndo(undo, redo, i18n("Copy effect")); 0721 return true; 0722 } 0723 } 0724 return m_allClips.at(clipId)->copyEffect(uuid, effectStack, itemRow); 0725 } 0726 0727 void TimelineItemModel::buildTrackCompositing(bool rebuild) 0728 { 0729 bool isMultiTrack = pCore->enableMultiTrack(false); 0730 auto it = m_allTracks.cbegin(); 0731 QScopedPointer<Mlt::Service> service(m_tractor->field()); 0732 QScopedPointer<Mlt::Field> field(m_tractor->field()); 0733 field->lock(); 0734 // Make sure all previous track compositing is removed 0735 if (rebuild) { 0736 while (service != nullptr && service->is_valid()) { 0737 if (service->type() == mlt_service_transition_type) { 0738 Mlt::Transition t(mlt_transition(service->get_service())); 0739 service.reset(service->producer()); 0740 if (t.get_int("internal_added") == 237) { 0741 // remove all compositing transitions 0742 field->disconnect_service(t); 0743 t.disconnect_all_producers(); 0744 } 0745 } else { 0746 service.reset(service->producer()); 0747 } 0748 } 0749 } 0750 QString composite = TransitionsRepository::get()->getCompositingTransition(); 0751 bool hasMixer = pCore->mixer() != nullptr; 0752 if (hasMixer) { 0753 pCore->mixer()->cleanup(); 0754 } 0755 int videoTracks = 0; 0756 int audioTracks = 0; 0757 while (it != m_allTracks.cend()) { 0758 int trackPos = getTrackMltIndex((*it)->getId()); 0759 if (!composite.isEmpty() && !(*it)->isAudioTrack()) { 0760 // video track, add composition 0761 std::unique_ptr<Mlt::Transition> transition = TransitionsRepository::get()->getTransition(composite); 0762 transition->set("internal_added", 237); 0763 transition->set("always_active", 1); 0764 transition->set_tracks(0, trackPos); 0765 field->plant_transition(*transition.get(), 0, trackPos); 0766 videoTracks++; 0767 } else if ((*it)->isAudioTrack()) { 0768 // audio mix 0769 std::unique_ptr<Mlt::Transition> transition = TransitionsRepository::get()->getTransition(QStringLiteral("mix")); 0770 transition->set("internal_added", 237); 0771 transition->set("always_active", 1); 0772 transition->set("accepts_blanks", 1); 0773 transition->set("sum", 1); 0774 transition->set_tracks(0, trackPos); 0775 field->plant_transition(*transition.get(), 0, trackPos); 0776 audioTracks++; 0777 if (hasMixer) { 0778 pCore->mixer()->registerTrack((*it)->getId(), (*it)->getTrackService(), getTrackTagById((*it)->getId()), 0779 (*it)->getProperty(QStringLiteral("kdenlive:track_name")).toString()); 0780 connect(pCore->mixer(), &MixerManager::showEffectStack, this, &TimelineItemModel::showTrackEffectStack); 0781 } 0782 } 0783 ++it; 0784 } 0785 field->unlock(); 0786 // Update sequence clip's AV status 0787 int currentClipType = m_tractor->get_int("kdenlive:clip_type"); 0788 int newClipType = audioTracks > 0 ? (videoTracks > 0 ? 0 : 1) : 2; 0789 if (currentClipType != newClipType) { 0790 m_tractor->set("kdenlive:sequenceproperties.hasAudio", audioTracks > 0 ? 1 : 0); 0791 m_tractor->set("kdenlive:sequenceproperties.hasVideo", videoTracks > 0 ? 1 : 0); 0792 m_tractor->set("kdenlive:clip_type", newClipType); 0793 } 0794 pCore->updateSequenceAVType(m_uuid, audioTracks + videoTracks); 0795 if (isMultiTrack) { 0796 pCore->enableMultiTrack(true); 0797 } 0798 if (composite.isEmpty()) { 0799 pCore->displayMessage(i18n("Could not setup track compositing, check your install"), MessageType::ErrorMessage); 0800 } 0801 } 0802 0803 void TimelineItemModel::notifyChange(const QModelIndex &topleft, const QModelIndex &bottomright, int role) 0804 { 0805 Q_EMIT dataChanged(topleft, bottomright, {role}); 0806 } 0807 0808 void TimelineItemModel::_beginRemoveRows(const QModelIndex &i, int j, int k) 0809 { 0810 // qDebug()<<"FORWARDING beginRemoveRows"<<i<<j<<k; 0811 beginRemoveRows(i, j, k); 0812 } 0813 void TimelineItemModel::_beginInsertRows(const QModelIndex &i, int j, int k) 0814 { 0815 // qDebug()<<"FORWARDING beginInsertRows"<<i<<j<<k; 0816 beginInsertRows(i, j, k); 0817 } 0818 void TimelineItemModel::_endRemoveRows() 0819 { 0820 // qDebug()<<"FORWARDING endRemoveRows"; 0821 endRemoveRows(); 0822 } 0823 void TimelineItemModel::_endInsertRows() 0824 { 0825 // qDebug()<<"FORWARDING endinsertRows"; 0826 endInsertRows(); 0827 } 0828 0829 void TimelineItemModel::_resetView() 0830 { 0831 beginResetModel(); 0832 endResetModel(); 0833 } 0834 0835 void TimelineItemModel::passSequenceProperties(const QMap<QString, QString> baseProperties) 0836 { 0837 QMapIterator<QString, QString> i(baseProperties); 0838 while (i.hasNext()) { 0839 i.next(); 0840 tractor()->set(QString("kdenlive:sequenceproperties.%1").arg(i.key()).toUtf8().constData(), i.value().toUtf8().constData()); 0841 } 0842 // Store groups data 0843 tractor()->set("kdenlive:sequenceproperties.groups", groupsData().toUtf8().constData()); 0844 tractor()->set("kdenlive:sequenceproperties.documentuuid", pCore->currentDoc()->uuid().toString().toUtf8().constData()); 0845 // Save timeline guides 0846 const QString guidesData = getGuideModel()->toJson(); 0847 tractor()->set("kdenlive:sequenceproperties.guides", guidesData.toUtf8().constData()); 0848 QPair<int, int> tracks = getAVtracksCount(); 0849 tractor()->set("kdenlive:sequenceproperties.hasAudio", tracks.first > 0 ? 1 : 0); 0850 tractor()->set("kdenlive:sequenceproperties.hasVideo", tracks.second > 0 ? 1 : 0); 0851 tractor()->set("kdenlive:sequenceproperties.tracksCount", tracks.first + tracks.second); 0852 0853 tractor()->set("kdenlive:sequenceproperties.position", pCore->getMonitorPosition()); 0854 0855 if (hasTimelinePreview()) { 0856 QPair<QStringList, QStringList> chunks = previewManager()->previewChunks(); 0857 tractor()->set("kdenlive:sequenceproperties.previewchunks", chunks.first.join(QLatin1Char(',')).toUtf8().constData()); 0858 tractor()->set("kdenlive:sequenceproperties.dirtypreviewchunks", chunks.second.join(QLatin1Char(',')).toUtf8().constData()); 0859 } 0860 } 0861 0862 void TimelineItemModel::processTimelineReplacement(QList<int> instances, const QString &originalId, const QString &replacementId, int maxDuration, 0863 bool replaceAudio, bool replaceVideo) 0864 { 0865 // Check for locked tracks 0866 QList<int> lockedTracks; 0867 for (const auto &track : m_allTracks) { 0868 if (track->isLocked()) { 0869 lockedTracks << track->getId(); 0870 } 0871 } 0872 // Check if some clips are longer than our replacement clip 0873 QList<int> notReplacedIds; 0874 for (auto &id : instances) { 0875 Q_ASSERT(m_allClips.count(id) > 0); 0876 if ((replaceAudio && m_allClips.at(id)->isAudioOnly()) || (replaceVideo && m_allClips.at(id)->clipState() == PlaylistState::VideoOnly)) { 0877 // Match, replace 0878 std::shared_ptr<ClipModel> clip = m_allClips.at(id); 0879 if (clip->getOut() > maxDuration || lockedTracks.contains(clip->getCurrentTrackId())) { 0880 notReplacedIds << id; 0881 } 0882 } 0883 } 0884 Fun local_redo = [this, instances, replacementId, replaceAudio, replaceVideo, notReplacedIds]() { 0885 int replaced = 0; 0886 for (auto &id : instances) { 0887 if (notReplacedIds.contains(id)) { 0888 continue; 0889 } 0890 Q_ASSERT(m_allClips.count(id) > 0); 0891 if ((replaceAudio && m_allClips.at(id)->isAudioOnly()) || (replaceVideo && m_allClips.at(id)->clipState() == PlaylistState::VideoOnly)) { 0892 // Match, replace 0893 std::shared_ptr<ClipModel> clip = m_allClips.at(id); 0894 clip->switchBinReference(replacementId, m_uuid); 0895 replaced++; 0896 QModelIndex ix = makeClipIndexFromID(id); 0897 Q_EMIT dataChanged(ix, ix, {NameRole}); 0898 } 0899 } 0900 if (!notReplacedIds.isEmpty()) { 0901 pCore->displayMessage(i18n("Clips replaced: %1. <b>Clips not replaced: %2</b>", replaced, notReplacedIds.size()), MessageType::ErrorMessage); 0902 } else { 0903 pCore->displayMessage(i18np("One clip replaced", "%1 clips replaced", replaced), MessageType::InformationMessage); 0904 } 0905 return replaced > 0; 0906 }; 0907 if (local_redo()) { 0908 Fun local_undo = [this, instances, originalId, replaceAudio, replaceVideo, notReplacedIds]() { 0909 int replaced = 0; 0910 for (auto &id : instances) { 0911 if (notReplacedIds.contains(id)) { 0912 continue; 0913 } 0914 Q_ASSERT(m_allClips.count(id) > 0); 0915 if ((replaceAudio && m_allClips.at(id)->isAudioOnly()) || (replaceVideo && m_allClips.at(id)->clipState() == PlaylistState::VideoOnly)) { 0916 // Match, replace 0917 std::shared_ptr<ClipModel> clip = m_allClips.at(id); 0918 clip->switchBinReference(originalId, m_uuid); 0919 replaced++; 0920 QModelIndex ix = makeClipIndexFromID(id); 0921 Q_EMIT dataChanged(ix, ix, {NameRole}); 0922 } 0923 } 0924 if (!notReplacedIds.isEmpty()) { 0925 pCore->displayMessage(i18n("Clips replaced: %1, Clips too long: %2", replaced, notReplacedIds.size()), MessageType::InformationMessage); 0926 } else { 0927 pCore->displayMessage(i18np("One clip replaced", "%1 clips replaced", replaced), MessageType::InformationMessage); 0928 } 0929 return replaced > 0; 0930 }; 0931 pCore->pushUndo(local_undo, local_redo, replaceAudio ? (replaceVideo ? i18n("Replace clip") : i18n("Replace audio")) : i18n("Replace video")); 0932 } 0933 }