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