core.cpp 34.3 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
#include "dialogs/subtitleedit.h"
33
#include "dialogs/textbasededit.h"
34
35
#include <mlt++/MltRepository.h>

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

46
std::unique_ptr<Core> Core::m_self;
47
Core::Core()
48
49
    : audioThumbCache(QStringLiteral("audioCache"), 2000000)
    , m_thumbProfile(nullptr)
50
    , m_capture(new MediaCapture(this))
51
52
53
{
}

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

62
63
Core::~Core()
{
64
65
66
    if (m_monitorManager) {
        delete m_monitorManager;
    }
67
    // delete m_binWidget;
68
69
70
    if (m_projectManager) {
        delete m_projectManager;
    }
71
    ClipController::mediaUnavailable.reset();
72
73
}

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

Nicolas Carion's avatar
Nicolas Carion committed
82
83
    qRegisterMetaType<audioShortVector>("audioShortVector");
    qRegisterMetaType<QVector<double>>("QVector<double>");
84
    qRegisterMetaType<QList<QAction*>>("QList<QAction*>");
Nicolas Carion's avatar
Nicolas Carion committed
85
86
87
88
    qRegisterMetaType<MessageType>("MessageType");
    qRegisterMetaType<stringMap>("stringMap");
    qRegisterMetaType<audioByteArray>("audioByteArray");
    qRegisterMetaType<QList<ItemInfo>>("QList<ItemInfo>");
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
89
    qRegisterMetaType<std::shared_ptr<Mlt::Producer>>("std::shared_ptr<Mlt::Producer>");
Nicolas Carion's avatar
Nicolas Carion committed
90
91
92
    qRegisterMetaType<QVector<int>>();
    qRegisterMetaType<QDomElement>("QDomElement");
    qRegisterMetaType<requestClipInfo>("requestClipInfo");
93
94
    
    // Check if we had a crash
95
96
    QFile lockFile(QDir::temp().absoluteFilePath(QStringLiteral("kdenlivelock")));
    if (lockFile.exists()) {
97
98
99
100
        // a previous instance crashed, propose to delete config files
        if (KMessageBox::questionYesNo(QApplication::activeWindow(), i18n("Kdenlive crashed on last startup.\nDo you want to reset the configuration files ?")) ==  KMessageBox::Yes) {
            return false;
        }
101
102
103
104
105
    } else {
        // Create lock file
        lockFile.open(QFile::WriteOnly);
        lockFile.write(QByteArray());
        lockFile.close();
106
    }
107

108
109
110
111
112
113
114
115
116
117
118
    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);
    }
119

Nicolas Carion's avatar
Nicolas Carion committed
120
    // load the profile from disk
121
    ProfileRepository::get()->refresh();
Nicolas Carion's avatar
Nicolas Carion committed
122
    // load default profile
123
124
125
126
    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);
127
    }
128
129

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

    m_self->m_projectItemModel = ProjectItemModel::construct();
135
    // Job manager must be created before bin to correctly connect
136
    m_self->m_jobManager.reset(new JobManager(m_self.get()));
137
    return true;
138
139
}

