MainWorkflow.cpp 12.1 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"
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"
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
#include "ThumbnailWorker.h"
54

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

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

    m_settings->createVar( SettingValue::List, "tracks", QVariantList(), "", "", SettingValue::Nothing );
80 81 82
    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 );
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
std::shared_ptr<Clip>
113
MainWorkflow::clip( const QUuid &uuid, unsigned int trackId )
114
{
115
    // TODO
116
}
117

118 119 120 121 122 123
void
MainWorkflow::trigger( Commands::Generic* command )
{
    m_undoStack->push( command );
}

124 125
void
MainWorkflow::clear()
126
{
127
    m_sequenceWorkflow->clear();
128
    emit cleared();
129
}
130

131 132 133 134 135 136
void
MainWorkflow::setClean()
{
    m_undoStack->setClean();
}

137 138 139 140 141 142
void
MainWorkflow::setPosition( qint64 newFrame )
{
    m_renderer->setPosition( newFrame );
}

143 144 145 146 147 148 149
void
MainWorkflow::setFps( double fps )
{
    Backend::instance()->profile().setFrameRate( fps * 100, 100 );
    emit fpsChanged( fps );
}

150 151 152 153 154 155 156 157 158
void
MainWorkflow::showEffectStack()
{
#ifdef HAVE_GUI
    auto w = new EffectStack( m_sequenceWorkflow->input() );
    w->show();
#endif
}

159 160
void
MainWorkflow::showEffectStack( quint32 trackId )
161
{
162 163 164 165
#ifdef HAVE_GUI
    auto w = new EffectStack( m_sequenceWorkflow->trackInput( trackId ) );
    w->show();
#endif
166 167
}

168 169
void
MainWorkflow::showEffectStack( const QString& uuid )
170
{
171
#ifdef HAVE_GUI
172
    auto w = new EffectStack( m_sequenceWorkflow->clip( uuid )->clip->input() );
173 174 175
    connect( w, &EffectStack::finished, Core::instance()->workflow(), [uuid]{ emit Core::instance()->workflow()->effectsUpdated( uuid ); } );
    w->show();
#endif
176 177
}

178 179
AbstractRenderer*
MainWorkflow::renderer()
180
{
181
    return m_renderer;
182 183
}

184 185 186 187 188 189
Commands::AbstractUndoStack*
MainWorkflow::undoStack()
{
    return m_undoStack.get();
}

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

196 197 198
bool
MainWorkflow::contains( const QUuid &uuid ) const
{
199 200
    auto clip = m_sequenceWorkflow->clip( uuid );
    return !clip == false;
201
}
202

203 204 205 206 207 208
quint32
MainWorkflow::trackCount() const
{
    return m_trackCount;
}

209
void
210
MainWorkflow::addClip( const QString& uuid, quint32 trackId, qint32 pos )
luyikei's avatar
luyikei committed
211
{
212 213
    vlmcDebug() << "Adding clip:" << uuid;
    auto command = new Commands::Clip::Add( m_sequenceWorkflow, uuid, trackId, pos );
214
    trigger( command );
luyikei's avatar
luyikei committed
215 216 217 218 219
}

QJsonObject
MainWorkflow::clipInfo( const QString& uuid )
{
220 221
    auto c = m_sequenceWorkflow->clip( uuid );
    if ( c != nullptr )
222
    {
223
        auto clip = c->clip;
224
        auto h = clip->toVariant().toHash();
225
        h["uuid"] = uuid;
226 227
        h["length"] = (qint64)( clip->input()->length() );
        h["name"] = clip->media()->title();
228
        h["audio"] = c->isAudio;
229 230
        h["position"] = m_sequenceWorkflow->position( uuid );
        h["trackId"] = m_sequenceWorkflow->trackId( uuid );
231
        h["filters"] = EffectHelper::toVariant( clip->input() );
232 233 234
        return QJsonObject::fromVariantHash( h );
    }
    return QJsonObject();
235 236
}

237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
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 );
}

254
void
255
MainWorkflow::moveClip( const QString& uuid, quint32 trackId, qint64 startFrame )
256
{
257
    trigger( new Commands::Clip::Move( m_sequenceWorkflow, uuid, trackId, startFrame ) );
258 259
}

260 261 262
void
MainWorkflow::resizeClip( const QString& uuid, qint64 newBegin, qint64 newEnd, qint64 newPos )
{
263
    trigger( new Commands::Clip::Resize( m_sequenceWorkflow, uuid, newBegin, newEnd, newPos ) );
264 265
}

266 267 268
void
MainWorkflow::removeClip( const QString& uuid )
{
269
    trigger( new Commands::Clip::Remove( m_sequenceWorkflow, uuid ) );
270 271
}

