File indexing completed on 2024-04-28 04:52:36

0001 /*
0002     SPDX-FileCopyrightText: 2017 Nicolas Carion
0003     SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0004 */
0005 
0006 #pragma once
0007 
0008 #include "definitions.h"
0009 #include "undohelper.hpp"
0010 #include <QReadWriteLock>
0011 #include <QSharedPointer>
0012 #include <memory>
0013 #include <mlt++/MltPlaylist.h>
0014 #include <mlt++/MltProfile.h>
0015 #include <mlt++/MltTractor.h>
0016 #include <unordered_map>
0017 #include <unordered_set>
0018 
0019 class TimelineModel;
0020 class ClipModel;
0021 class CompositionModel;
0022 class EffectStackModel;
0023 class AssetParameterModel;
0024 
0025 class MixInfo
0026 {
0027 public:
0028     int firstClipId = -1;
0029     int secondClipId = -1;
0030     /** @brief in and out of the first clip in the mix */
0031     std::pair<int, int> firstClipInOut = {-1, -1};
0032     /** @brief in and out of the second clip in the mix */
0033     std::pair<int, int> secondClipInOut = {-1, -1};
0034     /** @brief Distance between first clip out and cut pos */
0035     int mixOffset = 0;
0036 };
0037 
0038 /** @brief This class represents a Track object, as viewed by the backend.
0039    To allow same track transitions, a Track object corresponds to two Mlt::Playlist, between which we can switch when required by the transitions.
0040    In general, the Gui associated with it will send modification queries (such as resize or move), and this class authorize them or not depending on the
0041    validity of the modifications
0042 */
0043 class TrackModel
0044 {
0045 
0046 public:
0047     TrackModel() = delete;
0048     ~TrackModel();
0049 
0050     friend class ClipModel;
0051     friend class CompositionModel;
0052     friend class TimelineController;
0053     friend struct TimelineFunctions;
0054     friend class TimelineItemModel;
0055     friend class TimelineModel;
0056 
0057 private:
0058     /** This constructor is private, call the static construct instead */
0059     TrackModel(const std::weak_ptr<TimelineModel> &parent, int id = -1, const QString &trackName = QString(), bool audioTrack = false);
0060     TrackModel(const std::weak_ptr<TimelineModel> &parent, Mlt::Tractor mltTrack, int id = -1);
0061 
0062 public:
0063     /** @brief Creates a track, which references itself to the parent
0064        Returns the (unique) id of the created track
0065        @param id Requested id of the track. Automatic if id = -1
0066        @param pos is the optional position of the track. If left to -1, it will be added at the end
0067      */
0068     static int construct(const std::weak_ptr<TimelineModel> &parent, int id = -1, int pos = -1, const QString &trackName = QString(), bool audioTrack = false,
0069                          bool singleOperation = true);
0070 
0071     /** @brief returns the number of clips */
0072     int getClipsCount();
0073 
0074     /** @brief returns the number of compositions */
0075     int getCompositionsCount() const;
0076 
0077     /** @brief Perform a split at the requested position */
0078     bool splitClip(QSharedPointer<ClipModel> caller, int position);
0079 
0080     /** @brief Implicit conversion operator to access the underlying producer
0081      */
0082     operator Mlt::Producer &() { return *m_track.get(); }
0083 
0084     /** @brief Returns true if track is in locked state
0085      */
0086     bool isLocked() const;
0087     /** @brief Returns true if track is active in timeline, ie.
0088      * will receive insert/lift/overwrite/extract operations
0089      */
0090     bool isTimelineActive() const;
0091     /** @brief Returns true if track is active and not locked
0092      */
0093     bool shouldReceiveTimelineOp() const;
0094     /** @brief Returns true if track is an audio track
0095      */
0096     bool isAudioTrack() const;
0097     Mlt::Tractor *getTrackService();
0098     /** @brief Returns the track type (audio / video)
0099      */
0100     PlaylistState::ClipState trackType() const;
0101     /** @brief Returns true if track is disabled
0102      */
0103     bool isHidden() const;
0104     /** @brief Returns true if track is disabled
0105      */
0106     bool isMute() const;
0107 
0108     // TODO make protected
0109     QVariant getProperty(const QString &name) const;
0110     void setProperty(const QString &name, const QString &value);
0111     /** @brief Remove a composition between 2 same track clips */
0112     bool requestRemoveMix(std::pair<int, int> clipIds, Fun &undo, Fun &redo);
0113     /** @brief Create a composition between 2 same track clips */
0114     bool requestClipMix(const QString &mixId, std::pair<int, int> clipIds, std::pair<int, int> mixDurations, bool updateView, bool finalMove, Fun &undo,
0115                         Fun &redo, bool groupMove);
0116     /** @brief Get clip ids and in/out position for mixes in this clip */
0117     std::pair<MixInfo, MixInfo> getMixInfo(int cid) const;
0118     /** @brief Delete a mix composition */
0119     bool deleteMix(int clipId, bool final, bool notify = true);
0120     /** @brief Create a mix composition using clip ids */
0121     bool createMix(std::pair<int, int> clipIds, std::pair<int, int> mixData);
0122     bool createMix(MixInfo info, std::pair<QString, QVector<QPair<QString, QVariant>>> params, std::pair<int, int> tracks, bool finalMove);
0123     /** @brief Create a mix composition using mix info */
0124     bool createMix(MixInfo info, bool isAudio);
0125     /** @brief Change id of first clip in a mix (in case of clip cut) */
0126     bool reAssignEndMix(int currentId, int newId);
0127     /** @brief Get all necessary infos to clone a mix */
0128     std::pair<QString, QVector<QPair<QString, QVariant>>> getMixParams(int cid);
0129     /** @brief Get the mix tracks */
0130     std::pair<int, int> getMixTracks(int cid) const;
0131     void switchMix(int cid, const QString &composition, Fun &undo, Fun &redo);
0132     /** @brief Ensure we don't have unsynced mixes in the playlist (mixes without owner clip) */
0133     void syncronizeMixes(bool finalMove);
0134     /** @brief Remove a mix in the track (if its clip was removed) */
0135     void removeMix(const MixInfo &info);
0136     /** @brief Switch a clip from one playlist to the other */
0137     bool switchPlaylist(int clipId, int position, int sourcePlaylist, int destPlaylist);
0138     /** @brief Load a same track transition from project */
0139     bool loadMix(Mlt::Transition *t);
0140     /** @brief Set mix duration and mix cut pos on a clip */
0141     void setMixDuration(int cid, int mixDuration, int mixCut);
0142     int getMixDuration(int cid) const;
0143     /** @brief Get the assetparameter model for a mix */
0144     const std::shared_ptr<AssetParameterModel> mixModel(int cid);
0145     /** @brief Get a list of current effect stack zones */
0146     QVariantList stackZones() const;
0147     /** @brief Return true if a clip starts at pos in one of the trak playlists */
0148     bool hasClipStart(int pos);
0149     /** @brief Calculate a hash based on all clips an d mixes positions/playtime */
0150     QByteArray trackHash();
0151 
0152 protected:
0153     /** @brief This will lock the track: it will no longer allow insertion/deletion/resize of items
0154        This functions are dangerous to call directly since locking the track will potentially
0155        mess up with the undo/redo system (a track lock may make an undo impossible).
0156        Prefer calling TimelineModel::setTrackLockedState */
0157     void lock();
0158     void unlock();
0159 
0160     /** @brief Returns a lambda that performs a resize of the given clip.
0161        The lambda returns true if the operation succeeded, and otherwise nothing is modified
0162        This method is protected because it shouldn't be called directly. Call the function in the timeline instead.
0163        @param clipId is the id of the clip
0164        @param in is the new starting on the clip
0165        @param out is the new ending on the clip
0166        @param right is true if we change the right side of the clip, false otherwise
0167        @param hasMix is true if we change the right side of the clip, false otherwise
0168     */
0169     Fun requestClipResize_lambda(int clipId, int in, int out, bool right, bool hasMix = false, bool finalMove = false);
0170 
0171     /** @brief Performs an insertion of the given clip.
0172        Returns true if the operation succeeded, and otherwise, the track is not modified.
0173        This method is protected because it shouldn't be called directly. Call the function in the timeline instead.
0174        @param clip is the id of the clip
0175        @param position is the position where to insert the clip
0176        @param updateView whether we send update to the view
0177        @param finalMove if the move is finished (not while dragging), so we invalidate timeline preview / check project duration
0178        @param undo Lambda function containing the current undo stack. Will be updated with current operation
0179        @param redo Lambda function containing the current redo queue. Will be updated with current operation
0180     */
0181     bool requestClipInsertion(int clipId, int position, bool updateView, bool finalMove, Fun &undo, Fun &redo, bool groupMove = false, bool newInsertion = true,
0182                               const QList<int> &allowedClipMixes = {});
0183     /** @brief This function returns a lambda that performs the requested operation */
0184     Fun requestClipInsertion_lambda(int clipId, int position, bool updateView, bool finalMove, bool groupMove = false, const QList<int> &allowedClipMixes = {});
0185 
0186     /** @brief Performs an deletion of the given clip.
0187        Returns true if the operation succeeded, and otherwise, the track is not modified.
0188        This method is protected because it shouldn't be called directly. Call the function in the timeline instead.
0189        @param clipId is the id of the clip
0190        @param updateView whether we send update to the view
0191        @param finalMove if the move is finished (not while dragging), so we invalidate timeline preview / check project duration
0192        @param undo Lambda function containing the current undo stack. Will be updated with current operation
0193        @param redo Lambda function containing the current redo queue. Will be updated with current operation
0194        @param groupMove If true, this is part of a larger operation and some operations like checking track duration will not be performed and have to be
0195        performed separately
0196        @param finalDeletion If true, the clip will be deselected (should be false if this is a clip move doing delete/insert)
0197     */
0198     bool requestClipDeletion(int clipId, bool updateView, bool finalMove, Fun &undo, Fun &redo, bool groupMove, bool finalDeletion,
0199                              const QList<int> &allowedClipMixes = {});
0200     /** @brief This function returns a lambda that performs the requested operation */
0201     Fun requestClipDeletion_lambda(int clipId, bool updateView, bool finalMove, bool groupMove, bool finalDeletion);
0202 
0203     /** @brief Performs an insertion of the given composition.
0204        Returns true if the operation succeeded, and otherwise, the track is not modified.
0205        This method is protected because it shouldn't be called directly. Call the function in the timeline instead.
0206        Note that in Mlt, the composition insertion logic is not really at the track level, but we use that level to do collision checking
0207        @param compoId is the id of the composition
0208        @param position is the position where to insert the composition
0209        @param updateView whether we send update to the view
0210        @param undo Lambda function containing the current undo stack. Will be updated with current operation
0211        @param redo Lambda function containing the current redo queue. Will be updated with current operation
0212     */
0213     bool requestCompositionInsertion(int compoId, int position, bool updateView, bool finalMove, Fun &undo, Fun &redo);
0214     /** @brief This function returns a lambda that performs the requested operation */
0215     Fun requestCompositionInsertion_lambda(int compoId, int position, bool updateView, bool finalMove = false);
0216 
0217     bool requestCompositionDeletion(int compoId, bool updateView, bool finalMove, Fun &undo, Fun &redo, bool finalDeletion);
0218     Fun requestCompositionDeletion_lambda(int compoId, bool updateView, bool finalMove = false);
0219     Fun requestCompositionResize_lambda(int compoId, int in, int out = -1, bool logUndo = false);
0220 
0221     /** @brief Returns the size of the blank before or after the given clip
0222        @param clipId is the id of the clip
0223        @param after is true if we query the blank after, false otherwise
0224     */
0225     int getBlankSizeNearClip(int clipId, bool after);
0226     int getBlankSizeNearComposition(int compoId, bool after);
0227     int getBlankStart(int position);
0228     int getNextBlankStart(int position);
0229     /** @brief Returns the start of the blank on a specific playlist */
0230     int getBlankStart(int position, int track);
0231     int getBlankSizeAtPos(int frame);
0232     /** @brief Returns the start of the clip on a specific playlist */
0233     int getClipStart(int position, int track);
0234     int getClipEnd(int position, int track);
0235     /** @brief Returns true if clip at position is the last on playlist
0236      * @param position the position in playlist
0237      */
0238     bool isLastClip(int position);
0239 
0240     /** @brief Returns the best composition duration depending on clips on the track */
0241     int suggestCompositionLength(int position);
0242     /** @brief Returns the best composition duration depending on compositions on the track */
0243     QPair<int, int> validateCompositionLength(int pos, int offset, int duration, int endPos);
0244 
0245     /** @brief Returns the (unique) construction id of the track*/
0246     int getId() const;
0247 
0248     /** @brief This function is used only by the QAbstractItemModel
0249       Given a row in the model, retrieves the corresponding clip id. If it does not exist, returns -1
0250     */
0251     int getClipByRow(int row) const;
0252 
0253     /** @brief This function is used only by the QAbstractItemModel
0254       Given a row in the model, retrieves the corresponding composition id. If it does not exist, returns -1
0255     */
0256     int getCompositionByRow(int row) const;
0257     /** @brief This function is used only by the QAbstractItemModel
0258       Given a clip ID, returns the row of the clip.
0259     */
0260     int getRowfromClip(int clipId) const;
0261 
0262     /** @brief This function is used only by the QAbstractItemModel
0263       Given a composition ID, returns the row of the composition.
0264     */
0265     int getRowfromComposition(int compoId) const;
0266 
0267     /** @brief This is an helper function that test frame level consistency with the MLT structures */
0268     bool checkConsistency();
0269 
0270     /** @brief Returns true if we have a composition intersecting with the range [in,out]*/
0271     bool hasIntersectingComposition(int in, int out) const;
0272 
0273     /** @brief This is an helper function that returns the sub-playlist in which the clip is inserted, along with its index in the playlist
0274      @param position the position of the target clip*/
0275     std::pair<int, int> getClipIndexAt(int position, int playlist = -1);
0276     QSharedPointer<Mlt::Producer> getClipProducer(int clipId);
0277 
0278     /** @brief This is an helper function that checks in all playlists if the given position is a blank */
0279     bool isBlankAt(int position, int playlist = -1);
0280 
0281     /** @brief This is an helper function that returns the end of the blank that covers given position */
0282     int getBlankEnd(int position);
0283     /** @brief Same, but we restrict to a specific track*/
0284     int getBlankEnd(int position, int track);
0285 
0286     /** @brief Returns the clip id on this track at position requested, or -1 if no clip */
0287     int getClipByPosition(int position, int playlist = -1);
0288     /** @brief Returns the clip id on this track that starts at position requested, or -1 if no clip */
0289     int getClipByStartPosition(int position) const;
0290 
0291     /** @brief Returns the composition id on this track starting position requested, or -1 if not found */
0292     int getCompositionByPosition(int position);
0293     /** @brief Add a track effect */
0294     bool addEffect(const QString &effectId);
0295 
0296     /** @brief Returns a comma separated list of effect names */
0297     const QString effectNames() const;
0298 
0299     /** @brief Returns true if effect stack is enabled */
0300     bool stackEnabled() const;
0301 
0302     /** @brief Enable / disable the track's effect stack */
0303     void setEffectStackEnabled(bool enable);
0304 
0305     /** @brief This function removes the clip from the mlt object, and then insert it back in the same spot again.
0306      * This is used when some properties of the clip have changed, and we need this to refresh it */
0307     void replugClip(int clipId);
0308     void temporaryReplugClip(int cid);
0309     void temporaryUnplugClip(int clipId);
0310 
0311     int trackDuration() const;
0312 
0313     /** @brief Returns the list of the ids of the clips that intersect the given range */
0314     std::unordered_set<int> getClipsInRange(int position, int end = -1);
0315     /** @brief Returns the list of the ids of the compositions that intersect the given range */
0316     std::unordered_set<int> getCompositionsInRange(int position, int end);
0317 
0318     /** @brief Import effects from a service that contains some (another track) */
0319     bool importEffects(std::weak_ptr<Mlt::Service> service);
0320     /** @brief Copy effects from another effect stack */
0321     bool copyEffect(const std::shared_ptr<EffectStackModel> &stackModel, int rowId);
0322     /** @brief Returns true if we have a blank at position for duration */
0323     bool isAvailable(int position, int duration, int playlist);
0324     /** @brief Returns true if we have a blank at position for duration, with the exception of clip ids exception */
0325     bool isAvailableWithExceptions(int position, int duration, const QVector<int> &exceptions);
0326     /** @brief Returns the number of same track transitions (mix) in this track */
0327     int mixCount() const;
0328     /** @brief Returns true if the track has a same track transition for this clip (cid) */
0329     bool hasMix(int cid) const;
0330     /** @brief Returns true if this clip has a mix at start */
0331     bool hasStartMix(int cid) const;
0332     /** @brief Returns true if this clip has a mix at end */
0333     bool hasEndMix(int cid) const;
0334     /** @brief Returns the cid of the second partner or -1 if the given clip has no end mix */
0335     int getSecondMixPartner(int cid) const;
0336     /** @brief Returns the cut position if the composition is over a cut between 2 clips, -1 otherwise
0337      */
0338     int isOnCut(int cid);
0339     /** @brief Returns all mix info as xml */
0340     QDomElement mixXml(QDomDocument &document, int cid) const;
0341     /** @brief Check if a mix is reversed (moslty used in tests) */
0342     bool mixIsReversed(int cid) const;
0343     /** @brief Adjust effect stack length to current track duration */
0344     void adjustStackLength(int duration, int newDuration, Fun &undo, Fun &redo);
0345 
0346 public Q_SLOTS:
0347     /** Delete the current track and all its associated clips */
0348     void slotDelete();
0349 
0350 private:
0351     std::weak_ptr<TimelineModel> m_parent;
0352     /// this is the creation id of the track, used for book-keeping
0353     int m_id;
0354 
0355     // We fake two playlists to allow same track transitions.
0356     std::shared_ptr<Mlt::Tractor> m_track;
0357     Mlt::Playlist m_playlists[2];
0358     /// A list of clips having a same track transition, in the form: {first_clip_id, second_clip_id} where first_clip is placed before second_clip
0359     QMap<int, int> m_mixList;
0360 
0361     /** This is important to keep an ordered structure to store the clips, since we use their ids order as row order*/
0362     std::map<int, std::shared_ptr<ClipModel>> m_allClips;
0363     /** This is important to keep an ordered structure to store the compositions, since we use their ids order as row order*/
0364     std::map<int, std::shared_ptr<CompositionModel>> m_allCompositions;
0365 
0366     /** We store the positions of the compositions. In Melt, the compositions are not inserted at the track level, but we keep
0367      *  those positions here to check for moves and resize
0368      */
0369     std::map<int, int> m_compoPos;
0370 
0371     /// This is a lock that ensures safety in case of concurrent access
0372     mutable QReadWriteLock m_lock;
0373     void reverseCompositionXml(const QString &composition, QDomElement xml);
0374     void updateCompositionDirection(Mlt::Transition &transition, bool reverse);
0375 
0376 protected:
0377     bool m_softDelete;
0378     std::shared_ptr<EffectStackModel> m_effectStack;
0379     /// A list of same track transitions for this track, in the form: {second_clip_id, transition}
0380     std::unordered_map<int, std::shared_ptr<AssetParameterModel>> m_sameCompositions;
0381 };