140
void Core::initGUI(const QUrl &Url, const QString &clipsToLoad)
141
{
142
143
144
    m_profile = KdenliveSettings::default_profile();
    m_currentProfile = m_profile;
    profileChanged();
145
    m_mainWindow = new MainWindow();
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
146
    m_guiConstructed = true;
147
    QStringList styles = QQuickStyle::availableStyles();
148
    if (styles.contains(QLatin1String("org.kde.desktop"))) {
149
150
151
152
153
        QQuickStyle::setStyle("org.kde.desktop");
    } else if (styles.contains(QLatin1String("Fusion"))) {
        QQuickStyle::setStyle("Fusion");
    }

154
    connect(this, &Core::showConfigDialog, m_mainWindow, &MainWindow::slotPreferences);
155

Nicolas Carion's avatar
format    
Nicolas Carion committed
156
    // load default profile and ask user to select one if not found.
157
158
    if (m_profile.isEmpty()) {
        m_profile = ProjectManager::getDefaultProjectFormat();
159
        profileChanged();
160
161
        KdenliveSettings::setDefault_profile(m_profile);
    }
162

163
    if (!ProfileRepository::get()->profileExists(m_profile)) {
164
165
        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
166
167
        // TODO this simple widget should be improved and probably use profileWidget
        // we get the list of profiles
168
169
        QVector<QPair<QString, QString>> all_profiles = ProfileRepository::get()->getAllProfiles();
        QStringList all_descriptions;
Vincent Pinon's avatar
Vincent Pinon committed
170
        for (const auto &profile : qAsConst(all_profiles)) {
171
172
173
            all_descriptions << profile.first;
        }

Nicolas Carion's avatar
Nicolas Carion committed
174
        // ask the user
175
        bool ok;
Nicolas Carion's avatar
Nicolas Carion committed
176
        QString item = QInputDialog::getItem(m_mainWindow, i18n("Select Default Profile"), i18n("Profile:"), all_descriptions, 0, false, &ok);
177
178
        if (ok) {
            ok = false;
Vincent Pinon's avatar
Vincent Pinon committed
179
            for (const auto &profile : qAsConst(all_profiles)) {
180
181
182
183
184
185
186
                if (profile.first == item) {
                    m_profile = profile.second;
                    ok = true;
                }
            }
        }
        if (!ok) {
Nicolas Carion's avatar
Nicolas Carion committed
187
188
189
            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"));
190
            m_profile = QStringLiteral("dv_pal");
191
192
        }
        KdenliveSettings::setDefault_profile(m_profile);
193
        profileChanged();
194
195
    }

Till Theato's avatar
Till Theato committed
196
    m_projectManager = new ProjectManager(this);
197
198
    m_binWidget = new Bin(m_projectItemModel, m_mainWindow);
    m_library = new LibraryWidget(m_projectManager, m_mainWindow);
199
    m_subtitleWidget = new SubtitleEdit(m_mainWindow);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
200
    m_mixerWidget = new MixerManager(m_mainWindow);
201
    m_textEditWidget = new TextBasedEdit(m_mainWindow);
Laurent Montel's avatar
Laurent Montel committed
202
    connect(m_library, SIGNAL(addProjectClips(QList<QUrl>)), m_binWidget, SLOT(droppedUrls(QList<QUrl>)));
203
    connect(this, &Core::updateLibraryPath, m_library, &LibraryWidget::slotUpdateLibraryPath);
204
    connect(m_capture.get(), &MediaCapture::recordStateChanged, m_mixerWidget, &MixerManager::recordStateChanged);
205
    connect(m_mixerWidget, &MixerManager::updateRecVolume, m_capture.get(), &MediaCapture::setAudioVolume);
206
    m_monitorManager = new MonitorManager(this);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
207
    connect(m_monitorManager, &MonitorManager::cleanMixer, m_mixerWidget, &MixerManager::clearMixers);
208
209
210
211
212
    connect(m_subtitleWidget, &SubtitleEdit::addSubtitle, [this]() {
        if (m_guiConstructed && m_mainWindow->getCurrentTimeline()->controller()) {
            m_mainWindow->getCurrentTimeline()->controller()->addSubtitle();
        }
    });
213
214
215
216
217
    connect(m_subtitleWidget, &SubtitleEdit::cutSubtitle, [this](int id, int cursorPos) {
        if (m_guiConstructed && m_mainWindow->getCurrentTimeline()->controller()) {
            m_mainWindow->getCurrentTimeline()->controller()->cutSubtitle(id, cursorPos);
        }
    });
218
    
219
    // Producer queue, creating MLT::Producers on request
220
    /*
221
    m_producerQueue = new ProducerQueue(m_binController);
222
    connect(m_producerQueue, &ProducerQueue::gotFileProperties, m_binWidget, &Bin::slotProducerReady);
Laurent Montel's avatar
Laurent Montel committed
223
    connect(m_producerQueue, &ProducerQueue::replyGetImage, m_binWidget, &Bin::slotThumbnailReady);
224
225
    connect(m_producerQueue, &ProducerQueue::requestProxy,
            [this](const QString &id){ m_binWidget->startJob(id, AbstractClipJob::PROXYJOB);});
Laurent Montel's avatar
Laurent Montel committed
226
    connect(m_producerQueue, &ProducerQueue::removeInvalidClip, m_binWidget, &Bin::slotRemoveInvalidClip, Qt::DirectConnection);
Laurent Montel's avatar
Laurent Montel committed
227
    connect(m_producerQueue, SIGNAL(addClip(QString, QMap<QString, QString>)), m_binWidget, SLOT(slotAddUrl(QString, QMap<QString, QString>)));
228
    connect(m_binController.get(), SIGNAL(createThumb(QDomElement, QString, int)), m_producerQueue, SLOT(getFileProperties(QDomElement, QString, int)));
Laurent Montel's avatar
Laurent Montel committed
229
    connect(m_binWidget, &Bin::producerReady, m_producerQueue, &ProducerQueue::slotProcessingDone, Qt::DirectConnection);
Nicolas Carion's avatar
Nicolas Carion committed
230
    // TODO
231
    connect(m_producerQueue, SIGNAL(removeInvalidProxy(QString,bool)), m_binWidget, SLOT(slotRemoveInvalidProxy(QString,bool)));*/
232
233

    m_mainWindow->init();
234
235
236
    if (!Url.isEmpty()) {
        emit loadingMessageUpdated(i18n("Loading project..."));
    }
237
    projectManager()->init(Url, clipsToLoad);
238
    if (qApp->isSessionRestored()) {
Nicolas Carion's avatar
Nicolas Carion committed
239
        // NOTE: we are restoring only one window, because Kdenlive only uses one MainWindow
240
241
        m_mainWindow->restore(1, false);
    }
242
    QMetaObject::invokeMethod(pCore->projectManager(), "slotLoadOnOpen", Qt::QueuedConnection);
243
    m_mainWindow->show();
244
    QThreadPool::globalInstance()->setMaxThreadCount(qMin(4, QThreadPool::globalInstance()->maxThreadCount()));
245
246

    // Release startup crash lock file
247
248
    QFile lockFile(QDir::temp().absoluteFilePath(QStringLiteral("kdenlivelock")));
    lockFile.remove();
249
250
}

