core.cpp 27.9 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
/*
Copyright (C) 2014  Till Theato <root@ttill.de>
This file is part of kdenlive. See www.kdenlive.org.

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 3 of the License, or
(at your option) any later version.
*/

#include "core.h"
Nicolas Carion's avatar
Nicolas Carion committed
12
#include "bin/bin.h"
13
#include "bin/projectitemmodel.h"
14
#include "capture/mediacapture.h"
Nicolas Carion's avatar
Nicolas Carion committed
15
16
#include "doc/docundostack.hpp"
#include "doc/kdenlivedoc.h"
17
#include "jobs/jobmanager.h"
Nicolas Carion's avatar
Nicolas Carion committed
18
#include "kdenlive_debug.h"
19
#include "kdenlivesettings.h"
Nicolas Carion's avatar
Nicolas Carion committed
20
#include "library/librarywidget.h"
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
21
#include "audiomixer/mixermanager.hpp"
Nicolas Carion's avatar
Nicolas Carion committed
22
#include "mainwindow.h"
23
#include "mltconnection.h"
24
#include "mltcontroller/clipcontroller.h"
Nicolas Carion's avatar
Nicolas Carion committed
25
26
27
28
#include "monitor/monitormanager.h"
#include "profiles/profilemodel.hpp"
#include "profiles/profilerepository.hpp"
#include "project/projectmanager.h"
29
#include "timeline2/model/timelineitemmodel.hpp"
30
31
#include "timeline2/view/timelinecontroller.h"
#include "timeline2/view/timelinewidget.h"
32

33
34
#include <mlt++/MltRepository.h>

35
#include <KMessageBox>
Nicolas Carion's avatar
Nicolas Carion committed
36
#include <QCoreApplication>
37
#include <QInputDialog>
38
#include <QDir>
39
#include <QQuickStyle>
Vincent Pinon's avatar
Vincent Pinon committed
40
#include <locale>
41
42
43
44
#ifdef Q_OS_MAC
#include <xlocale.h>
#endif

45
std::unique_ptr<Core> Core::m_self;
46
Core::Core()
Nicolas Carion's avatar
Nicolas Carion committed
47
    : m_thumbProfile(nullptr)
48
    , m_capture(new MediaCapture(this))
49
50
51
{
}

52
53
54
void Core::prepareShutdown()
{
    m_guiConstructed = false;
55
    m_mainWindow->getCurrentTimeline()->controller()->prepareClose();
56
    projectItemModel()->blockSignals(true);
57
    QThreadPool::globalInstance()->clear();
58
59
}

60
61
Core::~Core()
{
62
    qDebug() << "deleting core";
63
64
65
    if (m_monitorManager) {
        delete m_monitorManager;
    }
66
    // delete m_binWidget;
67
68
69
    if (m_projectManager) {
        delete m_projectManager;
    }
70
    ClipController::mediaUnavailable.reset();
71
72
}

73
void Core::build(bool isAppImage, const QString &MltPath)
74
{
75
76
77
    if (m_self) {
        return;
    }
78
    m_self.reset(new Core());
79
    m_self->initLocale();
80

Nicolas Carion's avatar
Nicolas Carion committed
81
82
83
84
85
86
    qRegisterMetaType<audioShortVector>("audioShortVector");
    qRegisterMetaType<QVector<double>>("QVector<double>");
    qRegisterMetaType<MessageType>("MessageType");
    qRegisterMetaType<stringMap>("stringMap");
    qRegisterMetaType<audioByteArray>("audioByteArray");
    qRegisterMetaType<QList<ItemInfo>>("QList<ItemInfo>");
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
87
    qRegisterMetaType<std::shared_ptr<Mlt::Producer>>("std::shared_ptr<Mlt::Producer>");
Nicolas Carion's avatar
Nicolas Carion committed
88
89
90
    qRegisterMetaType<QVector<int>>();
    qRegisterMetaType<QDomElement>("QDomElement");
    qRegisterMetaType<requestClipInfo>("requestClipInfo");
91
92
93
94
95
96
97
98
99
100
101
102
    
    if (isAppImage) {
        QString appPath = qApp->applicationDirPath();
        KdenliveSettings::setFfmpegpath(QDir::cleanPath(appPath + QStringLiteral("/ffmpeg")));
        KdenliveSettings::setFfplaypath(QDir::cleanPath(appPath + QStringLiteral("/ffplay")));
        KdenliveSettings::setFfprobepath(QDir::cleanPath(appPath + QStringLiteral("/ffprobe")));
        KdenliveSettings::setRendererpath(QDir::cleanPath(appPath + QStringLiteral("/melt")));
        MltConnection::construct(QDir::cleanPath(appPath + QStringLiteral("/../share/mlt/profiles")));
    } else {
        // Open connection with Mlt
        MltConnection::construct(MltPath);
    }
103

Nicolas Carion's avatar
Nicolas Carion committed
104
    // load the profile from disk
105
    ProfileRepository::get()->refresh();
Nicolas Carion's avatar
Nicolas Carion committed
106
    // load default profile
107
108
109
110
    m_self->m_profile = KdenliveSettings::default_profile();
    if (m_self->m_profile.isEmpty()) {
        m_self->m_profile = ProjectManager::getDefaultProjectFormat();
        KdenliveSettings::setDefault_profile(m_self->m_profile);
111
    }
112
113

    // Init producer shown for unavailable media
114
    // TODO make it a more proper image, it currently causes a crash on exit
115
    ClipController::mediaUnavailable = std::make_shared<Mlt::Producer>(ProfileRepository::get()->getProfile(m_self->m_profile)->profile(), "color:blue");
116
    ClipController::mediaUnavailable->set("length", 99999999);
117
118

    m_self->m_projectItemModel = ProjectItemModel::construct();
119
    // Job manager must be created before bin to correctly connect
120
    m_self->m_jobManager.reset(new JobManager(m_self.get()));
121
122
}

