Commit 2f6274aa authored by luyikei's avatar luyikei

Implement the transition feature

parent 1181bafa
......@@ -23,6 +23,7 @@ vlmc_SOURCES = \
src/Main/main.cpp \
src/Media/Clip.cpp \
src/Media/Media.cpp \
src/Transition/Transition.cpp \
src/Project/Project.cpp \
src/Project/Workspace.cpp \
src/Project/WorkspaceWorker.cpp \
......@@ -94,6 +95,7 @@ vlmc_SOURCES += \
nodist_vlmc_SOURCES = \
src/Media/Clip.moc.cpp \
src/Transition/Transition.moc.cpp \
src/Workflow/SequenceWorkflow.moc.cpp \
src/EffectsEngine/EffectHelper.moc.cpp \
src/Workflow/Helper.moc.cpp \
......@@ -389,6 +391,7 @@ vlmc_QML = \
src/Gui/timeline/Ruler.qml \
src/Gui/timeline/Track.qml \
src/Gui/timeline/Clip.qml \
src/Gui/timeline/TransitionItem.qml \
src/Gui/timeline/TrackContainer.qml \
src/Gui/timeline/Marker.qml \
src/Gui/timeline/main.qml \
......
......@@ -37,7 +37,7 @@ namespace Backend
virtual void remove( int index ) = 0;
virtual bool append( IInput& input ) = 0;
// src and dist are frames.
virtual bool move( int src, int dist ) = 0;
virtual bool move( int64_t src, int64_t dist ) = 0;
virtual IInput* clip( int index ) const = 0;
virtual IInput* clipAt( int64_t position ) const = 0 ;
virtual bool resizeClip( int clip, int64_t begin, int64_t end ) = 0;
......
......@@ -108,7 +108,7 @@ MLTTrack::append( Backend::IInput& input )
}
bool
MLTTrack::move( int src, int dist )
MLTTrack::move( int64_t src, int64_t dist )
{
std::unique_ptr<Mlt::Producer> prod(
playlist()->replace_with_blank( playlist()->get_clip_index_at( src ) ) );
......
......@@ -54,7 +54,7 @@ class MLTTrack : public ITrack, public MLTInput
virtual bool insertAt( IInput& input, int64_t startFrame ) override;
virtual void remove( int index ) override;
virtual bool append( IInput& input ) override;
virtual bool move( int src, int dist ) override;
virtual bool move( int64_t src, int64_t dist ) override;
virtual IInput* clip( int index ) const override;
virtual IInput* clipAt( int64_t position ) const override;
virtual bool resizeClip( int clip, int64_t begin, int64_t end ) override;
......
......@@ -36,6 +36,7 @@
#include "AbstractUndoStack.h"
#include "Backend/IFilter.h"
#include "Library/Library.h"
#include "Transition/Transition.h"
#ifdef HAVE_GUI
# include "Gui/timeline/MarkerManager.h"
......@@ -570,6 +571,106 @@ Commands::Effect::Remove::internalUndo()
m_helper->setTarget( m_target.get() );
}
Commands::Transition::Add::Add( qint64 position, qint64 endPosition,
quint32 trackId, Workflow::TrackType type,
std::shared_ptr<SequenceWorkflow> const& workflow )
: m_position( position )
, m_endPosition( endPosition )
, m_trackId( trackId )
, m_type( type )
, m_workflow( workflow )
{
retranslate();
}
void
Commands::Transition::Add::internalRedo()
{
m_uuid = m_workflow->addTransition( m_position, m_endPosition, m_trackId, m_type );
}
void
Commands::Transition::Add::internalUndo()
{
m_workflow->removeTransition( m_uuid );
}
void
Commands::Transition::Add::retranslate()
{
setText( tr( "Adding transition" ) );
}
const QUuid&
Commands::Transition::Add::uuid()
{
return m_uuid;
}
Commands::Transition::Move::Move( const QUuid& uuid, qint64 position, qint64 endPosition,
std::shared_ptr<SequenceWorkflow> const& workflow )
: m_uuid( uuid )
, m_position( position )
, m_endPosition( endPosition )
, m_workflow( workflow )
{
auto transition = m_workflow->transition( uuid );
if ( transition == nullptr )
invalidate();
else
{
m_oldPosition = transition->position();
m_oldEndPosition = transition->endPosition();
}
invalidate();
}
void
Commands::Transition::Move::internalRedo()
{
bool ret = m_workflow->moveTransition( m_uuid, m_position, m_endPosition );
if ( ret == false )
invalidate();
}
void
Commands::Transition::Move::internalUndo()
{
bool ret = m_workflow->moveTransition( m_uuid, m_oldPosition, m_oldEndPosition );
if ( ret == false )
invalidate();
}
void
Commands::Transition::Move::retranslate()
{
setText( tr( "Moving transition" ) );
}
Commands::Transition::Remove::Remove( const QUuid& uuid, std::shared_ptr<SequenceWorkflow> const& workflow )
: m_uuid( uuid )
, m_workflow( workflow )
{
retranslate();
}
void
Commands::Transition::Remove::internalRedo()
{
m_transition = m_workflow->removeTransition( m_uuid );
}
void
Commands::Transition::Remove::internalUndo()
{
m_workflow->addTransition( m_transition );
}
void Commands::Transition::Remove::retranslate()
{
setText( tr( "Removing transition" ) );
}
#ifdef HAVE_GUI
Commands::Marker::Add::Add( QSharedPointer<MarkerManager> markerManager, quint64 pos )
: m_markerManager( markerManager )
......
......@@ -37,13 +37,14 @@
#include <memory>
class Clip;
class EffectHelper;
class Transition;
class MarkerManager;
namespace Backend
{
class IInput;
}
class EffectHelper;
class MarkerManager;
namespace Commands
{
......@@ -281,6 +282,60 @@ namespace Commands
};
}
namespace Transition
{
class Add : public Generic
{
public:
Add( qint64 position, qint64 endPosition,
quint32 trackId, Workflow::TrackType type,
std::shared_ptr<SequenceWorkflow> const& workflow );
virtual void internalRedo();
virtual void internalUndo();
virtual void retranslate();
const QUuid& uuid();
private:
qint64 m_position;
qint64 m_endPosition;
quint32 m_trackId;
Workflow::TrackType m_type;
QUuid m_uuid;
std::shared_ptr<SequenceWorkflow> m_workflow;
};
class Move : public Generic
{
public:
Move( const QUuid& uuid, qint64 position, qint64 endPosition,
std::shared_ptr<SequenceWorkflow> const& workflow );
virtual void internalRedo();
virtual void internalUndo();
virtual void retranslate();
private:
QUuid m_uuid;
qint64 m_position;
qint64 m_endPosition;
qint64 m_oldPosition;
qint64 m_oldEndPosition;
std::shared_ptr<SequenceWorkflow> m_workflow;
};
class Remove : public Generic
{
public:
Remove( const QUuid& uuid,
std::shared_ptr<SequenceWorkflow> const& workflow );
virtual void internalRedo();
virtual void internalUndo();
virtual void retranslate();
private:
QUuid m_uuid;
QSharedPointer<::Transition> m_transition;
std::shared_ptr<SequenceWorkflow> m_workflow;
};
}
#ifdef HAVE_GUI
// Gui commands
namespace Marker
......
......@@ -9,6 +9,7 @@ Item {
property int trackId
property string type
property ListModel clips
property ListModel transitionModel
Rectangle {
id: clipArea
......@@ -95,24 +96,73 @@ Item {
// Set a right position
//
// HACK: If magnetic mode, consider clips bigger
// but not if it's also selected because both of them will be moving
// but not if "clip" is also selected because both of them will be moving
// and we want to keep the same distance between them as much as possible
var clipMargin = useMagneticMode && findClipItem( clip.uuid ).selected === false ? magneticMargin : 0;
if ( cx + cw > newX && newX + sw > cx )
isCollided = true;
cw += clipMargin * 2
cx -= clipMargin
if ( cx + cw > newX && newX + sw > cx ) {
if ( cx > newX ) {
if ( cx - sw > 0 )
newX = cx - sw + clipMargin;
else
newX = oldX;
} else {
newX = cx + cw - clipMargin;
// Note that in transition mode, they will never collide.
if ( isTransitionMode === true ) {
leastDistance = magneticMargin;
if ( Math.abs( newX - cx ) < leastDistance ) {
leastDistance = Math.abs( newX - cx );
newX = cx
}
if ( Math.abs( newX + sw - cx ) < leastDistance ) {
leastDistance = Math.abs( newX + sw - cx );
newX = cx - sw;
}
if ( Math.abs( newX - ( cx + cw ) ) < leastDistance ) {
leastDistance = Math.abs( newX - ( cx + cw ) );
newX = cx + cw;
}
if ( Math.abs( newX + sw - ( cx + cw ) ) < leastDistance ) {
leastDistance = Math.abs( newX + sw - ( cx + cw ) );
newX = cx + cw - sw;
}
// If they overlap, create a transition
if ( cx + cw > newX && newX + sw > cx )
{
var toCreate = true;
for ( var i = 0; i < allTransitions.length; ++i ) {
var transitionItem = allTransitions[i];
if ( transitionItem.trackId === clip.trackId &&
transitionItem.type === clip.type &&
transitionItem.clips.indexOf( clip.uuid ) !== -1 &&
transitionItem.clips.indexOf( target.uuid ) !== -1
) {
transitionItem.position = ptof( Math.max( newX, cx ) );
transitionItem.endPosition = ptof( Math.min( newX + sw, cx + cw ) );
toCreate = false;
}
}
if ( toCreate === true ) {
currentTrack["transitions"].append( { "position": ptof( Math.max( newX, cx ) ),
"endPosition": ptof( Math.min( newX + sw, cx + cw ) ) } );
transitionItem = allTransitions[allTransitions.length - 1];
transitionItem.clips.push( clip.uuid );
transitionItem.clips.push( target.uuid );
}
}
}
else {
if ( cx + cw > newX && newX + sw > cx )
isCollided = true;
cw += clipMargin * 2
cx -= clipMargin
if ( cx + cw > newX && newX + sw > cx ) {
if ( cx > newX ) {
if ( cx - sw > 0 )
newX = cx - sw + clipMargin;
else
newX = oldX;
} else {
newX = cx + cw - clipMargin;
}
}
}
if ( isCollided )
break;
}
......@@ -338,7 +388,19 @@ Item {
}
Repeater {
id: repeater
id: transitionRepeater
model: transitionModel
delegate: TransitionItem {
height: track.height - 3
position: model.position
endPosition: model.endPosition
trackId: track.trackId
type: track.type
}
}
Repeater {
id: clipRepeater
model: clips
delegate: Clip {
height: track.height - 3
......
......@@ -18,6 +18,7 @@ ListView {
trackId: index
type: container.type
clips: model["clips"]
transitionModel: model["transitions"]
}
}
import QtQuick 2.0
Rectangle {
id: transition
x: ftop( position )
z: 1000
width: ftop( endPosition - position )
color: "transparent"
border.color: "#000000"
border.width: 1
property string uuid: "transitionUuid"
property int position
property int endPosition
property int trackId
property string type
property var clips: [] // clips overlapping
Component.onCompleted: {
allTransitions.push( transition );
}
Component.onDestruction: {
for ( var i = 0; i < allTransitions.length; ++i ) {
if ( allTransitions[i] === transition ) {
allTransitions.splice( i, 1 );
return;
}
}
}
Rectangle {
height: parent.height / 2
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
color: "#000000"
opacity: 0.5
}
}
......@@ -20,10 +20,12 @@ Rectangle {
property var allClips: [] // Actual clip item objects
property var allClipsDict: ({}) // Actual clip item objects
property var selectedClips: [] // Selected clip uuids
property var allTransitions: [] // Actual transition item objects
property var groups: [] // list of lists of clip uuids
property var linkedClipsDict: ({}) // Uuid
property alias isMagneticMode: magneticModeButton.selected
property bool isCutMode: false
property bool isTransitionMode: transitionModeButton.selected
property bool dragging: false
property int trackHeight: 60
......@@ -78,7 +80,7 @@ Rectangle {
function addTrack( trackType )
{
trackContainer( trackType )["tracks"].append( { "clips": [] } );
trackContainer( trackType )["tracks"].append( { "clips": [], "transitions": [] } );
}
function removeTrack( trackType )
......@@ -97,6 +99,7 @@ Rectangle {
newDict["libraryUuid"] = clipDict["libraryUuid"];
newDict["uuid"] = clipDict["uuid"];
newDict["trackId"] = trackId;
newDict["type"] = trackType;
newDict["name"] = clipDict["name"];
newDict["selected"] = clipDict["selected"] === false ? false : true ;
var tracks = trackContainer( trackType )["tracks"];
......@@ -329,10 +332,20 @@ Rectangle {
dragging = false;
sortSelectedClips();
for ( var i = 0; i < allTransitions.length; ++i )
{
if ( allTransitions[i].uuid === "transitionUuid" )
{
allTransitions[i].uuid =
workflow.addTransition( allTransitions[i].position, allTransitions[i].endPosition,
allTransitions[i].trackId, allTransitions[i].type );
}
}
// We don't want to rely on selectedClips while moving since it "will" be changed
// I'm aware that it's not the best solution but it's the safest solution for sure
var toMove = [];
for ( var i = 0; i < selectedClips.length; ++i )
for ( i = 0; i < selectedClips.length; ++i )
{
var clip = findClipItem( selectedClips[i] );
toMove.push( [clip.uuid, clip.newTrackId, clip.position] );
......@@ -525,6 +538,12 @@ Rectangle {
selected: true
}
PropertyButton {
id: transitionModeButton
text: "T"
selected: false
}
PropertyButton {
id: zoomInButton
text: "+"
......
#include "Transition.h"
#include "Backend/IMultiTrack.h"
#include "Backend/MLT/MLTTransition.h"
#include "Backend/MLT/MLTMultiTrack.h"
Transition::Transition( qint64 position, qint64 endPosition,
quint32 trackId, Workflow::TrackType type )
: m_uuid( QUuid::createUuid() )
, m_position( position )
, m_endPosition( endPosition )
, m_trackId( trackId )
, m_type( type )
, m_multitrack( new Backend::MLT::MLTMultiTrack )
{
addTransition( QSharedPointer<Backend::ITransition>( new Backend::MLT::MLTTransition( "mix" ) ) );
}
QUuid
Transition::uuid()
{
return m_uuid;
}
void
Transition::setUuid( const QUuid& uuid )
{
m_uuid = uuid;
}
qint64
Transition::position() const
{
return m_position;
}
void
Transition::setPosition( qint64 position )
{
m_position = position;
}
qint64
Transition::endPosition() const
{
return m_endPosition;
}
void
Transition::setEndPosition( qint64 endPosition )
{
m_endPosition = endPosition;
}
quint32
Transition::trackId() const
{
return m_trackId;
}
void
Transition::setTrackId( quint32 trackId )
{
m_trackId = trackId;
}
Workflow::TrackType
Transition::type() const
{
return m_type;
}
void
Transition::setType( Workflow::TrackType type )
{
m_type = type;
}
const QList<QSharedPointer<SequenceWorkflow::ClipInstance>>&
Transition::clips()
{
return m_clips;
}
void
Transition::addInternalClip( QSharedPointer<SequenceWorkflow::ClipInstance> clip,
QSharedPointer<SequenceWorkflow::ClipInstance> parent )
{
m_internalClips << clip;
m_clips << parent;
m_multitrack->setTrack( *clip->clip->input(), m_internalClips.length() - 1 );
if ( m_internalClips.length() < 2 )
return;
for ( auto transition : m_transitions )
m_multitrack->addTransition( *transition.data(), m_internalClips.length() - 2, m_internalClips.length() - 1 );
}
void
Transition::addTransition( QSharedPointer<Backend::ITransition> transition )
{
m_transitions << transition;
for ( int i = 0; i < m_internalClips.length() - 1; ++i )
m_multitrack->addTransition( *transition.data(), i, i + 1 );
}
void
Transition::removeClip( const QUuid& uuid )
{
for ( int i = 0; i < m_clips.length(); ++i )
{
if ( m_clips[i]->uuid == uuid )
{
m_internalClips.removeAt( i );
m_clips.removeAt( i );
m_multitrack->removeTrack( i );
--i;
}
}
}
Backend::IInput&
Transition::input()
{
return *m_multitrack.data();
}
#ifndef TRANSITION_H
#define TRANSITION_H
#include "Workflow/Types.h"
#include "Workflow/SequenceWorkflow.h"
#include <QObject>
#include <QUuid>
#include <QSharedPointer>
namespace Backend {
class ITransition;
class IMultiTrack;
class IInput;
}
class Transition : public QObject
{
Q_OBJECT
public:
explicit Transition( qint64 position, qint64 endPosition, quint32 trackId, Workflow::TrackType type );
QUuid uuid();
void setUuid( const QUuid& uuid );
qint64 position() const;
void setPosition( qint64 position );
qint64 endPosition() const;
void setEndPosition( qint64 endPosition );
quint32 trackId() const;
void setTrackId( quint32 trackId );
Workflow::TrackType type() const;
void setType( Workflow::TrackType type );
const QList<QSharedPointer<SequenceWorkflow::ClipInstance>>& clips();
void addInternalClip( QSharedPointer<SequenceWorkflow::ClipInstance> clip,
QSharedPointer<SequenceWorkflow::ClipInstance> parent );
void addTransition( QSharedPointer<Backend::ITransition> transition );
void removeClip( const QUuid& uuid );
Backend::IInput& input();
private:
QUuid m_uuid;
qint64 m_position;
qint64 m_endPosition;
quint32 m_trackId;
Workflow::TrackType m_type;
QSharedPointer<Backend::IMultiTrack> m_multitrack;
QList<QSharedPointer<Backend::ITransition>> m_transitions;
// Clips supposed to be overlapping
QList<QSharedPointer<SequenceWorkflow::ClipInstance>> m_clips;
// Clips supposed to be created for the transition
QList<QSharedPointer<SequenceWorkflow::ClipInstance>> m_internalClips;
};
#endif // TRANSITION_H
......@@ -305,6 +305,27 @@ MainWorkflow::addEffect( const QString &clipUuid, const QString &effectId )
return QStringLiteral( "" );
}
QString
MainWorkflow::addTransition( qint64 position, qint64 endPosition, quint32 trackId, const QString& type )
{
auto trackType = type == QStringLiteral( "Video" ) ? Workflow::VideoTrack : Workflow::AudioTrack;
auto command = new Commands::Transition::Add( position, endPosition, trackId, trackType, m_sequenceWorkflow );
trigger( command );
return command->uuid().toString();
}
void
MainWorkflow::moveTransition( const QUuid& uuid, qint64 position, qint64 endPosition )
{
trigger( new Commands::Transition::Move( uuid, position, endPosition, m_sequenceWorkflow ) );
}
void
MainWorkflow::removeTransition( const QUuid& uuid )
{
trigger( new Commands::Transition::Remove( uuid, m_sequenceWorkflow ) );
}
bool
MainWorkflow::startRenderToFile( const QString &outputFileName, quint32 width, quint32 height,
double fps, const QString &ar, quint32 vbitrate, quint32 abitrate,
......
......@@ -150,6 +150,16 @@ class MainWorkflow : public QObject
Q_INVOKABLE
QString addEffect( const QString& clipUuid, const QString& effectId );
Q_INVOKABLE
QString addTransition( qint64 position, qint64 endPosition,
quint32 trackId, const QString& type );
Q_INVOKABLE
void moveTransition( const QUuid& uuid, qint64 position, qint64 endPosition );
Q_INVOKABLE
void removeTransition( const QUuid& uuid );
bool startRenderToFile( const QString& outputFileName, quint32 width, quint32 height,
double fps, const QString& ar, quint32 vbitrate, quint32 abitrate,
quint32 nbChannels, quint32 sampleRate );
......
......@@ -34,6 +34,7 @@
#include "Library/Library.h"
#include "Tools/VlmcDebug.h"
#include "Media/Media.h"
#include "Transition/Transition.h"
SequenceWorkflow::SequenceWorkflow( size_t trackCount )
: m_multitrack( new Backend::MLT::MLTMultiTrack )
......@@ -91,7 +92,6 @@ SequenceWorkflow::moveClip( const QUuid& uuid, quint32 trackId, qint64 pos )
return false;
}
auto& c = it.value();