251
252
253
254
255
256
257
258
259
260
261
262
263
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
264
std::unique_ptr<Core> &Core::self()
265
{
266
    if (!m_self) {
267
        qWarning() << "Core has not been created";
268
    }
269
270
271
    return m_self;
}

Laurent Montel's avatar
Laurent Montel committed
272
MainWindow *Core::window()
273
274
275
276
{
    return m_mainWindow;
}

Till Theato's avatar
Till Theato committed
277
278
279
280
281
ProjectManager *Core::projectManager()
{
    return m_projectManager;
}

Laurent Montel's avatar
Laurent Montel committed
282
MonitorManager *Core::monitorManager()
283
284
285
286
{
    return m_monitorManager;
}

287
288
289
290
291
292
293
294
Monitor *Core::getMonitor(int id)
{
    if (id == Kdenlive::ClipMonitor) {
        return m_monitorManager->clipMonitor();
    }
    return m_monitorManager->projectMonitor();
}

295
296
297
298
299
Bin *Core::bin()
{
    return m_binWidget;
}

300
301
302
303
304
void Core::selectBinClip(const QString &clipId, int frame, const QPoint &zone)
{
    m_binWidget->selectClipById(clipId, frame, zone);
}

305
306
307
308
309
310
311
void Core::selectTimelineItem(int id)
{
    if (m_guiConstructed && m_mainWindow->getCurrentTimeline()->controller()->getModel()) {
        m_mainWindow->getCurrentTimeline()->controller()->getModel()->requestAddToSelection(id, true);
    }
}

312
std::shared_ptr<SubtitleModel> Core::getSubtitleModel(bool enforce)
313
314
{
    if (m_guiConstructed && m_mainWindow->getCurrentTimeline()->controller()->getModel()) {
315
316
317
318
319
320
        auto subModel = m_mainWindow->getCurrentTimeline()->controller()->getModel()->getSubtitleModel();
        if (enforce && subModel == nullptr) {
            m_mainWindow->slotEditSubtitle();
            subModel = m_mainWindow->getCurrentTimeline()->controller()->getModel()->getSubtitleModel();
        }
        return subModel;
321
322
323
324
    }
    return nullptr;
}

