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 }