File indexing completed on 2024-04-28 04:52:26
0001 /* 0002 SPDX-FileCopyrightText: 2017 Jean-Baptiste Mardelle 0003 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0004 */ 0005 #include "compositionmodel.hpp" 0006 #include "assets/keyframes/model/keyframemodellist.hpp" 0007 #include "timelinemodel.hpp" 0008 #include "trackmodel.hpp" 0009 #include "transitions/transitionsrepository.hpp" 0010 #include <QDebug> 0011 #include <mlt++/MltTransition.h> 0012 #include <utility> 0013 0014 CompositionModel::CompositionModel(std::weak_ptr<TimelineModel> parent, std::unique_ptr<Mlt::Transition> transition, int id, const QDomElement &transitionXml, 0015 const QString &transitionId, const QString &originalDecimalPoint, const QUuid uuid) 0016 : MoveableItem<Mlt::Transition>(std::move(parent), id) 0017 , AssetParameterModel(std::move(transition), transitionXml, transitionId, ObjectId(KdenliveObjectType::TimelineComposition, m_id, uuid), 0018 originalDecimalPoint) 0019 , m_a_track(-1) 0020 , m_duration(0) 0021 { 0022 m_compositionName = TransitionsRepository::get()->getName(transitionId); 0023 } 0024 0025 int CompositionModel::construct(const std::weak_ptr<TimelineModel> &parent, const QString &transitionId, const QString &originalDecimalPoint, int length, 0026 int id, std::unique_ptr<Mlt::Properties> sourceProperties) 0027 { 0028 std::unique_ptr<Mlt::Transition> transition = TransitionsRepository::get()->getTransition(transitionId); 0029 transition->set_in_and_out(0, length - 1); 0030 auto xml = TransitionsRepository::get()->getXml(transitionId); 0031 if (sourceProperties) { 0032 // Paste parameters from existing source composition 0033 QStringList sourceProps; 0034 for (int i = 0; i < sourceProperties->count(); i++) { 0035 sourceProps << sourceProperties->get_name(i); 0036 } 0037 QDomNodeList params = xml.elementsByTagName(QStringLiteral("parameter")); 0038 for (int i = 0; i < params.count(); ++i) { 0039 QDomElement currentParameter = params.item(i).toElement(); 0040 QString paramName = currentParameter.attribute(QStringLiteral("name")); 0041 if (!sourceProps.contains(paramName)) { 0042 continue; 0043 } 0044 QString paramValue = sourceProperties->get(paramName.toUtf8().constData()); 0045 currentParameter.setAttribute(QStringLiteral("value"), paramValue); 0046 } 0047 if (sourceProps.contains(QStringLiteral("force_track"))) { 0048 transition->set("force_track", sourceProperties->get_int("force_track")); 0049 } 0050 } 0051 QUuid timelineUuid; 0052 if (auto ptr = parent.lock()) { 0053 timelineUuid = ptr->uuid(); 0054 } 0055 std::shared_ptr<CompositionModel> composition( 0056 new CompositionModel(parent, std::move(transition), id, xml, transitionId, originalDecimalPoint, timelineUuid)); 0057 id = composition->m_id; 0058 composition->m_duration = length - 1; 0059 if (sourceProperties) { 0060 composition->prepareKeyframes(); 0061 } 0062 0063 if (auto ptr = parent.lock()) { 0064 ptr->registerComposition(composition); 0065 } else { 0066 qDebug() << "Error : construction of composition failed because parent timeline is not available anymore"; 0067 Q_ASSERT(false); 0068 } 0069 0070 return id; 0071 } 0072 0073 bool CompositionModel::requestResize(int size, bool right, Fun &undo, Fun &redo, bool logUndo, bool hasMix) 0074 { 0075 Q_UNUSED(hasMix); 0076 QWriteLocker locker(&m_lock); 0077 if (size <= 0) { 0078 return false; 0079 } 0080 int delta = getPlaytime() - size; 0081 qDebug() << "compo request resize to " << size << ", ACTUAL SZ: " << getPlaytime() << ", " << right << delta; 0082 int in = getIn(); 0083 int out = in + getPlaytime() - 1; 0084 int oldDuration = out - in; 0085 int old_in = in, old_out = out; 0086 if (right) { 0087 out -= delta; 0088 } else { 0089 in += delta; 0090 } 0091 // if the in becomes negative, we add the necessary length in out. 0092 if (in < 0) { 0093 out = out - in; 0094 in = 0; 0095 } 0096 0097 std::function<bool(void)> track_operation = []() { return true; }; 0098 std::function<bool(void)> track_reverse = []() { return true; }; 0099 if (m_currentTrackId != -1) { 0100 if (auto ptr = m_parent.lock()) { 0101 if (ptr->getTrackById(m_currentTrackId)->isLocked()) { 0102 return false; 0103 } 0104 track_operation = ptr->getTrackById(m_currentTrackId)->requestCompositionResize_lambda(m_id, in, out, logUndo); 0105 } else { 0106 qDebug() << "Error : Moving composition failed because parent timeline is not available anymore"; 0107 Q_ASSERT(false); 0108 } 0109 } else { 0110 // Perform resize only 0111 setInOut(in, out); 0112 } 0113 QVector<int> roles{TimelineModel::DurationRole}; 0114 if (!right) { 0115 roles.push_back(TimelineModel::StartRole); 0116 } 0117 Fun refresh = []() { return true; }; 0118 if (m_assetId == QLatin1String("slide")) { 0119 // Slide composition uses a keyframe at end of composition, so update last keyframe 0120 refresh = [this]() { 0121 QString animation(m_asset->get("rect")); 0122 if (animation.contains(QLatin1Char(';')) && !animation.contains(QLatin1String(";-1="))) { 0123 QString result = animation.section(QLatin1Char(';'), 0, 0); 0124 result.append(QStringLiteral(";-1=")); 0125 result.append(animation.section(QLatin1Char('='), -1)); 0126 m_asset->set("rect", result.toUtf8().constData()); 0127 } 0128 return true; 0129 }; 0130 refresh(); 0131 } else if (m_assetId == QLatin1String("wipe")) { 0132 // Slide composition uses a keyframe at end of composition, so update last keyframe 0133 refresh = [this]() { 0134 QString animation(m_asset->get("geometry")); 0135 if ((animation.contains(QLatin1Char(';')) && !animation.contains(QLatin1String(";-1="))) || animation.endsWith(QLatin1Char('='))) { 0136 if (animation.contains(QLatin1String(" 0%;")) || animation.contains(QLatin1String(" 0;"))) { 0137 // reverse anim 0138 m_asset->set("geometry", "0=0% 0% 100% 100% 0%;-1=0% 0% 100% 100% 100%"); 0139 } else { 0140 m_asset->set("geometry", "0=0% 0% 100% 100% 100%;-1=0% 0% 100% 100% 0%"); 0141 } 0142 } 0143 return true; 0144 }; 0145 refresh(); 0146 } 0147 Fun operation = [this, track_operation, roles]() { 0148 if (track_operation()) { 0149 // we send a list of roles to be updated 0150 if (m_currentTrackId != -1) { 0151 if (auto ptr = m_parent.lock()) { 0152 QModelIndex ix = ptr->makeCompositionIndexFromID(m_id); 0153 ptr->notifyChange(ix, ix, roles); 0154 } 0155 } 0156 return true; 0157 } 0158 return false; 0159 }; 0160 if (operation()) { 0161 // 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 0162 UPDATE_UNDO_REDO(refresh, refresh, undo, redo); 0163 if (m_currentTrackId != -1) { 0164 if (auto ptr = m_parent.lock()) { 0165 track_reverse = ptr->getTrackById(m_currentTrackId)->requestCompositionResize_lambda(m_id, old_in, old_out, logUndo); 0166 } 0167 } 0168 Fun reverse = [this, track_reverse, roles]() { 0169 if (track_reverse()) { 0170 if (m_currentTrackId != -1) { 0171 if (auto ptr = m_parent.lock()) { 0172 QModelIndex ix = ptr->makeCompositionIndexFromID(m_id); 0173 ptr->notifyChange(ix, ix, roles); 0174 } 0175 } 0176 return true; 0177 } 0178 return false; 0179 }; 0180 0181 if (logUndo) { 0182 auto kfr = getKeyframeModel(); 0183 if (kfr) { 0184 // Adjust keyframe length 0185 if (oldDuration > 0) { 0186 kfr->resizeKeyframes(0, oldDuration, 0, out - in, 0, right, undo, redo); 0187 } 0188 Fun refresh = [kfr]() { 0189 Q_EMIT kfr->modelChanged(); 0190 return true; 0191 }; 0192 refresh(); 0193 UPDATE_UNDO_REDO(refresh, refresh, undo, redo); 0194 } 0195 } 0196 UPDATE_UNDO_REDO(operation, reverse, undo, redo); 0197 return true; 0198 } 0199 return false; 0200 } 0201 0202 const QString CompositionModel::getProperty(const QString &name) const 0203 { 0204 READ_LOCK(); 0205 return QString::fromUtf8(service()->get(name.toUtf8().constData())); 0206 } 0207 0208 Mlt::Transition *CompositionModel::service() const 0209 { 0210 READ_LOCK(); 0211 return static_cast<Mlt::Transition *>(m_asset.get()); 0212 } 0213 0214 Mlt::Properties *CompositionModel::properties() 0215 { 0216 READ_LOCK(); 0217 return new Mlt::Properties(m_asset.get()->get_properties()); 0218 } 0219 0220 int CompositionModel::getPlaytime() const 0221 { 0222 READ_LOCK(); 0223 return m_duration + 1; 0224 } 0225 0226 int CompositionModel::getATrack() const 0227 { 0228 READ_LOCK(); 0229 return m_a_track == -1 ? -1 : service()->get_int("a_track"); 0230 } 0231 0232 void CompositionModel::setForceTrack(bool force) 0233 { 0234 READ_LOCK(); 0235 service()->set("force_track", force ? 1 : 0); 0236 } 0237 0238 int CompositionModel::getForcedTrack() const 0239 { 0240 QWriteLocker locker(&m_lock); 0241 return (service()->get_int("force_track") == 0 || m_a_track == -1) ? -1 : service()->get_int("a_track"); 0242 } 0243 0244 void CompositionModel::setATrack(int trackMltPosition, int trackId) 0245 { 0246 QWriteLocker locker(&m_lock); 0247 Q_ASSERT(trackId != getCurrentTrackId()); // can't compose with same track 0248 m_a_track = trackMltPosition; 0249 if (m_a_track >= 0) { 0250 service()->set("a_track", trackMltPosition); 0251 } 0252 if (m_currentTrackId != -1) { 0253 Q_EMIT compositionTrackChanged(); 0254 } 0255 } 0256 0257 KeyframeModel *CompositionModel::getEffectKeyframeModel() 0258 { 0259 prepareKeyframes(); 0260 std::shared_ptr<KeyframeModelList> listModel = getKeyframeModel(); 0261 if (listModel) { 0262 return listModel->getKeyModel(); 0263 } 0264 return nullptr; 0265 } 0266 0267 bool CompositionModel::showKeyframes() const 0268 { 0269 READ_LOCK(); 0270 return !service()->get_int("kdenlive:hide_keyframes"); 0271 } 0272 0273 void CompositionModel::setShowKeyframes(bool show) 0274 { 0275 QWriteLocker locker(&m_lock); 0276 service()->set("kdenlive:hide_keyframes", !show); 0277 } 0278 0279 const QString &CompositionModel::displayName() const 0280 { 0281 return m_compositionName; 0282 } 0283 0284 void CompositionModel::setInOut(int in, int out) 0285 { 0286 MoveableItem::setInOut(in, out); 0287 m_duration = out - in; 0288 setPosition(in); 0289 } 0290 0291 void CompositionModel::setGrab(bool grab) 0292 { 0293 QWriteLocker locker(&m_lock); 0294 if (grab == m_grabbed) { 0295 return; 0296 } 0297 m_grabbed = grab; 0298 if (auto ptr = m_parent.lock()) { 0299 QModelIndex ix = ptr->makeCompositionIndexFromID(m_id); 0300 Q_EMIT ptr->dataChanged(ix, ix, {TimelineModel::GrabbedRole}); 0301 } 0302 } 0303 0304 void CompositionModel::setSelected(bool sel) 0305 { 0306 QWriteLocker locker(&m_lock); 0307 if (sel == selected) { 0308 return; 0309 } 0310 selected = sel; 0311 if (auto ptr = m_parent.lock()) { 0312 if (m_currentTrackId != -1) { 0313 QModelIndex ix = ptr->makeCompositionIndexFromID(m_id); 0314 Q_EMIT ptr->dataChanged(ix, ix, {TimelineModel::SelectedRole}); 0315 } 0316 } 0317 } 0318 0319 void CompositionModel::setCurrentTrackId(int tid, bool finalMove) 0320 { 0321 Q_UNUSED(finalMove); 0322 MoveableItem::setCurrentTrackId(tid); 0323 } 0324 0325 int CompositionModel::getOut() const 0326 { 0327 return getPosition() + m_duration; 0328 } 0329 0330 int CompositionModel::getIn() const 0331 { 0332 return getPosition(); 0333 } 0334 0335 QDomElement CompositionModel::toXml(QDomDocument &document) 0336 { 0337 QDomElement container = document.createElement(QStringLiteral("composition")); 0338 container.setAttribute(QStringLiteral("id"), m_id); 0339 container.setAttribute(QStringLiteral("composition"), m_assetId); 0340 container.setAttribute(QStringLiteral("in"), getIn()); 0341 container.setAttribute(QStringLiteral("out"), getOut()); 0342 container.setAttribute(QStringLiteral("position"), getPosition()); 0343 if (auto ptr = m_parent.lock()) { 0344 int trackId = ptr->getTrackPosition(m_currentTrackId); 0345 container.setAttribute(QStringLiteral("track"), trackId); 0346 } 0347 container.setAttribute(QStringLiteral("a_track"), getATrack()); 0348 QScopedPointer<Mlt::Properties> props(properties()); 0349 for (int i = 0; i < props->count(); i++) { 0350 QString name = props->get_name(i); 0351 if (name.startsWith(QLatin1Char('_'))) { 0352 continue; 0353 } 0354 Xml::setXmlProperty(container, name, props->get(i)); 0355 } 0356 return container; 0357 }