325
std::shared_ptr<JobManager> Core::jobManager()
326
{
327
    return m_jobManager;
328
329
}

330
331
332
333
334
LibraryWidget *Core::library()
{
    return m_library;
}

335
336
337
338
339
TextBasedEdit *Core::textEditWidget()
{
    return m_textEditWidget;
}

340
341
342
343
344
SubtitleEdit *Core::subtitleWidget()
{
    return m_subtitleWidget;
}

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
345
346
347
348
349
MixerManager *Core::mixer()
{
    return m_mixerWidget;
}

350
351
void Core::initLocale()
{
352
    QLocale systemLocale = QLocale(); // For disabling group separator by default
353
354
355
356
    systemLocale.setNumberOptions(QLocale::OmitGroupSeparator);
    QLocale::setDefault(systemLocale);
}

Nicolas Carion's avatar
Nicolas Carion committed
357
std::unique_ptr<Mlt::Repository> &Core::getMltRepository()
358
{
Nicolas Carion's avatar
Nicolas Carion committed
359
    return MltConnection::self()->getMltRepository();
360
}
361

Nicolas Carion's avatar
Nicolas Carion committed
362
std::unique_ptr<ProfileModel> &Core::getCurrentProfile() const
363
{
364
    return ProfileRepository::get()->getProfile(m_currentProfile);
365
}
366

367
368
Mlt::Profile *Core::getProjectProfile()
{
369
370
371
    if (!m_projectProfile) {
        m_projectProfile = std::make_unique<Mlt::Profile>(m_currentProfile.toStdString().c_str());
        m_projectProfile->set_explicit(1);
372
    }
373
    return m_projectProfile.get();
374
375
}

376
377
378
379
380
const QString &Core::getCurrentProfilePath() const
{
    return m_currentProfile;
}

381
382
bool Core::setCurrentProfile(const QString &profilePath)
{
383
    if (m_currentProfile == profilePath) {
384
385
        // no change required, ensure timecode has correct fps
        m_timecode.setFormat(getCurrentProfile()->fps());
386
387
        return true;
    }
388
    if (ProfileRepository::get()->profileExists(profilePath)) {
389
        m_currentProfile = profilePath;
390
        m_thumbProfile.reset();
391
392
        if (m_projectProfile) {
            m_projectProfile->set_colorspace(getCurrentProfile()->colorspace());
393
394
            m_projectProfile->set_frame_rate(getCurrentProfile()->frame_rate_num(), getCurrentProfile()->frame_rate_den());
            m_projectProfile->set_height(getCurrentProfile()->height());
395
            m_projectProfile->set_progressive(getCurrentProfile()->progressive());
396
397
398
399
            m_projectProfile->set_sample_aspect(getCurrentProfile()->sample_aspect_num(), getCurrentProfile()->sample_aspect_den());
            m_projectProfile->set_display_aspect(getCurrentProfile()->display_aspect_num(), getCurrentProfile()->display_aspect_den());
            m_projectProfile->set_width(getCurrentProfile()->width());
            m_projectProfile->set_explicit(true);
400
        }
401
        // inform render widget
402
        m_timecode.setFormat(getCurrentProfile()->fps());
403
        profileChanged();
Vincent Pinon's avatar
Vincent Pinon committed
404
        emit m_mainWindow->updateRenderWidgetProfile();
405
        pCore->monitorManager()->resetProfiles();
Vincent Pinon's avatar
Vincent Pinon committed
406
        emit pCore->monitorManager()->updatePreviewScaling();
407
        if (m_guiConstructed && m_mainWindow->getCurrentTimeline()->controller()->getModel()) {
408
            m_mainWindow->getCurrentTimeline()->controller()->getModel()->updateProfile(getProjectProfile());
409
            checkProfileValidity();
410
            emit m_mainWindow->getCurrentTimeline()->controller()->frameFormatChanged();
411
        }
412
413
414
415
416
        return true;
    }
    return false;
}

