/***************************************************************************** * SequenceWorkflow.cpp : Manages tracks in a single sequence ***************************************************************************** * Copyright (C) 2008-2016 VideoLAN * * Authors: Yikei Lu * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. *****************************************************************************/ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "SequenceWorkflow.h" #include "Backend/MLT/MLTTrack.h" #include "Backend/MLT/MLTMultiTrack.h" #include "EffectsEngine/EffectHelper.h" #include "Workflow/MainWorkflow.h" #include "Main/Core.h" #include "Library/Library.h" #include "Tools/VlmcDebug.h" SequenceWorkflow::SequenceWorkflow( size_t trackCount ) : m_multitrack( new Backend::MLT::MLTMultiTrack ) , m_trackCount( trackCount ) { for ( int i = 0; i < trackCount; ++i ) { auto audioTrack = std::shared_ptr( new Backend::MLT::MLTTrack ); audioTrack->setVideoEnabled( false ); m_tracks[Workflow::AudioTrack] << audioTrack; auto videoTrack = std::shared_ptr( new Backend::MLT::MLTTrack ); videoTrack->setMute( true ); m_tracks[Workflow::VideoTrack] << videoTrack; auto multitrack = std::shared_ptr( new Backend::MLT::MLTMultiTrack ); multitrack->setTrack( *videoTrack, 0 ); multitrack->setTrack( *audioTrack, 1 ); m_multiTracks << multitrack; m_multitrack->setTrack( *multitrack, i ); } } SequenceWorkflow::~SequenceWorkflow() { delete m_multitrack; clear(); } bool SequenceWorkflow::addClip( std::shared_ptr const& clip, quint32 trackId, qint32 pos ) { auto ret = trackFromFormats( trackId, clip->formats() )->insertAt( *clip->input(), pos ); if ( ret == false ) return false; m_clips.insert( clip->uuid(), std::make_tuple( clip, trackId, pos ) ); return true; } QString SequenceWorkflow::addClip( const QUuid& uuid, quint32 trackId, qint32 pos, bool isAudioClip ) { Clip* clip = Core::instance()->library()->clip( uuid ); if ( clip == nullptr ) { vlmcCritical() << "Couldn't find an acceptable parent to be added."; return QUuid().toString(); } auto newClip = std::make_shared( clip ); if ( isAudioClip == true ) newClip->setFormats( Clip::Audio ); else newClip->setFormats( Clip::Video ); bool ret = trackFromFormats( trackId, newClip->formats() )->insertAt( *newClip->input(), pos ); if ( ret == false ) return QUuid().toString(); m_clips.insert( newClip->uuid(), std::make_tuple( newClip, trackId, pos ) ); return newClip->uuid().toString(); } bool SequenceWorkflow::moveClip( const QUuid& uuid, quint32 trackId, qint64 pos ) { auto it = m_clips.find( uuid ); if ( it == m_clips.end() ) { vlmcCritical() << "Couldn't find a clip " << uuid; return false; } auto clip = std::get( it.value() ); auto oldTrackId = std::get( it.value() ); auto oldPosition = std::get( it.value() ); if ( oldPosition == pos ) return true; auto track = trackFromFormats( oldTrackId, clip->formats() ); bool ret = true; if ( trackId != oldTrackId ) { removeClip( uuid ); ret = addClip( clip, trackId, pos ); } else { ret = track->move( std::get( it.value() ), pos ); } m_clips.erase( it ); m_clips.insert( uuid, std::make_tuple( clip, trackId, pos ) ); // TODO: If we detect collision too strictly, there will be a problem if we want to move multiple // clips at the same time. return ret; } bool SequenceWorkflow::resizeClip( const QUuid& uuid, qint64 newBegin, qint64 newEnd, qint64 newPos ) { auto it = m_clips.find( uuid ); if ( it == m_clips.end() ) { vlmcCritical() << "Couldn't find a clip " << uuid; return false; } auto clip = std::get( it.value() ); auto trackId = std::get( it.value() ); auto position = std::get( it.value() ); auto track = trackFromFormats( trackId, clip->formats() ); auto ret = track->resizeClip( track->clipIndexAt( position ), newBegin, newEnd ); if ( ret == false ) return false; ret = moveClip( uuid, trackId, newPos ); return ret; } std::shared_ptr SequenceWorkflow::removeClip( const QUuid& uuid ) { auto it = m_clips.find( uuid ); if ( it == m_clips.end() ) { vlmcCritical() << "Couldn't find a clip " << uuid; return std::shared_ptr( nullptr ); } auto clip = std::get( it.value() ); auto trackId = std::get( it.value() ); auto position = std::get( it.value() ); auto track = trackFromFormats( trackId, clip->formats() ); track->remove( track->clipIndexAt( position ) ); m_clips.erase( it ); clip->disconnect( this ); return clip; } bool SequenceWorkflow::linkClips( const QUuid& uuidA, const QUuid& uuidB ) { auto clipA = clip( uuidA ); auto clipB = clip( uuidB ); if ( !clipA || !clipB ) { vlmcCritical() << "Couldn't find clips: " << uuidA << " and " << uuidB; return false; } clipA->setLinkedClipUuid( clipB->uuid() ); clipB->setLinkedClipUuid( clipA->uuid() ); clipA->setLinked( true ); clipB->setLinked( true ); return true; } bool SequenceWorkflow::unlinkClips( const QUuid& uuidA, const QUuid& uuidB ) { auto clipA = clip( uuidA ); auto clipB = clip( uuidB ); if ( !clipA || !clipB ) { vlmcCritical() << "Couldn't find clips: " << uuidA << " and " << uuidB; return false; } clipA->setLinked( false ); clipB->setLinked( false ); return true; } QVariant SequenceWorkflow::toVariant() const { QVariantList l; for ( auto it = m_clips.begin(); it != m_clips.end(); ++it ) { auto clip = std::get( it.value() ); auto trackId = std::get( it.value() ); auto position = std::get( it.value() ); auto h = clip->toVariant().toHash(); h.insert( "position", position ); h.insert( "trackId", trackId ); l << h; } QVariantHash h{ { "clips", l }, { "filters", EffectHelper::toVariant( m_multitrack ) } }; return h; } void SequenceWorkflow::loadFromVariant( const QVariant& variant ) { for ( auto& var : variant.toMap()["clips"].toList() ) { auto m = var.toMap(); auto parentClip = Core::instance()->library()->clip( m["parent"].toString() ); if ( parentClip == nullptr ) { vlmcCritical() << "Couldn't find an acceptable parent to be added."; continue; } auto c = std::make_shared( parentClip, m["begin"].toLongLong(), m["end"].toLongLong() ); c->setUuid( m["uuid"].toString() ); c->setFormats( (Clip::Formats)m["formats"].toInt() ); addClip( c, m["trackId"].toUInt(), m["position"].toLongLong() ); auto isLinked = m["linked"].toBool(); c->setLinked( isLinked ); if ( isLinked == true ) c->setLinkedClipUuid( m["linkedClip"].toString() ); EffectHelper::loadFromVariant( m["filters"], c->input() ); emit Core::instance()->workflow()->clipAdded( c->uuid().toString() ); } EffectHelper::loadFromVariant( variant.toMap()["filters"], m_multitrack ); } void SequenceWorkflow::clear() { auto it = m_clips.begin(); while ( it != m_clips.end() ) { removeClip( it.key() ); // m_clips.begin() can be changed it = m_clips.begin(); } } std::shared_ptr SequenceWorkflow::clip( const QUuid& uuid ) { auto it = m_clips.find( uuid ); if ( it == m_clips.end() ) return std::shared_ptr( nullptr ); return std::get( it.value() ); } quint32 SequenceWorkflow::trackId( const QUuid& uuid ) { auto it = m_clips.find( uuid ); if ( it == m_clips.end() ) return 0; return std::get( it.value() ); } qint32 SequenceWorkflow::position( const QUuid& uuid ) { auto it = m_clips.find( uuid ); if ( it == m_clips.end() ) return 0; return std::get( it.value() ); } Backend::IInput* SequenceWorkflow::input() { return m_multitrack; } Backend::IInput* SequenceWorkflow::trackInput( quint32 trackId ) { Q_ASSERT( trackId < m_multiTracks.size() ); return m_multiTracks[trackId].get(); } std::shared_ptr SequenceWorkflow::trackFromFormats( quint32 trackId, Clip::Formats formats ) { if ( trackId >= (quint32)m_trackCount ) return nullptr; if ( formats.testFlag( Clip::Audio ) ) return m_tracks[Workflow::AudioTrack][trackId]; else if ( formats.testFlag( Clip::Video ) ) return m_tracks[Workflow::VideoTrack][trackId]; return nullptr; }