123
void Core::initGUI(const QUrl &Url, const QString &clipsToLoad)
124
{
Nicolas Carion's avatar
Nicolas Carion committed
125
    m_guiConstructed = true;
126
127
128
    m_profile = KdenliveSettings::default_profile();
    m_currentProfile = m_profile;
    profileChanged();
129
    m_mainWindow = new MainWindow();
130
131
132
133
134
135
136
    QStringList styles = QQuickStyle::availableStyles();
    if (styles.contains(QLatin1String("org.kde.desktop"))) {
        QQuickStyle::setStyle("org.kde.desktop");
    } else if (styles.contains(QLatin1String("Fusion"))) {
        QQuickStyle::setStyle("Fusion");
    }

137
    connect(this, &Core::showConfigDialog, m_mainWindow, &MainWindow::slotPreferences);
138

Nicolas Carion's avatar
format    
Nicolas Carion committed
139
    // load default profile and ask user to select one if not found.
140
141
    if (m_profile.isEmpty()) {
        m_profile = ProjectManager::getDefaultProjectFormat();
142
        profileChanged();
143
144
        KdenliveSettings::setDefault_profile(m_profile);
    }
145

146
    if (!ProfileRepository::get()->profileExists(m_profile)) {
147
148
        KMessageBox::sorry(m_mainWindow, i18n("The default profile of Kdenlive is not set or invalid, press OK to set it to a correct value."));

Nicolas Carion's avatar
Nicolas Carion committed
149
150
        // TODO this simple widget should be improved and probably use profileWidget
        // we get the list of profiles
151
152
        QVector<QPair<QString, QString>> all_profiles = ProfileRepository::get()->getAllProfiles();
        QStringList all_descriptions;
Nicolas Carion's avatar
Nicolas Carion committed
153
        for (const auto &profile : all_profiles) {
154
155
156
            all_descriptions << profile.first;
        }

Nicolas Carion's avatar
Nicolas Carion committed
157
        // ask the user
158
        bool ok;
Nicolas Carion's avatar
Nicolas Carion committed
159
        QString item = QInputDialog::getItem(m_mainWindow, i18n("Select Default Profile"), i18n("Profile:"), all_descriptions, 0, false, &ok);
160
161
        if (ok) {
            ok = false;
Nicolas Carion's avatar
Nicolas Carion committed
162
            for (const auto &profile : all_profiles) {
163
164
165
166
167
168
169
                if (profile.first == item) {
                    m_profile = profile.second;
                    ok = true;
                }
            }
        }
        if (!ok) {
Nicolas Carion's avatar
Nicolas Carion committed
170
171
172
            KMessageBox::error(
                m_mainWindow,
                i18n("The given profile is invalid. We default to the profile \"dv_pal\", but you can change this from Kdenlive's settings panel"));
173
            m_profile = QStringLiteral("dv_pal");
174
175
        }
        KdenliveSettings::setDefault_profile(m_profile);
176
        profileChanged();
177
178
    }

Till Theato's avatar
Till Theato committed
179
    m_projectManager = new ProjectManager(this);
180
181
    m_binWidget = new Bin(m_projectItemModel, m_mainWindow);
    m_library = new LibraryWidget(m_projectManager, m_mainWindow);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
182
    m_mixerWidget = new MixerManager(m_mainWindow);
Laurent Montel's avatar
Laurent Montel committed
183
    connect(m_library, SIGNAL(addProjectClips(QList<QUrl>)), m_binWidget, SLOT(droppedUrls(QList<QUrl>)));
184
    connect(this, &Core::updateLibraryPath, m_library, &LibraryWidget::slotUpdateLibraryPath);
185
    connect(m_capture.get(), &MediaCapture::recordStateChanged, m_mixerWidget, &MixerManager::recordStateChanged);
186
    m_monitorManager = new MonitorManager(this);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
187
    connect(m_monitorManager, &MonitorManager::cleanMixer, m_mixerWidget, &MixerManager::clearMixers);
188
    // Producer queue, creating MLT::Producers on request
189
    /*
190
    m_producerQueue = new ProducerQueue(m_binController);
191
    connect(m_producerQueue, &ProducerQueue::gotFileProperties, m_binWidget, &Bin::slotProducerReady);
Laurent Montel's avatar
Laurent Montel committed
192
    connect(m_producerQueue, &ProducerQueue::replyGetImage, m_binWidget, &Bin::slotThumbnailReady);
193
194
    connect(m_producerQueue, &ProducerQueue::requestProxy,
            [this](const QString &id){ m_binWidget->startJob(id, AbstractClipJob::PROXYJOB);});
Laurent Montel's avatar
Laurent Montel committed
195
    connect(m_producerQueue, &ProducerQueue::removeInvalidClip, m_binWidget, &Bin::slotRemoveInvalidClip, Qt::DirectConnection);
Laurent Montel's avatar
Laurent Montel committed
196
    connect(m_producerQueue, SIGNAL(addClip(QString, QMap<QString, QString>)), m_binWidget, SLOT(slotAddUrl(QString, QMap<QString, QString>)));
197
    connect(m_binController.get(), SIGNAL(createThumb(QDomElement, QString, int)), m_producerQueue, SLOT(getFileProperties(QDomElement, QString, int)));
Laurent Montel's avatar
Laurent Montel committed
198
    connect(m_binWidget, &Bin::producerReady, m_producerQueue, &ProducerQueue::slotProcessingDone, Qt::DirectConnection);
Nicolas Carion's avatar
Nicolas Carion committed
199
    // TODO
200
    connect(m_producerQueue, SIGNAL(removeInvalidProxy(QString,bool)), m_binWidget, SLOT(slotRemoveInvalidProxy(QString,bool)));*/
201
202

    m_mainWindow->init();
203
    projectManager()->init(Url, clipsToLoad);
204
    if (qApp->isSessionRestored()) {
Nicolas Carion's avatar
Nicolas Carion committed
205
        // NOTE: we are restoring only one window, because Kdenlive only uses one MainWindow
206
207
        m_mainWindow->restore(1, false);
    }
208
    QMetaObject::invokeMethod(pCore->projectManager(), "slotLoadOnOpen", Qt::QueuedConnection);
209
    m_mainWindow->show();
210
    QThreadPool::globalInstance()->setMaxThreadCount(qMin(4, QThreadPool::globalInstance()->maxThreadCount()));
211
212
}