417
418
void Core::checkProfileValidity()
{
419
    int offset = (getCurrentProfile()->profile().width() % 2) + (getCurrentProfile()->profile().height() % 2);
420
421
422
    if (offset > 0) {
        // Profile is broken, warn user
        if (m_binWidget) {
Vincent Pinon's avatar
Vincent Pinon committed
423
            emit m_binWidget->displayBinMessage(i18n("Your project profile is invalid, rendering might fail."), KMessageWidget::Warning);
424
425
426
427
        }
    }
}

428
429
430
431
432
double Core::getCurrentSar() const
{
    return getCurrentProfile()->sar();
}

433
double Core::getCurrentDar() const
434
435
436
437
{
    return getCurrentProfile()->dar();
}

438
double Core::getCurrentFps() const
439
440
441
{
    return getCurrentProfile()->fps();
}
442

443

444
445
QSize Core::getCurrentFrameDisplaySize() const
{
Nicolas Carion's avatar
Nicolas Carion committed
446
    return {(int)(getCurrentProfile()->height() * getCurrentDar() + 0.5), getCurrentProfile()->height()};
447
448
449
450
}

QSize Core::getCurrentFrameSize() const
{
Nicolas Carion's avatar
Nicolas Carion committed
451
    return {getCurrentProfile()->width(), getCurrentProfile()->height()};
452
}
453
454
455

void Core::requestMonitorRefresh()
{
456
    if (!m_guiConstructed) return;
457
458
    m_monitorManager->refreshProjectMonitor();
}
459

460
void Core::refreshProjectRange(QPair<int, int> range)
461
{
462
    if (!m_guiConstructed) return;
463
464
465
    m_monitorManager->refreshProjectRange(range);
}

466
int Core::getItemPosition(const ObjectId &id)
467
{
468
469
    if (!m_guiConstructed) return 0;
    switch (id.first) {
470
    case ObjectType::TimelineClip:
471
        if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isClip(id.second)) {
472
473
474
475
            return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getClipPosition(id.second);
        }
        break;
    case ObjectType::TimelineComposition:
476
        if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isComposition(id.second)) {
477
478
479
            return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getCompositionPosition(id.second);
        }
        break;
480
481
482
483
    case ObjectType::TimelineMix:
        if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isClip(id.second)) {
            return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getMixInOut(id.second).first;
        } else {
484
            qWarning() << "querying non clip properties";
485
486
        }
        break;
487
    case ObjectType::BinClip:
488
    case ObjectType::TimelineTrack:
489
    case ObjectType::Master:
490
491
        return 0;
        break;
492
    default:
493
        qWarning() << "unhandled object type";
494
495
496
497
    }
    return 0;
}

498
499
int Core::getItemIn(const ObjectId &id)
{
500
    if (!m_guiConstructed || !m_mainWindow->getCurrentTimeline() || !m_mainWindow->getCurrentTimeline()->controller()->getModel()) {
501
        qWarning() << "GUI not build";
502
503
        return 0;
    }
504
505
506
507
    switch (id.first) {
    case ObjectType::TimelineClip:
        if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isClip(id.second)) {
            return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getClipIn(id.second);
508
        } else {
509
            qWarning() << "querying non clip properties";
510
511
        }
        break;
512
    case ObjectType::TimelineMix:
513
514
    case ObjectType::TimelineComposition:
    case ObjectType::BinClip:
515
    case ObjectType::TimelineTrack:
516
    case ObjectType::Master:
517
518
519
        return 0;
        break;
    default:
520
        qWarning() << "unhandled object type";
521
522
523
524
    }
    return 0;
}

525
526
527
528
529
530
531
532
533
534
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:
535
        return PlaylistState::VideoOnly;
536
537
538
539
        break;
    case ObjectType::BinClip:
        return m_binWidget->getClipState(id.second);
        break;
540
541
    case ObjectType::TimelineTrack:
        return m_mainWindow->getCurrentTimeline()->controller()->getModel()->isAudioTrack(id.second) ? PlaylistState::AudioOnly : PlaylistState::VideoOnly;
542
543
544
    case ObjectType::Master:
        return PlaylistState::Disabled;
        break;
545
    default:
546
        qWarning() << "unhandled object type";
