MainWorkflow.cpp 11.5 KB
Newer Older
1 2 3 4
/*****************************************************************************
 * MainWorkflow.cpp : Will query all of the track workflows to render the final
 *                    image
 *****************************************************************************
5
 * Copyright (C) 2008-2016 VideoLAN
6
 *
7 8
 * Authors: Yikei Lu    <luyikei.qmltu@gmail.com>
 *          Hugo Beauzée-Luyssen <hugo@beauzee.fr>
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
 *
 * 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.
 *****************************************************************************/

25 26 27
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
28

Ludovic Fauvet's avatar
Ludovic Fauvet committed
29
#include "vlmc.h"
luyikei's avatar
luyikei committed
30 31
#include "Commands/Commands.h"
#include "Commands/AbstractUndoStack.h"
luyikei's avatar
luyikei committed
32
#include "Backend/MLT/MLTOutput.h"
luyikei's avatar
luyikei committed
33
#include "Backend/MLT/MLTMultiTrack.h"
34 35
#include "Backend/MLT/MLTTrack.h"
#include "Renderer/AbstractRenderer.h"
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
36
#ifdef HAVE_GUI
luyikei's avatar
luyikei committed
37
#include "EffectsEngine/EffectHelper.h"
38
#include "Gui/effectsengine/EffectStack.h"
luyikei's avatar
luyikei committed
39 40
#include "Gui/WorkflowFileRendererDialog.h"
#endif
41
#include "Project/Project.h"
42
#include "Media/Clip.h"
luyikei's avatar
luyikei committed
43
#include "Media/Media.h"
44
#include "Library/Library.h"
45
#include "MainWorkflow.h"
46
#include "Project/Project.h"
47
#include "SequenceWorkflow.h"
48
#include "Settings/Settings.h"
49
#include "Tools/VlmcDebug.h"
luyikei's avatar
luyikei committed
50
#include "Tools/RendererEventWatcher.h"
luyikei's avatar
luyikei committed
51
#include "Tools/OutputEventWatcher.h"
52
#include "Workflow/Types.h"
53

54
#include <QMutex>
55

luyikei's avatar
luyikei committed
56 57
MainWorkflow::MainWorkflow( Settings* projectSettings, int trackCount ) :
        m_trackCount( trackCount ),
58 59
        m_settings( new Settings ),
        m_renderer( new AbstractRenderer ),
60 61
        m_undoStack( new Commands::AbstractUndoStack ),
        m_sequenceWorkflow( new SequenceWorkflow( trackCount ) )
62
{
63
    connect( m_sequenceWorkflow.get(), &SequenceWorkflow::clipAdded, this, &MainWorkflow::clipAdded );
64
    connect( m_sequenceWorkflow.get(), &SequenceWorkflow::clipRemoved, this, &MainWorkflow::clipRemoved );
65
    connect( m_sequenceWorkflow.get(), &SequenceWorkflow::clipLinked, this, &MainWorkflow::clipLinked );
66
    connect( m_sequenceWorkflow.get(), &SequenceWorkflow::clipUnlinked, this, &MainWorkflow::clipUnlinked );
67
    connect( m_sequenceWorkflow.get(), &SequenceWorkflow::clipMoved, this, &MainWorkflow::clipMoved );
68
    connect( m_sequenceWorkflow.get(), &SequenceWorkflow::clipResized, this, &MainWorkflow::clipResized );
69
    m_renderer->setInput( m_sequenceWorkflow->input() );
70

luyikei's avatar
luyikei committed
71 72
    connect( m_renderer->eventWatcher(), &RendererEventWatcher::lengthChanged, this, &MainWorkflow::lengthChanged );
    connect( m_renderer->eventWatcher(), &RendererEventWatcher::endReached, this, &MainWorkflow::mainWorkflowEndReached );
luyikei's avatar
luyikei committed
73 74 75 76
    connect( m_renderer->eventWatcher(), &RendererEventWatcher::positionChanged, this, [this]( qint64 pos )
    {
        emit frameChanged( pos, Vlmc::Renderer );
    } );
luyikei's avatar
luyikei committed
77 78

    m_settings->createVar( SettingValue::List, "tracks", QVariantList(), "", "", SettingValue::Nothing );
luyikei's avatar
luyikei committed
79 80 81
    connect( m_settings, &Settings::postLoad, this, &MainWorkflow::postLoad, Qt::DirectConnection );
    connect( m_settings, &Settings::preSave, this, &MainWorkflow::preSave, Qt::DirectConnection );
    projectSettings->addSettings( "Workspace", *m_settings );
82 83

    connect( m_undoStack.get(), &Commands::AbstractUndoStack::cleanChanged, this, &MainWorkflow::cleanChanged );
84 85 86 87
}