213
214
215
216
217
218
219
220
221
222
223
224
225
void Core::buildLumaThumbs(const QStringList &values)
{
    for (auto &entry : values) {
        if (MainWindow::m_lumacache.contains(entry)) {
            continue;
        }
        QImage pix(entry);
        if (!pix.isNull()) {
            MainWindow::m_lumacache.insert(entry, pix.scaled(50, 30, Qt::KeepAspectRatio, Qt::SmoothTransformation));
        }
    }
}

Nicolas Carion's avatar
Nicolas Carion committed
226
std::unique_ptr<Core> &Core::self()
227
{
228
229
230
    if (!m_self) {
        qDebug() << "Error : Core has not been created";
    }
231
232
233
    return m_self;
}

Laurent Montel's avatar
Laurent Montel committed
234
MainWindow *Core::window()
235
236
237
238
{
    return m_mainWindow;
}

Till Theato's avatar
Till Theato committed
239
240
241
242
243
ProjectManager *Core::projectManager()
{
    return m_projectManager;
}

Laurent Montel's avatar
Laurent Montel committed
244
MonitorManager *Core::monitorManager()
245
246
247
248
{
    return m_monitorManager;
}

249
250
251
252
253
254
255
256
Monitor *Core::getMonitor(int id)
{
    if (id == Kdenlive::ClipMonitor) {
        return m_monitorManager->clipMonitor();
    }
    return m_monitorManager->projectMonitor();
}

257
258
259
260
261
Bin *Core::bin()
{
    return m_binWidget;
}

