1 /*
2 SPDX-FileCopyrightText: 2017 Jean-Baptiste Mardelle
3 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
4 */
5 #include "compositionmodel.hpp"
6 #include "assets/keyframes/model/keyframemodellist.hpp"
7 #include "timelinemodel.hpp"
8 #include "trackmodel.hpp"
9 #include "transitions/transitionsrepository.hpp"
10 #include <QDebug>
11 #include <mlt++/MltTransition.h>
12 #include <utility>
13
CompositionModel(std::weak_ptr<TimelineModel> parent,std::unique_ptr<Mlt::Transition> transition,int id,const QDomElement & transitionXml,const QString & transitionId,const QString & originalDecimalPoint)14 CompositionModel::CompositionModel(std::weak_ptr<TimelineModel> parent, std::unique_ptr<Mlt::Transition> transition, int id, const QDomElement &transitionXml,
15 const QString &transitionId, const QString &originalDecimalPoint)
16 : MoveableItem<Mlt::Transition>(std::move(parent), id)
17 , AssetParameterModel(std::move(transition), transitionXml, transitionId, {ObjectType::TimelineComposition, m_id}, originalDecimalPoint)
18 , m_a_track(-1)
19 , m_duration(0)
20 {
21 m_compositionName = TransitionsRepository::get()->getName(transitionId);
22 }
23
construct(const std::weak_ptr<TimelineModel> & parent,const QString & transitionId,const QString & originalDecimalPoint,int id,std::unique_ptr<Mlt::Properties> sourceProperties)24 int CompositionModel::construct(const std::weak_ptr<TimelineModel> &parent, const QString &transitionId, const QString &originalDecimalPoint, int id,
25 std::unique_ptr<Mlt::Properties> sourceProperties)
26 {
27 std::unique_ptr<Mlt::Transition> transition = TransitionsRepository::get()->getTransition(transitionId);
28 transition->set_in_and_out(0, 0);
29 auto xml = TransitionsRepository::get()->getXml(transitionId);
30 if (sourceProperties) {
31 // Paste parameters from existing source composition
32 QStringList sourceProps;
33 for (int i = 0; i < sourceProperties->count(); i++) {
34 sourceProps << sourceProperties->get_name(i);
35 }
36 QDomNodeList params = xml.elementsByTagName(QStringLiteral("parameter"));
37 for (int i = 0; i < params.count(); ++i) {
38 QDomElement currentParameter = params.item(i).toElement();
39 QString paramName = currentParameter.attribute(QStringLiteral("name"));
40 if (!sourceProps.contains(paramName)) {
41 continue;
42 }
43 QString paramValue = sourceProperties->get(paramName.toUtf8().constData());
44 currentParameter.setAttribute(QStringLiteral("value"), paramValue);
45 }
46 if (sourceProps.contains(QStringLiteral("force_track"))) {
47 transition->set("force_track", sourceProperties->get_int("force_track"));
48 }
49 }
50 std::shared_ptr<CompositionModel> composition(new CompositionModel(parent, std::move(transition), id, xml, transitionId, originalDecimalPoint));
51 id = composition->m_id;
52
53 if (auto ptr = parent.lock()) {
54 ptr->registerComposition(composition);
55 } else {
56 qDebug() << "Error : construction of composition failed because parent timeline is not available anymore";
57 Q_ASSERT(false);
58 }
59
60 return id;
61 }
62
requestResize(int size,bool right,Fun & undo,Fun & redo,bool logUndo,bool hasMix)63 bool CompositionModel::requestResize(int size, bool right, Fun &undo, Fun &redo, bool logUndo, bool hasMix)
64 {
65 Q_UNUSED(hasMix);
66 QWriteLocker locker(&m_lock);
67 if (size <= 0) {
68 return false;
69 }
70 int delta = getPlaytime() - size;
71 qDebug() << "compo request resize to " << size << ", ACTUAL SZ: " << getPlaytime() << ", " << right << delta;
72 int in = getIn();
73 int out = in + getPlaytime() - 1;
74 int oldDuration = out - in;
75 int old_in = in, old_out = out;
76 if (right) {
77 out -= delta;
78 } else {
79 in += delta;
80 }
81 // if the in becomes negative, we add the necessary length in out.
82 if (in < 0) {
83 out = out - in;
84 in = 0;
85 }
86
87 std::function<bool(void)> track_operation = []() { return true; };
88 std::function<bool(void)> track_reverse = []() { return true; };
89 if (m_currentTrackId != -1) {
90 if (auto ptr = m_parent.lock()) {
91 if (ptr->getTrackById(m_currentTrackId)->isLocked()) {
92 return false;
93 }
94 track_operation = ptr->getTrackById(m_currentTrackId)->requestCompositionResize_lambda(m_id, in, out, logUndo);
95 } else {
96 qDebug() << "Error : Moving composition failed because parent timeline is not available anymore";
97 Q_ASSERT(false);
98 }
99 } else {
100 // Perform resize only
101 setInOut(in, out);
102 }
103 QVector<int> roles{TimelineModel::DurationRole};
104 if (!right) {
105 roles.push_back(TimelineModel::StartRole);
106 }
107 Fun refresh = []() { return true; };
108 if (m_assetId == QLatin1String("slide")) {
109 // Slide composition uses a keyframe at end of composition, so update last keyframe
110 refresh = [this]() {
111 QString animation(m_asset->get("rect"));
112 if (animation.contains(QLatin1Char(';')) && !animation.contains(QLatin1String(";-1="))) {
113 QString result = animation.section(QLatin1Char(';'), 0, 0);
114 result.append(QStringLiteral(";-1="));
115 result.append(animation.section(QLatin1Char('='), -1));
116 m_asset->set("rect", result.toUtf8().constData());
117 }
118 return true;
119 };
120 refresh();
121 } else if (m_assetId == QLatin1String("wipe")) {
122 // Slide composition uses a keyframe at end of composition, so update last keyframe
123 refresh = [this]() {
124 QString animation(m_asset->get("geometry"));
125 if ((animation.contains(QLatin1Char(';')) && !animation.contains(QLatin1String(";-1="))) || animation.endsWith(QLatin1Char('='))) {
126 if (animation.contains(QLatin1String(" 0%;")) || animation.contains(QLatin1String(" 0;"))) {
127 // reverse anim
128 m_asset->set("geometry", "0=0% 0% 100% 100% 0%;-1=0% 0% 100% 100% 100%");
129 } else {
130 m_asset->set("geometry", "0=0% 0% 100% 100% 100%;-1=0% 0% 100% 100% 0%");
131 }
132 }
133 return true;
134 };
135 refresh();
136 }
137 Fun operation = [this, track_operation, roles]() {
138 if (track_operation()) {
139 // we send a list of roles to be updated
140 if (m_currentTrackId != -1) {
141 if (auto ptr = m_parent.lock()) {
142 QModelIndex ix = ptr->makeCompositionIndexFromID(m_id);
143 ptr->notifyChange(ix, ix, roles);
144 }
145 }
146 return true;
147 }
148 return false;
149 };
150 if (operation()) {
151 // 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
152 UPDATE_UNDO_REDO(refresh, refresh, undo, redo);
153 if (m_currentTrackId != -1) {
154 if (auto ptr = m_parent.lock()) {
155 track_reverse = ptr->getTrackById(m_currentTrackId)->requestCompositionResize_lambda(m_id, old_in, old_out, logUndo);
156 }
157 }
158 Fun reverse = [this, track_reverse, roles]() {
159 if (track_reverse()) {
160 if (m_currentTrackId != -1) {
161 if (auto ptr = m_parent.lock()) {
162 QModelIndex ix = ptr->makeCompositionIndexFromID(m_id);
163 ptr->notifyChange(ix, ix, roles);
164 }
165 }
166 return true;
167 }
168 return false;
169 };
170
171 if (logUndo) {
172 auto kfr = getKeyframeModel();
173 if (kfr) {
174 // Adjust keyframe length
175 if (oldDuration > 0) {
176 kfr->resizeKeyframes(0, oldDuration, 0, out - in, 0, right, undo, redo);
177 }
178 Fun refresh = [kfr]() {
179 emit kfr->modelChanged();
180 return true;
181 };
182 refresh();
183 UPDATE_UNDO_REDO(refresh, refresh, undo, redo);
184 }
185 }
186 UPDATE_UNDO_REDO(operation, reverse, undo, redo);
187 return true;
188 }
189 return false;
190 }
191
getProperty(const QString & name) const192 const QString CompositionModel::getProperty(const QString &name) const
193 {
194 READ_LOCK();
195 return QString::fromUtf8(service()->get(name.toUtf8().constData()));
196 }
197
service() const198 Mlt::Transition *CompositionModel::service() const
199 {
200 READ_LOCK();
201 return static_cast<Mlt::Transition *>(m_asset.get());
202 }
203
properties()204 Mlt::Properties *CompositionModel::properties()
205 {
206 READ_LOCK();
207 return new Mlt::Properties(m_asset.get()->get_properties());
208 }
209
getPlaytime() const210 int CompositionModel::getPlaytime() const
211 {
212 READ_LOCK();
213 return m_duration + 1;
214 }
215
getATrack() const216 int CompositionModel::getATrack() const
217 {
218 READ_LOCK();
219 return m_a_track == -1 ? -1 : service()->get_int("a_track");
220 }
221
setForceTrack(bool force)222 void CompositionModel::setForceTrack(bool force)
223 {
224 READ_LOCK();
225 service()->set("force_track", force ? 1 : 0);
226 }
227
getForcedTrack() const228 int CompositionModel::getForcedTrack() const
229 {
230 QWriteLocker locker(&m_lock);
231 return (service()->get_int("force_track") == 0 || m_a_track == -1) ? -1 : service()->get_int("a_track");
232 }
233
setATrack(int trackMltPosition,int trackId)234 void CompositionModel::setATrack(int trackMltPosition, int trackId)
235 {
236 QWriteLocker locker(&m_lock);
237 Q_ASSERT(trackId != getCurrentTrackId()); // can't compose with same track
238 m_a_track = trackMltPosition;
239 if (m_a_track >= 0) {
240 service()->set("a_track", trackMltPosition);
241 }
242 if (m_currentTrackId != -1) {
243 emit compositionTrackChanged();
244 }
245 }
246
getEffectKeyframeModel()247 KeyframeModel *CompositionModel::getEffectKeyframeModel()
248 {
249 prepareKeyframes();
250 std::shared_ptr<KeyframeModelList> listModel = getKeyframeModel();
251 if (listModel) {
252 return listModel->getKeyModel();
253 }
254 return nullptr;
255 }
256
showKeyframes() const257 bool CompositionModel::showKeyframes() const
258 {
259 READ_LOCK();
260 return !service()->get_int("kdenlive:hide_keyframes");
261 }
262
setShowKeyframes(bool show)263 void CompositionModel::setShowKeyframes(bool show)
264 {
265 QWriteLocker locker(&m_lock);
266 service()->set("kdenlive:hide_keyframes", !show);
267 }
268
displayName() const269 const QString &CompositionModel::displayName() const
270 {
271 return m_compositionName;
272 }
273
setInOut(int in,int out)274 void CompositionModel::setInOut(int in, int out)
275 {
276 MoveableItem::setInOut(in, out);
277 m_duration = out - in;
278 setPosition(in);
279 }
280
setGrab(bool grab)281 void CompositionModel::setGrab(bool grab)
282 {
283 QWriteLocker locker(&m_lock);
284 if (grab == m_grabbed) {
285 return;
286 }
287 m_grabbed = grab;
288 if (auto ptr = m_parent.lock()) {
289 QModelIndex ix = ptr->makeCompositionIndexFromID(m_id);
290 emit ptr->dataChanged(ix, ix, {TimelineModel::GrabbedRole});
291 }
292 }
293
setSelected(bool sel)294 void CompositionModel::setSelected(bool sel)
295 {
296 QWriteLocker locker(&m_lock);
297 if (sel == selected) {
298 return;
299 }
300 selected = sel;
301 if (auto ptr = m_parent.lock()) {
302 if (m_currentTrackId != -1) {
303 QModelIndex ix = ptr->makeCompositionIndexFromID(m_id);
304 emit ptr->dataChanged(ix, ix, {TimelineModel::SelectedRole});
305 }
306 }
307 }
308
setCurrentTrackId(int tid,bool finalMove)309 void CompositionModel::setCurrentTrackId(int tid, bool finalMove)
310 {
311 Q_UNUSED(finalMove);
312 MoveableItem::setCurrentTrackId(tid);
313 }
314
getOut() const315 int CompositionModel::getOut() const
316 {
317 return getPosition() + m_duration;
318 }
319
getIn() const320 int CompositionModel::getIn() const
321 {
322 return getPosition();
323 }
324
toXml(QDomDocument & document)325 QDomElement CompositionModel::toXml(QDomDocument &document)
326 {
327 QDomElement container = document.createElement(QStringLiteral("composition"));
328 container.setAttribute(QStringLiteral("id"), m_id);
329 container.setAttribute(QStringLiteral("composition"), m_assetId);
330 container.setAttribute(QStringLiteral("in"), getIn());
331 container.setAttribute(QStringLiteral("out"), getOut());
332 container.setAttribute(QStringLiteral("position"), getPosition());
333 if (auto ptr = m_parent.lock()) {
334 int trackId = ptr->getTrackPosition(m_currentTrackId);
335 container.setAttribute(QStringLiteral("track"), trackId);
336 }
337 container.setAttribute(QStringLiteral("a_track"), getATrack());
338 QScopedPointer<Mlt::Properties> props(properties());
339 for (int i = 0; i < props->count(); i++) {
340 QString name = props->get_name(i);
341 if (name.startsWith(QLatin1Char('_'))) {
342 continue;
343 }
344 Xml::setXmlProperty(container, name, props->get(i));
345 }
346 return container;
347 }
348