Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Kdenlive
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
259
Issues
259
List
Boards
Labels
Service Desk
Milestones
Merge Requests
14
Merge Requests
14
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Operations
Operations
Incidents
Environments
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Multimedia
Kdenlive
Commits
ba564eda
Commit
ba564eda
authored
Aug 28, 2020
by
Jean-Baptiste Mardelle
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Initial commit for same track transitions
parent
e83f8dc0
Changes
12
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
398 additions
and
21 deletions
+398
-21
src/mainwindow.cpp
src/mainwindow.cpp
+6
-0
src/timeline2/model/clipmodel.cpp
src/timeline2/model/clipmodel.cpp
+12
-0
src/timeline2/model/clipmodel.hpp
src/timeline2/model/clipmodel.hpp
+5
-0
src/timeline2/model/timelineitemmodel.cpp
src/timeline2/model/timelineitemmodel.cpp
+3
-0
src/timeline2/model/timelinemodel.cpp
src/timeline2/model/timelinemodel.cpp
+80
-0
src/timeline2/model/timelinemodel.hpp
src/timeline2/model/timelinemodel.hpp
+3
-0
src/timeline2/model/trackmodel.cpp
src/timeline2/model/trackmodel.cpp
+202
-21
src/timeline2/model/trackmodel.hpp
src/timeline2/model/trackmodel.hpp
+25
-0
src/timeline2/view/qml/Clip.qml
src/timeline2/view/qml/Clip.qml
+11
-0
src/timeline2/view/qml/Track.qml
src/timeline2/view/qml/Track.qml
+6
-0
src/timeline2/view/timelinecontroller.cpp
src/timeline2/view/timelinecontroller.cpp
+43
-0
src/timeline2/view/timelinecontroller.h
src/timeline2/view/timelinecontroller.h
+2
-0
No files found.
src/mainwindow.cpp
View file @
ba564eda
...
...
@@ -1131,6 +1131,12 @@ void MainWindow::setupActions()
addAction
(
QStringLiteral
(
"collapse_expand"
),
collapseItem
,
Qt
::
Key_Less
);
connect
(
collapseItem
,
&
QAction
::
triggered
,
this
,
&
MainWindow
::
slotCollapse
);
QAction
*
sameTrack
=
new
QAction
(
QIcon
::
fromTheme
(
QStringLiteral
(
"collapse-all"
)),
i18n
(
"Same Track"
),
this
);
addAction
(
QStringLiteral
(
"same_track"
),
sameTrack
,
Qt
::
Key_U
);
connect
(
sameTrack
,
&
QAction
::
triggered
,
[
this
]()
{
getCurrentTimeline
()
->
controller
()
->
sameTrack
();
});
// toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly);
/*QWidget * actionWidget;
...
...
src/timeline2/model/clipmodel.cpp
View file @
ba564eda
...
...
@@ -45,6 +45,8 @@ ClipModel::ClipModel(const std::shared_ptr<TimelineModel> &parent, std::shared_p
,
m_speed
(
speed
)
,
m_fakeTrack
(
-
1
)
,
m_positionOffset
(
0
)
,
m_subPlaylistIndex
(
0
)
,
m_mixDuration
(
0
)
{
m_producer
->
set
(
"kdenlive:id"
,
binClipId
.
toUtf8
().
constData
());
m_producer
->
set
(
"_kdenlive_cid"
,
m_id
);
...
...
@@ -648,6 +650,16 @@ void ClipModel::setPosition(int pos)
m_clipMarkerModel
->
updateSnapModelPos
(
pos
);
}
void
ClipModel
::
setMixDuration
(
int
mix
)
{
m_mixDuration
=
mix
;
}
int
ClipModel
::
getMixDuration
()
const
{
return
m_mixDuration
;
}
void
ClipModel
::
setInOut
(
int
in
,
int
out
)
{
MoveableItem
::
setInOut
(
in
,
out
);
...
...
src/timeline2/model/clipmodel.hpp
View file @
ba564eda
...
...
@@ -101,6 +101,8 @@ public:
void
setFakeTrackId
(
int
fid
);
int
getFakePosition
()
const
;
void
setFakePosition
(
int
fid
);
void
setMixDuration
(
int
mix
);
int
getMixDuration
()
const
;
void
setGrab
(
bool
grab
)
override
;
void
setSelected
(
bool
sel
)
override
;
...
...
@@ -248,6 +250,9 @@ protected:
// Remember last set track, so that we don't unnecessarily refresh the producer when deleting and re-adding a clip on same track
int
m_lastTrackId
=
-
1
;
// Duration of a same track mix.
int
m_mixDuration
;
};
#endif
src/timeline2/model/timelineitemmodel.cpp
View file @
ba564eda
...
...
@@ -197,6 +197,7 @@ QHash<int, QByteArray> TimelineItemModel::roleNames() const
roles
[
FakeTrackIdRole
]
=
"fakeTrackId"
;
roles
[
FakePositionRole
]
=
"fakePosition"
;
roles
[
StartRole
]
=
"start"
;
roles
[
MixRole
]
=
"mixDuration"
;
roles
[
DurationRole
]
=
"duration"
;
roles
[
MaxDurationRole
]
=
"maxDuration"
;
roles
[
MarkersRole
]
=
"markers"
;
...
...
@@ -333,6 +334,8 @@ QVariant TimelineItemModel::data(const QModelIndex &index, int role) const
return
clip
->
fadeIn
();
case
FadeOutRole
:
return
clip
->
fadeOut
();
case
MixRole
:
return
clip
->
getMixDuration
();
case
ReloadThumbRole
:
return
clip
->
forceThumbReload
;
case
PositionOffsetRole
:
...
...
src/timeline2/model/timelinemodel.cpp
View file @
ba564eda
...
...
@@ -609,6 +609,40 @@ bool TimelineModel::requestClipMove(int clipId, int trackId, int position, bool
return
true
;
};
}
Fun
move_mix
=
[]()
{
return
true
;
};
Fun
restore_mix
=
[]()
{
return
true
;
};
if
(
m_allClips
[
clipId
]
->
getMixDuration
()
>
0
)
{
std
::
pair
<
int
,
int
>
mixData
=
getTrackById_const
(
old_trackId
)
->
getMixInfo
(
clipId
);
int
offset
=
position
-
mixData
.
first
;
qDebug
()
<<
"==== MIX UPDATED: "
<<
mixData
.
second
<<
", OFFSET: "
<<
offset
;
if
(
finalMove
&&
(
old_trackId
!=
trackId
||
position
>=
mixData
.
first
+
mixData
.
second
))
{;
// Clip moved to another track, or outside of mix duration, delete mix
move_mix
=
[
this
,
old_trackId
,
clipId
]()
{
qDebug
()
<<
"======
\n
RESETTING SUB PLAYLIST
\n
===="
;
m_allClips
[
clipId
]
->
setSubPlaylistIndex
(
0
);
return
getTrackById_const
(
old_trackId
)
->
deleteMix
(
clipId
);
};
restore_mix
=
[
this
,
old_trackId
,
clipId
,
mixData
]()
{
m_allClips
[
clipId
]
->
setSubPlaylistIndex
(
1
);
return
getTrackById_const
(
old_trackId
)
->
createMix
(
clipId
,
mixData
);
};
qDebug
()
<<
"========
\n\n\n
DELETED MIX
\n\n
================"
;
move_mix
();
UPDATE_UNDO_REDO
(
move_mix
,
restore_mix
,
local_undo
,
local_redo
);
}
else
if
(
old_trackId
==
trackId
)
{
// Clip moved on same track, resize mix
move_mix
=
[
this
,
old_trackId
,
clipId
,
position
]()
{
return
getTrackById_const
(
old_trackId
)
->
resizeMix
(
clipId
,
position
);
};
restore_mix
=
[
this
,
old_trackId
,
clipId
,
position
]()
{
return
getTrackById_const
(
old_trackId
)
->
resizeMix
(
clipId
,
position
);
};
move_mix
();
UPDATE_UNDO_REDO
(
move_mix
,
restore_mix
,
local_undo
,
local_redo
);
}
}
if
(
old_trackId
!=
-
1
)
{
if
(
notifyViewOnly
)
{
PUSH_LAMBDA
(
update_model
,
local_undo
);
...
...
@@ -635,6 +669,52 @@ bool TimelineModel::requestClipMove(int clipId, int trackId, int position, bool
return
true
;
}
bool
TimelineModel
::
requestClipMixMove
(
int
clipId
,
int
trackId
,
int
position
,
bool
updateView
,
bool
invalidateTimeline
,
bool
finalMove
,
Fun
&
undo
,
Fun
&
redo
,
bool
groupMove
)
{
// qDebug() << "// FINAL MOVE: " << invalidateTimeline << ", UPDATE VIEW: " << updateView<<", FINAL: "<<finalMove;
if
(
trackId
==
-
1
)
{
return
false
;
}
Q_ASSERT
(
isClip
(
clipId
));
std
::
function
<
bool
(
void
)
>
local_undo
=
[]()
{
return
true
;
};
std
::
function
<
bool
(
void
)
>
local_redo
=
[]()
{
return
true
;
};
bool
ok
=
true
;
bool
notifyViewOnly
=
false
;
Fun
update_model
=
[]()
{
return
true
;
};
// Move on same track, simply inform the view
updateView
=
false
;
notifyViewOnly
=
true
;
update_model
=
[
clipId
,
this
,
trackId
,
invalidateTimeline
]()
{
qDebug
()
<<
"==== PROCESSING UPDATE MODEL"
;
QModelIndex
modelIndex
=
makeClipIndexFromID
(
clipId
);
notifyChange
(
modelIndex
,
modelIndex
,
StartRole
);
if
(
invalidateTimeline
&&
!
getTrackById_const
(
trackId
)
->
isAudioTrack
())
{
int
in
=
getClipPosition
(
clipId
);
emit
invalidateZone
(
in
,
in
+
getClipPlaytime
(
clipId
));
}
return
true
;
};
if
(
notifyViewOnly
)
{
PUSH_LAMBDA
(
update_model
,
local_undo
);
}
ok
=
getTrackById
(
trackId
)
->
requestClipMix
(
clipId
,
position
,
updateView
,
finalMove
,
local_undo
,
local_redo
,
groupMove
);
if
(
!
ok
)
{
qDebug
()
<<
"-------------
\n
MIX FAILED, REVERTING
\n\n
-------------------"
;
bool
undone
=
local_undo
();
Q_ASSERT
(
undone
);
return
false
;
}
update_model
();
if
(
notifyViewOnly
)
{
PUSH_LAMBDA
(
update_model
,
local_redo
);
}
qDebug
()
<<
"======== FINISHED MIX CREATION FOR CLIP: "
<<
clipId
;
UPDATE_UNDO_REDO
(
local_redo
,
local_undo
,
undo
,
redo
);
return
ok
;
}
bool
TimelineModel
::
requestFakeClipMove
(
int
clipId
,
int
trackId
,
int
position
,
bool
updateView
,
bool
logUndo
,
bool
invalidateTimeline
)
{
QWriteLocker
locker
(
&
m_lock
);
...
...
src/timeline2/model/timelinemodel.hpp
View file @
ba564eda
...
...
@@ -117,6 +117,7 @@ public:
IsProxyRole
,
/// clip only
ServiceRole
,
/// clip only
StartRole
,
/// clip only
MixRole
,
/// clip only
BinIdRole
,
/// clip only
TrackIdRole
,
FakeTrackIdRole
,
...
...
@@ -354,6 +355,8 @@ public:
@param logUndo if set to false, no undo object is stored
*/
Q_INVOKABLE
bool
requestClipMove
(
int
clipId
,
int
trackId
,
int
position
,
bool
moveMirrorTracks
=
true
,
bool
updateView
=
true
,
bool
logUndo
=
true
,
bool
invalidateTimeline
=
false
);
bool
requestClipMixMove
(
int
clipId
,
int
trackId
,
int
position
,
bool
updateView
,
bool
invalidateTimeline
,
bool
finalMove
,
Fun
&
undo
,
Fun
&
redo
,
bool
groupMove
);
/* @brief Move a composition to a specific position This action is undoable
Returns true on success. If it fails, nothing is modified. If the clip is
...
...
src/timeline2/model/trackmodel.cpp
View file @
ba564eda
...
...
@@ -23,6 +23,7 @@
#include "clipmodel.hpp"
#include "compositionmodel.hpp"
#include "effects/effectstack/model/effectstackmodel.hpp"
#include "transitions/transitionsrepository.hpp"
#include "kdenlivesettings.h"
#include "logger.hpp"
#include "snapmodel.hpp"
...
...
@@ -53,7 +54,7 @@ TrackModel::TrackModel(const std::weak_ptr<TimelineModel> &parent, int id, const
}
}
// For now we never use the second playlist, only planned for same track transitions
m_playlists
[
1
].
set
(
"hide"
,
3
);
//
m_playlists[1].set("hide", 3);
m_track
->
set
(
"kdenlive:trackheight"
,
KdenliveSettings
::
trackheight
());
m_effectStack
=
EffectStackModel
::
construct
(
m_mainPlaylist
,
{
ObjectType
::
TimelineTrack
,
m_id
},
ptr
->
m_undoStack
);
// TODO
...
...
@@ -129,19 +130,23 @@ int TrackModel::getClipsCount()
Fun
TrackModel
::
requestClipInsertion_lambda
(
int
clipId
,
int
position
,
bool
updateView
,
bool
finalMove
,
bool
groupMove
)
{
QWriteLocker
locker
(
&
m_lock
);
qDebug
()
<<
"== PROCESSING INSRET OF_: "
<<
clipId
;
// By default, insertion occurs in topmost track
// Find out the clip id at position
int
target_clip
=
m_playlists
[
0
].
get_clip_index_at
(
position
);
int
count
=
m_playlists
[
0
].
count
();
int
target_playlist
=
0
;
if
(
auto
ptr
=
m_parent
.
lock
())
{
Q_ASSERT
(
ptr
->
getClipPtr
(
clipId
)
->
getCurrentTrackId
()
==
-
1
);
target_playlist
=
ptr
->
getClipPtr
(
clipId
)
->
getSubPlaylistIndex
();
qDebug
()
<<
"==== GOT TRARGET PLAYLIST: "
<<
target_playlist
;
}
else
{
qDebug
()
<<
"impossible to get parent timeline"
;
Q_ASSERT
(
false
);
}
// Find out the clip id at position
int
target_clip
=
m_playlists
[
target_playlist
].
get_clip_index_at
(
position
);
int
count
=
m_playlists
[
target_playlist
].
count
();
// we create the function that has to be executed after the melt order. This is essentially book-keeping
auto
end_function
=
[
clipId
,
this
,
position
,
updateView
,
finalMove
](
int
subPlaylist
)
{
auto
end_function
=
[
clipId
,
this
,
position
,
updateView
,
finalMove
,
target_playlist
](
int
subPlaylist
)
{
if
(
auto
ptr
=
m_parent
.
lock
())
{
std
::
shared_ptr
<
ClipModel
>
clip
=
ptr
->
getClipPtr
(
clipId
);
m_allClips
[
clip
->
getId
()]
=
clip
;
// store clip
...
...
@@ -170,46 +175,46 @@ Fun TrackModel::requestClipInsertion_lambda(int clipId, int position, bool updat
qDebug
()
<<
"Error : Clip Insertion failed because timeline is not available anymore"
;
return
false
;
};
if
(
target_clip
>=
count
&&
isBlankA
t
(
position
))
{
if
(
target_clip
>=
count
&&
m_playlists
[
target_playlist
].
is_blank_a
t
(
position
))
{
// In that case, we append after, in the first playlist
return
[
this
,
position
,
clipId
,
end_function
,
finalMove
,
groupMove
]()
{
return
[
this
,
position
,
clipId
,
end_function
,
finalMove
,
groupMove
,
target_playlist
]()
{
if
(
isLocked
())
return
false
;
if
(
auto
ptr
=
m_parent
.
lock
())
{
// Lock MLT playlist so that we don't end up with an invalid frame being displayed
m_playlists
[
0
].
lock
();
m_playlists
[
target_playlist
].
lock
();
std
::
shared_ptr
<
ClipModel
>
clip
=
ptr
->
getClipPtr
(
clipId
);
clip
->
setCurrentTrackId
(
m_id
,
finalMove
);
int
index
=
m_playlists
[
0
].
insert_at
(
position
,
*
clip
,
1
);
m_playlists
[
0
].
consolidate_blanks
();
m_playlists
[
0
].
unlock
();
int
index
=
m_playlists
[
target_playlist
].
insert_at
(
position
,
*
clip
,
1
);
m_playlists
[
target_playlist
].
consolidate_blanks
();
m_playlists
[
target_playlist
].
unlock
();
if
(
finalMove
&&
!
groupMove
)
{
ptr
->
updateDuration
();
}
return
index
!=
-
1
&&
end_function
(
0
);
return
index
!=
-
1
&&
end_function
(
target_playlist
);
}
qDebug
()
<<
"Error : Clip Insertion failed because timeline is not available anymore"
;
return
false
;
};
}
if
(
isBlankA
t
(
position
))
{
int
blank_end
=
getBlankEnd
(
position
);
if
(
m_playlists
[
target_playlist
].
is_blank_a
t
(
position
))
{
int
blank_end
=
getBlankEnd
(
position
,
target_playlist
);
int
length
=
-
1
;
if
(
auto
ptr
=
m_parent
.
lock
())
{
std
::
shared_ptr
<
ClipModel
>
clip
=
ptr
->
getClipPtr
(
clipId
);
length
=
clip
->
getPlaytime
();
}
if
(
blank_end
>=
position
+
length
)
{
return
[
this
,
position
,
clipId
,
end_function
]()
{
return
[
this
,
position
,
clipId
,
end_function
,
target_playlist
]()
{
if
(
isLocked
())
return
false
;
if
(
auto
ptr
=
m_parent
.
lock
())
{
// Lock MLT playlist so that we don't end up with an invalid frame being displayed
m_playlists
[
0
].
lock
();
m_playlists
[
target_playlist
].
lock
();
std
::
shared_ptr
<
ClipModel
>
clip
=
ptr
->
getClipPtr
(
clipId
);
clip
->
setCurrentTrackId
(
m_id
);
int
index
=
m_playlists
[
0
].
insert_at
(
position
,
*
clip
,
1
);
m_playlists
[
0
].
consolidate_blanks
();
m_playlists
[
0
].
unlock
();
return
index
!=
-
1
&&
end_function
(
0
);
int
index
=
m_playlists
[
target_playlist
].
insert_at
(
position
,
*
clip
,
1
);
m_playlists
[
target_playlist
].
consolidate_blanks
();
m_playlists
[
target_playlist
].
unlock
();
return
index
!=
-
1
&&
end_function
(
target_playlist
);
}
qDebug
()
<<
"Error : Clip Insertion failed because timeline is not available anymore"
;
return
false
;
...
...
@@ -326,7 +331,7 @@ Fun TrackModel::requestClipDeletion_lambda(int clipId, bool updateView, bool fin
if
(
prod
!=
nullptr
)
{
m_playlists
[
target_track
].
consolidate_blanks
();
m_allClips
[
clipId
]
->
setCurrentTrackId
(
-
1
);
m_allClips
[
clipId
]
->
setSubPlaylistIndex
(
-
1
);
//
m_allClips[clipId]->setSubPlaylistIndex(-1);
m_allClips
.
erase
(
clipId
);
delete
prod
;
m_playlists
[
target_track
].
unlock
();
...
...
@@ -1352,3 +1357,179 @@ bool TrackModel::isAvailable(int position, int duration)
}
return
m_playlists
[
0
].
is_blank
(
start_clip
);
}
bool
TrackModel
::
requestClipMix
(
int
clipId
,
int
position
,
bool
updateView
,
bool
finalMove
,
Fun
&
undo
,
Fun
&
redo
,
bool
groupMove
)
{
QWriteLocker
locker
(
&
m_lock
);
// By default, insertion occurs in topmost track
// Find out the clip id at position
int
clipInitialPos
;
int
source_track
;
MixInfo
mixInfo
;
if
(
auto
ptr
=
m_parent
.
lock
())
{
// The clip that will be moved to playlist 1
std
::
shared_ptr
<
ClipModel
>
movedClip
(
ptr
->
getClipPtr
(
clipId
));
source_track
=
movedClip
->
getSubPlaylistIndex
();
clipInitialPos
=
movedClip
->
getPosition
();
mixInfo
.
mixDuration
=
clipInitialPos
-
position
;
mixInfo
.
mixPosition
=
position
;
}
else
{
// Error, timeline unavailable
return
false
;
}
int
dest_track
=
1
;
if
(
source_track
==
1
)
{
dest_track
=
0
;
}
// Create mix compositing
Fun
build_mix
=
[
clipId
,
mixInfo
,
this
]()
{
if
(
auto
ptr
=
m_parent
.
lock
())
{
std
::
shared_ptr
<
ClipModel
>
movedClip
(
ptr
->
getClipPtr
(
clipId
));
movedClip
->
setMixDuration
(
mixInfo
.
mixDuration
);
QModelIndex
ix
=
ptr
->
makeClipIndexFromID
(
clipId
);
emit
ptr
->
dataChanged
(
ix
,
ix
,
{
TimelineModel
::
StartRole
,
TimelineModel
::
MixRole
});
// Insert mix transition
if
(
isAudioTrack
())
{
std
::
shared_ptr
<
Mlt
::
Transition
>
t
(
new
Mlt
::
Transition
(
*
ptr
->
getProfile
(),
"mix"
));
t
->
set_in_and_out
(
mixInfo
.
mixPosition
,
mixInfo
.
mixPosition
+
mixInfo
.
mixDuration
);
m_track
->
plant_transition
(
*
t
.
get
(),
0
,
1
);
m_sameCompositions
[
clipId
]
=
t
;
}
else
{
std
::
shared_ptr
<
Mlt
::
Transition
>
t
(
new
Mlt
::
Transition
(
*
ptr
->
getProfile
(),
"luma"
));
t
->
set_in_and_out
(
mixInfo
.
mixPosition
,
mixInfo
.
mixPosition
+
mixInfo
.
mixDuration
);
m_track
->
plant_transition
(
*
t
.
get
(),
0
,
1
);
m_sameCompositions
[
clipId
]
=
t
;
}
}
return
true
;
};
Fun
destroy_mix
=
[
clipId
,
mixInfo
,
this
]()
{
if
(
auto
ptr
=
m_parent
.
lock
())
{
Mlt
::
Transition
&
transition
=
*
m_sameCompositions
[
clipId
].
get
();
std
::
shared_ptr
<
ClipModel
>
movedClip
(
ptr
->
getClipPtr
(
clipId
));
movedClip
->
setMixDuration
(
0
);
QModelIndex
ix
=
ptr
->
makeClipIndexFromID
(
clipId
);
emit
ptr
->
dataChanged
(
ix
,
ix
,
{
TimelineModel
::
StartRole
,
TimelineModel
::
MixRole
});
QScopedPointer
<
Mlt
::
Field
>
field
(
m_track
->
field
());
field
->
lock
();
field
->
disconnect_service
(
transition
);
field
->
unlock
();
m_sameCompositions
.
erase
(
clipId
);
}
return
true
;
};
// lock MLT playlist so that we don't end up with invalid frames in monitor
auto
operation
=
requestClipDeletion_lambda
(
clipId
,
updateView
,
finalMove
,
groupMove
,
finalMove
);
bool
res
=
operation
();
if
(
res
)
{
qDebug
()
<<
"=== CLIP DELETED; OK"
;
auto
reverse
=
requestClipInsertion_lambda
(
clipId
,
clipInitialPos
,
updateView
,
finalMove
,
groupMove
);
if
(
auto
ptr
=
m_parent
.
lock
())
{
ptr
->
getClipPtr
(
clipId
)
->
setSubPlaylistIndex
(
dest_track
);
}
auto
operation2
=
requestClipInsertion_lambda
(
clipId
,
position
,
updateView
,
finalMove
,
groupMove
);
res
=
res
&&
operation2
();
if
(
res
)
{
auto
reverse2
=
requestClipDeletion_lambda
(
clipId
,
updateView
,
finalMove
,
groupMove
,
finalMove
);
// Create mix composition
build_mix
();
qDebug
()
<<
"=============
\n
SECOND INSERT SUCCESS
\n\n
================="
;
PUSH_LAMBDA
(
operation2
,
operation
);
PUSH_LAMBDA
(
build_mix
,
operation
);
PUSH_LAMBDA
(
reverse
,
reverse2
);
PUSH_LAMBDA
(
reverse2
,
destroy_mix
);
UPDATE_UNDO_REDO
(
operation
,
destroy_mix
,
undo
,
redo
);
}
else
{
qDebug
()
<<
"=============
\n
SECOND INSERT FAILED
\n\n
================="
;
reverse
();
}
}
else
{
qDebug
()
<<
"=== CLIP DELETION FAILED"
;
}
return
res
;
}
std
::
pair
<
int
,
int
>
TrackModel
::
getMixInfo
(
int
clipId
)
const
{
std
::
pair
<
int
,
int
>
result
=
{
0
,
0
};
if
(
m_sameCompositions
.
count
(
clipId
)
>
0
)
{
result
.
first
=
m_sameCompositions
.
at
(
clipId
)
->
get_in
();
result
.
second
=
m_sameCompositions
.
at
(
clipId
)
->
get_out
()
-
result
.
first
;
}
return
result
;
}
bool
TrackModel
::
deleteMix
(
int
clipId
)
{
qDebug
()
<<
"=== DELETING MIX FROM CLIP: "
<<
clipId
;
if
(
m_sameCompositions
.
count
(
clipId
)
<=
0
)
{
return
false
;
}
if
(
auto
ptr
=
m_parent
.
lock
())
{
Mlt
::
Transition
&
transition
=
*
m_sameCompositions
[
clipId
].
get
();
std
::
shared_ptr
<
ClipModel
>
movedClip
(
ptr
->
getClipPtr
(
clipId
));
movedClip
->
setMixDuration
(
0
);
QModelIndex
ix
=
ptr
->
makeClipIndexFromID
(
clipId
);
emit
ptr
->
dataChanged
(
ix
,
ix
,
{
TimelineModel
::
StartRole
,
TimelineModel
::
MixRole
});
QScopedPointer
<
Mlt
::
Field
>
field
(
m_track
->
field
());
field
->
lock
();
field
->
disconnect_service
(
transition
);
field
->
unlock
();
m_sameCompositions
.
erase
(
clipId
);
return
true
;
}
return
false
;
}
bool
TrackModel
::
createMix
(
int
clipId
,
std
::
pair
<
int
,
int
>
mixData
)
{
if
(
m_sameCompositions
.
count
(
clipId
)
>
0
)
{
return
false
;
}
if
(
auto
ptr
=
m_parent
.
lock
())
{
std
::
shared_ptr
<
ClipModel
>
movedClip
(
ptr
->
getClipPtr
(
clipId
));
movedClip
->
setMixDuration
(
mixData
.
second
);
QModelIndex
ix
=
ptr
->
makeClipIndexFromID
(
clipId
);
emit
ptr
->
dataChanged
(
ix
,
ix
,
{
TimelineModel
::
StartRole
,
TimelineModel
::
MixRole
});
// Insert mix transition
if
(
isAudioTrack
())
{
std
::
shared_ptr
<
Mlt
::
Transition
>
t
(
new
Mlt
::
Transition
(
*
ptr
->
getProfile
(),
"mix"
));
t
->
set_in_and_out
(
mixData
.
first
,
mixData
.
first
+
mixData
.
second
);
m_track
->
plant_transition
(
*
t
.
get
(),
0
,
1
);
m_sameCompositions
[
clipId
]
=
t
;
}
else
{
std
::
shared_ptr
<
Mlt
::
Transition
>
t
(
new
Mlt
::
Transition
(
*
ptr
->
getProfile
(),
"luma"
));
t
->
set_in_and_out
(
mixData
.
first
,
mixData
.
first
+
mixData
.
second
);
m_track
->
plant_transition
(
*
t
.
get
(),
0
,
1
);
m_sameCompositions
[
clipId
]
=
t
;
}
return
true
;
}
return
false
;
}
bool
TrackModel
::
resizeMix
(
int
clipId
,
int
position
)
{
if
(
m_sameCompositions
.
count
(
clipId
)
<=
0
)
{
return
false
;
}
if
(
auto
ptr
=
m_parent
.
lock
())
{
Mlt
::
Transition
&
transition
=
*
m_sameCompositions
[
clipId
].
get
();
std
::
shared_ptr
<
ClipModel
>
movedClip
(
ptr
->
getClipPtr
(
clipId
));
int
in
=
position
;
int
out
=
transition
.
get_out
();
transition
.
set_in_and_out
(
in
,
out
);
int
updatedDuration
=
out
-
in
;
movedClip
->
setMixDuration
(
qMax
(
1
,
updatedDuration
));
QModelIndex
ix
=
ptr
->
makeClipIndexFromID
(
clipId
);
emit
ptr
->
dataChanged
(
ix
,
ix
,
{
TimelineModel
::
StartRole
,
TimelineModel
::
MixRole
});
return
true
;
}
return
false
;
}
src/timeline2/model/trackmodel.hpp
View file @
ba564eda
...
...
@@ -37,6 +37,19 @@ class ClipModel;
class
CompositionModel
;
class
EffectStackModel
;
class
MixInfo
{
public:
int
firstClipId
;
int
secondClipId
;
int
firstClipOut
;
int
secondClipIn
;
int
secondClipDuration
;
int
mixPosition
;
int
mixDuration
;
};
/* @brief This class represents a Track object, as viewed by the backend.
To allow same track transitions, a Track object corresponds to two Mlt::Playlist, between which we can switch when required by the transitions.
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
...
...
@@ -109,6 +122,16 @@ public:
// TODO make protected
QVariant
getProperty
(
const
QString
&
name
)
const
;
void
setProperty
(
const
QString
&
name
,
const
QString
&
value
);
/** @brief Create a composition between 2 same track clips */
bool
requestClipMix
(
int
clipId
,
int
position
,
bool
updateView
,
bool
finalMove
,
Fun
&
undo
,
Fun
&
redo
,
bool
groupMove
);
/** @brief Get in/out position for mix composition */
std
::
pair
<
int
,
int
>
getMixInfo
(
int
position
)
const
;
/** @brief Delete a mix composition */
bool
deleteMix
(
int
clipId
);
/** @brief Create a mix composition */
bool
createMix
(
int
clipId
,
std
::
pair
<
int
,
int
>
mixData
);
/** @brief Resize a mix composition */
bool
resizeMix
(
int
clipId
,
int
offset
);
protected:
/* @brief This will lock the track: it will no longer allow insertion/deletion/resize of items
...
...
@@ -281,6 +304,7 @@ private:
std
::
shared_ptr
<
Mlt
::
Tractor
>
m_track
;
std
::
shared_ptr
<
Mlt
::
Producer
>
m_mainPlaylist
;
Mlt
::
Playlist
m_playlists
[
2
];
QList
<
MixInfo
>
m_mixList
;
std
::
map
<
int
,
std
::
shared_ptr
<
ClipModel
>>
m_allClips
;
/*this is important to keep an
ordered structure to store the clips, since we use their ids order as row order*/
...
...
@@ -295,6 +319,7 @@ private:
protected:
std
::
shared_ptr
<
EffectStackModel
>
m_effectStack
;
std
::
unordered_map
<
int
,
std
::
shared_ptr
<
Mlt
::
Transition
>>
m_sameCompositions
;
};
#endif
src/timeline2/view/qml/Clip.qml
View file @
ba564eda
...
...
@@ -33,6 +33,7 @@ Rectangle {
property
string
effectNames
property
bool
isProxy
:
false
property
int
modelStart
property
int
mixDuration
:
0
property
real
scrollX
:
0
property
int
inPoint
:
0
property
int
outPoint
:
0
...
...
@@ -329,6 +330,16 @@ Rectangle {
anchors.margins
:
clipRoot
.
border
.
width
//clip: true
property
bool
showDetails
:
(
!
clipRoot
.
selected
||
!
effectRow
.
visible
)
&&
container
.
height
>
2.2
*
labelRect
.
height
Rectangle
{
// Mix indicator
anchors.left
:
parent
.
left
anchors.top
:
parent
.
top
anchors.bottom
:
parent
.
bottom
width
:
clipRoot
.
mixDuration
*
timeScale
color
:
'
red
'
opacity
:
0.5
}
Repeater
{
// Clip markers
...
...
src/timeline2/view/qml/Track.qml
View file @
ba564eda
...
...
@@ -68,6 +68,12 @@ Item{
value
:
model
.
fakePosition
when
:
loader
.
status
==
Loader
.
Ready
&&
loader
.
item
&&
isClip
(
model
.
clipType
)
}
Binding
{
target
:
loader
.
item
property
:
"
mixDuration
"
value
:
model
.
mixDuration
when
:
loader
.
status
==
Loader
.
Ready
&&
loader
.
item
&&
isClip
(
model
.
clipType
)
}
Binding
{
target
:
loader
.
item
property
:
"
selected
"
...
...
src/timeline2/view/timelinecontroller.cpp
View file @
ba564eda
...
...
@@ -3664,3 +3664,46 @@ void TimelineController::addTracks(int videoTracks, int audioTracks)
undo
();
}
}
void
TimelineController
::
sameTrack
()
{
qDebug
()
<<
"=== PROCESS SAME TRACK COMPO"
;
auto
sel
=
m_model
->
getCurrentSelection
();
if
(
sel
.
empty
())
{
return
;
}
QList
<
int
>
selectedItems
;
int
selectedTrack
=
-
1
;
for
(
int
s
:
sel
)
{
int
tid
=
m_model
->
getItemTrackId
(
s
);
if
(
selectedTrack
==
-
1
)
{
selectedTrack
=
tid
;
selectedItems
<<
s
;
}
else
if
(
tid
==
selectedTrack
)
{
// We found our second clip
selectedItems
<<
s
;
break
;
}
}
if
(
selectedItems
.
count
()
==
2
)
{
int
mixPosition
;
int
idToMove
;
if
(
m_model
->
getItemPosition
(
selectedItems
.
at
(
0
))
<
m_model
->
getItemPosition
(
selectedItems
.
at
(
1
)))
{
mixPosition
=
m_model
->
getItemPosition
(
selectedItems
.
at
(
0
))
+
m_model
->
getItemPlaytime
(
selectedItems
.
at
(
0
))
-
75
;
idToMove
=
selectedItems
.
at
(
1
);
}
else
{
mixPosition
=
m_model
->
getItemPosition
(
selectedItems
.
at
(
1
))
+
m_model
->
getItemPlaytime
(
selectedItems
.
at
(
1
))
-
75
;
idToMove
=
selectedItems
.
at
(
0
);
}
/*if (m_model->m_groups->isInGroup(idToMove)) {