262
263
264
265
266
void Core::selectBinClip(const QString &clipId, int frame, const QPoint &zone)
{
    m_binWidget->selectClipById(clipId, frame, zone);
}

267
std::shared_ptr<JobManager> Core::jobManager()
268
{
269
    return m_jobManager;
270
271
}

272
273
274
275
276
LibraryWidget *Core::library()
{
    return m_library;
}

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
277
278
279
280
281
MixerManager *Core::mixer()
{
    return m_mixerWidget;
}

282
283
284
void Core::initLocale()
{
    QLocale systemLocale = QLocale();
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
285
#ifndef Q_OS_MAC
Laurent Montel's avatar
Laurent Montel committed
286
    setlocale(LC_NUMERIC, nullptr);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
287
#else
Laurent Montel's avatar
Laurent Montel committed
288
    setlocale(LC_NUMERIC_MASK, nullptr);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
289
#endif
290
291
292

// localeconv()->decimal_point does not give reliable results on Windows
#ifndef Q_OS_WIN
293
    char *separator = localeconv()->decimal_point;
294
    if (QString::fromUtf8(separator) != QChar(systemLocale.decimalPoint())) {
Nicolas Carion's avatar
Nicolas Carion committed
295
        // qCDebug(KDENLIVE_LOG)<<"------\n!!! system locale is not similar to Qt's locale... be prepared for bugs!!!\n------";
296
297
        // HACK: There is a locale conflict, so set locale to C
        // Make sure to override exported values or it won't work
Vincent Pinon's avatar
Vincent Pinon committed
298
        qputenv("LANG", "C");
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
299
#ifndef Q_OS_MAC
300
        setlocale(LC_NUMERIC, "C");
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
301
302
303
#else
        setlocale(LC_NUMERIC_MASK, "C");
#endif
304
305
        systemLocale = QLocale::c();
    }
306
#endif
307
308
309
310
311

    systemLocale.setNumberOptions(QLocale::OmitGroupSeparator);
    QLocale::setDefault(systemLocale);
}

Nicolas Carion's avatar
Nicolas Carion committed
312
std::unique_ptr<Mlt::Repository> &Core::getMltRepository()
313
{
Nicolas Carion's avatar
Nicolas Carion committed
314
    return MltConnection::self()->getMltRepository();
315
}
316

Nicolas Carion's avatar
Nicolas Carion committed
317
std::unique_ptr<ProfileModel> &Core::getCurrentProfile() const
318
{
319
    return ProfileRepository::get()->getProfile(m_currentProfile);
320
}
321

322
323
Mlt::Profile *Core::getProjectProfile()
{
324
325
326
    if (!m_projectProfile) {
        m_projectProfile = std::make_unique<Mlt::Profile>(m_currentProfile.toStdString().c_str());
        m_projectProfile->set_explicit(1);
327
    }
328
    return m_projectProfile.get();
329
330
}

331
332
333
334
335
const QString &Core::getCurrentProfilePath() const
{
    return m_currentProfile;
}

336
337
bool Core::setCurrentProfile(const QString &profilePath)
{
338
339
340
341
    if (m_currentProfile == profilePath) {
        // no change required
        return true;
    }
342
    if (ProfileRepository::get()->profileExists(profilePath)) {
343
        m_currentProfile = profilePath;
344
        m_thumbProfile.reset();
345
346
347
348
349
        if (m_projectProfile) {
            m_projectProfile->set_width(getCurrentProfile()->width());
            m_projectProfile->set_height(getCurrentProfile()->height());
            m_projectProfile->set_display_aspect(getCurrentProfile()->display_aspect_num(), getCurrentProfile()->display_aspect_den());
            m_projectProfile->set_sample_aspect(getCurrentProfile()->sample_aspect_num(), getCurrentProfile()->sample_aspect_den());
350
            m_projectProfile->set_frame_rate(getCurrentProfile()->frame_rate_num(), getCurrentProfile()->frame_rate_den());
351
352
353
            m_projectProfile->set_colorspace(getCurrentProfile()->colorspace());
            m_projectProfile->set_progressive(getCurrentProfile()->progressive());
        }
354
        // inform render widget
355
        profileChanged();
356
        m_mainWindow->updateRenderWidgetProfile();
357
        if (m_guiConstructed && m_mainWindow->getCurrentTimeline()->controller()->getModel()) {
358
            m_mainWindow->getCurrentTimeline()->controller()->getModel()->updateProfile(getProjectProfile());
359
            checkProfileValidity();
360
        }
361
362
363
364
365
        return true;
    }
    return false;
}