MainWorkflow::~MainWorkflow()
{
88
    m_renderer->stop();
89
    delete m_renderer;
luyikei's avatar
luyikei committed
90
    delete m_settings;
91 92
}

93
void
94
MainWorkflow::unmuteTrack( unsigned int trackId, Workflow::TrackType trackType )
95
{
96
    // TODO
97
}
98

99
void
luyikei's avatar
luyikei committed
100
MainWorkflow::muteClip( const QUuid& uuid, unsigned int trackId )
101
{
102
    // TODO
103 104 105
}

void
luyikei's avatar
luyikei committed
106
MainWorkflow::unmuteClip( const QUuid& uuid, unsigned int trackId )
107
{
108
    // TODO
109 110
}

111 112 113 114 115 116
void
MainWorkflow::trigger( Commands::Generic* command )
{
    m_undoStack->push( command );
}

117 118
void
MainWorkflow::clear()
119
{
120
    m_sequenceWorkflow->clear();
121
    emit cleared();
122
}
123

124 125 126 127 128 129
void
MainWorkflow::setClean()
{
    m_undoStack->setClean();
}

luyikei's avatar
luyikei committed
130 131 132 133 134 135
void
MainWorkflow::setPosition( qint64 newFrame )
{
    m_renderer->setPosition( newFrame );
}

136 137 138 139 140 141 142
void
MainWorkflow::setFps( double fps )
{
    Backend::instance()->profile().setFrameRate( fps * 100, 100 );
    emit fpsChanged( fps );
}

143 144 145 146 147 148 149 150 151
void
MainWorkflow::showEffectStack()
{
#ifdef HAVE_GUI
    auto w = new EffectStack( m_sequenceWorkflow->input() );
    w->show();
#endif
}

152 153
void
MainWorkflow::showEffectStack( quint32 trackId )
154
{
155 156 157 158
#ifdef HAVE_GUI
    auto w = new EffectStack( m_sequenceWorkflow->trackInput( trackId ) );
    w->show();
#endif
159 160
}

161 162
void
MainWorkflow::showEffectStack( const QString& uuid )
163
{
164
#ifdef HAVE_GUI
165
    auto w = new EffectStack( m_sequenceWorkflow->clip( uuid )->clip->input() );
166 167 168
    connect( w, &EffectStack::finished, Core::instance()->workflow(), [uuid]{ emit Core::instance()->workflow()->effectsUpdated( uuid ); } );
    w->show();
#endif
169 170
}

171 172
AbstractRenderer*
MainWorkflow::renderer()
173
{
174
    return m_renderer;
175 176
}

177 178 179 180 181 182
Commands::AbstractUndoStack*
MainWorkflow::undoStack()
{
    return m_undoStack.get();
}

183
int
luyikei's avatar
luyikei committed
184
MainWorkflow::getTrackCount() const
185
{
luyikei's avatar
luyikei committed
186
    return m_trackCount;
187
}
Hugo Beauzee-Luyssen's avatar
Hugo Beauzee-Luyssen committed
188

189 190 191 192 193 194
quint32
MainWorkflow::trackCount() const
{
    return m_trackCount;
}

195
void
196
MainWorkflow::addClip( const QString& uuid, quint32 trackId, qint32 pos )
luyikei's avatar
luyikei committed
197
{
198 199
    vlmcDebug() << "Adding clip:" << uuid;
    auto command = new Commands::Clip::Add( m_sequenceWorkflow, uuid, trackId, pos );
200
    trigger( command );
luyikei's avatar
luyikei committed
201 202 203 204 205
}