luyikei's avatar
luyikei committed
272 273 274 275 276 277
void
MainWorkflow::splitClip( const QUuid& uuid, qint64 newClipPos, qint64 newClipBegin )
{
    trigger( new Commands::Clip::Split( m_sequenceWorkflow, uuid, newClipPos, newClipBegin ) );
}

278 279 280
void
MainWorkflow::linkClips( const QString& uuidA, const QString& uuidB )
{
281
    trigger( new Commands::Clip::Link( m_sequenceWorkflow, uuidA, uuidB ) );
282 283
}

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

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

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

    return QStringLiteral( "" );
}

309 310 311
void
MainWorkflow::takeThumbnail( const QString& uuid, quint32 pos )
{
312 313 314 315 316
    vlmcDebug() << "Generating thumbnail for" << uuid;
    // We need to fetch the clip from the library. This clip is being added to the library
    // and doesn't have an instance ID yet
    auto swClip = m_sequenceWorkflow->clip( uuid );
    auto clip = swClip->clip;
317
    auto worker = new ThumbnailWorker( uuid, clip->media()->mrl(),
318
                                       pos, clip->input()->width(), clip->input()->height() );
luyikei's avatar
luyikei committed
319 320 321 322 323 324 325 326
    auto t = new QThread;
    worker->moveToThread( t );
    connect( t, &QThread::started, worker, &ThumbnailWorker::run );
    connect( worker, &ThumbnailWorker::imageReady, this, &MainWorkflow::thumbnailUpdated, Qt::DirectConnection );
    connect( worker, &ThumbnailWorker::imageReady, t, &QThread::quit );
    connect( t, &QThread::finished, worker, &ThumbnailWorker::deleteLater );
    connect( t, &QThread::finished, t, &QThread::deleteLater );
    t->start();
327 328
}

luyikei's avatar
luyikei committed
329 330 331 332 333 334 335
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();

336
    if ( canRender() == false )
luyikei's avatar
luyikei committed
337 338
        return false;

luyikei's avatar
luyikei committed
339
    Backend::MLT::MLTFFmpegOutput output;
340
    auto input = m_sequenceWorkflow->input();
luyikei's avatar
luyikei committed
341 342 343 344 345 346
    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
347
    auto temp = ar.split( "/" );
luyikei's avatar
luyikei committed
348 349 350 351 352
    output.setAspectRatio( temp[0].toInt(), temp[1].toInt() );
    output.setVideoBitrate( vbitrate );
    output.setAudioBitrate( abitrate );
    output.setChannels( nbChannels );
    output.setAudioSampleRate( sampleRate );
353
    output.connect( *input );
luyikei's avatar
luyikei committed
354

355
#ifdef HAVE_GUI
356
    WorkflowFileRendererDialog  dialog( width, height, input->playableLength(), m_renderer->eventWatcher() );
luyikei's avatar
luyikei committed
357 358
    dialog.setModal( true );
    dialog.setOutputFileName( outputFileName );
luyikei's avatar
luyikei committed
359 360
    connect( &cEventWatcher, &OutputEventWatcher::stopped, &dialog, &WorkflowFileRendererDialog::accept );
    connect( &dialog, &WorkflowFileRendererDialog::stop, this, [&output]{ output.stop(); } );
361
    connect( m_renderer->eventWatcher(), &RendererEventWatcher::positionChanged, &dialog,
362
             [this, input, &dialog, width, height]( qint64 pos )
363 364
    {
        // Update the preview per five seconds
365
        if ( pos % qRound( input->fps() * 5 ) == 0 )
366
        {
367
            dialog.updatePreview( input->image( width, height ) );
368 369
        }
    });
luyikei's avatar
luyikei committed
370 371
#endif

luyikei's avatar
luyikei committed
372 373
    connect( &cEventWatcher, &OutputEventWatcher::stopped, this, [&output]{ output.stop(); } );
    connect( this, &MainWorkflow::mainWorkflowEndReached, this, [&output]{ output.stop(); } );
luyikei's avatar
luyikei committed
374

375
    input->setPosition( 0 );
luyikei's avatar
luyikei committed
376
    output.start();
luyikei's avatar
luyikei committed
377

378
#ifdef HAVE_GUI
luyikei's avatar
luyikei committed
379 380 381
    if ( dialog.exec() == QDialog::Rejected )
        return false;
#else
luyikei's avatar
luyikei committed
382
    while ( output.isStopped() == false )
luyikei's avatar
luyikei committed
383 384 385 386 387 388 389 390
        SleepS( 1 );
#endif
    return true;
}

bool
MainWorkflow::canRender()
{
391
    return m_sequenceWorkflow->input()->playableLength() > 0;
luyikei's avatar
luyikei committed
392 393
}

394 395 396
void
MainWorkflow::preSave()
{
397
    m_settings->value( "tracks" )->set( m_sequenceWorkflow->toVariant() );
398 399 400 401 402
}

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