1 /* 2 SPDX-FileCopyrightText: 2017 Nicolas Carion 3 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 4 */ 5 6 #ifndef TIMELINEMODEL_H 7 #define TIMELINEMODEL_H 8 9 #include "definitions.h" 10 #include "undohelper.hpp" 11 #include "trackmodel.hpp" 12 #include <QAbstractItemModel> 13 #include <QReadWriteLock> 14 #include <cassert> 15 #include <memory> 16 #include <mlt++/MltTractor.h> 17 #include <unordered_map> 18 #include <unordered_set> 19 #include <vector> 20 21 class AssetParameterModel; 22 class EffectStackModel; 23 class ClipModel; 24 class CompositionModel; 25 class DocUndoStack; 26 class GroupsModel; 27 class SnapModel; 28 class SubtitleModel; 29 class TimelineItemModel; 30 class TrackModel; 31 32 /** @brief This class represents a Timeline object, as viewed by the backend. 33 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 34 validity of the modifications. 35 36 This class also serves to keep track of all objects. It holds pointers to all tracks and clips, and gives them unique IDs on creation. These Ids are used in 37 any interactions with the objects and have nothing to do with Melt IDs. 38 39 This is the entry point for any modifications that has to be made on an element. The dataflow beyond this entry point may vary, for example when the user 40 request a clip resize, the call is deferred to the clip itself, that check if there is enough data to extend by the requested amount, compute the new in and 41 out, and then asks the track if there is enough room for extension. To avoid any confusion on which function to call first, remember to always call the 42 version in timeline. This is also required to generate the Undo/Redo operators 43 44 The undo/redo system is designed around lambda functions. Each time a function executes an elementary change to the model, it writes the corresponding 45 operation and its reverse, respectively in the redo and the undo lambdas. This way, if an operation fails for some reason, we can easily cancel the steps 46 that have been done so far without corrupting anything. The other advantage is that operations are easy to compose, and you get a undo/redo pair for free no 47 matter in which way you combine them. 48 49 Most of the modification functions are named requestObjectAction. Eg, if the object is a clip and we want to move it, we call requestClipMove. These 50 functions always return a bool indicating success, and when they return false they should guarantee than nothing has been modified. Most of the time, these 51 functions come in two versions: the first one is the entry point if you want to perform only the action (and not compose it with other actions). This version 52 will generally automatically push and Undo object on the Application stack, in case the user later wants to cancel the operation. It also generally goes the 53 extra mile to ensure the operation is done in a way that match the user's expectation: for example requestClipMove checks whether the clip belongs to a group 54 and in that case actually mouves the full group. The other version of the function, if it exists, is intended for composition (using the action as part of a 55 complex operation). It takes as input the undo/redo lambda corresponding to the action that is being performed and accumulates on them. Note that this 56 version does the minimal job: in the example of the requestClipMove, it will not move the full group if the clip is in a group. 57 58 Generally speaking, we don't check ahead of time if an action is going to succeed or not before applying it. 59 We just apply it naively, and if it fails at some point, we use the undo operator that we are constructing on the fly to revert what we have done so far. 60 For example, when we move a group of clips, we apply the move operation to all the clips inside this group (in the right order). If none fails, we are good, 61 otherwise we revert what we've already done. 62 This kind of behaviour frees us from the burden of simulating the actions before actually applying theme. This is a good thing because this simulation step 63 would be very sensitive to corruptions and small discrepancies, which we try to avoid at all cost. 64 65 66 It derives from AbstractItemModel (indirectly through TimelineItemModel) to provide the model to the QML interface. An itemModel is organized with row and 67 columns that contain the data. It can be hierarchical, meaning that a given index (row,column) can contain another level of rows and column. 68 Our organization is as follows: at the top level, each row contains a track. These rows are in the same order as in the actual timeline. 69 Then each of this row contains itself sub-rows that correspond to the clips. 70 Here the order of these sub-rows is unrelated to the chronological order of the clips, 71 but correspond to their Id order. For example, if you have three clips, with ids 12, 45 and 150, they will receive row index 0,1 and 2. 72 This is because the order actually doesn't matter since the clips are rendered based on their positions rather than their row order. 73 The id order has been chosen because it is consistent with a valid ordering of the clips. 74 The columns are never used, so the data is always in column 0 75 76 An ModelIndex in the ItemModel consists of a row number, a column number, and a parent index. In our case, tracks have always an empty parent, and the clip 77 have a track index as parent. 78 A ModelIndex can also store one additional integer, and we exploit this feature to store the unique ID of the object it corresponds to. 79 80 */ 81 class TimelineModel : public QAbstractItemModel_shared_from_this<TimelineModel> 82 { 83 Q_OBJECT 84 85 protected: 86 /** @brief this constructor should not be called. Call the static construct instead 87 */ 88 TimelineModel(Mlt::Profile *profile, std::weak_ptr<DocUndoStack> undo_stack); 89 90 public: 91 friend class TrackModel; 92 template <typename T> friend class MoveableItem; 93 friend class ClipModel; 94 friend class CompositionModel; 95 friend class GroupsModel; 96 friend class TimelineController; 97 friend class SubtitleModel; 98 friend class MarkerListModel; 99 friend class TimeRemap; 100 friend struct TimelineFunctions; 101 102 /// Two level model: tracks and clips on track 103 enum { 104 NameRole = Qt::UserRole + 1, 105 ResourceRole, /// clip only 106 IsProxyRole, /// clip only 107 ServiceRole, /// clip only 108 StartRole, /// clip only 109 MixRole, /// clip only, the duration of the mix 110 MixCutRole, /// The original cut position for the mix 111 BinIdRole, /// clip only 112 TrackIdRole, 113 FakeTrackIdRole, 114 FakePositionRole, 115 MarkersRole, /// clip only 116 PlaylistStateRole, /// clip only 117 StatusRole, /// clip only 118 TypeRole, /// clip only 119 KeyframesRole, 120 DurationRole, 121 FinalMoveRole, 122 MaxDurationRole, 123 InPointRole, /// clip only 124 OutPointRole, /// clip only 125 FramerateRole, /// clip only 126 GroupedRole, /// clip only 127 HasAudio, /// clip only 128 CanBeAudioRole, /// clip only 129 CanBeVideoRole, /// clip only 130 IsDisabledRole, /// track only 131 IsAudioRole, 132 SortRole, 133 TagRole, /// clip only 134 ShowKeyframesRole, 135 AudioLevelsRole, /// clip only 136 AudioChannelsRole, /// clip only 137 AudioStreamRole, /// clip only 138 AudioMultiStreamRole, /// clip only 139 AudioStreamIndexRole, /// clip only 140 IsCompositeRole, /// track only 141 IsLockedRole, /// track only 142 HeightRole, /// track only 143 TrackTagRole, /// track only 144 FadeInRole, /// clip only 145 FadeOutRole, /// clip only 146 FileHashRole, /// clip only 147 SpeedRole, /// clip only 148 ReloadThumbRole, /// clip only 149 PositionOffsetRole, /// clip only 150 TimeRemapRole, /// clip only 151 ItemATrack, /// composition only 152 ItemIdRole, 153 ThumbsFormatRole, /// track only 154 EffectNamesRole, /// track and clip only 155 EffectsEnabledRole, /// track and clip only 156 GrabbedRole, /// clip+composition only 157 SelectedRole, /// clip+composition only 158 TrackActiveRole, /// track only 159 AudioRecordRole, /// track only 160 EffectZonesRole /// track only 161 }; 162 163 ~TimelineModel() override; tractor() const164 Mlt::Tractor *tractor() const { return m_tractor.get(); } 165 /** @brief Load tracks from the current tractor, used on project opening 166 */ 167 void loadTractor(); 168 169 /** @brief Returns the current tractor's producer, useful for control seeking, playing, etc 170 */ 171 std::shared_ptr<Mlt::Producer> producer(); 172 Mlt::Profile *getProfile(); 173 174 /** @brief returns the number of tracks */ 175 int getTracksCount() const; 176 /** @brief returns the number of video and audio tracks */ 177 QPair<int, int> getAVtracksCount() const; 178 /** @brief returns the ids of all audio or video tracks */ 179 QList<int> getTracksIds(bool audio) const; 180 181 /** @brief returns the ids of all the tracks */ 182 std::unordered_set<int> getAllTracksIds() const; 183 184 /** @brief returns the track index (id) from its position */ 185 int getTrackIndexFromPosition(int pos) const; 186 187 /** @brief returns the track index (id) from its position */ 188 Q_INVOKABLE bool isAudioTrack(int trackId) const; 189 190 /** @brief returns the number of clips */ 191 int getClipsCount() const; 192 193 /** @brief returns the number of compositions */ 194 int getCompositionsCount() const; 195 196 /** @brief Returns the id of the track containing clip (-1 if it is not inserted) 197 @param clipId Id of the clip to test */ 198 Q_INVOKABLE int getClipTrackId(int clipId) const; 199 200 /** @brief Returns the id of the track containing composition (-1 if it is not inserted) 201 @param clipId Id of the composition to test */ 202 Q_INVOKABLE int getCompositionTrackId(int compoId) const; 203 204 /** @brief Convenience function that calls either of the previous ones based on item type*/ 205 Q_INVOKABLE int getItemTrackId(int itemId) const; 206 207 Q_INVOKABLE int getCompositionPosition(int compoId) const; 208 int getSubtitlePosition(int subId) const; 209 int getCompositionPlaytime(int compoId) const; 210 std::pair<int, int> getMixInOut(int cid) const; 211 int getMixDuration(int cid) const; 212 213 /** @brief Returns an item position, item can be clip or composition */ 214 Q_INVOKABLE int getItemPosition(int itemId) const; 215 /** @brief Returns an item duration, item can be clip or composition */ 216 int getItemPlaytime(int itemId) const; 217 218 /** @brief Returns the current speed of a clip */ 219 double getClipSpeed(int clipId) const; 220 221 /** @brief Helper function to query the amount of free space around a clip 222 * @param clipId: the queried clip. If it is not inserted on a track, this functions returns 0 223 * @param after: if true, we return the blank after the clip, otherwise, before. 224 */ 225 int getBlankSizeNearClip(int clipId, bool after) const; 226 227 /** @brief if the clip belongs to a AVSplit group, then return the id of the other corresponding clip. Otherwise, returns -1 */ 228 int getClipSplitPartner(int clipId) const; 229 230 /** @brief Helper function that returns true if the given ID corresponds to a clip */ 231 Q_INVOKABLE bool isClip(int id) const; 232 233 /** @brief Helper function that returns true if the given ID corresponds to a composition */ 234 Q_INVOKABLE bool isComposition(int id) const; 235 236 Q_INVOKABLE bool isSubTitle(int id) const; 237 238 /** @brief Helper function that returns true if the given ID corresponds to a timeline item (composition or clip) */ 239 Q_INVOKABLE bool isItem(int id) const; 240 241 /** @brief Helper function that returns true if the given ID corresponds to a track */ 242 Q_INVOKABLE bool isTrack(int id) const; 243 244 /** @brief Helper function that returns true if the given ID corresponds to a group */ 245 Q_INVOKABLE bool isGroup(int id) const; 246 247 /** @brief Given a composition Id, returns its underlying parameter model */ 248 std::shared_ptr<AssetParameterModel> getCompositionParameterModel(int compoId) const; 249 /** @brief Given a clip Id, returns its underlying effect stack model */ 250 std::shared_ptr<EffectStackModel> getClipEffectStackModel(int clipId) const; 251 /** @brief Given a clip Id, returns its mix transition stack model */ 252 std::shared_ptr<EffectStackModel> getClipMixStackModel(int clipId) const; 253 254 /** @brief Returns the position of clip (-1 if it is not inserted) 255 @param clipId Id of the clip to test 256 */ 257 Q_INVOKABLE int getClipPosition(int clipId) const; 258 Q_INVOKABLE bool addClipEffect(int clipId, const QString &effectId, bool notify = true); 259 Q_INVOKABLE bool addTrackEffect(int trackId, const QString &effectId); 260 bool removeFade(int clipId, bool fromStart); 261 Q_INVOKABLE bool copyClipEffect(int clipId, const QString &sourceId); 262 Q_INVOKABLE bool copyTrackEffect(int trackId, const QString &sourceId); 263 bool adjustEffectLength(int clipId, const QString &effectId, int duration, int initialDuration); 264 265 /** @brief Returns the closest snap point within snapDistance 266 */ 267 Q_INVOKABLE int suggestSnapPoint(int pos, int snapDistance); 268 269 /** @brief Return the previous track of same type as source trackId, or trackId if no track found */ 270 Q_INVOKABLE int getPreviousTrackId(int trackId); 271 /** @brief Return the next track of same type as source trackId, or trackId if no track found */ 272 Q_INVOKABLE int getNextTrackId(int trackId); 273 274 /** @brief Returns true if the clip has a mix composition at the end 275 @param clipId Id of the clip to test 276 */ 277 Q_INVOKABLE bool hasClipEndMix(int clipId) const; 278 279 /** @brief Returns the in cut position of a clip 280 @param clipId Id of the clip to test 281 */ 282 int getClipIn(int clipId) const; 283 /** @brief Returns the in and playtime of a clip 284 @param clipId Id of the clip to test 285 */ 286 QPoint getClipInDuration(int clipId) const; 287 288 /** @brief Returns the clip state (audio/video only) 289 */ 290 PlaylistState::ClipState getClipState(int clipId) const; 291 292 /** @brief Returns the bin id of the clip master 293 @param clipId Id of the clip to test 294 */ 295 const QString getClipBinId(int clipId) const; 296 297 /** @brief Returns the duration of a clip 298 @param clipId Id of the clip to test 299 */ 300 int getClipPlaytime(int clipId) const; 301 302 /** @brief Returns the size of the clip's frame (widthxheight) 303 @param clipId Id of the clip to test 304 */ 305 QSize getClipFrameSize(int clipId) const; 306 /** @brief Returns the number of clips in a given track 307 @param trackId Id of the track to test 308 */ 309 int getTrackClipsCount(int trackId) const; 310 311 /** @brief Returns the number of compositions in a given track 312 @param trackId Id of the track to test 313 */ 314 int getTrackCompositionsCount(int trackId) const; 315 316 /** @brief Returns the position of the track in the order of the tracks 317 @param trackId Id of the track to test 318 */ 319 int getTrackPosition(int trackId) const; 320 321 /** @brief Returns the track's index in terms of mlt's internal representation 322 */ 323 int getTrackMltIndex(int trackId) const; 324 /** @brief Returns a sort position for tracks. 325 * @param separated: if true, the tracks will be sorted like: V2,V1,A1,A2 326 * Otherwise, the tracks will be sorted like V2,A2,V1,A1 327 */ 328 int getTrackSortValue(int trackId, int separated) const; 329 330 /** @brief Returns the ids of the tracks below the given track in the order of the tracks 331 Returns an empty list if no track available 332 @param trackId Id of the track to test 333 */ 334 QList<int> getLowerTracksId(int trackId, TrackType type = TrackType::AnyTrack) const; 335 336 /** @brief Returns the MLT track index of the video track just below the given track 337 @param trackId Id of the track to test 338 */ 339 int getPreviousVideoTrackPos(int trackId) const; 340 /** @brief Returns the Track id of the video track just below the given track 341 @param trackId Id of the track to test 342 */ 343 int getPreviousVideoTrackIndex(int trackId) const; 344 345 /** @brief Returns the Id of the corresponding audio track. If trackId corresponds to video1, this will return audio 1 and so on */ 346 int getMirrorAudioTrackId(int trackId) const; 347 int getMirrorVideoTrackId(int trackId) const; 348 int getMirrorTrackId(int trackId) const; 349 350 /** @brief Sets a track in a given lock state 351 Locked tracks can't receive any operations (resize, move, insertion, deletion...) 352 @param trackId is of the track to alter 353 @param lock if true, the track will be locked, otherwise unlocked. 354 */ 355 Q_INVOKABLE void setTrackLockedState(int trackId, bool lock); 356 357 /** @brief Move a clip to a specific position 358 This action is undoable 359 Returns true on success. If it fails, nothing is modified. 360 If the clip is not in inserted in a track yet, it gets inserted for the first time. 361 If the clip is in a group, the call is deferred to requestGroupMove 362 @param clipId is the ID of the clip 363 @param trackId is the ID of the target track 364 @param position is the position where we want to move 365 @param updateView if set to false, no signal is sent to qml 366 @param logUndo if set to false, no undo object is stored 367 */ 368 Q_INVOKABLE bool requestClipMove(int clipId, int trackId, int position, bool moveMirrorTracks = true, bool updateView = true, bool logUndo = true, bool invalidateTimeline = false, bool revertMove = false); 369 Q_INVOKABLE bool requestSubtitleMove(int clipId, int position, bool updateView = true, bool logUndo = true, bool invalidateTimeline = false); 370 bool requestSubtitleMove(int clipId, int position, bool updateView, bool first, bool last, bool invalidateTimeline, Fun &undo, Fun &redo); 371 int cutSubtitle(int position, Fun &undo, Fun &redo); 372 bool requestClipMix(const QString &mixId, std::pair<int, int> clipIds, std::pair<int, int> mixDurations, int trackId, int position, bool updateView, bool invalidateTimeline, bool finalMove, Fun &undo, Fun &redo, bool groupMove); 373 374 /** @brief Move a composition to a specific position This action is undoable 375 Returns true on success. If it fails, nothing is modified. If the clip is 376 not in inserted in a track yet, it gets inserted for the first time. If 377 the clip is in a group, the call is deferred to requestGroupMove @param 378 transid is the ID of the composition @param trackId is the ID of the 379 track */ 380 Q_INVOKABLE bool requestCompositionMove(int compoId, int trackId, int position, bool updateView = true, bool logUndo = true); 381 382 /* Same function, but accumulates undo and redo, and doesn't check 383 for group*/ 384 bool requestClipMove(int clipId, int trackId, int position, bool moveMirrorTracks, bool updateView, bool invalidateTimeline, bool finalMove, Fun &undo, Fun &redo, bool revertMove = false, bool groupMove = false, QMap <int, int> moving_clips = QMap <int, int>(), std::pair<MixInfo, MixInfo>mixData = {}); 385 bool requestCompositionMove(int transid, int trackId, int compositionTrack, int position, bool updateView, bool finalMove, Fun &undo, Fun &redo); 386 387 /** @brief When timeline edit mode is insert or overwrite, we fake the move (as it will overlap existing clips, and only process the real move on drop */ 388 bool requestFakeClipMove(int clipId, int trackId, int position, bool updateView, bool invalidateTimeline, Fun &undo, Fun &redo); 389 bool requestFakeClipMove(int clipId, int trackId, int position, bool updateView, bool logUndo, bool invalidateTimeline); 390 bool requestFakeGroupMove(int clipId, int groupId, int delta_track, int delta_pos, bool updateView = true, bool logUndo = true); 391 bool requestFakeGroupMove(int clipId, int groupId, int delta_track, int delta_pos, bool updateView, bool finalMove, Fun &undo, Fun &redo, 392 bool allowViewRefresh = true); 393 394 /** @brief Given an intended move, try to suggest a more valid one 395 (accounting for snaps and missing UI calls) 396 @param clipId id of the clip to 397 move 398 @param trackId id of the target track 399 @param position target position 400 @param snapDistance the maximum distance for a snap result, -1 for no snapping 401 of the clip 402 @param dontRefreshMasterClip when false, no view refresh is attempted 403 @returns a list in the form {position, trackId} 404 */ 405 Q_INVOKABLE QVariantList suggestItemMove(int itemId, int trackId, int position, int cursorPosition, int snapDistance = -1); 406 Q_INVOKABLE QVariantList suggestClipMove(int clipId, int trackId, int position, int cursorPosition, int snapDistance = -1, bool moveMirrorTracks = true); 407 Q_INVOKABLE int suggestSubtitleMove(int subId, int position, int cursorPosition, int snapDistance); 408 Q_INVOKABLE QVariantList suggestCompositionMove(int compoId, int trackId, int position, int cursorPosition, int snapDistance = -1); 409 /** @brief returns the frame pos adjusted to edit mode 410 */ 411 Q_INVOKABLE int adjustFrame(int frame, int trackId); 412 413 /** @brief Request clip insertion at given position. This action is undoable 414 Returns true on success. If it fails, nothing is modified. 415 @param binClipId id of the clip in the bin 416 @param track Id of the track where to insert 417 @param position Requested position 418 @param ID return parameter of the id of the inserted clip 419 @param logUndo if set to false, no undo object is stored 420 @param refreshView whether the view should be refreshed 421 @param useTargets: if true, the Audio/video split will occur on the set targets. Otherwise, they will be computed as an offset from the middle line 422 */ 423 bool requestClipInsertion(const QString &binClipId, int trackId, int position, int &id, bool logUndo = true, bool refreshView = false, 424 bool useTargets = true); 425 /* Same function, but accumulates undo and redo*/ 426 bool requestClipInsertion(const QString &binClipId, int trackId, int position, int &id, bool logUndo, bool refreshView, bool useTargets, Fun &undo, 427 Fun &redo, QVector<int> allowedTracks = QVector<int>()); 428 429 /** @brief Switch current composition type 430 * @param cid the id of the composition we want to change 431 * @param compoId the name of the new composition we want to insert 432 */ 433 void switchComposition(int cid, const QString &compoId); 434 /** @brief Plant a same track composition in track tid 435 */ 436 void plantMix(int tid, Mlt::Transition *t); 437 bool removeMixWithUndo(int cid, Fun &undo, Fun &redo); 438 bool removeMix(int cid); 439 /** @brief Returns a list of the master effects zones 440 */ 441 QVariantList getMasterEffectZones() const; 442 /** @brief Returns a list of proxied clips at position pos 443 */ 444 QStringList getProxiesAt(int position); 445 446 protected: 447 /** @brief Creates a new clip instance without inserting it. 448 This action is undoable, returns true on success 449 @param binClipId: Bin id of the clip to insert 450 @param id: return parameter for the id of the newly created clip. 451 @param state: The desired clip state (original, audio/video only). 452 */ 453 bool requestClipCreation(const QString &binClipId, int &id, PlaylistState::ClipState state, int audioStream, double speed, bool warp_pitch, Fun &undo, Fun &redo); 454 455 /** @brief Switch item selection status */ 456 void setSelected(int itemId, bool sel); 457 458 public: 459 /** @brief Deletes the given clip or composition from the timeline. 460 This action is undoable. 461 Returns true on success. If it fails, nothing is modified. 462 If the clip/composition is in a group, the call is deferred to requestGroupDeletion 463 @param clipId is the ID of the clip/composition 464 @param logUndo if set to false, no undo object is stored */ 465 Q_INVOKABLE bool requestItemDeletion(int itemId, bool logUndo = true); 466 /* Same function, but accumulates undo and redo*/ 467 bool requestItemDeletion(int itemId, Fun &undo, Fun &redo, bool logUndo = false); 468 469 /** @brief Move a group to a specific position 470 This action is undoable 471 Returns true on success. If it fails, nothing is modified. 472 If the clips in the group are not in inserted in a track yet, they get inserted for the first time. 473 @param clipId is the id of the clip that triggers the group move 474 @param groupId is the id of the group 475 @param delta_track is the delta applied to the track index 476 @param delta_pos is the requested position change 477 @param updateView if set to false, no signal is sent to qml for the clip clipId 478 @param logUndo if set to true, an undo object is created 479 @param allowViewRefresh if false, the view will never get updated (useful for suggestMove) 480 */ 481 bool requestGroupMove(int itemId, int groupId, int delta_track, int delta_pos, bool moveMirrorTracks = true, bool updateView = true, bool logUndo = true, bool revertMove = false); 482 bool requestGroupMove(int itemId, int groupId, int delta_track, int delta_pos, bool updateView, bool finalMove, Fun &undo, Fun &redo, bool revertMove = false, bool moveMirrorTracks = true, 483 bool allowViewRefresh = true, QVector<int> allowedTracks = QVector<int>()); 484 485 /** @brief Deletes all clips inside the group that contains the given clip. 486 This action is undoable 487 Note that if their is a hierarchy of groups, all of them will be deleted. 488 Returns true on success. If it fails, nothing is modified. 489 @param clipId is the id of the clip that triggers the group deletion 490 */ 491 Q_INVOKABLE bool requestGroupDeletion(int clipId, bool logUndo = true); 492 bool requestGroupDeletion(int clipId, Fun &undo, Fun &redo); 493 494 /** @brief Change the duration of an item (clip or composition) 495 * This action is undoable 496 * Returns the real size reached (can be different, if snapping occurs). 497 * If it fails, nothing is modified, and -1 is returned 498 * @param itemId is the ID of the item 499 * @param size is the new size of the item 500 * @param right is true if we change the right side of the item, false otherwise 501 * @param logUndo if set to true, an undo object is created 502 * @param snap if set to true, the resize order will be coerced to use the snapping grid 503 * if @param allowSingleResize is false, then the resize will also be applied to any clip in the same AV group (allow resizing audio and video at the same 504 * time) 505 */ 506 Q_INVOKABLE int requestItemResize(int itemId, int size, bool right, bool logUndo = true, int snapDistance = -1, bool allowSingleResize = false); 507 508 /** @brief Same function, but accumulates undo and redo and doesn't deal with snapping*/ 509 bool requestItemResize(int itemId, int &size, bool right, bool logUndo, Fun &undo, Fun &redo, bool blockUndo = false); 510 511 /** @brief @todo TODO */ 512 int requestItemRippleResize(const std::shared_ptr<TimelineItemModel> &timeline, int itemId, int size, bool right, bool logUndo = true, int snapDistance = -1, bool allowSingleResize = false); 513 /** @brief @todo TODO */ 514 bool requestItemRippleResize(const std::shared_ptr<TimelineItemModel> &timeline, int itemId, int size, bool right, bool logUndo, Fun &undo, Fun &redo, bool blockUndo = false); 515 516 /** @brief Move ("slip") in and out point of a clip by the given offset 517 This action is undoable 518 @param itemId is the ID of the clip 519 @param offset is how many frames in and out point should be slipped 520 @param logUndo if set to true, an undo object is created 521 @param allowSingleResize is false, then the resize will also be applied to any clip in the same group 522 @return The request offset (can be different from real offset). If it fails, nothing is modified, and 0 is returned 523 */ 524 Q_INVOKABLE int requestClipSlip(int itemId, int offset, bool logUndo = true, bool allowSingleResize = false); 525 526 /** @brief Same function, but accumulates undo and redo 527 * @return If it fails, nothing is modified, and false is returned 528 */ 529 bool requestClipSlip(int itemId, int offset, bool logUndo, Fun &undo, Fun &redo, bool blockUndo = false); 530 531 /** @brief Slip all the clips of the current timeline selection 532 * @see requestClipSlip 533 */ 534 Q_INVOKABLE int requestSlipSelection(int offset, bool logUndo); 535 536 /** @brief Returns a proposed size for clip resize, checking for collisions */ 537 Q_INVOKABLE int requestItemSpeedChange(int itemId, int size, bool right, int snapDistance); 538 /** @brief Returns a list of {id, position duration} for all elements in the group*/ 539 Q_INVOKABLE const QVariantList getGroupData(int itemId); 540 Q_INVOKABLE void processGroupResize(QVariantList startPos, QVariantList endPos, bool right); 541 542 Q_INVOKABLE int requestClipResizeAndTimeWarp(int itemId, int size, bool right, int snapDistance, bool allowSingleResize, double speed); 543 544 /** @brief Group together a set of ids 545 The ids are either a group ids or clip ids. The involved clip must already be inserted in a track 546 This action is undoable 547 Returns the group id on success, -1 if it fails and nothing is modified. 548 Typically, ids would be ids of clips, but for convenience, some of them can be ids of groups as well. 549 @param ids Set of ids to group 550 */ 551 int requestClipsGroup(const std::unordered_set<int> &ids, bool logUndo = true, GroupType type = GroupType::Normal); 552 int requestClipsGroup(const std::unordered_set<int> &ids, Fun &undo, Fun &redo, GroupType type = GroupType::Normal); 553 554 /** @brief Destruct the topmost group containing clip 555 This action is undoable 556 Returns true on success. If it fails, nothing is modified. 557 @param id of the clip to degroup (all clips belonging to the same group will be ungrouped as well) 558 */ 559 bool requestClipUngroup(int itemId, bool logUndo = true); 560 /* Same function, but accumulates undo and redo*/ 561 bool requestClipUngroup(int itemId, Fun &undo, Fun &redo); 562 /** @brief convenience functions for several ids at the same time */ 563 bool requestClipsUngroup(const std::unordered_set<int> &itemIds, bool logUndo = true); 564 565 /** @brief Create a track at given position 566 This action is undoable 567 Returns true on success. If it fails, nothing is modified. 568 @param Requested position (order). If set to -1, the track is inserted last. 569 @param id is a return parameter that holds the id of the resulting track (-1 on failure) 570 */ 571 bool requestTrackInsertion(int pos, int &id, const QString &trackName = QString(), bool audioTrack = false); 572 /* Same function, but accumulates undo and redo*/ 573 bool requestTrackInsertion(int pos, int &id, const QString &trackName, bool audioTrack, Fun &undo, Fun &redo, bool addCompositing = true); 574 575 /** @brief Delete track with given id 576 This also deletes all the clips contained in the track. 577 This action is undoable 578 Returns true on success. If it fails, nothing is modified. 579 @param trackId id of the track to delete 580 */ 581 bool requestTrackDeletion(int trackId); 582 /** @brief Same function, but accumulates undo and redo*/ 583 bool requestTrackDeletion(int trackId, Fun &undo, Fun &redo); 584 585 /** @brief Get project duration 586 Returns the duration in frames 587 */ 588 int duration() const; 589 static int seekDuration; /// Duration after project end where seeking is allowed 590 591 /** @brief Get all the elements of the same group as the given clip. 592 If there is a group hierarchy, only the topmost group is considered. 593 @param clipId id of the clip to test 594 */ 595 std::unordered_set<int> getGroupElements(int clipId); 596 597 /** @brief Removes all the elements on the timeline (tracks and clips) 598 */ 599 bool requestReset(Fun &undo, Fun &redo); 600 /** @brief Updates the current the pointer to the current undo_stack 601 Must be called for example when the doc change 602 */ 603 void setUndoStack(std::weak_ptr<DocUndoStack> undo_stack); 604 605 protected: 606 /** @brief Requests the best snapped position for a clip 607 @param pos is the clip's requested position 608 @param length is the clip's duration 609 @param pts snap points to ignore (for example currently moved clip) 610 @param snapDistance the maximum distance for a snap result, -1 for no snapping 611 @returns best snap position or -1 if no snap point is near 612 */ 613 int getBestSnapPos(int referencePos, int diff, std::vector<int> pts = std::vector<int>(), int cursorPosition = 0, int snapDistance = -1); 614 615 /** @brief Returns the best possible size for a clip on resize 616 */ 617 int requestItemResizeInfo(int itemId, int in, int out, int size, bool right, int snapDistance); 618 619 /** @brief Returns a list of in/out of all items in the group of itemId 620 */ 621 const std::vector<int> getBoundaries(int itemId); 622 623 public: 624 /** @brief Requests the next snapped point 625 @param pos is the current position 626 */ 627 int getNextSnapPos(int pos, std::vector<int> &snaps); 628 629 /** @brief Requests the previous snapped point 630 @param pos is the current position 631 */ 632 int getPreviousSnapPos(int pos, std::vector<int> &snaps); 633 634 /** @brief Add a new snap point 635 @param pos is the current position 636 */ 637 void addSnap(int pos); 638 639 /** @brief Remove snap point 640 @param pos is the current position 641 */ 642 void removeSnap(int pos); 643 644 /** @brief Request composition insertion at given position. 645 This action is undoable 646 Returns true on success. If it fails, nothing is modified. 647 @param transitionId Identifier of the Mlt transition to insert (as given by repository) 648 @param track Id of the track where to insert 649 @param position Requested position 650 @param length Requested initial length. 651 @param id return parameter of the id of the inserted composition 652 @param logUndo if set to false, no undo object is stored 653 */ 654 bool requestCompositionInsertion(const QString &transitionId, int trackId, int position, int length, std::unique_ptr<Mlt::Properties> transProps, int &id, 655 bool logUndo = true); 656 /* Same function, but accumulates undo and redo*/ 657 bool requestCompositionInsertion(const QString &transitionId, int trackId, int compositionTrack, int position, int length, 658 std::unique_ptr<Mlt::Properties> transProps, int &id, Fun &undo, Fun &redo, bool finalMove = false, QString originalDecimalPoint = QString()); 659 660 /** @brief This function change the global (timeline-wise) enabled state of the effects 661 It disables/enables track and clip effects (recursively) 662 */ 663 void setTimelineEffectsEnabled(bool enabled); 664 665 /** @brief Get a timeline clip id by its position or -1 if not found 666 */ 667 int getClipByPosition(int trackId, int position) const; 668 int getClipByStartPosition(int trackId, int position) const; 669 670 /** @brief Get a timeline composition id by its starting position or -1 if not found 671 */ 672 int getCompositionByPosition(int trackId, int position) const; 673 /** @brief Get a timeline subtitle id by its starting position or -1 if not found 674 */ 675 int getSubtitleByStartPosition(int position) const; 676 int getSubtitleByPosition(int position) const; 677 678 /** @brief Returns a list of all items that are intersect with a given range. 679 * @param trackId is the id of the track for concerned items. Setting trackId to -1 returns items on all tracks 680 * @param start is the position where we the items should start 681 * @param end is the position after which items will not be selected, set to -1 to get all clips on track 682 * @param listCompositions if enabled, the list will also contains composition ids 683 */ 684 std::unordered_set<int> getItemsInRange(int trackId, int start, int end = -1, bool listCompositions = true); 685 /** @brief define current project's subtitle model */ 686 void setSubModel(std::shared_ptr<SubtitleModel> model); 687 688 /** @brief Returns a list of all luma files used in the project 689 */ 690 QStringList extractCompositionLumas() const; 691 /** @brief Returns a list of all external files used by effects in the timeline 692 */ 693 QStringList extractExternalEffectFiles() const; 694 /** @brief Inform asset view of duration change 695 */ 696 virtual void adjustAssetRange(int clipId, int in, int out); 697 698 void requestClipReload(int clipId, int forceDuration = -1); 699 void requestClipUpdate(int clipId, const QVector<int> &roles); 700 /** @brief define current edit mode (normal, insert, overwrite */ 701 void setEditMode(TimelineMode::EditMode mode); 702 TimelineMode::EditMode editMode() const; 703 Q_INVOKABLE bool normalEdit() const; 704 705 /** @brief Returns the effectstack of a given clip. */ 706 std::shared_ptr<EffectStackModel> getClipEffectStack(int itemId); 707 std::shared_ptr<EffectStackModel> getTrackEffectStackModel(int trackId); 708 std::shared_ptr<EffectStackModel> getMasterEffectStackModel(); 709 710 /** @brief Add slowmotion effect to clip in timeline. 711 @param clipId id of the target clip 712 @param speed: speed in percentage. 100 corresponds to original speed, 50 to half the speed 713 This functions create an undo object and also apply the effect to the corresponding audio if there is any. 714 Returns true on success, false otherwise (and nothing is modified) 715 */ 716 Q_INVOKABLE bool requestClipTimeWarp(int clipId, double speed, bool pitchCompensate, bool changeDuration); 717 /** @brief Same function as above, but doesn't check for paired audio and accumulate undo/redo 718 */ 719 bool requestClipTimeWarp(int clipId, double speed, bool pitchCompensate, bool changeDuration, Fun &undo, Fun &redo); 720 bool requestClipTimeRemap(int clipId, bool enable = true); 721 bool requestClipTimeRemap(int clipId, bool enable, Fun &undo, Fun &redo); 722 std::shared_ptr<Mlt::Producer> getClipProducer(int clipId); 723 724 void replugClip(int clipId); 725 726 /** @brief Refresh the tractor profile in case a change was requested. */ 727 void updateProfile(Mlt::Profile *profile); 728 729 /** @brief Clear the current selection 730 @param onDeletion is true when the selection is cleared as a result of a deletion 731 */ 732 Q_INVOKABLE bool requestClearSelection(bool onDeletion = false); 733 734 /** @brief On groups deletion, ensure the groups were not selected, clear selection otherwise 735 @param groups The group ids 736 */ 737 void clearGroupSelectionOnDelete(std::vector<int>groups); 738 // same function with undo/redo accumulation 739 void requestClearSelection(bool onDeletion, Fun &undo, Fun &redo); 740 741 /** @brief Select a given mix in timeline 742 @param cid clip id 743 */ 744 Q_INVOKABLE void requestMixSelection(int cid); 745 746 /** @brief Add the given item to the selection 747 If @param clear is true, the selection is first cleared 748 */ 749 Q_INVOKABLE void requestAddToSelection(int itemId, bool clear = false); 750 751 /** @brief Remove the given item from the selection */ 752 Q_INVOKABLE void requestRemoveFromSelection(int itemId); 753 754 /** @brief Set the selection to the set of given ids */ 755 bool requestSetSelection(const std::unordered_set<int> &ids); 756 // same function with undo/redo 757 bool requestSetSelection(const std::unordered_set<int> &ids, Fun &undo, Fun &redo); 758 759 /** @brief Returns a set containing all the items in the selection */ 760 std::unordered_set<int> getCurrentSelection() const; 761 762 /** @brief Do some cleanup before closing */ 763 void prepareClose(); 764 /** @brief Import project's master effects */ 765 void importMasterEffects(std::weak_ptr<Mlt::Service> service); 766 /** @brief Create a mix selection with currently selected clip. If delta = -1, mix with previous clip, +1 with next clip and 0 will check cursor position*/ 767 bool mixClip(int idToMove = -1, const QString &mixId = QStringLiteral("luma"), int delta = 0); 768 Q_INVOKABLE bool resizeStartMix(int cid, int duration, bool singleResize); 769 void requestResizeMix(int cid, int duration, MixAlignment align, int rightFrames = -1); 770 /** @brief Get Mix cut pos (the duration of the mix on the right clip) */ 771 int getMixCutPos(int cid) const; 772 MixAlignment getMixAlign(int cid) const; 773 std::shared_ptr<SubtitleModel> getSubtitleModel(); 774 /** @brief Get the frame size of the clip above a composition */ 775 const QSize getCompositionSizeOnTrack(const ObjectId &id); 776 /** @brief Get a track tag (A1, V1, V2,...) through its id */ 777 const QString getTrackTagById(int trackId) const; 778 /** @brief returns true if track is empty at position on playlist */ 779 bool trackIsBlankAt(int tid, int pos, int playlist) const; 780 /** @brief returns true if track is empty at position on playlist */ 781 bool trackIsAvailable(int tid, int pos, int duration, int playlist) const; 782 /** @brief returns the position of the clip start on a playlist */ 783 int getClipStartAt(int tid, int pos, int playlist) const; 784 int getClipEndAt(int tid, int pos, int playlist) const; 785 786 protected: 787 /** @brief Register a new track. This is a call-back meant to be called from TrackModel 788 @param pos indicates the number of the track we are adding. If this is -1, then we add at the end. 789 */ 790 void registerTrack(std::shared_ptr<TrackModel> track, int pos = -1, bool doInsert = true); 791 792 /** @brief Register a new clip. This is a call-back meant to be called from ClipModel 793 */ 794 void registerClip(const std::shared_ptr<ClipModel> &clip, bool registerProducer = false); 795 796 /** @brief Register a new composition. This is a call-back meant to be called from CompositionModel 797 */ 798 void registerComposition(const std::shared_ptr<CompositionModel> &composition); 799 800 void registerSubtitle(int id, GenTime startTime, bool temporary = false); 801 void deregisterSubtitle(int id, bool temporary = false); 802 /** @brief Returns the index for a subtitle's id (it's position in the list 803 */ 804 int positionForIndex(int id); 805 806 /** @brief Register a new group. This is a call-back meant to be called from GroupsModel 807 */ 808 void registerGroup(int groupId); 809 810 /** @brief Deregister and destruct the track with given id. 811 @param updateView Whether to send updates to the model. Must be false when called from a constructor/destructor 812 */ 813 Fun deregisterTrack_lambda(int id); 814 815 /** @brief Return a lambda that deregisters and destructs the clip with given id. 816 Note that the clip must already be deleted from its track and groups. 817 */ 818 Fun deregisterClip_lambda(int id); 819 820 /** @brief Return a lambda that deregisters and destructs the composition with given id. 821 */ 822 Fun deregisterComposition_lambda(int compoId); 823 824 /** @brief Deregister a group with given id 825 */ 826 void deregisterGroup(int id); 827 828 /** @brief Helper function to get a pointer to the track, given its id 829 */ 830 std::shared_ptr<TrackModel> getTrackById(int trackId); 831 const std::shared_ptr<TrackModel> getTrackById_const(int trackId) const; 832 833 /** @brief Helper function to get a pointer to a clip, given its id*/ 834 std::shared_ptr<ClipModel> getClipPtr(int clipId) const; 835 836 /** @brief Helper function to get a pointer to a composition, given its id*/ 837 std::shared_ptr<CompositionModel> getCompositionPtr(int compoId) const; 838 839 /** @brief Returns next valid unique id to create an object 840 */ 841 static int getNextId(); 842 843 /** @brief unplant and the replant all the compositions in the correct order 844 @param currentCompo is the id of a compo that have not yet been planted, if any. Otherwise send -1 845 */ 846 bool replantCompositions(int currentCompo, bool updateView); 847 848 /** @brief Unplant the composition with given Id */ 849 bool unplantComposition(int compoId); 850 851 /** @brief Internal functions to delete a clip or a composition. In general, you should call requestItemDeletion */ 852 bool requestClipDeletion(int clipId, Fun &undo, Fun &redo); 853 bool requestCompositionDeletion(int compositionId, Fun &undo, Fun &redo); 854 bool requestSubtitleDeletion(int clipId, Fun &undo, Fun &redo, bool first, bool last); 855 856 /** @brief Check tracks duration and update black track accordingly */ 857 void updateDuration(); 858 859 /** @brief Attempt to make a clip move without ever updating the view */ 860 bool requestClipMoveAttempt(int clipId, int trackId, int position); 861 862 int getSubtitleIndex(int subId) const; 863 std::pair<int, GenTime> getSubtitleIdFromIndex(int index) const; 864 865 public: 866 /** @brief Debugging function that checks consistency with Mlt objects */ 867 bool checkConsistency(); 868 869 protected: 870 /** @brief Refresh project monitor if cursor was inside range */ 871 void checkRefresh(int start, int end); 872 873 /** @brief Send signal to require clearing effet/composition view */ 874 void clearAssetView(int itemId); 875 876 bool m_blockRefresh; 877 878 signals: 879 /** @brief signal triggered by clearAssetView */ 880 void requestClearAssetView(int); 881 void requestMonitorRefresh(); 882 /** @brief signal triggered by track operations */ 883 void invalidateZone(int in, int out); 884 /** @brief signal triggered when a track duration changed (insertion/deletion) */ 885 void durationUpdated(); 886 887 /** @brief Signal sent whenever the selection changes */ 888 void selectionChanged(); 889 /** @brief Signal sent whenever the selected mix changes */ 890 void selectedMixChanged(int cid, const std::shared_ptr<AssetParameterModel> &asset, bool refreshOnly = false); 891 /** @brief Signal when a track is deleted so we make sure we don't store its id */ 892 void checkTrackDeletion(int tid); 893 /** @brief Emitted when a clip is deleted to check if it was not used in timeline qml */ 894 void checkItemDeletion(int cid); 895 896 protected: 897 std::unique_ptr<Mlt::Tractor> m_tractor; 898 std::shared_ptr<EffectStackModel> m_masterStack; 899 std::shared_ptr<Mlt::Service> m_masterService; 900 std::list<std::shared_ptr<TrackModel>> m_allTracks; 901 902 std::unordered_map<int, std::list<std::shared_ptr<TrackModel>>::iterator> 903 m_iteratorTable; // this logs the iterator associated which each track id. This allows easy access of a track based on its id. 904 905 std::unordered_map<int, std::shared_ptr<ClipModel>> m_allClips; // the keys are the clip id, and the values are the corresponding pointers 906 907 std::unordered_map<int, std::shared_ptr<CompositionModel>> 908 m_allCompositions; // the keys are the composition id, and the values are the corresponding pointers 909 910 std::map<int, GenTime> m_allSubtitles; 911 912 static int next_id; /// next valid id to assign 913 914 std::unique_ptr<GroupsModel> m_groups; 915 std::shared_ptr<SnapModel> m_snaps; 916 std::shared_ptr<SubtitleModel> m_subtitleModel; 917 918 std::unordered_set<int> m_allGroups; /// ids of all the groups 919 920 std::weak_ptr<DocUndoStack> m_undoStack; 921 922 Mlt::Profile *m_profile; 923 924 // The black track producer. Its length / out should always be adjusted to the projects's length 925 std::unique_ptr<Mlt::Producer> m_blackClip; 926 927 mutable QReadWriteLock m_lock; // This is a lock that ensures safety in case of concurrent access 928 929 bool m_timelineEffectsEnabled; 930 931 bool m_id; // id of the timeline itself 932 933 /** @brief id of the selection. If -1, there is no selection, if positive, then it might either be the id of the selection group, or the id of an individual 934 * item, or, finally, the id of a group which is not of type selection. The last case happens when the selection exactly matches an existing group 935 * (in that case we cannot further group it because the selection would have only one child, which is prohibited by design) */ 936 int m_currentSelection = -1; 937 int m_selectedMix = -1; 938 939 /// The index of the temporary overlay track in tractor, or -1 if not connected 940 int m_overlayTrackCount; 941 942 /// The preferred audio target for clip insertion in the form {timeline track id, bin clip stream index} 943 QMap <int, int> m_audioTarget; 944 /** @brief The list of audio streams available from the selected bin clip, in the form: {stream index, stream description} */ 945 QMap <int, QString> m_binAudioTargets; 946 /// The preferred video target for clip insertion or -1 if not defined 947 int m_videoTarget; 948 /// Timeline editing mode 949 TimelineMode::EditMode m_editMode; 950 bool m_closing; 951 952 // what follows are some virtual function that corresponds to the QML. They are implemented in TimelineItemModel 953 protected: 954 /** @brief Rebuild track compositing */ 955 virtual void buildTrackCompositing(bool rebuild = false) = 0; 956 virtual void _beginRemoveRows(const QModelIndex &, int, int) = 0; 957 virtual void _beginInsertRows(const QModelIndex &, int, int) = 0; 958 virtual void _endRemoveRows() = 0; 959 virtual void _endInsertRows() = 0; 960 virtual void notifyChange(const QModelIndex &topleft, const QModelIndex &bottomright, bool start, bool duration, bool updateThumb) = 0; 961 virtual void notifyChange(const QModelIndex &topleft, const QModelIndex &bottomright, const QVector<int> &roles) = 0; 962 virtual void notifyChange(const QModelIndex &topleft, const QModelIndex &bottomright, int role) = 0; 963 virtual QModelIndex makeClipIndexFromID(int) const = 0; 964 virtual QModelIndex makeCompositionIndexFromID(int) const = 0; 965 virtual QModelIndex makeTrackIndexFromID(int) const = 0; 966 virtual void _resetView() = 0; 967 }; 968 #endif 969