366
367
368
369
370
371
372
373
374
375
376
void Core::checkProfileValidity()
{
    int offset = (getCurrentProfile()->profile().width() % 8) + (getCurrentProfile()->profile().height() % 2);
    if (offset > 0) {
        // Profile is broken, warn user
        if (m_binWidget) {
            m_binWidget->displayBinMessage(i18n("Your project profile is invalid, rendering might fail."), KMessageWidget::Warning);
        }
    }
}

377
378
379
380
381
double Core::getCurrentSar() const
{
    return getCurrentProfile()->sar();
}

382
double Core::getCurrentDar() const
383
384
385
386
{
    return getCurrentProfile()->dar();
}

387
double Core::getCurrentFps() const
388
389
390
{
    return getCurrentProfile()->fps();
}
391
392
393

QSize Core::getCurrentFrameDisplaySize() const
{
Nicolas Carion's avatar
Nicolas Carion committed
394
    return {(int)(getCurrentProfile()->height() * getCurrentDar() + 0.5), getCurrentProfile()->height()};
395
396
397
398
}

QSize Core::getCurrentFrameSize() const
{
Nicolas Carion's avatar
Nicolas Carion committed
399
    return {getCurrentProfile()->width(), getCurrentProfile()->height()};
400
}
401
402
403

void Core::requestMonitorRefresh()
{
404
    if (!m_guiConstructed) return;
405
406
    m_monitorManager->refreshProjectMonitor();
}
407

408
409
void Core::refreshProjectRange(QSize range)
{
410
    if (!m_guiConstructed) return;
411
412
413
    m_monitorManager->refreshProjectRange(range);
}

414
int Core::getItemPosition(const ObjectId &id)
415
{
416
417
    if (!m_guiConstructed) return 0;
    switch (id.first) {
418
    case ObjectType::TimelineClip:
419
        if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isClip(id.second)) {
420
421
422
423
            return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getClipPosition(id.second);
        }
        break;
    case ObjectType::TimelineComposition:
424
        if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isComposition(id.second)) {
425
426
427
            return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getCompositionPosition(id.second);
        }
        break;
428
    case ObjectType::BinClip:
429
    case ObjectType::TimelineTrack:
430
    case ObjectType::Master:
431
432
        return 0;
        break;
433
434
435
436
437
438
    default:
        qDebug() << "ERROR: unhandled object type";
    }
    return 0;
}

439
440
int Core::getItemIn(const ObjectId &id)
{
441
    if (!m_guiConstructed || !m_mainWindow->getCurrentTimeline() || !m_mainWindow->getCurrentTimeline()->controller()->getModel()) {
442
        qDebug() << "/ / // QUERYING ITEM IN BUT GUI NOT BUILD!!";
443
444
        return 0;
    }
445
446
447
448
    switch (id.first) {
    case ObjectType::TimelineClip:
        if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isClip(id.second)) {
            return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getClipIn(id.second);
449
450
        } else {
            qDebug()<<"// ERROR QUERYING NON CLIP PROPERTIES\n\n!!!!!!!!!!!!!!!!!!!!!!!!!!";
451
452
453
454
        }
        break;
    case ObjectType::TimelineComposition:
    case ObjectType::BinClip:
455
    case ObjectType::TimelineTrack:
456
    case ObjectType::Master:
457
458
459
460
461
462
463
464
        return 0;
        break;
    default:
        qDebug() << "ERROR: unhandled object type";
    }
    return 0;
}

465
466
467
468
469
470
471
472
473
474
PlaylistState::ClipState Core::getItemState(const ObjectId &id)
{
    if (!m_guiConstructed) return PlaylistState::Disabled;
    switch (id.first) {
    case ObjectType::TimelineClip:
        if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isClip(id.second)) {
            return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getClipState(id.second);
        }
        break;
    case ObjectType::TimelineComposition:
475
        return PlaylistState::VideoOnly;
476
477
478
479
        break;
    case ObjectType::BinClip:
        return m_binWidget->getClipState(id.second);
        break;
480
481
    case ObjectType::TimelineTrack:
        return m_mainWindow->getCurrentTimeline()->controller()->getModel()->isAudioTrack(id.second) ? PlaylistState::AudioOnly : PlaylistState::VideoOnly;
482
483
484
    case ObjectType::Master:
        return PlaylistState::Disabled;
        break;
485
486
487
488
489
490
491
    default:
        qDebug() << "ERROR: unhandled object type";
        break;
    }
    return PlaylistState::Disabled;
}

492
493
int Core::getItemDuration(const ObjectId &id)
{
494
495
    if (!m_guiConstructed) return 0;
    switch (id.first) {
496
    case ObjectType::TimelineClip:
497
        if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isClip(id.second)) {
498
499
500
501
            return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getClipPlaytime(id.second);
        }
        break;
    case ObjectType::TimelineComposition:
502
        if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isComposition(id.second)) {
503
504
505
            return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getCompositionPlaytime(id.second);
        }
        break;
