File indexing completed on 2024-05-12 08:54:45
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 };