QJsonObject
MainWorkflow::clipInfo( const QString& uuid )
{
206 207
    auto c = m_sequenceWorkflow->clip( uuid );
    if ( c != nullptr )
208
    {
209
        auto clip = c->clip;
210
        auto h = clip->toVariant().toHash();
211
        h["uuid"] = uuid;
212
        vlmcWarning() << "library UUID: " << h["libraryUuid"];
213 214
        h["length"] = (qint64)( clip->input()->length() );
        h["name"] = clip->media()->title();
215
        h["audio"] = c->isAudio;
216 217
        h["position"] = m_sequenceWorkflow->position( uuid );
        h["trackId"] = m_sequenceWorkflow->trackId( uuid );
218
        h["filters"] = EffectHelper::toVariant( clip->input() );
219 220 221
        return QJsonObject::fromVariantHash( h );
    }
    return QJsonObject();
222 223
}

224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
QJsonObject
MainWorkflow::libraryClipInfo( const QString& uuid )
{
    auto c = Core::instance()->library()->clip( uuid );
    if ( c == nullptr )
        return {};
    auto h = c->toVariant().toHash();
    h["length"] = (qint64)( c->input()->length() );
    h["name"] = c->media()->title();
    h["audio"] = c->media()->hasAudioTracks();
    h["video"] = c->media()->hasVideoTracks();
    h["begin"] = c->begin();
    h["end"] = c->end();
    h["uuid"] = "libraryUuid";
    return QJsonObject::fromVariantHash( h );
}

luyikei's avatar
luyikei committed
241
void
242
MainWorkflow::moveClip( const QString& uuid, quint32 trackId, qint64 startFrame )
luyikei's avatar
luyikei committed
243
{
244
    auto clip = m_sequenceWorkflow->clip( uuid );
245
    trigger( new Commands::Clip::Move( m_sequenceWorkflow, uuid, trackId, startFrame ) );
246 247
    for ( const auto& lcUuid : clip->linkedClips )
        trigger( new Commands::Clip::Move( m_sequenceWorkflow, lcUuid.toString(), trackId, startFrame ) );
luyikei's avatar
luyikei committed
248 249
}

luyikei's avatar
luyikei committed
250 251 252
void
MainWorkflow::resizeClip( const QString& uuid, qint64 newBegin, qint64 newEnd, qint64 newPos )
{
253
    auto clip = m_sequenceWorkflow->clip( uuid );
254
    trigger( new Commands::Clip::Resize( m_sequenceWorkflow, uuid, newBegin, newEnd, newPos ) );
255 256
    for ( const auto& lcUuid : clip->linkedClips )
        trigger( new Commands::Clip::Resize( m_sequenceWorkflow, lcUuid, newBegin, newEnd, newPos ) );
luyikei's avatar
luyikei committed
257 258
}

luyikei's avatar
luyikei committed
259 260 261
void
MainWorkflow::removeClip( const QString& uuid )
{
262
    auto clip = m_sequenceWorkflow->clip( uuid );
263
    trigger( new Commands::Clip::Remove( m_sequenceWorkflow, uuid ) );
264 265
    for ( const auto& lcUuid : clip->linkedClips )
        trigger( new Commands::Clip::Remove( m_sequenceWorkflow, lcUuid ) );
luyikei's avatar
luyikei committed
266 267
}

luyikei's avatar
luyikei committed
268 269 270 271 272 273
void
MainWorkflow::splitClip( const QUuid& uuid, qint64 newClipPos, qint64 newClipBegin )
{
    trigger( new Commands::Clip::Split( m_sequenceWorkflow, uuid, newClipPos, newClipBegin ) );
}

luyikei's avatar
luyikei committed
274 275 276
void
MainWorkflow::linkClips( const QString& uuidA, const QString& uuidB )
{
277
    trigger( new Commands::Clip::Link( m_sequenceWorkflow, uuidA, uuidB ) );
278 279 280 281 282 283
}

void
MainWorkflow::unlinkClips( const QString& uuidA, const QString& uuidB )
{
    trigger( new Commands::Clip::Unlink( m_sequenceWorkflow, uuidA, uuidB ) );
luyikei's avatar
luyikei committed
284 285
}