547
548
549
550
551
        break;
    }
    return PlaylistState::Disabled;
}

552
553
int Core::getItemDuration(const ObjectId &id)
{
554
555
    if (!m_guiConstructed) return 0;
    switch (id.first) {
556
    case ObjectType::TimelineClip:
557
        if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isClip(id.second)) {
558
559
560
561
            return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getClipPlaytime(id.second);
        }
        break;
    case ObjectType::TimelineComposition:
562
        if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isComposition(id.second)) {
563
564
565
            return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getCompositionPlaytime(id.second);
        }
        break;
566
    case ObjectType::BinClip:
567
        return (int)m_binWidget->getClipDuration(id.second);
568
        break;
569
    case ObjectType::TimelineTrack:
570
    case ObjectType::Master:
571
        return m_mainWindow->getCurrentTimeline()->controller()->duration() - 1;
572
573
574
575
576
    case ObjectType::TimelineMix:
        if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isClip(id.second)) {
            std::pair<int, int> mixInOut = m_mainWindow->getCurrentTimeline()->controller()->getModel()->getMixInOut(id.second);
            return (mixInOut.second - mixInOut.first);
        } else {
577
            qWarning() << "querying non clip properties";
578
579
        }
        break;
580
    default:
581
        qWarning() << "unhandled object type";
582
583
584
585
    }
    return 0;
}

586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
QSize Core::getItemFrameSize(const ObjectId &id)
{
    if (!m_guiConstructed) return QSize();
    switch (id.first) {
    case ObjectType::TimelineClip:
        if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isClip(id.second)) {
            return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getClipFrameSize(id.second);
        }
        break;
    case ObjectType::BinClip:
        return m_binWidget->getFrameSize(id.second);
        break;
    case ObjectType::TimelineTrack:
    case ObjectType::Master:
        return pCore->getCurrentFrameSize();
    default:
        qWarning() << "unhandled object type";
    }
    return pCore->getCurrentFrameSize();
}

607
608
int Core::getItemTrack(const ObjectId &id)
{
609
610
    if (!m_guiConstructed) return 0;
    switch (id.first) {
611
612
    case ObjectType::TimelineClip:
    case ObjectType::TimelineComposition:
613
    case ObjectType::TimelineMix:
614
615
616
        return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getItemTrackId(id.second);
        break;
    default:
617
        qWarning() << "unhandled object type";
618
619
620
621
    }
    return 0;
}

622
void Core::refreshProjectItem(const ObjectId &id)
623
{
624
    if (!m_guiConstructed || m_mainWindow->getCurrentTimeline()->loading) return;
625
    switch (id.first) {
626
    case ObjectType::TimelineClip:
627
    case ObjectType::TimelineMix:
628
        if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isClip(id.second)) {
629
630
631
632
            m_mainWindow->getCurrentTimeline()->controller()->refreshItem(id.second);
        }
        break;
    case ObjectType::TimelineComposition:
633
        if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isComposition(id.second)) {
634
635
636
637
            m_mainWindow->getCurrentTimeline()->controller()->refreshItem(id.second);
        }
        break;
    case ObjectType::TimelineTrack:
638
        if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isTrack(id.second)) {
639
640
641
642
            requestMonitorRefresh();
        }
        break;
    case ObjectType::BinClip:
643
        m_monitorManager->activateMonitor(Kdenlive::ClipMonitor);
644
        m_monitorManager->refreshClipMonitor();
645
646
647
        if (m_monitorManager->projectMonitorVisible() && m_mainWindow->getCurrentTimeline()->controller()->refreshIfVisible(id.second)) {
            m_monitorManager->refreshTimer.start();
        }
648
        break;
649
650
651
    case ObjectType::Master:
        requestMonitorRefresh();
        break;
652
    default:
653
        qWarning() << "unhandled object type";
654
    }
655
656
}

657
658
659
660
661
662
663
664
bool Core::hasTimelinePreview() const
{
    if (!m_guiConstructed) {
        return false;
    }
    return m_mainWindow->getCurrentTimeline()->controller()->renderedChunks().size() > 0;
}

665
666
667
668
KdenliveDoc *Core::currentDoc()
{
    return m_projectManager->current();
}
669