506
    case ObjectType::BinClip:
507
        return (int)m_binWidget->getClipDuration(id.second);
508
        break;
509
    case ObjectType::TimelineTrack:
510
    case ObjectType::Master:
511
        return m_mainWindow->getCurrentTimeline()->controller()->duration();
512
513
    default:
        qDebug() << "ERROR: unhandled object type";
514
515
516
517
518
519
    }
    return 0;
}

int Core::getItemTrack(const ObjectId &id)
{
520
521
    if (!m_guiConstructed) return 0;
    switch (id.first) {
522
523
524
525
526
527
    case ObjectType::TimelineClip:
    case ObjectType::TimelineComposition:
        return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getItemTrackId(id.second);
        break;
    default:
        qDebug() << "ERROR: unhandled object type";
528
529
530
531
    }
    return 0;
}

532
void Core::refreshProjectItem(const ObjectId &id)
533
{
534
    if (!m_guiConstructed || m_mainWindow->getCurrentTimeline()->loading) return;
535
    switch (id.first) {
536
    case ObjectType::TimelineClip:
537
        if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isClip(id.second)) {
538
539
540
541
            m_mainWindow->getCurrentTimeline()->controller()->refreshItem(id.second);
        }
        break;
    case ObjectType::TimelineComposition:
542
        if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isComposition(id.second)) {
543
544
545
546
            m_mainWindow->getCurrentTimeline()->controller()->refreshItem(id.second);
        }
        break;
    case ObjectType::TimelineTrack:
547
        if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isTrack(id.second)) {
548
549
550
551
            requestMonitorRefresh();
        }
        break;
    case ObjectType::BinClip:
552
        m_monitorManager->activateMonitor(Kdenlive::ClipMonitor);
553
        m_monitorManager->refreshClipMonitor();
554
555
556
        if (m_monitorManager->projectMonitorVisible() && m_mainWindow->getCurrentTimeline()->controller()->refreshIfVisible(id.second)) {
            m_monitorManager->refreshTimer.start();
        }
557
        break;
558
559
560
    case ObjectType::Master:
        requestMonitorRefresh();
        break;
561
562
563
    default:
        qDebug() << "ERROR: unhandled object type";
    }
564
565
}

566
567
568
569
570
571
572
573
bool Core::hasTimelinePreview() const
{
    if (!m_guiConstructed) {
        return false;
    }
    return m_mainWindow->getCurrentTimeline()->controller()->renderedChunks().size() > 0;
}

574
575
576
577
KdenliveDoc *Core::currentDoc()
{
    return m_projectManager->current();
}
578

579
580
581
582
583
void Core::setDocumentModified()
{
    m_projectManager->current()->setModified();;
}

584
585
586
587
588
589
590
591
int Core::projectDuration() const
{
    if (!m_guiConstructed) {
        return 0;
    }
    return m_mainWindow->getCurrentTimeline()->controller()->duration();
}

592
593
594
595
void Core::profileChanged()
{
    GenTime::setFps(getCurrentFps());
}
596
597
598

void Core::pushUndo(const Fun &undo, const Fun &redo, const QString &text)
{
Nicolas Carion's avatar
Nicolas Carion committed
599
    undoStack()->push(new FunctionalUndoCommand(undo, redo, text));
600
}
601
602
603

void Core::pushUndo(QUndoCommand *command)
{
Nicolas Carion's avatar
Nicolas Carion committed
604
    undoStack()->push(command);
605
}
606
607
608

void Core::displayMessage(const QString &message, MessageType type, int timeout)
{
Nicolas Carion's avatar
Nicolas Carion committed
609
    if (m_mainWindow) {
610
611
612
613
614
        if (type == ProcessingJobMessage || type == OperationCompletedMessage) {
            m_mainWindow->displayProgressMessage(message, type, timeout);
        } else {
            m_mainWindow->displayMessage(message, type, timeout);
        }
Nicolas Carion's avatar
Nicolas Carion committed
615
616
617
    } else {
        qDebug() << message;
    }
618
}
619

620
621
void Core::displayBinMessage(const QString &text, int type, const QList<QAction *> &actions)
{
Nicolas Carion's avatar
Nicolas Carion committed
622
    m_binWidget->doDisplayMessage(text, (KMessageWidget::MessageType)type, actions);
623
624
}

625
626
627
628
629
void Core::displayBinLogMessage(const QString &text, int type, const QString &logInfo)
{
    m_binWidget->doDisplayMessage(text, (KMessageWidget::MessageType)type, logInfo);
}