luyikei's avatar
luyikei committed
286 287 288
QString
MainWorkflow::addEffect( const QString &clipUuid, const QString &effectId )
{
289
    std::shared_ptr<EffectHelper> newEffect;
luyikei's avatar
luyikei committed
290 291 292

    try
    {
293
        newEffect.reset( new EffectHelper( effectId ) );
luyikei's avatar
luyikei committed
294 295 296 297 298 299
    }
    catch( Backend::InvalidServiceException& e )
    {
        return QStringLiteral( "" );
    }

300
    auto clip = m_sequenceWorkflow->clip( clipUuid );
301
    if ( clip && clip->clip->input() )
302
    {
303
        trigger( new Commands::Effect::Add( newEffect, clip->clip->input() ) );
304 305 306
        emit effectsUpdated( clipUuid );
        return newEffect->uuid().toString();
    }
luyikei's avatar
luyikei committed
307 308 309 310

    return QStringLiteral( "" );
}

luyikei's avatar
luyikei committed
311 312 313 314 315 316 317
bool
MainWorkflow::startRenderToFile( const QString &outputFileName, quint32 width, quint32 height,
                                 double fps, const QString &ar, quint32 vbitrate, quint32 abitrate,
                                 quint32 nbChannels, quint32 sampleRate )
{
    m_renderer->stop();

318
    if ( canRender() == false )
luyikei's avatar
luyikei committed
319 320
        return false;

luyikei's avatar
luyikei committed
321
    Backend::MLT::MLTFFmpegOutput output;
322
    auto input = m_sequenceWorkflow->input();
luyikei's avatar
luyikei committed
323 324 325 326 327 328
    OutputEventWatcher            cEventWatcher;
    output.setCallback( &cEventWatcher );
    output.setTarget( qPrintable( outputFileName ) );
    output.setWidth( width );
    output.setHeight( height );
    output.setFrameRate( fps * 100, 100 );
luyikei's avatar
luyikei committed
329
    auto temp = ar.split( "/" );
luyikei's avatar
luyikei committed
330 331 332 333 334
    output.setAspectRatio( temp[0].toInt(), temp[1].toInt() );
    output.setVideoBitrate( vbitrate );
    output.setAudioBitrate( abitrate );
    output.setChannels( nbChannels );
    output.setAudioSampleRate( sampleRate );
335
    output.connect( *input );
luyikei's avatar
luyikei committed
336

Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
337
#ifdef HAVE_GUI
338
    WorkflowFileRendererDialog  dialog( width, height, input->playableLength(), m_renderer->eventWatcher() );
luyikei's avatar
luyikei committed
339 340
    dialog.setModal( true );
    dialog.setOutputFileName( outputFileName );
luyikei's avatar
luyikei committed
341
    connect( &dialog, &WorkflowFileRendererDialog::stop, this, [&output]{ output.stop(); } );
342
    connect( m_renderer->eventWatcher(), &RendererEventWatcher::positionChanged, &dialog,
343
             [this, input, &dialog, width, height]( qint64 pos )
344 345
    {
        // Update the preview per five seconds
346
        if ( pos % qRound( input->fps() * 5 ) == 0 )
347
        {
348
            dialog.updatePreview( input->image( width, height ) );
349 350
        }
    });
luyikei's avatar
luyikei committed
351 352
#endif

luyikei's avatar
luyikei committed
353
    connect( this, &MainWorkflow::mainWorkflowEndReached, this, [&output]{ output.stop(); } );
354
    connect( this, &MainWorkflow::mainWorkflowEndReached, &dialog, &WorkflowFileRendererDialog::accept );
luyikei's avatar
luyikei committed
355

356
    input->setPosition( 0 );
luyikei's avatar
luyikei committed
357
    output.start();
luyikei's avatar
luyikei committed
358

Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
359
#ifdef HAVE_GUI
luyikei's avatar
luyikei committed
360 361 362
    if ( dialog.exec() == QDialog::Rejected )
        return false;
#else
luyikei's avatar
luyikei committed
363
    while ( output.isStopped() == false )
luyikei's avatar
luyikei committed
364 365 366 367 368 369 370 371
        SleepS( 1 );
#endif
    return true;
}

bool
MainWorkflow::canRender()
{
372
    return m_sequenceWorkflow->input()->playableLength() > 0;
luyikei's avatar
luyikei committed
373 374
}

luyikei's avatar
luyikei committed
375 376 377
void
MainWorkflow::preSave()
{
378
    m_settings->value( "tracks" )->set( m_sequenceWorkflow->toVariant() );
luyikei's avatar
luyikei committed
379 380 381 382 383
}

void
MainWorkflow::postLoad()
{
384
    m_sequenceWorkflow->loadFromVariant( m_settings->value( "tracks" )->get() );
385
}