MainWorkflow.cpp 11.2 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"
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"
luyikei's avatar
luyikei committed
36
#include "EffectsEngine/EffectHelper.h"
37
#ifdef HAVE_GUI
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"
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 <QJsonArray>
55
#include <QMutex>
56

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

72 73 74
    connect( m_renderer->eventWatcher().data(), &RendererEventWatcher::lengthChanged, this, &MainWorkflow::lengthChanged );
    connect( m_renderer->eventWatcher().data(), &RendererEventWatcher::endReached, this, &MainWorkflow::mainWorkflowEndReached );
    connect( m_renderer->eventWatcher().data(), &RendererEventWatcher::positionChanged, this, [this]( qint64 pos )
75
    {
76
        emit frameChanged( pos, m_sequenceWorkflow->input()->playableLength(), Vlmc::Renderer );
77
    }, Qt::DirectConnection );
78 79

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

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

MainWorkflow::~MainWorkflow()
{
89
    m_renderer->stop();
90
    delete m_renderer;
91
    delete m_settings;
92 93
}

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

100
void
101
MainWorkflow::muteClip( const QUuid& uuid, unsigned int trackId )
102
{
103
    // TODO
104 105 106
}

void
107
MainWorkflow::unmuteClip( const QUuid& uuid, unsigned int trackId )
108
{
109
    // TODO
110 111
}

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

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

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

131 132 133 134 135 136
void
MainWorkflow::setPosition( qint64 newFrame )
{
    m_renderer->setPosition( newFrame );
}

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

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

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

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

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

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

184
int
185
MainWorkflow::getTrackCount() const
186
{
187
    return m_trackCount;
188
}
189

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

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

QJsonObject
MainWorkflow::clipInfo( const QString& uuid )
{
207 208
    auto c = m_sequenceWorkflow->clip( uuid );
    if ( c != nullptr )
209
    {
210
        auto clip = c->clip;
211
        auto h = clip->toVariant().toHash();
212
        h["uuid"] = uuid;
213
        h["length"] = clip->length();
214
        h["name"] = clip->media()->title();
215
        h["audio"] = c->isAudio;
216 217 218 219 220 221

        QStringList linkedClipList;
        for ( const auto& linkedClipUuid : c->linkedClips )
            linkedClipList << linkedClipUuid.toString();
        h["linkedClips"] = QJsonArray::fromStringList( linkedClipList );

222 223
        h["position"] = m_sequenceWorkflow->position( uuid );
        h["trackId"] = m_sequenceWorkflow->trackId( uuid );
224
        h["filters"] = EffectHelper::toVariant( clip->input() );
225 226 227
        return QJsonObject::fromVariantHash( h );
    }
    return QJsonObject();
228 229
}

230 231 232 233 234 235 236 237 238 239 240 241 242
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();
243
    h["uuid"] = c->uuid().toString();
244 245 246
    return QJsonObject::fromVariantHash( h );
}

247
void
248
MainWorkflow::moveClip( const QString& uuid, quint32 trackId, qint64 startFrame )
249
{
250
    trigger( new Commands::Clip::Move( m_sequenceWorkflow, uuid, trackId, startFrame ) );
251 252
}

253 254 255
void
MainWorkflow::resizeClip( const QString& uuid, qint64 newBegin, qint64 newEnd, qint64 newPos )
{
256
    trigger( new Commands::Clip::Resize( m_sequenceWorkflow, uuid, newBegin, newEnd, newPos ) );
257 258
}

259 260 261
void
MainWorkflow::removeClip( const QString& uuid )
{
262
    trigger( new Commands::Clip::Remove( m_sequenceWorkflow, uuid ) );
263 264
}

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

271 272 273
void
MainWorkflow::linkClips( const QString& uuidA, const QString& uuidB )
{
274
    trigger( new Commands::Clip::Link( m_sequenceWorkflow, uuidA, uuidB ) );
275 276 277 278 279 280
}

void
MainWorkflow::unlinkClips( const QString& uuidA, const QString& uuidB )
{
    trigger( new Commands::Clip::Unlink( m_sequenceWorkflow, uuidA, uuidB ) );
281 282
}

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

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

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

    return QStringLiteral( "" );
}

luyikei's avatar
luyikei committed
308 309 310 311 312 313 314
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();

315
    if ( canRender() == false )
luyikei's avatar
luyikei committed
316 317
        return false;

luyikei's avatar
luyikei committed
318
    Backend::MLT::MLTFFmpegOutput output;
319
    auto input = m_sequenceWorkflow->input();
luyikei's avatar
luyikei committed
320 321 322 323 324 325
    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
326
    auto temp = ar.split( "/" );
luyikei's avatar
luyikei committed
327 328 329 330 331
    output.setAspectRatio( temp[0].toInt(), temp[1].toInt() );
    output.setVideoBitrate( vbitrate );
    output.setAudioBitrate( abitrate );
    output.setChannels( nbChannels );
    output.setAudioSampleRate( sampleRate );
332
    output.connect( *input );
luyikei's avatar
luyikei committed
333

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

352
    input->setPosition( 0 );
luyikei's avatar
luyikei committed
353
    output.start();
luyikei's avatar
luyikei committed
354

355
#ifdef HAVE_GUI
luyikei's avatar
luyikei committed
356 357 358
    if ( dialog.exec() == QDialog::Rejected )
        return false;
#else
luyikei's avatar
luyikei committed
359
    while ( output.isStopped() == false )
luyikei's avatar
luyikei committed
360 361 362 363 364 365 366 367
        SleepS( 1 );
#endif
    return true;
}

bool
MainWorkflow::canRender()
{
368
    return m_sequenceWorkflow->input()->playableLength() > 0;
luyikei's avatar
luyikei committed
369 370
}

371 372 373
void
MainWorkflow::preSave()
{
374
    m_settings->value( "tracks" )->set( m_sequenceWorkflow->toVariant() );
375 376 377 378 379
}

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