670
671
672
673
674
Timecode Core::timecode() const
{
    return m_timecode;
}

675
676
677
678
679
void Core::setDocumentModified()
{
    m_projectManager->current()->setModified();;
}

680
681
682
683
684
685
686
687
int Core::projectDuration() const
{
    if (!m_guiConstructed) {
        return 0;
    }
    return m_mainWindow->getCurrentTimeline()->controller()->duration();
}

688
689
690
691
void Core::profileChanged()
{
    GenTime::setFps(getCurrentFps());
}
692
693
694

void Core::pushUndo(const Fun &undo, const Fun &redo, const QString &text)
{
Nicolas Carion's avatar
Nicolas Carion committed
695
    undoStack()->push(new FunctionalUndoCommand(undo, redo, text));
696
}
697
698
699

void Core::pushUndo(QUndoCommand *command)
{
Nicolas Carion's avatar
Nicolas Carion committed
700
    undoStack()->push(command);
701
}
702

703
704
705
706
707
int Core::undoIndex() const
{
    return m_projectManager->undoStack()->index();
}

708
709
void Core::displayMessage(const QString &message, MessageType type, int timeout)
{
Nicolas Carion's avatar
Nicolas Carion committed
710
    if (m_mainWindow) {
711
        if (type == ProcessingJobMessage || type == OperationCompletedMessage) {
Vincent Pinon's avatar
Vincent Pinon committed
712
            emit m_mainWindow->displayProgressMessage(message, type, timeout);
713
        } else {
Vincent Pinon's avatar
Vincent Pinon committed
714
            emit m_mainWindow->displayMessage(message, type, timeout);
715
        }
Nicolas Carion's avatar
Nicolas Carion committed
716
717
718
    } else {
        qDebug() << message;
    }
719
}
720

721
void Core::displayBinMessage(const QString &text, int type, const QList<QAction *> &actions, bool showClose, BinMessage::BinCategory messageCategory)
722
{
723
    m_binWidget->doDisplayMessage(text, (KMessageWidget::MessageType)type, actions, showClose, messageCategory);
724
725
}

726
727
728
729
730
void Core::displayBinLogMessage(const QString &text, int type, const QString &logInfo)
{
    m_binWidget->doDisplayMessage(text, (KMessageWidget::MessageType)type, logInfo);
}

731
732
void Core::clearAssetPanel(int itemId)
{
Vincent Pinon's avatar
Vincent Pinon committed
733
    if (m_guiConstructed) emit m_mainWindow->clearAssetPanel(itemId);
734
}
735

736
737
std::shared_ptr<EffectStackModel> Core::getItemEffectStack(int itemType, int itemId)
{
738
    if (!m_guiConstructed) return nullptr;
739
    switch (itemType) {
740
741
742
    case (int)ObjectType::TimelineClip:
        return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getClipEffectStack(itemId);
    case (int)ObjectType::TimelineTrack:
743
        return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getTrackEffectStackModel(itemId);
744
745
746
        break;
    case (int)ObjectType::BinClip:
        return m_binWidget->getClipEffectStack(itemId);
747
748
    case (int)ObjectType::Master:
        return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getMasterEffectStackModel();
749
750
    default:
        return nullptr;
751
752
    }
}
Nicolas Carion's avatar
Nicolas Carion committed
753
754
755

std::shared_ptr<DocUndoStack> Core::undoStack()
{
756
    return projectManager()->undoStack();
Nicolas Carion's avatar
Nicolas Carion committed
757
}
758

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
759
QMap<int, QString> Core::getTrackNames(bool videoOnly)
760
{
761
    if (!m_guiConstructed) return QMap<int, QString>();
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
762
    return m_mainWindow->getCurrentTimeline()->controller()->getTrackNames(videoOnly);
763
764
}

Nicolas Carion's avatar
Nicolas Carion committed
765
QPair<int, int> Core::getCompositionATrack(int cid) const
766
{
Nicolas Carion's avatar
Nicolas Carion committed
767
    if (!m_guiConstructed) return {};
768
769
770
    return m_mainWindow->getCurrentTimeline()->controller()->getCompositionATrack(cid);
}