630
631
void Core::clearAssetPanel(int itemId)
{
Nicolas Carion's avatar
Nicolas Carion committed
632
    if (m_guiConstructed) m_mainWindow->clearAssetPanel(itemId);
633
}
634

635
636
std::shared_ptr<EffectStackModel> Core::getItemEffectStack(int itemType, int itemId)
{
637
    if (!m_guiConstructed) return nullptr;
638
    switch (itemType) {
639
640
641
    case (int)ObjectType::TimelineClip:
        return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getClipEffectStack(itemId);
    case (int)ObjectType::TimelineTrack:
642
        return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getTrackEffectStackModel(itemId);
643
644
645
        break;
    case (int)ObjectType::BinClip:
        return m_binWidget->getClipEffectStack(itemId);
646
647
    case (int)ObjectType::Master:
        return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getMasterEffectStackModel();
648
649
    default:
        return nullptr;
650
651
    }
}
Nicolas Carion's avatar
Nicolas Carion committed
652
653
654

std::shared_ptr<DocUndoStack> Core::undoStack()
{
655
    return projectManager()->undoStack();
Nicolas Carion's avatar
Nicolas Carion committed
656
}
657

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
658
QMap<int, QString> Core::getTrackNames(bool videoOnly)
659
{
660
    if (!m_guiConstructed) return QMap<int, QString>();
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
661
    return m_mainWindow->getCurrentTimeline()->controller()->getTrackNames(videoOnly);
662
663
}

Nicolas Carion's avatar
Nicolas Carion committed
664
QPair<int, int> Core::getCompositionATrack(int cid) const
665
{
Nicolas Carion's avatar
Nicolas Carion committed
666
    if (!m_guiConstructed) return {};
667
668
669
    return m_mainWindow->getCurrentTimeline()->controller()->getCompositionATrack(cid);
}

670
671
672
673
674
bool Core::compositionAutoTrack(int cid) const
{
    return m_mainWindow->getCurrentTimeline()->controller()->compositionAutoTrack(cid);
}

675
676
void Core::setCompositionATrack(int cid, int aTrack)
{
677
    if (!m_guiConstructed) return;
678
679
    m_mainWindow->getCurrentTimeline()->controller()->setCompositionATrack(cid, aTrack);
}
680
681
682
683
684

std::shared_ptr<ProjectItemModel> Core::projectItemModel()
{
    return m_projectItemModel;
}
685

686
687
688
689
690
691
void Core::invalidateRange(QSize range)
{
    if (!m_mainWindow || m_mainWindow->getCurrentTimeline()->loading) return;
    m_mainWindow->getCurrentTimeline()->controller()->invalidateZone(range.width(), range.height());
}

692
693
void Core::invalidateItem(ObjectId itemId)
{
694
    if (!m_mainWindow || !m_mainWindow->getCurrentTimeline() || m_mainWindow->getCurrentTimeline()->loading) return;
695
    switch (itemId.first) {
696
    case ObjectType::TimelineClip:
697
    case ObjectType::TimelineComposition:
698
        m_mainWindow->getCurrentTimeline()->controller()->invalidateItem(itemId.second);
699
700
        break;
    case ObjectType::TimelineTrack:
701
        m_mainWindow->getCurrentTimeline()->controller()->invalidateTrack(itemId.second);
702
        break;
703
704
705
    case ObjectType::BinClip:
        m_binWidget->invalidateClip(QString::number(itemId.second));
        break;
706
707
708
    case ObjectType::Master:
        m_mainWindow->getCurrentTimeline()->controller()->invalidateZone(0, -1);
        break;
709
    default:
710
        // compositions should not have effects
711
        break;
712
713
    }
}
714
715
716
717
718

double Core::getClipSpeed(int id) const
{
    return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getClipSpeed(id);
}
719
720
721

void Core::updateItemKeyframes(ObjectId id)
{
722
    if (id.first == ObjectType::TimelineClip && m_mainWindow) {
723
724
725
726
        m_mainWindow->getCurrentTimeline()->controller()->updateClip(id.second, {TimelineModel::KeyframesRole});
    }
}

727
728
void Core::updateItemModel(ObjectId id, const QString &service)
{
729
    if (m_mainWindow && id.first == ObjectType::TimelineClip && !m_mainWindow->getCurrentTimeline()->loading && service.startsWith(QLatin1String("fade"))) {
730
        bool startFade = service == QLatin1String("fadein") || service == QLatin1String("fade_from_black");
Nicolas Carion's avatar
Nicolas Carion committed
731
        m_mainWindow->getCurrentTimeline()->controller()->updateClip(id.second, {startFade ? TimelineModel::FadeInRole : TimelineModel::FadeOutRole});
732
733
734
    }
}

