SequenceWorkflow.cpp 11.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
/*****************************************************************************
 * SequenceWorkflow.cpp : Manages tracks in a single sequence
 *****************************************************************************
 * Copyright (C) 2008-2016 VideoLAN
 *
 * Authors: Yikei Lu    <luyikei.qmltu@gmail.com>
 *
 * 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"
36
#include "Media/Media.h"
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66

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<Backend::ITrack>( new Backend::MLT::MLTTrack );
        audioTrack->setVideoEnabled( false );
        m_tracks[Workflow::AudioTrack] << audioTrack;

        auto videoTrack = std::shared_ptr<Backend::ITrack>( new Backend::MLT::MLTTrack );
        videoTrack->setMute( true );
        m_tracks[Workflow::VideoTrack] << videoTrack;

        auto multitrack = std::shared_ptr<Backend::IMultiTrack>( 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();
}

67
QUuid
68
SequenceWorkflow::addClip( QSharedPointer<::Clip> clip, quint32 trackId, qint32 pos, const QUuid& uuid, bool isAudioClip )
69
{
70 71
    auto t = track( trackId, isAudioClip );
    auto ret = t->insertAt( *clip->input(), pos );
72
    if ( ret == false )
73
        return {};
74
    auto c = QSharedPointer<ClipInstance>::create( clip,
75
                                           uuid.isNull() == true ? QUuid::createUuid() : uuid,
76 77
                                           trackId, pos, isAudioClip );
    vlmcDebug() << "adding" << (isAudioClip ? "audio" : "video") <<  "clip instance:" << c->uuid;
78
    m_clips.insert( c->uuid, c ) ;
79
    clip->setOnTimeline( true );
80
    emit clipAdded( c->uuid.toString() );
81
    return c->uuid;
82 83 84 85 86 87 88 89
}

bool
SequenceWorkflow::moveClip( const QUuid& uuid, quint32 trackId, qint64 pos )
{
    auto it = m_clips.find( uuid );
    if ( it == m_clips.end() )
    {
90
        vlmcCritical() << "Couldn't find a clip:" << uuid;
91 92
        return false;
    }
93 94 95 96
    auto& c = it.value();
    auto clip = c->clip;
    auto oldTrackId = c->trackId;
    auto oldPosition = c->pos;
97
    if ( oldPosition == pos && oldTrackId == trackId )
98
        return true;
99
    auto t = track( oldTrackId, c->isAudio );
100 101
    if ( trackId != oldTrackId )
    {
102 103 104 105 106 107 108 109
        // Don't call removeClip/addClip as they would destroy & recreate clip instances for nothing.
        // Simply fiddle with the track to move the clip around
        t->remove( t->clipIndexAt( oldPosition ) );
        auto newTrack = track( trackId, c->isAudio );
        if ( newTrack->insertAt( *clip->input(), pos ) == false )
            return false;
        c->pos = pos;
        c->trackId = trackId;
110
    }
111
    else
112
    {
113 114
        if ( t->move( oldPosition, pos ) == false )
            return false;
115
        c->pos = pos;
116
    }
117
    emit clipMoved( uuid.toString() );
118
    // CAUTION: You must not move a clip to a place where it would overlap another clip!
119
    return true;
120 121 122 123 124 125 126 127
}

bool
SequenceWorkflow::resizeClip( const QUuid& uuid, qint64 newBegin, qint64 newEnd, qint64 newPos )
{
    auto it = m_clips.find( uuid );
    if ( it == m_clips.end() )
    {
128
        vlmcCritical() << "Couldn't find a clip:" << uuid;
129 130
        return false;
    }
131
    auto& c = it.value();
132 133
    auto trackId = c->trackId;
    auto position = c->pos;
134
    auto t = track( trackId, c->isAudio );
135 136 137 138 139 140 141 142 143
    auto clipIndex = t->clipIndexAt( position );
    // This will only duplicate the clip once; no need to panic about endless duplications
    if ( c->duplicateClipForResize( newBegin, newEnd ) == true )
    {
        vlmcDebug() << "Duplicating clip for resize" << c->uuid << "is now using" << c->clip->uuid();
        t->remove( clipIndex );
        t->insertAt( *c->clip->input(), position );
    }
    auto ret = t->resizeClip( clipIndex, newBegin, newEnd );
144 145 146
    if ( ret == false )
        return false;
    ret = moveClip( uuid, trackId, newPos );
147
    emit clipResized( uuid.toString() );
148 149 150
    return ret;
}

151
QSharedPointer<SequenceWorkflow::ClipInstance>
152 153
SequenceWorkflow::removeClip( const QUuid& uuid )
{
154
    vlmcDebug() << "Removing clip instance" << uuid;
155 156 157
    auto it = m_clips.find( uuid );
    if ( it == m_clips.end() )
    {
158
        vlmcCritical() << "Couldn't find a sequence workflow clip:" << uuid;
159
        return {};
160
    }
161 162 163 164
    auto c = it.value();
    auto clip = c->clip;
    auto trackId = c->trackId;
    auto position = c->pos;
165 166
    auto t = track( trackId, c->isAudio );
    t->remove( t->clipIndexAt( position ) );
167 168
    m_clips.erase( it );
    clip->disconnect( this );
169 170 171 172 173
    bool onTimeline = false;
    for ( const auto& clipInstance : m_clips )
        if ( clipInstance->clip->uuid() == clip->uuid() )
            onTimeline = true;
    clip->setOnTimeline( onTimeline );
174
    emit clipRemoved( uuid.toString() );
175
    return c;
176 177 178 179 180 181

}

bool
SequenceWorkflow::linkClips( const QUuid& uuidA, const QUuid& uuidB )
{
182 183
    auto clipA = clip( uuidA );
    auto clipB = clip( uuidB );
184 185
    if ( !clipA || !clipB )
    {
186
        vlmcCritical() << "Couldn't find clips:" << uuidA << "and" << uuidB;
187 188
        return false;
    }
189 190 191
    clipA->linkedClips.append( uuidB );
    clipB->linkedClips.append( uuidA );
    emit clipLinked( uuidA.toString(), uuidB.toString() );
192 193 194 195 196 197
    return true;
}

bool
SequenceWorkflow::unlinkClips( const QUuid& uuidA, const QUuid& uuidB )
{
198
    vlmcDebug() << "Unlinking clips" << uuidA.toString() << "and" << uuidB.toString();
199 200
    auto clipA = clip( uuidA );
    auto clipB = clip( uuidB );
201 202
    if ( !clipA || !clipB )
    {
203
        vlmcCritical() << "Couldn't find clips:" << uuidA << "and" << uuidB;
204 205
        return false;
    }
206 207 208 209 210 211 212 213 214 215 216
    bool ret = true;
    if ( clipA->linkedClips.removeOne( uuidB ) == false )
    {
        ret = false;
        vlmcWarning() << "Failed to unlink" << uuidB << "from Clip instance" << uuidA;
    }
    if ( clipB->linkedClips.removeOne( uuidA ) == false )
    {
        ret = false;
        vlmcWarning() << "Failed to unlink" << uuidA << "from Clip instance" << uuidB;
    }
217 218
    if ( ret == true )
        emit clipUnlinked( uuidA.toString(), uuidB.toString() );
219
    return ret;
220 221 222 223 224 225 226 227
}

QVariant
SequenceWorkflow::toVariant() const
{
    QVariantList l;
    for ( auto it = m_clips.begin(); it != m_clips.end(); ++it )
    {
228 229 230 231 232 233
        auto c = it.value();
        QVariantHash h = {
            { "uuid", c->uuid.toString() },
            { "clipUuid", c->clip->uuid().toString() },
            { "position", c->pos },
            { "trackId", c->trackId },
234 235
            { "filters", EffectHelper::toVariant( c->clip->input() ) },
            { "isAudio",c->isAudio }
236
        };
237 238 239 240
        QList<QVariant> linkedClipList;
        for ( const auto& uuid : c->linkedClips )
            linkedClipList.append( uuid.toString() );
        h["linkedClips"] = linkedClipList;
241 242 243 244 245 246 247 248 249 250 251
        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() )
    {
252
        auto m = var.toMap();
253
        auto clip = Core::instance()->library()->clip( m["clipUuid"].toUuid() );
254

255 256
        if ( clip == nullptr )
        {
257
            vlmcCritical() << "Couldn't find an acceptable library clip to be added.";
258 259
            continue;
        }
260

261 262
        Q_ASSERT( m.contains( "uuid" ) && m.contains( "isAudio" ) );

263
        auto uuid = m["uuid"].toUuid();
264 265 266
        auto isAudio = m["isAudio"].toBool();
        //FIXME: Add missing clip type handling. We don't know if we're adding an audio clip or not
        addClip( clip, m["trackId"].toUInt(), m["position"].toLongLong(), uuid, isAudio );
267
        auto c = m_clips[uuid];
268

269 270
        auto linkedClipsList = m["linkedClips"].toList();
        for ( const auto& uuidVar : linkedClipsList )
271
        {
272 273 274 275 276
            auto linkedClipUuid = uuidVar.toUuid();
            c->linkedClips.append( linkedClipUuid );
            auto it = m_clips.find( linkedClipUuid );
            if ( it != m_clips.end() )
                emit clipLinked( uuid.toString(), linkedClipUuid.toString() );
277
        }
278

279
        EffectHelper::loadFromVariant( m["filters"], clip->input() );
280 281 282 283 284 285 286
    }
    EffectHelper::loadFromVariant( variant.toMap()["filters"], m_multitrack );
}

void
SequenceWorkflow::clear()
{
287 288
    while ( !m_clips.empty() )
        removeClip( m_clips.begin().key() );
289 290
}

291
QSharedPointer<SequenceWorkflow::ClipInstance>
292 293 294 295
SequenceWorkflow::clip( const QUuid& uuid )
{
    auto it = m_clips.find( uuid );
    if ( it == m_clips.end() )
296
        return {};
297
    return it.value();
298 299 300 301 302 303 304 305
}

quint32
SequenceWorkflow::trackId( const QUuid& uuid )
{
    auto it = m_clips.find( uuid );
    if ( it == m_clips.end() )
        return 0;
306
    return it.value()->trackId;
307 308 309 310 311 312 313 314
}

qint32
SequenceWorkflow::position( const QUuid& uuid )
{
    auto it = m_clips.find( uuid );
    if ( it == m_clips.end() )
        return 0;
315
    return it.value()->pos;
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331
}

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<Backend::ITrack>
332
SequenceWorkflow::track( quint32 trackId, bool isAudio )
333 334 335
{
    if ( trackId >= (quint32)m_trackCount )
        return nullptr;
336
    if ( isAudio == true )
337
        return m_tracks[Workflow::AudioTrack][trackId];
338
    return m_tracks[Workflow::VideoTrack][trackId];
339
}
340

341
SequenceWorkflow::ClipInstance::ClipInstance(QSharedPointer<::Clip> c, const QUuid& uuid, quint32 tId, qint64 p, bool isAudio )
342 343 344 345
    : clip( c )
    , uuid( uuid )
    , trackId( tId )
    , pos( p )
346
    , isAudio( isAudio )
347 348 349 350 351 352
    , m_hasClonedClip( false )
{
}

bool
SequenceWorkflow::ClipInstance::duplicateClipForResize( qint64 begin, qint64 end )
353
{
354 355 356 357 358
    if ( m_hasClonedClip == true )
        return false;
    clip = clip->media()->cut( begin, end );
    m_hasClonedClip = true;
    return true;
359
}