771
772
773
774
775
bool Core::compositionAutoTrack(int cid) const
{
    return m_mainWindow->getCurrentTimeline()->controller()->compositionAutoTrack(cid);
}

776
777
void Core::setCompositionATrack(int cid, int aTrack)
{
778
    if (!m_guiConstructed) return;
779
780
    m_mainWindow->getCurrentTimeline()->controller()->setCompositionATrack(cid, aTrack);
}
781
782
783
784
785

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

787
void Core::invalidateRange(QPair<int, int> range)
788
{
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
789
    if (!m_guiConstructed || m_mainWindow->getCurrentTimeline()->loading) return;
790
    m_mainWindow->getCurrentTimeline()->controller()->invalidateZone(range.first, range.second);
791
792
}

793
794
void Core::invalidateItem(ObjectId itemId)
{
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
795
    if (!m_guiConstructed || !m_mainWindow->getCurrentTimeline() || m_mainWindow->getCurrentTimeline()->loading) return;
796
    switch (itemId.first) {
797
    case ObjectType::TimelineClip:
798
    case ObjectType::TimelineComposition:
799
        m_mainWindow->getCurrentTimeline()->controller()->invalidateItem(itemId.second);
800
801
        break;
    case ObjectType::TimelineTrack:
802
        m_mainWindow->getCurrentTimeline()->controller()->invalidateTrack(itemId.second);
803
        break;
804
805
806
    case ObjectType::BinClip:
        m_binWidget->invalidateClip(QString::number(itemId.second));
        break;
807
808
809
    case ObjectType::Master:
        m_mainWindow->getCurrentTimeline()->controller()->invalidateZone(0, -1);
        break;
810
    default:
811
        // compositions should not have effects
812
        break;
813
814
    }
}
815
816
817
818
819

double Core::getClipSpeed(int id) const
{
    return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getClipSpeed(id);
}
820
821
822

void Core::updateItemKeyframes(ObjectId id)
{
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
823
    if (id.first == ObjectType::TimelineClip && m_guiConstructed) {
824
825
826
827
        m_mainWindow->getCurrentTimeline()->controller()->updateClip(id.second, {TimelineModel::KeyframesRole});
    }
}

828
829
void Core::updateItemModel(ObjectId id, const QString &service)
{
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
830
    if (m_guiConstructed && id.first == ObjectType::TimelineClip && !m_mainWindow->getCurrentTimeline()->loading && service.startsWith(QLatin1String("fade"))) {
831
        bool startFade = service == QLatin1String("fadein") || service == QLatin1String("fade_from_black");
Nicolas Carion's avatar
Nicolas Carion committed
832
        m_mainWindow->getCurrentTimeline()->controller()->updateClip(id.second, {startFade ? TimelineModel::FadeInRole : TimelineModel::FadeOutRole});
833
834
835
    }
}

836
837
838
839
void Core::showClipKeyframes(ObjectId id, bool enable)
{
    if (id.first == ObjectType::TimelineClip) {
        m_mainWindow->getCurrentTimeline()->controller()->showClipKeyframes(id.second, enable);
840
841
    } else if (id.first == ObjectType::TimelineComposition) {
        m_mainWindow->getCurrentTimeline()->controller()->showCompositionKeyframes(id.second, enable);
842
843
    }
}
844
845
846

Mlt::Profile *Core::thumbProfile()
{
847
    QMutexLocker lck(&m_thumbProfileMutex);
848
    if (!m_thumbProfile) {
Nicolas Carion's avatar
Nicolas Carion committed
849
        m_thumbProfile = std::make_unique<Mlt::Profile>(m_currentProfile.toStdString().c_str());
850
        double factor = 144. / m_thumbProfile->height();
851
        m_thumbProfile->set_height(144);
852
853
854
        int width = m_thumbProfile->width() * factor + 0.5;
        if (width % 2 > 0) {
            width ++;
855
        }
856
        m_thumbProfile->set_width(width);
857
858
859
    }
    return m_thumbProfile.get();
}
860

861
862
int Core::getTimelinePosition() const
{
863
864
    if (m_guiConstructed) {
        return m_monitorManager->projectMonitor()->position();
865
866
867
868
    }