735
736
737
738
void Core::showClipKeyframes(ObjectId id, bool enable)
{
    if (id.first == ObjectType::TimelineClip) {
        m_mainWindow->getCurrentTimeline()->controller()->showClipKeyframes(id.second, enable);
739
740
    } else if (id.first == ObjectType::TimelineComposition) {
        m_mainWindow->getCurrentTimeline()->controller()->showCompositionKeyframes(id.second, enable);
741
742
    }
}
743
744
745

Mlt::Profile *Core::thumbProfile()
{
746
    QMutexLocker lck(&m_thumbProfileMutex);
747
    if (!m_thumbProfile) {
Nicolas Carion's avatar
Nicolas Carion committed
748
        m_thumbProfile = std::make_unique<Mlt::Profile>(m_currentProfile.toStdString().c_str());
749
750
        m_thumbProfile->set_height(144);
        int width = 144 * m_thumbProfile->dar() + 0.5;
751
752
753
        if (width % 8 > 0) {
            width += 8 - width % 8;
        }
754
755
756
757
        m_thumbProfile->set_width(width);
    }
    return m_thumbProfile.get();
}
758

759
760
int Core::getTimelinePosition() const
{
761
762
    if (m_guiConstructed) {
        return m_monitorManager->projectMonitor()->position();
763
764
765
766
    }
    return 0;
}

767
768
769
770
771
772
773
void Core::triggerAction(const QString &name)
{
    QAction *action = m_mainWindow->actionCollection()->action(name);
    if (action) {
        action->trigger();
    }
}
774
775
776
777
778

void Core::clean()
{
    m_self.reset();
}
779

780
void Core::startMediaCapture(int tid, bool checkAudio, bool checkVideo)
781
782
{
    if (checkAudio && checkVideo) {
783
        m_capture->recordVideo(tid, true);
784
    } else if (checkAudio) {
785
        m_capture->recordAudio(tid, true);
786
    }
787
    m_mediaCaptureFile = m_capture->getCaptureOutputLocation();
788
789
}

790
void Core::stopMediaCapture(int tid, bool checkAudio, bool checkVideo)
791
792
{
    if (checkAudio && checkVideo) {
793
        m_capture->recordVideo(tid, false);
794
    } else if (checkAudio) {
795
        m_capture->recordAudio(tid, false);
796
797
798
799
800
801
802
803
804
805
806
807
808
    }
}

QStringList Core::getAudioCaptureDevices()
{
    return m_capture->getAudioCaptureDevices();
}

int Core::getMediaCaptureState()
{
    return m_capture->getState();
}

809
810
811
812
813
bool Core::isMediaCapturing()
{
    return m_capture->isRecording();
}

814
MediaCapture *Core::getAudioDevice()
815
{
816
    return m_capture.get();
817
}
818

819
QString Core::getProjectFolderName()
820
{
821
822
823
824
    if (currentDoc()) {
        return currentDoc()->projectDataFolder() + QDir::separator();
    }
    return QString();
825
}
826
827
828
829
830
831
832
833
834
835
836

QString Core::getTimelineClipBinId(int cid)
{
    if (!m_guiConstructed) {
        return QString();
    }
    if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isClip(cid)) {
        return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getClipBinId(cid);
    }
    return QString();
}
837
838
839

int Core::getDurationFromString(const QString &time)
{
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
840
841
842
    if (!m_guiConstructed) {
        return 0;
    }
843
844
845
    const QString duration = currentDoc()->timecode().reformatSeparators(time);
    return currentDoc()->timecode().getFrameCount(duration);
}
846
847
848
849
850

void Core::processInvalidFilter(const QString service, const QString id, const QString message)
{
    if (m_guiConstructed) m_mainWindow->assetPanelWarning(service, id, message);
}
851

852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
void Core::updateProjectTags(QMap <QString, QString> tags)
{
    // Clear previous tags
    for (int i = 1 ; i< 20; i++) {
        QString current = currentDoc()->getDocumentProperty(QString("tag%1").arg(i));
        if (current.isEmpty()) {
            break;
        } else {
            currentDoc()->setDocumentProperty(QString("tag%1").arg(i), QString());
        }
    }
    QMapIterator<QString, QString> j(tags);
    int i = 1;
    while (j.hasNext()) {
        j.next();
        currentDoc()->setDocumentProperty(QString("tag%1").arg(i), QString("%1:%2").arg(j.key()).arg(j.value()));
        i++;
    }
870
}