File indexing completed on 2024-04-28 04:52:25
0001 /* 0002 SPDX-FileCopyrightText: 2017 Nicolas Carion 0003 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0004 */ 0005 #include "clipmodel.hpp" 0006 #include "bin/projectclip.h" 0007 #include "bin/projectitemmodel.h" 0008 #include "clipsnapmodel.hpp" 0009 #include "core.h" 0010 #include "effects/effectstack/model/effectstackmodel.hpp" 0011 #ifdef CRASH_AUTO_TEST 0012 #include "logger.hpp" 0013 #else 0014 #define TRACE_CONSTR(...) 0015 #endif 0016 #include "macros.hpp" 0017 #include "timelinemodel.hpp" 0018 #include "trackmodel.hpp" 0019 #include <QDebug> 0020 #include <effects/effectsrepository.hpp> 0021 #include <mlt++/MltProducer.h> 0022 #include <utility> 0023 0024 ClipModel::ClipModel(const std::shared_ptr<TimelineModel> &parent, std::shared_ptr<Mlt::Producer> prod, const QString &binClipId, int id, 0025 PlaylistState::ClipState state, double speed) 0026 : MoveableItem<Mlt::Producer>(parent, id) 0027 , m_producer(std::move(prod)) 0028 , m_effectStack(EffectStackModel::construct(m_producer, ObjectId(KdenliveObjectType::TimelineClip, m_id, parent->uuid()), parent->m_undoStack)) 0029 , m_clipMarkerModel(new ClipSnapModel()) 0030 , m_binClipId(binClipId) 0031 , forceThumbReload(false) 0032 , m_currentState(state) 0033 , m_speed(speed) 0034 , m_fakeTrack(-1) 0035 , m_fakePosition(-1) 0036 , m_positionOffset(0) 0037 , m_subPlaylistIndex(0) 0038 , m_mixDuration(0) 0039 , m_mixCutPos(0) 0040 , m_hasTimeRemap(hasTimeRemap()) 0041 { 0042 m_producer->set("kdenlive:id", binClipId.toUtf8().constData()); 0043 m_producer->set("_kdenlive_cid", m_id); 0044 std::shared_ptr<ProjectClip> binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId); 0045 m_canBeVideo = binClip->hasVideo(); 0046 m_canBeAudio = binClip->hasAudio(); 0047 m_clipType = binClip->clipType(); 0048 if (binClip) { 0049 m_endlessResize = !binClip->hasLimitedDuration(); 0050 } else { 0051 m_endlessResize = false; 0052 } 0053 QObject::connect(m_effectStack.get(), &EffectStackModel::dataChanged, [&](const QModelIndex &, const QModelIndex &, const QVector<int> &roles) { 0054 qDebug() << "// GOT CLIP STACK DATA CHANGE: " << roles; 0055 if (m_currentTrackId != -1) { 0056 if (auto ptr = m_parent.lock()) { 0057 QModelIndex ix = ptr->makeClipIndexFromID(m_id); 0058 Q_EMIT ptr->dataChanged(ix, ix, roles); 0059 qDebug() << "// GOT CLIP STACK DATA CHANGE DONE: " << ix << " = " << roles; 0060 } 0061 } 0062 }); 0063 } 0064 0065 int ClipModel::construct(const std::shared_ptr<TimelineModel> &parent, const QString &binClipId, int id, PlaylistState::ClipState state, int audioStream, 0066 double speed, bool warp_pitch) 0067 { 0068 id = (id == -1 ? TimelineModel::getNextId() : id); 0069 std::shared_ptr<ProjectClip> binClip = pCore->projectItemModel()->getClipByBinID(binClipId); 0070 0071 // We refine the state according to what the clip can actually produce 0072 std::pair<bool, bool> videoAudio = stateToBool(state); 0073 videoAudio.first = videoAudio.first && binClip->hasVideo(); 0074 videoAudio.second = videoAudio.second && binClip->hasAudio(); 0075 state = stateFromBool(videoAudio); 0076 qDebug() << "// GET TIMELINE PROD FOR STREAM: " << audioStream; 0077 std::shared_ptr<Mlt::Producer> cutProducer = binClip->getTimelineProducer(-1, id, state, audioStream, speed); 0078 std::shared_ptr<ClipModel> clip(new ClipModel(parent, cutProducer, binClipId, id, state, speed)); 0079 if (!qFuzzyCompare(speed, 1.)) { 0080 cutProducer->parent().set("warp_pitch", warp_pitch ? 1 : 0); 0081 } 0082 qDebug() << "==== BUILT CLIP STREAM: " << clip->audioStream(); 0083 TRACE_CONSTR(clip.get(), parent, binClipId, id, state, speed); 0084 clip->setClipState_lambda(state)(); 0085 parent->registerClip(clip); 0086 clip->m_clipMarkerModel->setReferenceModel(binClip->getMarkerModel(), speed); 0087 return id; 0088 } 0089 0090 void ClipModel::allSnaps(std::vector<int> &snaps, int offset) const 0091 { 0092 m_clipMarkerModel->allSnaps(snaps, offset); 0093 } 0094 0095 int ClipModel::construct(const std::shared_ptr<TimelineModel> &parent, const QString &binClipId, const std::shared_ptr<Mlt::Producer> &producer, 0096 PlaylistState::ClipState state, int tid, const QString &originalDecimalPoint, int playlist) 0097 { 0098 0099 // we hand the producer to the bin clip, and in return we get a cut to a good master producer 0100 // We might not be able to use directly the producer that we receive as an argument, because it cannot share the same master producer with any other 0101 // clipModel (due to a mlt limitation, see ProjectClip doc) 0102 int id = TimelineModel::getNextId(); 0103 std::shared_ptr<ProjectClip> binClip = pCore->projectItemModel()->getClipByBinID(binClipId); 0104 0105 // We refine the state according to what the clip can actually produce 0106 std::pair<bool, bool> videoAudio = stateToBool(state); 0107 videoAudio.first = videoAudio.first && binClip->hasVideo(); 0108 videoAudio.second = videoAudio.second && binClip->hasAudio(); 0109 state = stateFromBool(videoAudio); 0110 0111 double speed = 1.0; 0112 bool warp_pitch = false; 0113 if (producer->parent().property_exists("warp_speed")) { 0114 speed = producer->parent().get_double("warp_speed"); 0115 warp_pitch = producer->parent().get_int("warp_pitch"); 0116 } 0117 auto result = binClip->giveMasterAndGetTimelineProducer(id, producer, state, tid, playlist == 1); 0118 std::shared_ptr<ClipModel> clip(new ClipModel(parent, result.first, binClipId, id, state, speed)); 0119 if (warp_pitch) { 0120 result.first->parent().set("warp_pitch", 1); 0121 } 0122 clip->setClipState_lambda(state)(); 0123 clip->setSubPlaylistIndex(playlist, -1); 0124 parent->registerClip(clip); 0125 if (clip->m_endlessResize) { 0126 // Ensure parent is long enough 0127 if (producer->parent().get_out() < producer->get_length() - 1) { 0128 int out = producer->get_length(); 0129 producer->parent().set("length", out + 1); 0130 producer->parent().set("out", out); 0131 } 0132 } 0133 clip->m_effectStack->importEffects(producer, state, result.second, originalDecimalPoint); 0134 clip->m_clipMarkerModel->setReferenceModel(binClip->getMarkerModel(), speed); 0135 return id; 0136 } 0137 0138 void ClipModel::registerClipToBin(std::shared_ptr<Mlt::Producer> service, bool registerProducer) 0139 { 0140 std::shared_ptr<ProjectClip> binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId); 0141 if (!binClip) { 0142 qDebug() << "Error : Bin clip for id: " << m_binClipId << " NOT AVAILABLE!!!"; 0143 } 0144 qDebug() << "REGISTRATION " << m_id << "ptr count" << m_parent.use_count(); 0145 binClip->registerService(m_parent, m_id, std::move(service), registerProducer); 0146 } 0147 0148 void ClipModel::deregisterClipToBin(const QUuid &uuid) 0149 { 0150 std::shared_ptr<ProjectClip> binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId); 0151 binClip->deregisterTimelineClip(m_id, isAudioOnly(), uuid); 0152 } 0153 0154 ClipModel::~ClipModel() = default; 0155 0156 bool ClipModel::requestResize(int size, bool right, Fun &undo, Fun &redo, bool logUndo, bool hasMix) 0157 { 0158 QWriteLocker locker(&m_lock); 0159 // qDebug() << "RESIZE CLIP" << m_id << "target size=" << size << "right=" << right << "endless=" << m_endlessResize << "length" << 0160 // m_producer->get_length()<<" > "<<m_producer->get("kdenlive:duration")<<" = "<<m_producer->get("kdenlive:maxduration"); 0161 if (!m_endlessResize && (size <= 0 || size > m_producer->get_length()) && !hasTimeRemap()) { 0162 return false; 0163 } 0164 int delta = getPlaytime() - size; 0165 if (delta == 0) { 0166 return true; 0167 } 0168 int in = m_producer->get_in(); 0169 int out = m_producer->get_out(); 0170 int oldIn = m_position; 0171 int oldOut = m_position + out - in; 0172 int old_in = in, old_out = out; 0173 // check if there is enough space on the chosen side 0174 if (!m_endlessResize) { 0175 if (!right && in + delta < 0) { 0176 return false; 0177 } 0178 if (right && (out - delta >= m_producer->get_length()) && !hasTimeRemap()) { 0179 return false; 0180 } 0181 } 0182 if (right) { 0183 out -= delta; 0184 } else { 0185 in += delta; 0186 } 0187 // qDebug() << "Resize facts delta =" << delta << "old in" << old_in << "old_out" << old_out << "in" << in << "out" << out; 0188 std::function<bool(void)> track_operation = []() { return true; }; 0189 std::function<bool(void)> track_reverse = []() { return true; }; 0190 int outPoint = out; 0191 int inPoint = in; 0192 int offset = 0; 0193 int trackDuration = 0; 0194 if (m_endlessResize) { 0195 offset = inPoint; 0196 outPoint = out - in; 0197 inPoint = 0; 0198 } 0199 bool closing = false; 0200 // Ensure producer is long enough 0201 if (m_endlessResize && outPoint > m_producer->parent().get_length()) { 0202 m_producer->parent().set("length", outPoint + 1); 0203 m_producer->parent().set("out", outPoint); 0204 m_producer->set("length", outPoint + 1); 0205 } 0206 if (m_currentTrackId != -1) { 0207 if (auto ptr = m_parent.lock()) { 0208 if (ptr->getTrackById(m_currentTrackId)->isLocked()) { 0209 return false; 0210 } 0211 closing = ptr->m_closing; 0212 if (right && ptr->getTrackById_const(m_currentTrackId)->isLastClip(getPosition())) { 0213 trackDuration = ptr->getTrackById_const(m_currentTrackId)->trackDuration(); 0214 } 0215 track_operation = ptr->getTrackById(m_currentTrackId)->requestClipResize_lambda(m_id, inPoint, outPoint, right, hasMix, logUndo); 0216 } else { 0217 qDebug() << "Error : Moving clip failed because parent timeline is not available anymore"; 0218 Q_ASSERT(false); 0219 } 0220 } 0221 QVector<int> roles{TimelineModel::DurationRole}; 0222 if (!right) { 0223 roles.push_back(TimelineModel::StartRole); 0224 roles.push_back(TimelineModel::InPointRole); 0225 } else { 0226 roles.push_back(TimelineModel::OutPointRole); 0227 } 0228 Fun operation = [this, inPoint, outPoint, roles, logUndo, track_operation]() { 0229 if (track_operation()) { 0230 setInOut(inPoint, outPoint); 0231 if (logUndo && !m_endlessResize) { 0232 Q_EMIT pCore->clipInstanceResized(m_binClipId); 0233 } 0234 return true; 0235 } 0236 return false; 0237 }; 0238 Fun postProcess = [this, roles, oldIn, oldOut, right, logUndo]() { 0239 if (m_currentTrackId > -1) { 0240 if (auto ptr = m_parent.lock()) { 0241 QModelIndex ix = ptr->makeClipIndexFromID(m_id); 0242 ptr->notifyChange(ix, ix, roles); 0243 // invalidate timeline preview 0244 if (logUndo && !ptr->getTrackById_const(m_currentTrackId)->isAudioTrack()) { 0245 if (right) { 0246 int newOut = m_position + getOut() - getIn(); 0247 if (oldOut < newOut) { 0248 Q_EMIT ptr->invalidateZone(oldOut, newOut); 0249 } else { 0250 Q_EMIT ptr->invalidateZone(newOut, oldOut); 0251 } 0252 } else { 0253 if (oldIn < m_position) { 0254 Q_EMIT ptr->invalidateZone(oldIn, m_position); 0255 } else { 0256 Q_EMIT ptr->invalidateZone(m_position, oldIn); 0257 } 0258 } 0259 } 0260 } 0261 } 0262 return true; 0263 }; 0264 if (operation()) { 0265 // Now, we are in the state in which the timeline should be when we try to revert current action. So we can build the reverse action from here 0266 if (m_currentTrackId != -1) { 0267 if (auto ptr = m_parent.lock()) { 0268 if (trackDuration > 0 && !closing) { 0269 // Operation changed parent track duration, update effect stack 0270 int newDuration = ptr->getTrackById_const(m_currentTrackId)->trackDuration(); 0271 if (logUndo || trackDuration != newDuration) { 0272 // A clip move changed the track duration, update track effects 0273 ptr->getTrackById(m_currentTrackId)->m_effectStack->adjustStackLength(true, 0, trackDuration, 0, newDuration, 0, undo, redo, logUndo); 0274 } 0275 } 0276 track_reverse = ptr->getTrackById(m_currentTrackId)->requestClipResize_lambda(m_id, old_in, old_out, right, hasMix, logUndo); 0277 } 0278 } 0279 Fun reverse = [this, old_in, old_out, track_reverse, logUndo, roles]() { 0280 if (track_reverse()) { 0281 setInOut(old_in, old_out); 0282 if (logUndo && !m_endlessResize) { 0283 Q_EMIT pCore->clipInstanceResized(m_binClipId); 0284 } 0285 return true; 0286 } 0287 qDebug() << "============\n+++++++++++++++++\nREVRSE TRACK OP FAILED FOR: " << m_id << "\n\n++++++++++++++++"; 0288 return false; 0289 }; 0290 Fun preProcess = [this, roles, oldIn, oldOut, newIn = m_position, newOut = m_position + getOut() - getIn(), right, logUndo]() { 0291 if (m_currentTrackId > -1) { 0292 if (auto ptr = m_parent.lock()) { 0293 QModelIndex ix = ptr->makeClipIndexFromID(m_id); 0294 ptr->notifyChange(ix, ix, roles); 0295 // invalidate timeline preview 0296 if (logUndo && !ptr->getTrackById_const(m_currentTrackId)->isAudioTrack()) { 0297 if (right) { 0298 if (oldOut < newOut) { 0299 Q_EMIT ptr->invalidateZone(oldOut, newOut); 0300 } else { 0301 Q_EMIT ptr->invalidateZone(newOut, oldOut); 0302 } 0303 } else { 0304 if (oldIn < newIn) { 0305 Q_EMIT ptr->invalidateZone(oldIn, newIn); 0306 } else { 0307 Q_EMIT ptr->invalidateZone(newIn, oldIn); 0308 } 0309 } 0310 } 0311 } 0312 } 0313 return true; 0314 }; 0315 if (logUndo) { 0316 qDebug() << "----------\n-----------\n// ADJUSTING EFFECT LENGTH, LOGUNDO " << logUndo << ", " << old_in << "/" << inPoint << "-" << outPoint 0317 << ", " << m_producer->get_playtime(); 0318 } 0319 0320 if (!closing && logUndo) { 0321 if (hasTimeRemap()) { 0322 // Add undo /redo ops to resize keyframes 0323 requestRemapResize(in, out, old_in, old_out, reverse, operation); 0324 } 0325 adjustEffectLength(right, old_in, inPoint, old_out - old_in, m_producer->get_playtime(), offset, reverse, operation, logUndo); 0326 } 0327 postProcess(); 0328 PUSH_LAMBDA(postProcess, operation); 0329 PUSH_LAMBDA(preProcess, reverse); 0330 UPDATE_UNDO_REDO(operation, reverse, undo, redo); 0331 return true; 0332 } 0333 return false; 0334 } 0335 0336 bool ClipModel::requestSlip(int offset, Fun &undo, Fun &redo, bool logUndo) 0337 { 0338 QWriteLocker locker(&m_lock); 0339 if (offset == 0 || m_endlessResize) { 0340 return true; 0341 } 0342 int in = m_producer->get_in(); 0343 int out = m_producer->get_out(); 0344 int old_in = in, old_out = out; 0345 offset = qBound(out - m_producer->get_length() + 1, offset, in); 0346 int inPoint = in - offset; 0347 int outPoint = out - offset; 0348 0349 Q_ASSERT(outPoint >= m_producer->get_playtime() - 1); 0350 Q_ASSERT(outPoint < m_producer->get_length()); 0351 Q_ASSERT(inPoint >= 0); 0352 Q_ASSERT(inPoint <= m_producer->get_length() - m_producer->get_playtime()); 0353 Q_ASSERT(inPoint < outPoint); 0354 Q_ASSERT(out - in == outPoint - inPoint); 0355 0356 if (m_currentTrackId != -1) { 0357 if (auto ptr = m_parent.lock()) { 0358 if (ptr->getTrackById(m_currentTrackId)->isLocked()) { 0359 return false; 0360 } 0361 } else { 0362 qDebug() << "Error : Slipping clip failed because parent timeline is not available anymore"; 0363 Q_ASSERT(false); 0364 } 0365 } 0366 QVector<int> roles{TimelineModel::StartRole, TimelineModel::InPointRole, TimelineModel::OutPointRole}; 0367 Fun operation = [this, inPoint, outPoint, roles, logUndo]() { 0368 setInOut(inPoint, outPoint); 0369 if (m_currentTrackId > -1) { 0370 if (auto ptr = m_parent.lock()) { 0371 QModelIndex ix = ptr->makeClipIndexFromID(m_id); 0372 ptr->notifyChange(ix, ix, roles); 0373 pCore->refreshProjectMonitorOnce(); 0374 // invalidate timeline preview 0375 if (logUndo && !ptr->getTrackById_const(m_currentTrackId)->isAudioTrack()) { 0376 Q_EMIT ptr->invalidateZone(m_position, m_position + getPlaytime()); 0377 } 0378 } 0379 } 0380 return true; 0381 }; 0382 0383 qDebug() << "=== SLIP CLIP" 0384 << "pos" << m_position << "offset" << offset << "old_in" << old_in << "old_out" << old_out << "inPoint" << inPoint << "outPoint" << outPoint 0385 << "endless" << m_endlessResize << "playtime" << getPlaytime() << "fulllength" << m_producer->get_length(); 0386 ; 0387 0388 if (operation()) { 0389 Fun reverse = []() { return true; }; 0390 // Now, we are in the state in which the timeline should be when we try to revert current action. So we can build the reverse action from here 0391 reverse = [this, old_in, old_out, logUndo, roles]() { 0392 setInOut(old_in, old_out); 0393 if (m_currentTrackId > -1) { 0394 if (auto ptr = m_parent.lock()) { 0395 QModelIndex ix = ptr->makeClipIndexFromID(m_id); 0396 ptr->notifyChange(ix, ix, roles); 0397 pCore->refreshProjectMonitorOnce(); 0398 if (logUndo && !ptr->getTrackById_const(m_currentTrackId)->isAudioTrack()) { 0399 Q_EMIT ptr->invalidateZone(m_position, m_position + getPlaytime()); 0400 } 0401 } 0402 } 0403 return true; 0404 }; 0405 qDebug() << "----------\n-----------\n// ADJUSTING EFFECT LENGTH, LOGUNDO " << logUndo << ", " << old_in << "/" << inPoint << ", " 0406 << m_producer->get_playtime(); 0407 0408 adjustEffectLength(true, old_in, inPoint, old_out - old_in, m_producer->get_playtime(), offset, reverse, operation, logUndo); 0409 UPDATE_UNDO_REDO(operation, reverse, undo, redo); 0410 return true; 0411 } 0412 return false; 0413 } 0414 const QString ClipModel::getProperty(const QString &name) const 0415 { 0416 READ_LOCK(); 0417 if (service()->parent().is_valid()) { 0418 return QString::fromUtf8(service()->parent().get(name.toUtf8().constData())); 0419 } 0420 return QString::fromUtf8(service()->get(name.toUtf8().constData())); 0421 } 0422 0423 int ClipModel::getIntProperty(const QString &name) const 0424 { 0425 READ_LOCK(); 0426 if (service()->parent().is_valid()) { 0427 return service()->parent().get_int(name.toUtf8().constData()); 0428 } 0429 return service()->get_int(name.toUtf8().constData()); 0430 } 0431 0432 QSize ClipModel::getFrameSize() const 0433 { 0434 READ_LOCK(); 0435 std::shared_ptr<ProjectClip> binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId); 0436 if (binClip) { 0437 return binClip->getFrameSize(); 0438 } 0439 return QSize(); 0440 } 0441 0442 Mlt::Producer *ClipModel::service() const 0443 { 0444 READ_LOCK(); 0445 return m_producer.get(); 0446 } 0447 0448 bool ClipModel::isChain() const 0449 { 0450 READ_LOCK(); 0451 return m_producer->parent().type() == mlt_service_chain_type; 0452 } 0453 0454 bool ClipModel::hasTimeRemap() const 0455 { 0456 READ_LOCK(); 0457 if (m_producer->parent().type() == mlt_service_chain_type) { 0458 Mlt::Chain fromChain(m_producer->parent()); 0459 int count = fromChain.link_count(); 0460 for (int i = 0; i < count; i++) { 0461 QScopedPointer<Mlt::Link> fromLink(fromChain.link(i)); 0462 if (fromLink && fromLink->is_valid() && fromLink->property_exists("mlt_service")) { 0463 if (fromLink->get("mlt_service") == QLatin1String("timeremap")) { 0464 return true; 0465 } 0466 } 0467 } 0468 } 0469 return false; 0470 } 0471 0472 void ClipModel::requestRemapResize(int inPoint, int outPoint, int oldIn, int oldOut, Fun &undo, Fun &redo) 0473 { 0474 Mlt::Chain fromChain(m_producer->parent()); 0475 int count = fromChain.link_count(); 0476 for (int ix = 0; ix < count; ix++) { 0477 QScopedPointer<Mlt::Link> fromLink(fromChain.link(ix)); 0478 if (fromLink && fromLink->is_valid() && fromLink->get("mlt_service")) { 0479 if (fromLink->get("mlt_service") == QLatin1String("timeremap")) { 0480 // Found a timeremap effect, read params 0481 std::shared_ptr<Mlt::Link> link = std::make_shared<Mlt::Link>(fromChain.link(ix)->get_link()); 0482 if (!link->property_exists("time_map")) { 0483 link->set("time_map", fromLink->get("map")); 0484 } 0485 (void)link->anim_get_rect("time_map", 0); 0486 Mlt::Animation anim = link->get_animation("time_map"); 0487 QString oldKfrData = anim.serialize_cut(mlt_time_clock, 0, m_producer->get_length()); 0488 QStringList str = oldKfrData.split(QLatin1Char(';')); 0489 QMap<int, int> keyframes; 0490 for (auto &s : str) { 0491 int pos = m_producer->time_to_frames(s.section(QLatin1Char('='), 0, 0).toUtf8().constData()); 0492 int val = GenTime(s.section(QLatin1Char('='), 1).toDouble()).frames(pCore->getCurrentFps()); 0493 if (s == str.constLast()) { 0494 // HACK: we always set last keyframe 1 frame after in MLT to ensure we have a correct last frame 0495 pos--; 0496 } 0497 keyframes.insert(pos, val); 0498 } 0499 if (keyframes.contains(inPoint) && keyframes.lastKey() == outPoint) { 0500 // Nothing to do, abort 0501 return; 0502 } 0503 // Adjust start keyframes 0504 QList<int> toDelete; 0505 QMap<int, int> toAdd; 0506 if (inPoint != oldIn && !keyframes.contains(inPoint)) { 0507 if (inPoint < oldIn) { 0508 // Move oldIn keyframe to new in 0509 if (keyframes.contains(oldIn)) { 0510 int delta = oldIn - inPoint; 0511 toAdd.insert(inPoint, qMax(0, keyframes.value(oldIn) - delta)); 0512 toDelete << oldIn; 0513 } else { 0514 // Move first keyframe available 0515 bool found = false; 0516 QMapIterator<int, int> i(keyframes); 0517 while (i.hasNext()) { 0518 i.next(); 0519 if (i.key() > oldIn) { 0520 int delta = i.key() - inPoint; 0521 toAdd.insert(inPoint, qMax(0, i.value() - delta)); 0522 toDelete << i.key(); 0523 found = true; 0524 break; 0525 } 0526 } 0527 if (!found) { 0528 // Add standard keyframe 0529 toAdd.insert(inPoint, inPoint); 0530 } 0531 } 0532 } else if (outPoint != oldOut && !keyframes.contains(outPoint)) { 0533 // inpoint moved forwards, delete previous 0534 if (keyframes.contains(oldIn)) { 0535 int delta = inPoint - oldIn; 0536 toAdd.insert(inPoint, qMax(0, keyframes.value(oldIn) + delta)); 0537 } else { 0538 toAdd.insert(inPoint, inPoint); 0539 } 0540 // Remove all keyframes before 0541 QMapIterator<int, int> i(keyframes); 0542 while (i.hasNext()) { 0543 i.next(); 0544 if (i.key() == 0) { 0545 // Don't remove 0 keyframe 0546 continue; 0547 } 0548 if (i.key() < inPoint) { 0549 toDelete << i.key(); 0550 } else { 0551 break; 0552 } 0553 } 0554 } 0555 } 0556 if (outPoint != oldOut) { 0557 if (outPoint > oldOut) { 0558 if (keyframes.contains(oldOut)) { 0559 int delta = outPoint - oldOut; 0560 toAdd.insert(outPoint, keyframes.value(oldOut) + delta); 0561 toDelete << oldOut; 0562 } else { 0563 // Add defaut keyframe 0564 toAdd.insert(outPoint, outPoint); 0565 } 0566 } else { 0567 // Clip reduced 0568 if (keyframes.contains(oldOut)) { 0569 int delta = oldOut - outPoint; 0570 toAdd.insert(outPoint, keyframes.value(oldOut) - delta); 0571 } else { 0572 // Add defaut keyframe 0573 toAdd.insert(outPoint, outPoint); 0574 } 0575 // Delete all keyframes after outpoint 0576 QMapIterator<int, int> i(keyframes); 0577 while (i.hasNext()) { 0578 i.next(); 0579 if (i.key() > outPoint) { 0580 toDelete << i.key(); 0581 } 0582 } 0583 } 0584 } 0585 // Remove all requested keyframes 0586 for (int d : qAsConst(toDelete)) { 0587 keyframes.remove(d); 0588 } 0589 // Add replacement keyframes 0590 QMapIterator<int, int> i(toAdd); 0591 while (i.hasNext()) { 0592 i.next(); 0593 keyframes.insert(i.key(), i.value()); 0594 } 0595 QStringList result; 0596 QMapIterator<int, int> j(keyframes); 0597 int offset = 0; 0598 while (j.hasNext()) { 0599 j.next(); 0600 if (j.key() == keyframes.lastKey()) { 0601 // HACK: we always set last keyframe 1 frame after in MLT to ensure we have a correct last frame 0602 offset = 1; 0603 } 0604 result << QString("%1=%2") 0605 .arg(m_producer->frames_to_time(j.key() + offset, mlt_time_clock)) 0606 .arg(GenTime(j.value(), pCore->getCurrentFps()).seconds()); 0607 } 0608 Fun operation = [this, kfrData = result.join(QLatin1Char(';'))]() { 0609 setRemapValue("time_map", kfrData.toUtf8().constData()); 0610 if (auto ptr = m_parent.lock()) { 0611 QModelIndex ix = ptr->makeClipIndexFromID(m_id); 0612 ptr->notifyChange(ix, ix, TimelineModel::FinalMoveRole); 0613 } 0614 return true; 0615 }; 0616 Fun reverse = [this, oldKfrData]() { 0617 setRemapValue("time_map", oldKfrData.toUtf8().constData()); 0618 if (auto ptr = m_parent.lock()) { 0619 QModelIndex ix = ptr->makeClipIndexFromID(m_id); 0620 ptr->notifyChange(ix, ix, TimelineModel::FinalMoveRole); 0621 } 0622 return true; 0623 }; 0624 operation(); 0625 PUSH_LAMBDA(operation, redo); 0626 PUSH_FRONT_LAMBDA(reverse, undo); 0627 } 0628 } 0629 } 0630 } 0631 0632 int ClipModel::getRemapInputDuration() const 0633 { 0634 Mlt::Chain fromChain(m_producer->parent()); 0635 int count = fromChain.link_count(); 0636 for (int i = 0; i < count; i++) { 0637 QScopedPointer<Mlt::Link> fromLink(fromChain.link(i)); 0638 if (fromLink && fromLink->is_valid() && fromLink->get("mlt_service")) { 0639 if (fromLink->get("mlt_service") == QLatin1String("timeremap")) { 0640 // Found a timeremap effect, read params 0641 std::shared_ptr<Mlt::Link> link = std::make_shared<Mlt::Link>(fromChain.link(i)->get_link()); 0642 if (!link->property_exists("time_map")) { 0643 link->set("time_map", fromLink->get("map")); 0644 } 0645 QString mapData = link->get("time_map"); 0646 int min = GenTime(link->anim_get_double("time_map", getIn())).frames(pCore->getCurrentFps()); 0647 QStringList str = mapData.split(QLatin1Char(';')); 0648 int max = -1; 0649 for (auto &s : str) { 0650 int val = GenTime(s.section(QLatin1Char('='), 1).toDouble()).frames(pCore->getCurrentFps()); 0651 if (val > max) { 0652 max = val; 0653 } 0654 } 0655 return max - min; 0656 } 0657 } 0658 } 0659 return 0; 0660 } 0661 0662 void ClipModel::setRemapValue(const QString &name, const QString &value) 0663 { 0664 if (m_producer->parent().type() != mlt_service_chain_type) { 0665 return; 0666 } 0667 Mlt::Chain fromChain(m_producer->parent()); 0668 int count = fromChain.link_count(); 0669 for (int i = 0; i < count; i++) { 0670 QScopedPointer<Mlt::Link> fromLink(fromChain.link(i)); 0671 if (fromLink && fromLink->is_valid() && fromLink->get("mlt_service")) { 0672 if (fromLink->get("mlt_service") == QLatin1String("timeremap")) { 0673 // Found a timeremap effect, read params 0674 std::shared_ptr<Mlt::Link> link = std::make_shared<Mlt::Link>(fromChain.link(i)->get_link()); 0675 link->set(name.toUtf8().constData(), value.toUtf8().constData()); 0676 return; 0677 } 0678 } 0679 } 0680 } 0681 0682 QMap<QString, QString> ClipModel::getRemapValues() const 0683 { 0684 QMap<QString, QString> result; 0685 if (m_producer->parent().type() != mlt_service_chain_type) { 0686 return result; 0687 } 0688 Mlt::Chain fromChain(m_producer->parent()); 0689 int count = fromChain.link_count(); 0690 for (int i = 0; i < count; i++) { 0691 QScopedPointer<Mlt::Link> fromLink(fromChain.link(i)); 0692 if (fromLink && fromLink->is_valid() && fromLink->get("mlt_service")) { 0693 if (fromLink->get("mlt_service") == QLatin1String("timeremap")) { 0694 // Found a timeremap effect, read params 0695 std::shared_ptr<Mlt::Link> link = std::make_shared<Mlt::Link>(fromChain.link(i)->get_link()); 0696 // Ensure animation uses time not frames 0697 if (!link->property_exists("time_map")) { 0698 link->set("time_map", link->get("map")); 0699 } 0700 (void)link->anim_get_double("time_map", 0); 0701 Mlt::Animation anim = link->get_animation("time_map"); 0702 result.insert(QStringLiteral("time_map"), anim.serialize_cut(mlt_time_clock, 0, m_producer->get_length())); 0703 result.insert(QStringLiteral("pitch"), link->get("pitch")); 0704 result.insert(QStringLiteral("image_mode"), link->get("image_mode")); 0705 break; 0706 } 0707 } 0708 } 0709 return result; 0710 } 0711 0712 std::shared_ptr<Mlt::Producer> ClipModel::getProducer() 0713 { 0714 READ_LOCK(); 0715 return m_producer; 0716 } 0717 0718 int ClipModel::getPlaytime() const 0719 { 0720 READ_LOCK(); 0721 return m_producer->get_playtime(); 0722 } 0723 0724 void ClipModel::setTimelineEffectsEnabled(bool enabled) 0725 { 0726 QWriteLocker locker(&m_lock); 0727 m_effectStack->setEffectStackEnabled(enabled); 0728 } 0729 0730 bool ClipModel::addEffect(const QString &effectId) 0731 { 0732 QWriteLocker locker(&m_lock); 0733 if (EffectsRepository::get()->isAudioEffect(effectId)) { 0734 if (m_currentState == PlaylistState::VideoOnly) { 0735 return false; 0736 } 0737 } else if (m_currentState == PlaylistState::AudioOnly) { 0738 return false; 0739 } 0740 if (EffectsRepository::get()->isTextEffect(effectId) && m_clipType != ClipType::Text) { 0741 return false; 0742 } 0743 m_effectStack->appendEffect(effectId, true); 0744 return true; 0745 } 0746 0747 bool ClipModel::addEffectWithUndo(const QString &effectId, Fun &undo, Fun &redo) 0748 { 0749 QWriteLocker locker(&m_lock); 0750 if (EffectsRepository::get()->isAudioEffect(effectId)) { 0751 if (m_currentState == PlaylistState::VideoOnly) { 0752 return false; 0753 } 0754 } else if (m_currentState == PlaylistState::AudioOnly) { 0755 return false; 0756 } 0757 if (EffectsRepository::get()->isTextEffect(effectId) && m_clipType != ClipType::Text) { 0758 return false; 0759 } 0760 return m_effectStack->appendEffectWithUndo(effectId, undo, redo); 0761 } 0762 0763 bool ClipModel::copyEffect(const QUuid &uuid, const std::shared_ptr<EffectStackModel> &stackModel, int rowId) 0764 { 0765 QWriteLocker locker(&m_lock); 0766 QDomDocument doc; 0767 m_effectStack->copyXmlEffect(stackModel->rowToXml(uuid, rowId, doc)); 0768 return true; 0769 } 0770 0771 bool ClipModel::copyEffectWithUndo(const QUuid &uuid, const std::shared_ptr<EffectStackModel> &stackModel, int rowId, Fun &undo, Fun &redo) 0772 { 0773 QWriteLocker locker(&m_lock); 0774 QDomDocument doc; 0775 m_effectStack->copyXmlEffectWithUndo(stackModel->rowToXml(uuid, rowId, doc), undo, redo); 0776 return true; 0777 } 0778 0779 bool ClipModel::importEffects(std::shared_ptr<EffectStackModel> stackModel) 0780 { 0781 QWriteLocker locker(&m_lock); 0782 m_effectStack->importEffects(std::move(stackModel), m_currentState); 0783 return true; 0784 } 0785 0786 bool ClipModel::importEffects(std::weak_ptr<Mlt::Service> service) 0787 { 0788 QWriteLocker locker(&m_lock); 0789 m_effectStack->importEffects(std::move(service), m_currentState); 0790 return true; 0791 } 0792 0793 bool ClipModel::removeFade(bool fromStart) 0794 { 0795 QWriteLocker locker(&m_lock); 0796 m_effectStack->removeFade(fromStart); 0797 return true; 0798 } 0799 0800 bool ClipModel::adjustEffectLength(bool adjustFromEnd, int oldIn, int newIn, int oldDuration, int duration, int offset, Fun &undo, Fun &redo, bool logUndo) 0801 { 0802 QWriteLocker locker(&m_lock); 0803 return m_effectStack->adjustStackLength(adjustFromEnd, oldIn, oldDuration, newIn, duration, offset, undo, redo, logUndo); 0804 } 0805 0806 bool ClipModel::adjustEffectLength(const QString &effectName, int duration, int originalDuration, Fun &undo, Fun &redo) 0807 { 0808 QWriteLocker locker(&m_lock); 0809 Fun operation = [this, duration, effectName, originalDuration]() { 0810 return m_effectStack->adjustFadeLength(duration, effectName.startsWith(QLatin1String("fadein")) || effectName.startsWith(QLatin1String("fade_to_")), 0811 audioEnabled(), !isAudioOnly(), originalDuration > 0); 0812 }; 0813 if (operation() && originalDuration > 0) { 0814 Fun reverse = [this, originalDuration, effectName]() { 0815 return m_effectStack->adjustFadeLength(originalDuration, 0816 effectName.startsWith(QLatin1String("fadein")) || effectName.startsWith(QLatin1String("fade_to_")), 0817 audioEnabled(), !isAudioOnly(), true); 0818 }; 0819 UPDATE_UNDO_REDO(operation, reverse, undo, redo); 0820 } 0821 return true; 0822 } 0823 0824 bool ClipModel::audioEnabled() const 0825 { 0826 READ_LOCK(); 0827 return stateToBool(m_currentState).second; 0828 } 0829 0830 bool ClipModel::isAudioOnly() const 0831 { 0832 READ_LOCK(); 0833 return m_currentState == PlaylistState::AudioOnly; 0834 } 0835 0836 void ClipModel::refreshProducerFromBin(int trackId, PlaylistState::ClipState state, int stream, double speed, bool hasPitch, bool secondPlaylist, 0837 bool timeremap) 0838 { 0839 // We require that the producer is not in the track when we refresh the producer, because otherwise the modification will not be propagated. Remove the clip 0840 // first, refresh, and then replant. 0841 QWriteLocker locker(&m_lock); 0842 int in = getIn(); 0843 int out = getOut(); 0844 if (!qFuzzyCompare(speed, m_speed) && !qFuzzyIsNull(speed)) { 0845 in = int(in * std::abs(m_speed / speed)); 0846 out = in + getPlaytime() - 1; 0847 // prevent going out of the clip's range 0848 out = std::min(out, int(double(m_producer->get_length()) * std::abs(m_speed / speed)) - 1); 0849 m_speed = speed; 0850 qDebug() << "changing speed" << in << out << m_speed; 0851 } 0852 QString remapMap; 0853 int remapPitch = 0; 0854 QString remapBlend; 0855 if (m_hasTimeRemap) { 0856 if (m_producer->parent().type() == mlt_service_chain_type) { 0857 Mlt::Chain fromChain(m_producer->parent()); 0858 int count = fromChain.link_count(); 0859 for (int i = 0; i < count; i++) { 0860 QScopedPointer<Mlt::Link> fromLink(fromChain.link(i)); 0861 if (fromLink && fromLink->is_valid() && fromLink->get("mlt_service")) { 0862 if (fromLink->get("mlt_service") == QLatin1String("timeremap")) { 0863 // Found a timeremap effect, read params 0864 if (!fromLink->property_exists("time_map")) { 0865 fromLink->set("time_map", fromLink->get("map")); 0866 } 0867 remapMap = fromLink->get("time_map"); 0868 remapPitch = fromLink->get_int("pitch"); 0869 remapBlend = fromLink->get("image_mode"); 0870 break; 0871 } 0872 } 0873 } 0874 } else { 0875 qDebug() << "=== NON CHAIN ON REFRESH!!!"; 0876 } 0877 } 0878 ProjectClip::TimeWarpInfo remapInfo; 0879 remapInfo.enableRemap = timeremap; 0880 if (timeremap) { 0881 remapInfo.timeMapData = remapMap; 0882 remapInfo.pitchShift = remapPitch; 0883 remapInfo.imageMode = remapBlend; 0884 } 0885 0886 std::shared_ptr<ProjectClip> binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId); 0887 std::shared_ptr<Mlt::Producer> binProducer = binClip->getTimelineProducer(trackId, m_id, state, stream, m_speed, secondPlaylist, remapInfo); 0888 m_producer = std::move(binProducer); 0889 m_producer->set_in_and_out(in, out); 0890 if (m_hasTimeRemap != hasTimeRemap()) { 0891 m_hasTimeRemap = !m_hasTimeRemap; 0892 // producer is not on a track, no data refresh needed 0893 } 0894 if (m_hasTimeRemap) { 0895 // Restore timeremap parameters 0896 if (m_producer->parent().type() == mlt_service_chain_type) { 0897 Mlt::Chain fromChain(m_producer->parent()); 0898 int count = fromChain.link_count(); 0899 for (int i = 0; i < count; i++) { 0900 QScopedPointer<Mlt::Link> fromLink(fromChain.link(i)); 0901 if (fromLink && fromLink->is_valid() && fromLink->property_exists("mlt_service")) { 0902 if (fromLink->get("mlt_service") == QLatin1String("timeremap")) { 0903 // Found a timeremap effect, read params 0904 fromLink->set("time_map", remapMap.toUtf8().constData()); 0905 fromLink->set("pitch", remapPitch); 0906 fromLink->set("image_mode", remapBlend.toUtf8().constData()); 0907 break; 0908 } 0909 } 0910 } 0911 } 0912 } 0913 if (hasPitch) { 0914 // Check if pitch shift is enabled 0915 m_producer->parent().set("warp_pitch", 1); 0916 } else if (!qFuzzyCompare(m_speed, 1.)) { 0917 m_producer->parent().set("warp_pitch", 0); 0918 } 0919 // replant effect stack in updated service 0920 int activeEffect = m_effectStack->getActiveEffect(); 0921 m_effectStack->resetService(m_producer); 0922 m_producer->set("kdenlive:id", binClip->clipId().toUtf8().constData()); 0923 m_producer->set("_kdenlive_cid", m_id); 0924 if (activeEffect > 0) { 0925 m_producer->set("kdenlive:activeeffect", activeEffect); 0926 } 0927 m_endlessResize = !binClip->hasLimitedDuration(); 0928 } 0929 0930 void ClipModel::refreshProducerFromBin(int trackId) 0931 { 0932 if (trackId == -1) { 0933 trackId = m_currentTrackId; 0934 } 0935 bool hasPitch = false; 0936 if (!qFuzzyCompare(getSpeed(), 1.)) { 0937 hasPitch = m_producer->parent().get_int("warp_pitch") == 1; 0938 } 0939 int stream = m_producer->parent().get_int("audio_index"); 0940 refreshProducerFromBin(trackId, m_currentState, stream, 0, hasPitch, m_subPlaylistIndex == 1, hasTimeRemap()); 0941 } 0942 0943 bool ClipModel::useTimeRemapProducer(bool enable, Fun &undo, Fun &redo) 0944 { 0945 if (m_endlessResize) { 0946 // no timewarp for endless producers 0947 return false; 0948 } 0949 std::function<bool(void)> local_undo = []() { return true; }; 0950 std::function<bool(void)> local_redo = []() { return true; }; 0951 int audioStream = getIntProperty(QStringLiteral("audio_index")); 0952 QMap<QString, QString> remapProperties; 0953 remapProperties.insert(QStringLiteral("image_mode"), QStringLiteral("nearest")); 0954 if (!enable) { 0955 // Store the remap properties 0956 if (m_producer->parent().type() == mlt_service_chain_type) { 0957 Mlt::Chain fromChain(m_producer->parent()); 0958 int count = fromChain.link_count(); 0959 for (int i = 0; i < count; i++) { 0960 QScopedPointer<Mlt::Link> fromLink(fromChain.link(i)); 0961 if (fromLink && fromLink->is_valid() && fromLink->get("mlt_service")) { 0962 if (fromLink->get("mlt_service") == QLatin1String("timeremap")) { 0963 // Found a timeremap effect, read params 0964 remapProperties.insert(QStringLiteral("time_map"), fromLink->get("time_map")); 0965 remapProperties.insert(QStringLiteral("pitch"), fromLink->get("pitch")); 0966 remapProperties.insert(QStringLiteral("image_mode"), fromLink->get("image_mode")); 0967 break; 0968 } 0969 } 0970 } 0971 } else { 0972 qDebug() << "=== NON CHAIN ON REFRESH!!!"; 0973 } 0974 } 0975 auto operation = useTimeRemapProducer_lambda(enable, audioStream, remapProperties); 0976 auto reverse = useTimeRemapProducer_lambda(!enable, audioStream, remapProperties); 0977 if (operation()) { 0978 UPDATE_UNDO_REDO(operation, reverse, local_undo, local_redo); 0979 UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo); 0980 return true; 0981 } 0982 return false; 0983 } 0984 0985 Fun ClipModel::useTimeRemapProducer_lambda(bool enable, int audioStream, const QMap<QString, QString> &remapProperties) 0986 { 0987 QWriteLocker locker(&m_lock); 0988 return [enable, audioStream, remapProperties, this]() { 0989 refreshProducerFromBin(m_currentTrackId, m_currentState, audioStream, 0, false, false, enable); 0990 if (enable) { 0991 QMapIterator<QString, QString> j(remapProperties); 0992 if (m_producer->parent().type() == mlt_service_chain_type) { 0993 Mlt::Chain fromChain(m_producer->parent()); 0994 int count = fromChain.link_count(); 0995 for (int i = 0; i < count; i++) { 0996 QScopedPointer<Mlt::Link> fromLink(fromChain.link(i)); 0997 if (fromLink && fromLink->is_valid() && fromLink->get("mlt_service")) { 0998 if (fromLink->get("mlt_service") == QLatin1String("timeremap")) { 0999 while (j.hasNext()) { 1000 j.next(); 1001 fromLink->set(j.key().toUtf8().constData(), j.value().toUtf8().constData()); 1002 } 1003 break; 1004 } 1005 } 1006 } 1007 } 1008 } 1009 return true; 1010 }; 1011 } 1012 1013 bool ClipModel::useTimewarpProducer(double speed, bool pitchCompensate, bool changeDuration, Fun &undo, Fun &redo) 1014 { 1015 if (m_endlessResize) { 1016 // no timewarp for endless producers 1017 return false; 1018 } 1019 std::function<bool(void)> local_undo = []() { return true; }; 1020 std::function<bool(void)> local_redo = []() { return true; }; 1021 double previousSpeed = getSpeed(); 1022 int oldDuration = getPlaytime(); 1023 int newDuration = qRound(oldDuration * std::fabs(m_speed / speed)); 1024 int oldOut = getOut(); 1025 int oldIn = getIn(); 1026 bool revertSpeed = false; 1027 if (speed < 0) { 1028 if (previousSpeed > 0) { 1029 revertSpeed = true; 1030 } 1031 } else if (previousSpeed < 0) { 1032 revertSpeed = true; 1033 } 1034 bool hasPitch = getIntProperty(QStringLiteral("warp_pitch")); 1035 int audioStream = getIntProperty(QStringLiteral("audio_index")); 1036 auto operation = useTimewarpProducer_lambda(speed, audioStream, pitchCompensate); 1037 auto reverse = useTimewarpProducer_lambda(previousSpeed, audioStream, hasPitch); 1038 if (revertSpeed || (changeDuration && oldOut >= newDuration)) { 1039 // in that case, we are going to shrink the clip when changing the producer. We must undo that when reloading the old producer 1040 reverse = [reverse, oldIn, oldOut, this]() { 1041 bool res = reverse(); 1042 if (res) { 1043 setInOut(oldIn, oldOut); 1044 } 1045 return res; 1046 }; 1047 } 1048 if (revertSpeed) { 1049 int out = getOut() + 1; 1050 int in = qMax(0, qRound((m_producer->get_length() - 1 - out) * std::fabs(m_speed / speed))); 1051 out = in + newDuration; 1052 operation = [operation, in, out, this]() { 1053 bool res = operation(); 1054 if (res) { 1055 setInOut(in, out); 1056 } else { 1057 } 1058 return res; 1059 }; 1060 } 1061 if (operation()) { 1062 UPDATE_UNDO_REDO(operation, reverse, local_undo, local_redo); 1063 // When calculating duration, result can be a few frames longer than possible duration so adjust 1064 if (changeDuration) { 1065 int requestedDuration = qMin(newDuration, getMaxDuration() - getIn()); 1066 if (requestedDuration != getPlaytime()) { 1067 bool res = requestResize(requestedDuration, true, local_undo, local_redo, true); 1068 if (!res) { 1069 qDebug() << "==== CLIP WARP UPDATE DURATION FAILED!!!!"; 1070 local_undo(); 1071 return false; 1072 } 1073 } 1074 } 1075 adjustEffectLength(false, oldIn, getIn(), oldOut - oldIn, m_producer->get_playtime(), 0, local_undo, local_redo, true); 1076 UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo); 1077 return true; 1078 } 1079 qDebug() << "tw: operation fail"; 1080 return false; 1081 } 1082 1083 Fun ClipModel::useTimewarpProducer_lambda(double speed, int stream, bool pitchCompensate) 1084 { 1085 QWriteLocker locker(&m_lock); 1086 return [speed, stream, pitchCompensate, this]() { 1087 qDebug() << "timeWarp producer" << speed; 1088 refreshProducerFromBin(m_currentTrackId, m_currentState, stream, speed, pitchCompensate); 1089 return true; 1090 }; 1091 } 1092 1093 const QString &ClipModel::binId() const 1094 { 1095 return m_binClipId; 1096 } 1097 1098 std::shared_ptr<MarkerListModel> ClipModel::getMarkerModel() const 1099 { 1100 READ_LOCK(); 1101 return pCore->projectItemModel()->getClipByBinID(m_binClipId)->getMarkerModel(); 1102 } 1103 1104 int ClipModel::audioChannels() const 1105 { 1106 READ_LOCK(); 1107 return pCore->projectItemModel()->getClipByBinID(m_binClipId)->audioChannels(); 1108 } 1109 1110 bool ClipModel::audioMultiStream() const 1111 { 1112 READ_LOCK(); 1113 return pCore->projectItemModel()->getClipByBinID(m_binClipId)->audioStreamsCount() > 1; 1114 } 1115 1116 int ClipModel::audioStream() const 1117 { 1118 return m_producer->parent().get_int("audio_index"); 1119 } 1120 1121 int ClipModel::audioStreamIndex() const 1122 { 1123 READ_LOCK(); 1124 return pCore->projectItemModel()->getClipByBinID(m_binClipId)->audioStreamIndex(m_producer->parent().get_int("audio_index")) + 1; 1125 } 1126 1127 int ClipModel::fadeIn() const 1128 { 1129 return m_effectStack->getFadePosition(true); 1130 } 1131 1132 int ClipModel::fadeOut() const 1133 { 1134 return m_effectStack->getFadePosition(false); 1135 } 1136 1137 double ClipModel::getSpeed() const 1138 { 1139 return m_speed; 1140 } 1141 1142 KeyframeModel *ClipModel::getKeyframeModel() 1143 { 1144 return m_effectStack->getEffectKeyframeModel(); 1145 } 1146 1147 bool ClipModel::showKeyframes() const 1148 { 1149 READ_LOCK(); 1150 return !service()->get_int("kdenlive:hide_keyframes"); 1151 } 1152 1153 void ClipModel::setShowKeyframes(bool show) 1154 { 1155 QWriteLocker locker(&m_lock); 1156 service()->set("kdenlive:hide_keyframes", !show); 1157 } 1158 1159 void ClipModel::setPosition(int pos) 1160 { 1161 MoveableItem::setPosition(pos); 1162 m_clipMarkerModel->updateSnapModelPos(pos); 1163 } 1164 1165 void ClipModel::setMixDuration(int mix, int cutOffset) 1166 { 1167 if (mix == 0) { 1168 // Deleting a mix 1169 m_mixCutPos = 0; 1170 } else { 1171 // Creating a new mix 1172 m_mixCutPos = cutOffset; 1173 } 1174 m_mixDuration = mix; 1175 if (m_mixCutPos > 0) { 1176 m_clipMarkerModel->updateSnapMixPosition(m_mixDuration - m_mixCutPos); 1177 } 1178 } 1179 1180 void ClipModel::setMixDuration(int mix) 1181 { 1182 m_mixDuration = mix; 1183 if (m_mixDuration == 0) { 1184 m_mixCutPos = 0; 1185 } 1186 m_clipMarkerModel->updateSnapMixPosition(m_mixDuration - m_mixCutPos); 1187 } 1188 1189 int ClipModel::getMixDuration() const 1190 { 1191 return m_mixDuration; 1192 } 1193 1194 int ClipModel::getMixCutPosition() const 1195 { 1196 return m_mixCutPos; 1197 } 1198 1199 void ClipModel::setInOut(int in, int out) 1200 { 1201 MoveableItem::setInOut(in, out); 1202 m_clipMarkerModel->updateSnapModelInOut({in, out, qMax(0, m_mixDuration - m_mixCutPos)}); 1203 } 1204 1205 void ClipModel::setCurrentTrackId(int tid, bool finalMove) 1206 { 1207 if (tid == m_currentTrackId) { 1208 return; 1209 } 1210 bool registerSnap = m_currentTrackId == -1 && tid > -1; 1211 1212 if (m_currentTrackId > -1 && tid == -1) { 1213 // Removing clip 1214 m_clipMarkerModel->deregisterSnapModel(); 1215 } 1216 MoveableItem::setCurrentTrackId(tid, finalMove); 1217 if (registerSnap) { 1218 if (auto ptr = m_parent.lock()) { 1219 m_clipMarkerModel->registerSnapModel(ptr->m_snaps, getPosition(), getIn(), getOut(), m_speed); 1220 } 1221 } 1222 1223 if (finalMove && m_lastTrackId != m_currentTrackId) { 1224 if (tid != -1) { 1225 refreshProducerFromBin(m_currentTrackId); 1226 } 1227 m_lastTrackId = m_currentTrackId; 1228 } 1229 } 1230 1231 Fun ClipModel::setClipState_lambda(PlaylistState::ClipState state) 1232 { 1233 QWriteLocker locker(&m_lock); 1234 return [this, state]() { 1235 if (auto ptr = m_parent.lock()) { 1236 m_currentState = state; 1237 // Enforce producer reload 1238 m_lastTrackId = -1; 1239 if (m_currentTrackId != -1 && ptr->isClip(m_id)) { // if this is false, the clip is being created. Don't update model in that case 1240 refreshProducerFromBin(m_currentTrackId); 1241 QModelIndex ix = ptr->makeClipIndexFromID(m_id); 1242 Q_EMIT ptr->dataChanged(ix, ix, {TimelineModel::StatusRole}); 1243 } 1244 return true; 1245 } 1246 return false; 1247 }; 1248 } 1249 1250 bool ClipModel::setClipState(PlaylistState::ClipState state, Fun &undo, Fun &redo) 1251 { 1252 if (state == PlaylistState::VideoOnly && !canBeVideo()) { 1253 return false; 1254 } 1255 if (state == PlaylistState::AudioOnly && !canBeAudio()) { 1256 return false; 1257 } 1258 if (state == m_currentState) { 1259 return true; 1260 } 1261 auto old_state = m_currentState; 1262 auto operation = setClipState_lambda(state); 1263 if (operation()) { 1264 auto reverse = setClipState_lambda(old_state); 1265 UPDATE_UNDO_REDO(operation, reverse, undo, redo); 1266 return true; 1267 } 1268 return false; 1269 } 1270 1271 PlaylistState::ClipState ClipModel::clipState() const 1272 { 1273 READ_LOCK(); 1274 return m_currentState; 1275 } 1276 1277 ClipType::ProducerType ClipModel::clipType() const 1278 { 1279 READ_LOCK(); 1280 return m_clipType; 1281 } 1282 1283 void ClipModel::passTimelineProperties(const std::shared_ptr<ClipModel> &other) 1284 { 1285 READ_LOCK(); 1286 Mlt::Properties source(m_producer->get_properties()); 1287 Mlt::Properties dest(other->service()->get_properties()); 1288 dest.pass_list(source, "kdenlive:hide_keyframes,kdenlive:activeeffect"); 1289 } 1290 1291 bool ClipModel::canBeVideo() const 1292 { 1293 return m_canBeVideo; 1294 } 1295 1296 bool ClipModel::canBeAudio() const 1297 { 1298 return m_canBeAudio; 1299 } 1300 1301 const QString ClipModel::effectNames() const 1302 { 1303 READ_LOCK(); 1304 return m_effectStack->effectNames(); 1305 } 1306 1307 bool ClipModel::stackEnabled() const 1308 { 1309 READ_LOCK(); 1310 return m_effectStack->isStackEnabled(); 1311 } 1312 1313 const QStringList ClipModel::externalFiles() const 1314 { 1315 READ_LOCK(); 1316 return m_effectStack->externalFiles(); 1317 } 1318 1319 int ClipModel::getFakeTrackId() const 1320 { 1321 return m_fakeTrack; 1322 } 1323 1324 void ClipModel::setFakeTrackId(int fid) 1325 { 1326 m_fakeTrack = fid; 1327 } 1328 1329 int ClipModel::getFakePosition() const 1330 { 1331 return m_fakePosition; 1332 } 1333 1334 void ClipModel::setFakePosition(int fpos) 1335 { 1336 m_fakePosition = fpos; 1337 } 1338 1339 QDomElement ClipModel::toXml(QDomDocument &document) 1340 { 1341 QDomElement container = document.createElement(QStringLiteral("clip")); 1342 container.setAttribute(QStringLiteral("binid"), m_binClipId); 1343 container.setAttribute(QStringLiteral("id"), m_id); 1344 container.setAttribute(QStringLiteral("in"), getIn()); 1345 container.setAttribute(QStringLiteral("out"), getOut()); 1346 container.setAttribute(QStringLiteral("position"), getPosition()); 1347 container.setAttribute(QStringLiteral("state"), m_currentState); 1348 container.setAttribute(QStringLiteral("playlist"), m_subPlaylistIndex); 1349 if (auto ptr = m_parent.lock()) { 1350 int trackId = ptr->getTrackPosition(m_currentTrackId); 1351 container.setAttribute(QStringLiteral("track"), trackId); 1352 if (ptr->isAudioTrack(getCurrentTrackId())) { 1353 container.setAttribute(QStringLiteral("audioTrack"), 1); 1354 int partner = ptr->getClipSplitPartner(m_id); 1355 if (partner != -1) { 1356 int mirrorId = ptr->getMirrorVideoTrackId(m_currentTrackId); 1357 if (mirrorId > -1) { 1358 mirrorId = ptr->getTrackPosition(mirrorId); 1359 } 1360 container.setAttribute(QStringLiteral("mirrorTrack"), mirrorId); 1361 } else { 1362 container.setAttribute(QStringLiteral("mirrorTrack"), QStringLiteral("-1")); 1363 } 1364 } 1365 } 1366 container.setAttribute(QStringLiteral("speed"), QString::number(m_speed, 'f')); 1367 container.setAttribute(QStringLiteral("audioStream"), getIntProperty(QStringLiteral("audio_index"))); 1368 if (!qFuzzyCompare(m_speed, 1.)) { 1369 container.setAttribute(QStringLiteral("warp_pitch"), getIntProperty(QStringLiteral("warp_pitch"))); 1370 } 1371 if (m_hasTimeRemap) { 1372 if (m_producer->parent().type() == mlt_service_chain_type) { 1373 Mlt::Chain fromChain(m_producer->parent()); 1374 int count = fromChain.link_count(); 1375 for (int i = 0; i < count; i++) { 1376 QScopedPointer<Mlt::Link> fromLink(fromChain.link(i)); 1377 if (fromLink && fromLink->is_valid() && fromLink->get("mlt_service")) { 1378 if (fromLink->get("mlt_service") == QLatin1String("timeremap")) { 1379 // Found a timeremap effect, read params 1380 container.setAttribute(QStringLiteral("timemap"), fromLink->get("time_map")); 1381 container.setAttribute(QStringLiteral("timepitch"), fromLink->get_int("pitch")); 1382 container.setAttribute(QStringLiteral("timeblend"), fromLink->get("image_mode")); 1383 break; 1384 } 1385 } 1386 } 1387 } else { 1388 qDebug() << "=== NON CHAIN ON REFRESH!!!"; 1389 } 1390 } 1391 container.appendChild(m_effectStack->toXml(document)); 1392 return container; 1393 } 1394 1395 bool ClipModel::checkConsistency() 1396 { 1397 if (!m_effectStack->checkConsistency()) { 1398 qDebug() << "Consistency check failed for effectstack"; 1399 return false; 1400 } 1401 if (m_currentTrackId == -1) { 1402 qDebug() << ":::: CLIP IS NOT INSERTED IN A TRACK"; 1403 return true; 1404 } 1405 std::shared_ptr<ProjectClip> binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId); 1406 const QUuid timelineUuid = getUuid(); 1407 auto instances = binClip->timelineInstances(timelineUuid); 1408 bool found = instances.contains(m_id); 1409 if (!found) { 1410 qDebug() << "ERROR: binClip doesn't acknowledge timeline clip existence: " << m_id << ", CURRENT TRACK: " << m_currentTrackId; 1411 return false; 1412 } 1413 1414 if (m_currentState == PlaylistState::VideoOnly && !m_canBeVideo) { 1415 qDebug() << "ERROR: clip is in video state but doesn't have video"; 1416 return false; 1417 } 1418 if (m_currentState == PlaylistState::AudioOnly && !m_canBeAudio) { 1419 qDebug() << "ERROR: clip is in video state but doesn't have video"; 1420 return false; 1421 } 1422 // TODO: check speed 1423 1424 return true; 1425 } 1426 1427 int ClipModel::getSubPlaylistIndex() const 1428 { 1429 return m_subPlaylistIndex; 1430 } 1431 1432 void ClipModel::setSubPlaylistIndex(int index, int trackId) 1433 { 1434 if (m_subPlaylistIndex == index) { 1435 return; 1436 } 1437 m_subPlaylistIndex = index; 1438 if (trackId > -1) { 1439 refreshProducerFromBin(trackId); 1440 } 1441 } 1442 1443 void ClipModel::setOffset(int offset) 1444 { 1445 m_positionOffset = offset; 1446 if (auto ptr = m_parent.lock()) { 1447 QModelIndex ix = ptr->makeClipIndexFromID(m_id); 1448 Q_EMIT ptr->dataChanged(ix, ix, {TimelineModel::PositionOffsetRole}); 1449 } 1450 } 1451 1452 void ClipModel::setGrab(bool grab) 1453 { 1454 QWriteLocker locker(&m_lock); 1455 if (grab == m_grabbed) { 1456 return; 1457 } 1458 m_grabbed = grab; 1459 if (auto ptr = m_parent.lock()) { 1460 QModelIndex ix = ptr->makeClipIndexFromID(m_id); 1461 Q_EMIT ptr->dataChanged(ix, ix, {TimelineModel::GrabbedRole}); 1462 } 1463 } 1464 1465 void ClipModel::setSelected(bool sel) 1466 { 1467 QWriteLocker locker(&m_lock); 1468 if (sel == selected) { 1469 return; 1470 } 1471 selected = sel; 1472 if (auto ptr = m_parent.lock()) { 1473 if (m_currentTrackId != -1) { 1474 QModelIndex ix = ptr->makeClipIndexFromID(m_id); 1475 Q_EMIT ptr->dataChanged(ix, ix, {TimelineModel::SelectedRole}); 1476 } 1477 } 1478 } 1479 1480 void ClipModel::clearOffset() 1481 { 1482 if (m_positionOffset != 0) { 1483 setOffset(0); 1484 } 1485 } 1486 1487 int ClipModel::getOffset() const 1488 { 1489 return m_positionOffset; 1490 } 1491 1492 int ClipModel::getMaxDuration() const 1493 { 1494 READ_LOCK(); 1495 if (m_endlessResize) { 1496 return -1; 1497 } 1498 return m_producer->get_length(); 1499 } 1500 1501 const QString ClipModel::clipName() const 1502 { 1503 return pCore->projectItemModel()->getClipByBinID(m_binClipId)->clipName(); 1504 } 1505 1506 const QString ClipModel::clipTag() const 1507 { 1508 if (KdenliveSettings::tagsintimeline()) { 1509 return pCore->projectItemModel()->getClipByBinID(m_binClipId)->tags(); 1510 } 1511 return QString(); 1512 } 1513 1514 FileStatus::ClipStatus ClipModel::clipStatus() const 1515 { 1516 std::shared_ptr<ProjectClip> binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId); 1517 return binClip->clipStatus(); 1518 } 1519 1520 QString ClipModel::clipHash() const 1521 { 1522 QDomDocument document; 1523 QDomElement container = document.createElement(QStringLiteral("clip")); 1524 container.setAttribute(QStringLiteral("service"), m_producer->parent().get("mlt_service")); 1525 container.setAttribute(QStringLiteral("in"), getIn()); 1526 container.setAttribute(QStringLiteral("out"), getOut()); 1527 container.setAttribute(QStringLiteral("position"), getPosition()); 1528 container.setAttribute(QStringLiteral("state"), m_currentState); 1529 container.setAttribute(QStringLiteral("playlist"), m_subPlaylistIndex); 1530 container.setAttribute(QStringLiteral("speed"), QString::number(m_speed, 'f')); 1531 container.setAttribute(QStringLiteral("audioStream"), getIntProperty(QStringLiteral("audio_index"))); 1532 std::vector<int> snaps; 1533 allSnaps(snaps); 1534 QString snapData; 1535 for (auto &s : snaps) { 1536 snapData.append(QString::number(s)); 1537 } 1538 container.setAttribute(QStringLiteral("markers"), snapData); 1539 document.appendChild(container); 1540 container.appendChild(m_effectStack->toXml(document)); 1541 return document.toString(); 1542 } 1543 1544 const QString ClipModel::clipThumbPath() 1545 { 1546 std::shared_ptr<ProjectClip> binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId); 1547 if (binClip) { 1548 return binClip->baseThumbPath(); 1549 } 1550 return QString(); 1551 } 1552 1553 void ClipModel::switchBinReference(const QString newId, const QUuid &uuid) 1554 { 1555 deregisterClipToBin(uuid); 1556 m_binClipId = newId; 1557 refreshProducerFromBin(-1); 1558 registerClipToBin(getProducer(), false); 1559 if (auto ptr = m_parent.lock()) { 1560 ptr->replugClip(m_id); 1561 QVector<int> roles{TimelineModel::ClipThumbRole}; 1562 QModelIndex ix = ptr->makeClipIndexFromID(m_id); 1563 ptr->notifyChange(ix, ix, roles); 1564 // invalidate timeline preview 1565 if (!ptr->getTrackById_const(m_currentTrackId)->isAudioTrack()) { 1566 Q_EMIT ptr->invalidateZone(m_position, m_position + getPlaytime()); 1567 } 1568 } 1569 }