File indexing completed on 2022-10-04 15:37:18

0001 /*
0002 SPDX-FileCopyrightText: 2014 Till Theato <root@ttill.de>
0003 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0004 */
0005 
0006 #include "core.h"
0007 #include "audiomixer/mixermanager.hpp"
0008 #include "bin/bin.h"
0009 #include "bin/projectitemmodel.h"
0010 #include "capture/mediacapture.h"
0011 #include "dialogs/proxytest.h"
0012 #include "dialogs/subtitleedit.h"
0013 #include "dialogs/textbasededit.h"
0014 #include "dialogs/timeremap.h"
0015 #include "doc/docundostack.hpp"
0016 #include "doc/kdenlivedoc.h"
0017 #include "kdenlive_debug.h"
0018 #include "kdenlivesettings.h"
0019 #include "library/librarywidget.h"
0020 #include "mainwindow.h"
0021 #include "mltconnection.h"
0022 #include "mltcontroller/clipcontroller.h"
0023 #include "monitor/monitormanager.h"
0024 #include "profiles/profilemodel.hpp"
0025 #include "profiles/profilerepository.hpp"
0026 #include "project/projectmanager.h"
0027 #include "timeline2/model/timelineitemmodel.hpp"
0028 #include "timeline2/view/timelinecontroller.h"
0029 #include "timeline2/view/timelinewidget.h"
0030 #include <mlt++/MltRepository.h>
0031 
0032 #include <KMessageBox>
0033 #include <QCoreApplication>
0034 #include <QDir>
0035 #include <QInputDialog>
0036 #include <QQuickStyle>
0037 #include <locale>
0038 #ifdef Q_OS_MAC
0039 #include <xlocale.h>
0040 #endif
0041 
0042 std::unique_ptr<Core> Core::m_self;
0043 Core::Core(const QString &packageType)
0044     : audioThumbCache(QStringLiteral("audioCache"), 2000000)
0045     , taskManager(this)
0046     , m_packageType(packageType)
0047     , m_thumbProfile(nullptr)
0048     , m_capture(new MediaCapture(this))
0049 {
0050 }
0051 
0052 void Core::prepareShutdown()
0053 {
0054     m_guiConstructed = false;
0055     // m_mainWindow->getCurrentTimeline()->controller()->prepareClose();
0056     projectItemModel()->blockSignals(true);
0057     QThreadPool::globalInstance()->clear();
0058 }
0059 
0060 Core::~Core()
0061 {
0062     if (m_monitorManager) {
0063         delete m_monitorManager;
0064     }
0065     if (m_projectManager) {
0066         delete m_projectManager;
0067     }
0068     ClipController::mediaUnavailable.reset();
0069 }
0070 
0071 bool Core::build(const QString &packageType, bool testMode)
0072 {
0073     if (m_self) {
0074         return true;
0075     }
0076     m_self.reset(new Core(packageType));
0077     m_self->initLocale();
0078 
0079     qRegisterMetaType<audioShortVector>("audioShortVector");
0080     qRegisterMetaType<QVector<double>>("QVector<double>");
0081     qRegisterMetaType<QList<QAction *>>("QList<QAction*>");
0082     qRegisterMetaType<MessageType>("MessageType");
0083     qRegisterMetaType<stringMap>("stringMap");
0084     qRegisterMetaType<audioByteArray>("audioByteArray");
0085     qRegisterMetaType<QList<ItemInfo>>("QList<ItemInfo>");
0086     qRegisterMetaType<std::shared_ptr<Mlt::Producer>>("std::shared_ptr<Mlt::Producer>");
0087     qRegisterMetaType<QVector<int>>();
0088     qRegisterMetaType<QDomElement>("QDomElement");
0089     qRegisterMetaType<requestClipInfo>("requestClipInfo");
0090     qRegisterMetaType<QVector<QPair<QString, QVariant>>>("paramVector");
0091     qRegisterMetaType<ProfileParam *>("ProfileParam*");
0092 
0093     if (!testMode) {
0094         // Check if we had a crash
0095         QFile lockFile(QDir::temp().absoluteFilePath(QStringLiteral("kdenlivelock")));
0096         if (lockFile.exists()) {
0097             // a previous instance crashed, propose to delete config files
0098             if (KMessageBox::questionYesNo(QApplication::activeWindow(),
0099                                            i18n("Kdenlive crashed on last startup.\nDo you want to reset the configuration files ?")) == KMessageBox::Yes) {
0100                 // Release startup crash lock file
0101                 QFile lockFile(QDir::temp().absoluteFilePath(QStringLiteral("kdenlivelock")));
0102                 lockFile.remove();
0103                 return false;
0104             }
0105         } else {
0106             // Create lock file
0107             lockFile.open(QFile::WriteOnly);
0108             lockFile.write(QByteArray());
0109             lockFile.close();
0110         }
0111     }
0112 
0113     m_self->m_projectItemModel = ProjectItemModel::construct();
0114     return true;
0115 }
0116 
0117 void Core::initGUI(bool inSandbox, const QString &MltPath, const QUrl &Url, const QString &clipsToLoad)
0118 {
0119     m_profile = KdenliveSettings::default_profile();
0120     m_currentProfile = m_profile;
0121     m_mainWindow = new MainWindow();
0122     m_guiConstructed = true;
0123 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0124 
0125     QStringList styles = QQuickStyle::availableStyles();
0126     if (styles.contains(QLatin1String("org.kde.desktop"))) {
0127         QQuickStyle::setStyle("org.kde.desktop");
0128     } else if (styles.contains(QLatin1String("Fusion"))) {
0129         QQuickStyle::setStyle("Fusion");
0130     }
0131     // ELSE Qt6 see: https://doc.qt.io/qt-6/qtquickcontrols-changes-qt6.html#custom-styles-are-now-proper-qml-modules
0132 #endif
0133 
0134     connect(this, &Core::showConfigDialog, m_mainWindow, &MainWindow::slotPreferences);
0135 
0136     m_projectManager = new ProjectManager(this);
0137     Bin *bin = new Bin(m_projectItemModel, m_mainWindow);
0138     m_mainWindow->addBin(bin);
0139 
0140     connect(bin, &Bin::requestShowClipProperties, bin, &Bin::showClipProperties);
0141     connect(m_projectItemModel.get(), &ProjectItemModel::refreshPanel, m_mainWindow->activeBin(), &Bin::refreshPanel);
0142     connect(m_projectItemModel.get(), &ProjectItemModel::refreshClip, m_mainWindow->activeBin(), &Bin::refreshClip);
0143     connect(m_projectItemModel.get(), static_cast<void (ProjectItemModel::*)(const QStringList &, const QModelIndex &)>(&ProjectItemModel::itemDropped),
0144             m_mainWindow->activeBin(), static_cast<void (Bin::*)(const QStringList &, const QModelIndex &)>(&Bin::slotItemDropped));
0145     connect(m_projectItemModel.get(), static_cast<void (ProjectItemModel::*)(const QList<QUrl> &, const QModelIndex &)>(&ProjectItemModel::itemDropped),
0146             m_mainWindow->activeBin(), static_cast<const QString (Bin::*)(const QList<QUrl> &, const QModelIndex &)>(&Bin::slotItemDropped));
0147     connect(m_projectItemModel.get(), &ProjectItemModel::effectDropped, m_mainWindow->activeBin(), &Bin::slotEffectDropped);
0148     connect(m_projectItemModel.get(), &ProjectItemModel::addTag, m_mainWindow->activeBin(), &Bin::slotTagDropped);
0149     connect(m_projectItemModel.get(), &QAbstractItemModel::dataChanged, m_mainWindow->activeBin(), &Bin::slotItemEdited);
0150 
0151     m_library = new LibraryWidget(m_projectManager, m_mainWindow);
0152     m_subtitleWidget = new SubtitleEdit(m_mainWindow);
0153     m_textEditWidget = new TextBasedEdit(m_mainWindow);
0154     m_timeRemapWidget = new TimeRemap(m_mainWindow);
0155     connect(m_library, SIGNAL(addProjectClips(QList<QUrl>)), m_mainWindow->getBin(), SLOT(droppedUrls(QList<QUrl>)));
0156     connect(this, &Core::updateLibraryPath, m_library, &LibraryWidget::slotUpdateLibraryPath);
0157     m_monitorManager = new MonitorManager(this);
0158     m_mixerWidget = new MixerManager(m_mainWindow);
0159     connect(m_capture.get(), &MediaCapture::recordStateChanged, m_mixerWidget, &MixerManager::recordStateChanged);
0160     connect(m_mixerWidget, &MixerManager::updateRecVolume, m_capture.get(), &MediaCapture::setAudioVolume);
0161     connect(m_monitorManager, &MonitorManager::cleanMixer, m_mixerWidget, &MixerManager::clearMixers);
0162 
0163     connect(m_subtitleWidget, &SubtitleEdit::addSubtitle, m_mainWindow, &MainWindow::slotAddSubtitle);
0164     connect(m_subtitleWidget, &SubtitleEdit::cutSubtitle, this, [this](int id, int cursorPos) {
0165         if (m_guiConstructed && m_mainWindow->getCurrentTimeline()->controller()) {
0166             m_mainWindow->getCurrentTimeline()->controller()->cutSubtitle(id, cursorPos);
0167         }
0168     });
0169 
0170     // The MLT Factory will be initiated there, all MLT classes will be usable only after this
0171     if (inSandbox) {
0172         // In a sandbox enviroment we need to search some paths recursively
0173         QString appPath = qApp->applicationDirPath();
0174         KdenliveSettings::setFfmpegpath(QDir::cleanPath(appPath + QStringLiteral("/ffmpeg")));
0175         KdenliveSettings::setFfplaypath(QDir::cleanPath(appPath + QStringLiteral("/ffplay")));
0176         KdenliveSettings::setFfprobepath(QDir::cleanPath(appPath + QStringLiteral("/ffprobe")));
0177         KdenliveSettings::setRendererpath(QDir::cleanPath(appPath + QStringLiteral("/melt")));
0178         m_mainWindow->init(QDir::cleanPath(appPath + QStringLiteral("/../share/mlt/profiles")));
0179     } else {
0180         // Open connection with Mlt
0181         m_mainWindow->init(MltPath);
0182     }
0183     m_mixerWidget->checkAudioLevelVersion();
0184     m_projectItemModel->buildPlaylist();
0185     // load the profiles from disk
0186     ProfileRepository::get()->refresh();
0187     // load default profile
0188     m_profile = KdenliveSettings::default_profile();
0189     // load default profile and ask user to select one if not found.
0190     if (m_profile.isEmpty()) {
0191         m_profile = ProjectManager::getDefaultProjectFormat();
0192         KdenliveSettings::setDefault_profile(m_profile);
0193     }
0194     profileChanged();
0195 
0196     if (!ProfileRepository::get()->profileExists(m_profile)) {
0197         KMessageBox::error(m_mainWindow, i18n("The default profile of Kdenlive is not set or invalid, press OK to set it to a correct value."));
0198 
0199         // TODO this simple widget should be improved and probably use profileWidget
0200         // we get the list of profiles
0201         QVector<QPair<QString, QString>> all_profiles = ProfileRepository::get()->getAllProfiles();
0202         QStringList all_descriptions;
0203         for (const auto &profile : qAsConst(all_profiles)) {
0204             all_descriptions << profile.first;
0205         }
0206 
0207         // ask the user
0208         bool ok;
0209         QString item = QInputDialog::getItem(m_mainWindow, i18nc("@title:window", "Select Default Profile"), i18n("Profile:"), all_descriptions, 0, false, &ok);
0210         if (ok) {
0211             ok = false;
0212             for (const auto &profile : qAsConst(all_profiles)) {
0213                 if (profile.first == item) {
0214                     m_profile = profile.second;
0215                     ok = true;
0216                 }
0217             }
0218         }
0219         if (!ok) {
0220             KMessageBox::error(
0221                 m_mainWindow,
0222                 i18n("The given profile is invalid. We default to the profile \"dv_pal\", but you can change this from Kdenlive's settings panel"));
0223             m_profile = QStringLiteral("dv_pal");
0224         }
0225         KdenliveSettings::setDefault_profile(m_profile);
0226         profileChanged();
0227     }
0228     // Init producer shown for unavailable media
0229     // TODO make it a more proper image, it currently causes a crash on exit
0230     ClipController::mediaUnavailable = std::make_shared<Mlt::Producer>(ProfileRepository::get()->getProfile(m_self->m_profile)->profile(), "color:blue");
0231     ClipController::mediaUnavailable->set("length", 99999999);
0232 
0233     if (!Url.isEmpty()) {
0234         emit loadingMessageUpdated(i18n("Loading project…"));
0235     }
0236     projectManager()->init(Url, clipsToLoad);
0237     if (qApp->isSessionRestored()) {
0238         // NOTE: we are restoring only one window, because Kdenlive only uses one MainWindow
0239         m_mainWindow->restore(1, false);
0240     }
0241     QMetaObject::invokeMethod(pCore->projectManager(), "slotLoadOnOpen", Qt::QueuedConnection);
0242     m_mainWindow->show();
0243     bin->slotUpdatePalette();
0244     emit m_mainWindow->GUISetupDone();
0245 }
0246 
0247 void Core::buildLumaThumbs(const QStringList &values)
0248 {
0249     for (auto &entry : values) {
0250         if (MainWindow::m_lumacache.contains(entry)) {
0251             continue;
0252         }
0253         QImage pix(entry);
0254         if (!pix.isNull()) {
0255             MainWindow::m_lumacache.insert(entry, pix.scaled(50, 30, Qt::KeepAspectRatio, Qt::SmoothTransformation));
0256         }
0257     }
0258 }
0259 
0260 const QString Core::nameForLumaFile(const QString &filename)
0261 {
0262     static QMap<QString, QString> names;
0263     names.insert("square2-bars.pgm", i18nc("Luma transition name", "Square 2 Bars"));
0264     names.insert("checkerboard_small.pgm", i18nc("Luma transition name", "Checkerboard Small"));
0265     names.insert("horizontal_blinds.pgm", i18nc("Luma transition name", "Horizontal Blinds"));
0266     names.insert("radial.pgm", i18nc("Luma transition name", "Radial"));
0267     names.insert("linear_x.pgm", i18nc("Luma transition name", "Linear X"));
0268     names.insert("bi-linear_x.pgm", i18nc("Luma transition name", "Bi-Linear X"));
0269     names.insert("linear_y.pgm", i18nc("Luma transition name", "Linear Y"));
0270     names.insert("bi-linear_y.pgm", i18nc("Luma transition name", "Bi-Linear Y"));
0271     names.insert("square.pgm", i18nc("Luma transition name", "Square"));
0272     names.insert("square2.pgm", i18nc("Luma transition name", "Square 2"));
0273     names.insert("cloud.pgm", i18nc("Luma transition name", "Cloud"));
0274     names.insert("symmetric_clock.pgm", i18nc("Luma transition name", "Symmetric Clock"));
0275     names.insert("radial-bars.pgm", i18nc("Luma transition name", "Radial Bars"));
0276     names.insert("spiral.pgm", i18nc("Luma transition name", "Spiral"));
0277     names.insert("spiral2.pgm", i18nc("Luma transition name", "Spiral 2"));
0278     names.insert("curtain.pgm", i18nc("Luma transition name", "Curtain"));
0279     names.insert("burst.pgm", i18nc("Luma transition name", "Burst"));
0280     names.insert("clock.pgm", i18nc("Luma transition name", "Clock"));
0281 
0282     names.insert("luma01.pgm", i18nc("Luma transition name", "Bar Horizontal"));
0283     names.insert("luma02.pgm", i18nc("Luma transition name", "Bar Vertical"));
0284     names.insert("luma03.pgm", i18nc("Luma transition name", "Barn Door Horizontal"));
0285     names.insert("luma04.pgm", i18nc("Luma transition name", "Barn Door Vertical"));
0286     names.insert("luma05.pgm", i18nc("Luma transition name", "Barn Door Diagonal SW-NE"));
0287     names.insert("luma06.pgm", i18nc("Luma transition name", "Barn Door Diagonal NW-SE"));
0288     names.insert("luma07.pgm", i18nc("Luma transition name", "Diagonal Top Left"));
0289     names.insert("luma08.pgm", i18nc("Luma transition name", "Diagonal Top Right"));
0290     names.insert("luma09.pgm", i18nc("Luma transition name", "Matrix Waterfall Horizontal"));
0291     names.insert("luma10.pgm", i18nc("Luma transition name", "Matrix Waterfall Vertical"));
0292     names.insert("luma11.pgm", i18nc("Luma transition name", "Matrix Snake Horizontal"));
0293     names.insert("luma12.pgm", i18nc("Luma transition name", "Matrix Snake Parallel Horizontal"));
0294     names.insert("luma13.pgm", i18nc("Luma transition name", "Matrix Snake Vertical"));
0295     names.insert("luma14.pgm", i18nc("Luma transition name", "Matrix Snake Parallel Vertical"));
0296     names.insert("luma15.pgm", i18nc("Luma transition name", "Barn V Up"));
0297     names.insert("luma16.pgm", i18nc("Luma transition name", "Iris Circle"));
0298     names.insert("luma17.pgm", i18nc("Luma transition name", "Double Iris"));
0299     names.insert("luma18.pgm", i18nc("Luma transition name", "Iris Box"));
0300     names.insert("luma19.pgm", i18nc("Luma transition name", "Box Bottom Right"));
0301     names.insert("luma20.pgm", i18nc("Luma transition name", "Box Bottom Left"));
0302     names.insert("luma21.pgm", i18nc("Luma transition name", "Box Right Center"));
0303     names.insert("luma22.pgm", i18nc("Luma transition name", "Clock Top"));
0304 
0305     return names.contains(filename) ? names.constFind(filename).value() : filename;
0306 }
0307 
0308 std::unique_ptr<Core> &Core::self()
0309 {
0310     if (!m_self) {
0311         qWarning() << "Core has not been created";
0312     }
0313     return m_self;
0314 }
0315 
0316 MainWindow *Core::window()
0317 {
0318     return m_mainWindow;
0319 }
0320 
0321 ProjectManager *Core::projectManager()
0322 {
0323     return m_projectManager;
0324 }
0325 
0326 MonitorManager *Core::monitorManager()
0327 {
0328     return m_monitorManager;
0329 }
0330 
0331 Monitor *Core::getMonitor(int id)
0332 {
0333     if (id == Kdenlive::ClipMonitor) {
0334         return m_monitorManager->clipMonitor();
0335     }
0336     return m_monitorManager->projectMonitor();
0337 }
0338 
0339 Bin *Core::bin()
0340 {
0341     return m_mainWindow->getBin();
0342 }
0343 
0344 Bin *Core::activeBin()
0345 {
0346     return m_mainWindow->activeBin();
0347 }
0348 
0349 void Core::selectBinClip(const QString &clipId, bool activateMonitor, int frame, const QPoint &zone)
0350 {
0351     m_mainWindow->activeBin()->selectClipById(clipId, frame, zone, activateMonitor);
0352 }
0353 
0354 void Core::selectTimelineItem(int id)
0355 {
0356     if (m_guiConstructed && m_mainWindow->getCurrentTimeline()->model()) {
0357         m_mainWindow->getCurrentTimeline()->model()->requestAddToSelection(id, true);
0358     }
0359 }
0360 
0361 std::shared_ptr<SubtitleModel> Core::getSubtitleModel(bool enforce)
0362 {
0363     if (m_guiConstructed && m_mainWindow->getCurrentTimeline()->model()) {
0364         auto subModel = m_mainWindow->getCurrentTimeline()->model()->getSubtitleModel();
0365         if (enforce && subModel == nullptr) {
0366             m_mainWindow->slotEditSubtitle();
0367             subModel = m_mainWindow->getCurrentTimeline()->model()->getSubtitleModel();
0368         }
0369         return subModel;
0370     }
0371     return nullptr;
0372 }
0373 
0374 LibraryWidget *Core::library()
0375 {
0376     return m_library;
0377 }
0378 
0379 TextBasedEdit *Core::textEditWidget()
0380 {
0381     return m_textEditWidget;
0382 }
0383 
0384 TimeRemap *Core::timeRemapWidget()
0385 {
0386     return m_timeRemapWidget;
0387 }
0388 
0389 bool Core::currentRemap(const QString &clipId)
0390 {
0391     return m_timeRemapWidget == nullptr ? false : m_timeRemapWidget->currentClip() == clipId;
0392 }
0393 
0394 SubtitleEdit *Core::subtitleWidget()
0395 {
0396     return m_subtitleWidget;
0397 }
0398 
0399 MixerManager *Core::mixer()
0400 {
0401     return m_mixerWidget;
0402 }
0403 
0404 void Core::initLocale()
0405 {
0406     QLocale systemLocale = QLocale(); // For disabling group separator by default
0407     systemLocale.setNumberOptions(QLocale::OmitGroupSeparator);
0408     QLocale::setDefault(systemLocale);
0409 }
0410 
0411 ToolType::ProjectTool Core::activeTool()
0412 {
0413     return m_mainWindow->getCurrentTimeline()->activeTool();
0414 }
0415 
0416 std::unique_ptr<Mlt::Repository> &Core::getMltRepository()
0417 {
0418     return MltConnection::self()->getMltRepository();
0419 }
0420 
0421 std::unique_ptr<ProfileModel> &Core::getCurrentProfile() const
0422 {
0423     return ProfileRepository::get()->getProfile(m_currentProfile);
0424 }
0425 
0426 Mlt::Profile &Core::getMonitorProfile()
0427 {
0428     return m_monitorProfile;
0429 }
0430 
0431 Mlt::Profile *Core::getProjectProfile()
0432 {
0433     if (!m_projectProfile) {
0434         m_projectProfile = std::make_unique<Mlt::Profile>(m_currentProfile.toStdString().c_str());
0435         m_projectProfile->set_explicit(true);
0436         updateMonitorProfile();
0437     }
0438     return m_projectProfile.get();
0439 }
0440 
0441 void Core::updateMonitorProfile()
0442 {
0443     m_monitorProfile.set_colorspace(m_projectProfile->colorspace());
0444     m_monitorProfile.set_frame_rate(m_projectProfile->frame_rate_num(), m_projectProfile->frame_rate_den());
0445     m_monitorProfile.set_width(m_projectProfile->width());
0446     m_monitorProfile.set_height(m_projectProfile->height());
0447     m_monitorProfile.set_progressive(m_projectProfile->progressive());
0448     m_monitorProfile.set_sample_aspect(m_projectProfile->sample_aspect_num(), m_projectProfile->sample_aspect_den());
0449     m_monitorProfile.set_display_aspect(m_projectProfile->display_aspect_num(), m_projectProfile->display_aspect_den());
0450     m_monitorProfile.set_explicit(true);
0451     emit monitorProfileUpdated();
0452 }
0453 
0454 const QString &Core::getCurrentProfilePath() const
0455 {
0456     return m_currentProfile;
0457 }
0458 
0459 bool Core::setCurrentProfile(const QString &profilePath)
0460 {
0461     if (m_currentProfile == profilePath) {
0462         // no change required, ensure timecode has correct fps
0463         m_timecode.setFormat(getCurrentProfile()->fps());
0464         return true;
0465     }
0466     if (ProfileRepository::get()->profileExists(profilePath)) {
0467         m_currentProfile = profilePath;
0468         m_thumbProfile.reset();
0469         if (m_projectProfile) {
0470             m_projectProfile->set_colorspace(getCurrentProfile()->colorspace());
0471             m_projectProfile->set_frame_rate(getCurrentProfile()->frame_rate_num(), getCurrentProfile()->frame_rate_den());
0472             m_projectProfile->set_height(getCurrentProfile()->height());
0473             m_projectProfile->set_progressive(getCurrentProfile()->progressive());
0474             m_projectProfile->set_sample_aspect(getCurrentProfile()->sample_aspect_num(), getCurrentProfile()->sample_aspect_den());
0475             m_projectProfile->set_display_aspect(getCurrentProfile()->display_aspect_num(), getCurrentProfile()->display_aspect_den());
0476             m_projectProfile->set_width(getCurrentProfile()->width());
0477             m_projectProfile->get_profile()->description = qstrdup(getCurrentProfile()->description().toUtf8().constData());
0478             m_projectProfile->set_explicit(true);
0479             updateMonitorProfile();
0480         }
0481         // inform render widget
0482         m_timecode.setFormat(getCurrentProfile()->fps());
0483         profileChanged();
0484         if (m_guiConstructed) {
0485             emit m_mainWindow->updateRenderWidgetProfile();
0486             m_monitorManager->resetProfiles();
0487             emit m_monitorManager->updatePreviewScaling();
0488             if (m_mainWindow->hasTimeline() && m_mainWindow->getCurrentTimeline()->model()) {
0489                 m_mainWindow->getCurrentTimeline()->model()->updateProfile(getProjectProfile());
0490                 m_mainWindow->getCurrentTimeline()->model()->updateFieldOrderFilter(getCurrentProfile());
0491                 checkProfileValidity();
0492                 emit m_mainWindow->getCurrentTimeline()->controller()->frameFormatChanged();
0493             }
0494         }
0495         return true;
0496     }
0497     return false;
0498 }
0499 
0500 void Core::checkProfileValidity()
0501 {
0502     int offset = (getCurrentProfile()->profile().width() % 2) + (getCurrentProfile()->profile().height() % 2);
0503     if (offset > 0) {
0504         // Profile is broken, warn user
0505         if (m_mainWindow->getBin()) {
0506             emit m_mainWindow->getBin()->displayBinMessage(i18n("Your project profile is invalid, rendering might fail."), KMessageWidget::Warning);
0507         }
0508     }
0509 }
0510 
0511 double Core::getCurrentSar() const
0512 {
0513     return getCurrentProfile()->sar();
0514 }
0515 
0516 double Core::getCurrentDar() const
0517 {
0518     return getCurrentProfile()->dar();
0519 }
0520 
0521 double Core::getCurrentFps() const
0522 {
0523     return getCurrentProfile()->fps();
0524 }
0525 
0526 QSize Core::getCurrentFrameDisplaySize() const
0527 {
0528     return {qRound(getCurrentProfile()->height() * getCurrentDar()), getCurrentProfile()->height()};
0529 }
0530 
0531 QSize Core::getCurrentFrameSize() const
0532 {
0533     return {getCurrentProfile()->width(), getCurrentProfile()->height()};
0534 }
0535 
0536 void Core::refreshProjectMonitorOnce()
0537 {
0538     if (!m_guiConstructed) return;
0539     m_monitorManager->refreshProjectMonitor();
0540 }
0541 
0542 void Core::refreshProjectRange(QPair<int, int> range)
0543 {
0544     if (!m_guiConstructed) return;
0545     m_monitorManager->refreshProjectRange(range);
0546 }
0547 
0548 const QSize Core::getCompositionSizeOnTrack(const ObjectId &id)
0549 {
0550     return m_mainWindow->getCurrentTimeline()->model()->getCompositionSizeOnTrack(id);
0551 }
0552 
0553 QPair<int, QString> Core::currentTrackInfo() const
0554 {
0555     if (m_mainWindow->getCurrentTimeline()->controller()) {
0556         int tid = m_mainWindow->getCurrentTimeline()->controller()->activeTrack();
0557         if (tid >= 0) {
0558             return {m_mainWindow->getCurrentTimeline()->model()->getTrackMltIndex(tid), m_mainWindow->getCurrentTimeline()->model()->getTrackTagById(tid)};
0559         }
0560         if (m_mainWindow->getCurrentTimeline()->model()->isSubtitleTrack(tid)) {
0561             return {tid, i18n("Subtitles")};
0562         }
0563     }
0564     return {-1, QString()};
0565 }
0566 
0567 int Core::getItemPosition(const ObjectId &id)
0568 {
0569     if (!m_guiConstructed) return 0;
0570     switch (id.first) {
0571     case ObjectType::TimelineClip:
0572         if (m_mainWindow->getCurrentTimeline()->model()->isClip(id.second)) {
0573             return m_mainWindow->getCurrentTimeline()->model()->getClipPosition(id.second);
0574         }
0575         break;
0576     case ObjectType::TimelineComposition:
0577         if (m_mainWindow->getCurrentTimeline()->model()->isComposition(id.second)) {
0578             return m_mainWindow->getCurrentTimeline()->model()->getCompositionPosition(id.second);
0579         }
0580         break;
0581     case ObjectType::TimelineMix:
0582         if (m_mainWindow->getCurrentTimeline()->model()->isClip(id.second)) {
0583             return m_mainWindow->getCurrentTimeline()->model()->getMixInOut(id.second).first;
0584         } else {
0585             qWarning() << "querying non clip properties";
0586         }
0587         break;
0588     case ObjectType::BinClip:
0589     case ObjectType::TimelineTrack:
0590     case ObjectType::Master:
0591         return 0;
0592     default:
0593         qWarning() << "unhandled object type";
0594     }
0595     return 0;
0596 }
0597 
0598 int Core::getItemIn(const ObjectId &id)
0599 {
0600     if (!m_guiConstructed || !m_mainWindow->getCurrentTimeline() || !m_mainWindow->getCurrentTimeline()->model()) {
0601         qWarning() << "GUI not build";
0602         return 0;
0603     }
0604     switch (id.first) {
0605     case ObjectType::TimelineClip:
0606         if (m_mainWindow->getCurrentTimeline()->model()->isClip(id.second)) {
0607             return m_mainWindow->getCurrentTimeline()->model()->getClipIn(id.second);
0608         } else {
0609             qWarning() << "querying non clip properties";
0610         }
0611         break;
0612     case ObjectType::TimelineMix:
0613     case ObjectType::TimelineComposition:
0614     case ObjectType::BinClip:
0615     case ObjectType::TimelineTrack:
0616     case ObjectType::Master:
0617         return 0;
0618     default:
0619         qWarning() << "unhandled object type";
0620     }
0621     return 0;
0622 }
0623 
0624 PlaylistState::ClipState Core::getItemState(const ObjectId &id)
0625 {
0626     if (!m_guiConstructed) return PlaylistState::Disabled;
0627     switch (id.first) {
0628     case ObjectType::TimelineClip:
0629         if (m_mainWindow->getCurrentTimeline()->model()->isClip(id.second)) {
0630             return m_mainWindow->getCurrentTimeline()->model()->getClipState(id.second);
0631         }
0632         break;
0633     case ObjectType::TimelineComposition:
0634         return PlaylistState::VideoOnly;
0635     case ObjectType::BinClip:
0636         return m_mainWindow->getBin()->getClipState(id.second);
0637     case ObjectType::TimelineTrack:
0638         return m_mainWindow->getCurrentTimeline()->model()->isAudioTrack(id.second) ? PlaylistState::AudioOnly : PlaylistState::VideoOnly;
0639     case ObjectType::Master:
0640         return PlaylistState::Disabled;
0641     default:
0642         qWarning() << "unhandled object type";
0643         break;
0644     }
0645     return PlaylistState::Disabled;
0646 }
0647 
0648 int Core::getItemDuration(const ObjectId &id)
0649 {
0650     if (!m_guiConstructed) return 0;
0651     switch (id.first) {
0652     case ObjectType::TimelineClip:
0653         if (m_mainWindow->getCurrentTimeline()->model()->isClip(id.second)) {
0654             return m_mainWindow->getCurrentTimeline()->model()->getClipPlaytime(id.second);
0655         }
0656         break;
0657     case ObjectType::TimelineComposition:
0658         if (m_mainWindow->getCurrentTimeline()->model()->isComposition(id.second)) {
0659             return m_mainWindow->getCurrentTimeline()->model()->getCompositionPlaytime(id.second);
0660         }
0661         break;
0662     case ObjectType::BinClip:
0663         return int(m_mainWindow->getBin()->getClipDuration(id.second));
0664     case ObjectType::TimelineTrack:
0665     case ObjectType::Master:
0666         return m_mainWindow->getCurrentTimeline()->controller()->duration() - 1;
0667     case ObjectType::TimelineMix:
0668         if (m_mainWindow->getCurrentTimeline()->model()->isClip(id.second)) {
0669             return m_mainWindow->getCurrentTimeline()->model()->getMixDuration(id.second);
0670         } else {
0671             qWarning() << "querying non clip properties";
0672         }
0673         break;
0674     default:
0675         qWarning() << "unhandled object type: " << (int)id.first;
0676     }
0677     return 0;
0678 }
0679 
0680 QSize Core::getItemFrameSize(const ObjectId &id)
0681 {
0682     if (!m_guiConstructed) return QSize();
0683     switch (id.first) {
0684     case ObjectType::TimelineClip:
0685         if (m_mainWindow->getCurrentTimeline()->model()->isClip(id.second)) {
0686             return m_mainWindow->getCurrentTimeline()->model()->getClipFrameSize(id.second);
0687         }
0688         break;
0689     case ObjectType::BinClip:
0690         return m_mainWindow->getBin()->getFrameSize(id.second);
0691     case ObjectType::TimelineTrack:
0692     case ObjectType::Master:
0693     case ObjectType::TimelineComposition:
0694     case ObjectType::TimelineMix:
0695         return pCore->getCurrentFrameSize();
0696     default:
0697         qWarning() << "unhandled object type frame size";
0698     }
0699     return pCore->getCurrentFrameSize();
0700 }
0701 
0702 int Core::getItemTrack(const ObjectId &id)
0703 {
0704     if (!m_guiConstructed) return 0;
0705     switch (id.first) {
0706     case ObjectType::TimelineClip:
0707     case ObjectType::TimelineComposition:
0708     case ObjectType::TimelineMix:
0709         return m_mainWindow->getCurrentTimeline()->model()->getItemTrackId(id.second);
0710     default:
0711         qWarning() << "unhandled object type";
0712     }
0713     return 0;
0714 }
0715 
0716 void Core::refreshProjectItem(const ObjectId &id)
0717 {
0718     if (!m_guiConstructed || m_mainWindow->getCurrentTimeline()->loading) return;
0719     switch (id.first) {
0720     case ObjectType::TimelineClip:
0721     case ObjectType::TimelineMix:
0722         if (m_mainWindow->getCurrentTimeline()->model()->isClip(id.second)) {
0723             m_mainWindow->getCurrentTimeline()->controller()->refreshItem(id.second);
0724         }
0725         break;
0726     case ObjectType::TimelineComposition:
0727         if (m_mainWindow->getCurrentTimeline()->model()->isComposition(id.second)) {
0728             m_mainWindow->getCurrentTimeline()->controller()->refreshItem(id.second);
0729         }
0730         break;
0731     case ObjectType::TimelineTrack:
0732         if (m_mainWindow->getCurrentTimeline()->model()->isTrack(id.second)) {
0733             refreshProjectMonitorOnce();
0734         }
0735         break;
0736     case ObjectType::BinClip:
0737         if (m_monitorManager->clipMonitorVisible()) {
0738             m_monitorManager->activateMonitor(Kdenlive::ClipMonitor);
0739             m_monitorManager->refreshClipMonitor(true);
0740         }
0741         if (m_monitorManager->projectMonitorVisible() && m_mainWindow->getCurrentTimeline()->controller()->refreshIfVisible(id.second)) {
0742             m_monitorManager->refreshTimer.start();
0743         }
0744         break;
0745     case ObjectType::Master:
0746         refreshProjectMonitorOnce();
0747         break;
0748     default:
0749         qWarning() << "unhandled object type";
0750     }
0751 }
0752 
0753 bool Core::hasTimelinePreview() const
0754 {
0755     if (!m_guiConstructed) {
0756         return false;
0757     }
0758     return m_mainWindow->getCurrentTimeline()->controller()->renderedChunks().size() > 0;
0759 }
0760 
0761 KdenliveDoc *Core::currentDoc()
0762 {
0763     return m_projectManager->current();
0764 }
0765 
0766 Timecode Core::timecode() const
0767 {
0768     return m_timecode;
0769 }
0770 
0771 void Core::setDocumentModified()
0772 {
0773     m_projectManager->current()->setModified();
0774     ;
0775 }
0776 
0777 int Core::projectDuration() const
0778 {
0779     if (!m_guiConstructed) {
0780         return 0;
0781     }
0782     return m_mainWindow->getCurrentTimeline()->controller()->duration();
0783 }
0784 
0785 void Core::profileChanged()
0786 {
0787     GenTime::setFps(getCurrentFps());
0788 }
0789 
0790 void Core::pushUndo(const Fun &undo, const Fun &redo, const QString &text)
0791 {
0792     undoStack()->push(new FunctionalUndoCommand(undo, redo, text));
0793 }
0794 
0795 void Core::pushUndo(QUndoCommand *command)
0796 {
0797     undoStack()->push(command);
0798 }
0799 
0800 int Core::undoIndex() const
0801 {
0802     return m_projectManager->undoStack()->index();
0803 }
0804 
0805 void Core::displaySelectionMessage(const QString &message)
0806 {
0807     if (m_mainWindow) {
0808         emit m_mainWindow->displaySelectionMessage(message);
0809     }
0810 }
0811 
0812 void Core::displayMessage(const QString &message, MessageType type, int timeout)
0813 {
0814     if (m_mainWindow) {
0815         if (type == ProcessingJobMessage || type == OperationCompletedMessage) {
0816             emit m_mainWindow->displayProgressMessage(message, type, timeout);
0817         } else {
0818             emit m_mainWindow->displayMessage(message, type, timeout);
0819         }
0820     } else {
0821         qDebug() << message;
0822     }
0823 }
0824 
0825 void Core::loadingClips(int count)
0826 {
0827     emit m_mainWindow->displayProgressMessage(i18n("Loading clips"), MessageType::ProcessingJobMessage, count);
0828 }
0829 
0830 void Core::displayBinMessage(const QString &text, int type, const QList<QAction *> &actions, bool showClose, BinMessage::BinCategory messageCategory)
0831 {
0832     m_mainWindow->getBin()->doDisplayMessage(text, KMessageWidget::MessageType(type), actions, showClose, messageCategory);
0833 }
0834 
0835 void Core::displayBinLogMessage(const QString &text, int type, const QString &logInfo)
0836 {
0837     m_mainWindow->getBin()->doDisplayMessage(text, KMessageWidget::MessageType(type), logInfo);
0838 }
0839 
0840 void Core::clearAssetPanel(int itemId)
0841 {
0842     if (m_guiConstructed) emit m_mainWindow->clearAssetPanel(itemId);
0843 }
0844 
0845 std::shared_ptr<EffectStackModel> Core::getItemEffectStack(int itemType, int itemId)
0846 {
0847     if (!m_guiConstructed) return nullptr;
0848     switch (itemType) {
0849     case int(ObjectType::TimelineClip):
0850         return m_mainWindow->getCurrentTimeline()->model()->getClipEffectStack(itemId);
0851     case int(ObjectType::TimelineTrack):
0852         return m_mainWindow->getCurrentTimeline()->model()->getTrackEffectStackModel(itemId);
0853     case int(ObjectType::BinClip):
0854         return m_mainWindow->getBin()->getClipEffectStack(itemId);
0855     case int(ObjectType::Master):
0856         return m_mainWindow->getCurrentTimeline()->model()->getMasterEffectStackModel();
0857     default:
0858         return nullptr;
0859     }
0860 }
0861 
0862 std::shared_ptr<DocUndoStack> Core::undoStack()
0863 {
0864     return projectManager()->undoStack();
0865 }
0866 
0867 QMap<int, QString> Core::getTrackNames(bool videoOnly)
0868 {
0869     if (!m_guiConstructed) return QMap<int, QString>();
0870     return m_mainWindow->getCurrentTimeline()->controller()->getTrackNames(videoOnly);
0871 }
0872 
0873 QPair<int, int> Core::getCompositionATrack(int cid) const
0874 {
0875     if (!m_guiConstructed) return {};
0876     return m_mainWindow->getCurrentTimeline()->controller()->getCompositionATrack(cid);
0877 }
0878 
0879 bool Core::compositionAutoTrack(int cid) const
0880 {
0881     return m_mainWindow->getCurrentTimeline()->controller()->compositionAutoTrack(cid);
0882 }
0883 
0884 void Core::setCompositionATrack(int cid, int aTrack)
0885 {
0886     if (!m_guiConstructed) return;
0887     m_mainWindow->getCurrentTimeline()->controller()->setCompositionATrack(cid, aTrack);
0888 }
0889 
0890 std::shared_ptr<ProjectItemModel> Core::projectItemModel()
0891 {
0892     return m_projectItemModel;
0893 }
0894 
0895 void Core::invalidateRange(QPair<int, int> range)
0896 {
0897     if (!m_guiConstructed || m_mainWindow->getCurrentTimeline()->loading) return;
0898     m_mainWindow->getCurrentTimeline()->controller()->invalidateZone(range.first, range.second);
0899 }
0900 
0901 void Core::invalidateItem(ObjectId itemId)
0902 {
0903     if (!m_guiConstructed || !m_mainWindow->getCurrentTimeline() || m_mainWindow->getCurrentTimeline()->loading) return;
0904     switch (itemId.first) {
0905     case ObjectType::TimelineClip:
0906     case ObjectType::TimelineComposition:
0907         m_mainWindow->getCurrentTimeline()->controller()->invalidateItem(itemId.second);
0908         break;
0909     case ObjectType::TimelineTrack:
0910         m_mainWindow->getCurrentTimeline()->controller()->invalidateTrack(itemId.second);
0911         break;
0912     case ObjectType::BinClip:
0913         m_mainWindow->getBin()->invalidateClip(QString::number(itemId.second));
0914         break;
0915     case ObjectType::Master:
0916         m_mainWindow->getCurrentTimeline()->controller()->invalidateZone(0, -1);
0917         break;
0918     default:
0919         // compositions should not have effects
0920         break;
0921     }
0922 }
0923 
0924 double Core::getClipSpeed(int id) const
0925 {
0926     return m_mainWindow->getCurrentTimeline()->model()->getClipSpeed(id);
0927 }
0928 
0929 void Core::updateItemKeyframes(ObjectId id)
0930 {
0931     if (id.first == ObjectType::TimelineClip && m_guiConstructed) {
0932         m_mainWindow->getCurrentTimeline()->controller()->updateClip(id.second, {TimelineModel::KeyframesRole});
0933     }
0934 }
0935 
0936 void Core::updateItemModel(ObjectId id, const QString &service)
0937 {
0938     if (m_guiConstructed && id.first == ObjectType::TimelineClip && !m_mainWindow->getCurrentTimeline()->loading && service.startsWith(QLatin1String("fade"))) {
0939         bool startFade = service.startsWith(QLatin1String("fadein")) || service.startsWith(QLatin1String("fade_from_"));
0940         m_mainWindow->getCurrentTimeline()->controller()->updateClip(id.second, {startFade ? TimelineModel::FadeInRole : TimelineModel::FadeOutRole});
0941     }
0942 }
0943 
0944 void Core::showClipKeyframes(ObjectId id, bool enable)
0945 {
0946     if (id.first == ObjectType::TimelineClip) {
0947         m_mainWindow->getCurrentTimeline()->controller()->showClipKeyframes(id.second, enable);
0948     } else if (id.first == ObjectType::TimelineComposition) {
0949         m_mainWindow->getCurrentTimeline()->controller()->showCompositionKeyframes(id.second, enable);
0950     }
0951 }
0952 
0953 Mlt::Profile *Core::thumbProfile()
0954 {
0955     QMutexLocker lck(&m_thumbProfileMutex);
0956     if (!m_thumbProfile) {
0957         m_thumbProfile = std::make_unique<Mlt::Profile>(m_currentProfile.toStdString().c_str());
0958         double factor = 144. / m_thumbProfile->height();
0959         m_thumbProfile->set_height(144);
0960         int width = qRound(m_thumbProfile->width() * factor);
0961         if (width % 2 > 0) {
0962             width++;
0963         }
0964         m_thumbProfile->set_width(width);
0965         m_thumbProfile->set_explicit(true);
0966     }
0967     return m_thumbProfile.get();
0968 }
0969 
0970 int Core::getTimelinePosition() const
0971 {
0972     if (m_guiConstructed) {
0973         return m_monitorManager->projectMonitor()->position();
0974     }
0975     return 0;
0976 }
0977 
0978 void Core::triggerAction(const QString &name)
0979 {
0980     QAction *action = m_mainWindow->actionCollection()->action(name);
0981     if (action) {
0982         action->trigger();
0983     }
0984 }
0985 
0986 const QString Core::actionText(const QString &name)
0987 {
0988     QAction *action = m_mainWindow->actionCollection()->action(name);
0989     if (action) {
0990         return action->toolTip();
0991     }
0992     return QString();
0993 }
0994 
0995 void Core::clean()
0996 {
0997     m_self.reset();
0998 }
0999 
1000 void Core::startMediaCapture(int tid, bool checkAudio, bool checkVideo)
1001 {
1002     // TODO: fix video capture
1003     /*if (checkAudio && checkVideo) {
1004         m_capture->recordVideo(tid, true);
1005     } else*/
1006     if (checkAudio) {
1007         m_capture->recordAudio(tid, true);
1008     }
1009     m_mediaCaptureFile = m_capture->getCaptureOutputLocation();
1010 }
1011 
1012 void Core::stopMediaCapture(int tid, bool checkAudio, bool checkVideo)
1013 {
1014     // TODO: fix video capture
1015     /*if (checkAudio && checkVideo) {
1016         m_capture->recordVideo(tid, false);
1017     } else*/
1018     if (checkAudio) {
1019         m_capture->recordAudio(tid, false);
1020     }
1021 }
1022 
1023 void Core::monitorAudio(int tid, bool monitor)
1024 {
1025     m_mainWindow->getCurrentTimeline()->controller()->switchTrackRecord(tid, monitor);
1026     if (monitor && pCore->monitorManager()->projectMonitor()->isPlaying()) {
1027         pCore->monitorManager()->projectMonitor()->stop();
1028     }
1029 }
1030 
1031 void Core::startRecording()
1032 {
1033     int trackId = m_capture->startCapture();
1034     m_mainWindow->getCurrentTimeline()->startAudioRecord(trackId);
1035     pCore->monitorManager()->slotPlay();
1036 }
1037 
1038 QStringList Core::getAudioCaptureDevices()
1039 {
1040     return m_capture->getAudioCaptureDevices();
1041 }
1042 
1043 int Core::getMediaCaptureState()
1044 {
1045     return m_capture->getState();
1046 }
1047 
1048 bool Core::isMediaMonitoring() const
1049 {
1050     return m_capture->isMonitoring();
1051 }
1052 
1053 bool Core::isMediaCapturing() const
1054 {
1055     return m_capture->isRecording();
1056 }
1057 
1058 void Core::switchCapture()
1059 {
1060     emit recordAudio(-1, !isMediaCapturing());
1061 }
1062 
1063 MediaCapture *Core::getAudioDevice()
1064 {
1065     return m_capture.get();
1066 }
1067 
1068 void Core::resetAudioMonitoring()
1069 {
1070     if (m_capture && m_capture->isMonitoring()) {
1071         m_capture->switchMonitorState(false);
1072         m_capture->switchMonitorState(true);
1073     }
1074 }
1075 
1076 QString Core::getProjectFolderName(bool folderForAudio)
1077 {
1078     if (currentDoc()) {
1079         return currentDoc()->projectDataFolder(QStringLiteral(), folderForAudio) + QDir::separator();
1080     }
1081     return QString();
1082 }
1083 
1084 QString Core::getTimelineClipBinId(int cid)
1085 {
1086     if (!m_guiConstructed) {
1087         return QString();
1088     }
1089     if (m_mainWindow->getCurrentTimeline()->model()->isClip(cid)) {
1090         return m_mainWindow->getCurrentTimeline()->model()->getClipBinId(cid);
1091     }
1092     return QString();
1093 }
1094 std::unordered_set<QString> Core::getAllTimelineTracksId()
1095 {
1096     std::unordered_set<int> timelineClipIds = m_mainWindow->getCurrentTimeline()->model()->getItemsInRange(-1, 0);
1097     std::unordered_set<QString> tClipBinIds;
1098     for (int id : timelineClipIds) {
1099         auto idString = m_mainWindow->getCurrentTimeline()->model()->getClipBinId(id);
1100         tClipBinIds.insert(idString);
1101     }
1102     return tClipBinIds;
1103 }
1104 
1105 int Core::getDurationFromString(const QString &time)
1106 {
1107     return m_timecode.getFrameCount(time);
1108 }
1109 
1110 void Core::processInvalidFilter(const QString &service, const QString &id, const QString &message)
1111 {
1112     if (m_guiConstructed) emit m_mainWindow->assetPanelWarning(service, id, message);
1113 }
1114 
1115 void Core::updateProjectTags(int previousCount, const QMap<int, QStringList> &tags)
1116 {
1117     if (previousCount > tags.size()) {
1118         // Clear previous tags
1119         for (int i = 1; i <= previousCount; i++) {
1120             QString current = currentDoc()->getDocumentProperty(QString("tag%1").arg(i));
1121             if (!current.isEmpty()) {
1122                 currentDoc()->setDocumentProperty(QString("tag%1").arg(i), QString());
1123             }
1124         }
1125     }
1126     QMapIterator<int, QStringList> j(tags);
1127     int i = 1;
1128     while (j.hasNext()) {
1129         j.next();
1130         currentDoc()->setDocumentProperty(QString("tag%1").arg(i), QString("%1:%2").arg(j.value().at(1), j.value().at(2)));
1131         i++;
1132     }
1133 }
1134 
1135 std::unique_ptr<Mlt::Producer> Core::getMasterProducerInstance()
1136 {
1137     if (m_guiConstructed && m_mainWindow->getCurrentTimeline()) {
1138         std::unique_ptr<Mlt::Producer> producer(
1139             m_mainWindow->getCurrentTimeline()->controller()->tractor()->cut(0, m_mainWindow->getCurrentTimeline()->controller()->duration() - 1));
1140         return producer;
1141     }
1142     return nullptr;
1143 }
1144 
1145 std::unique_ptr<Mlt::Producer> Core::getTrackProducerInstance(int tid)
1146 {
1147     if (m_guiConstructed && m_mainWindow->getCurrentTimeline()) {
1148         std::unique_ptr<Mlt::Producer> producer(new Mlt::Producer(m_mainWindow->getCurrentTimeline()->controller()->trackProducer(tid)));
1149         return producer;
1150     }
1151     return nullptr;
1152 }
1153 
1154 bool Core::enableMultiTrack(bool enable)
1155 {
1156     if (!m_guiConstructed || !m_mainWindow->getCurrentTimeline()) {
1157         return false;
1158     }
1159     bool isMultiTrack = pCore->monitorManager()->isMultiTrack();
1160     if (isMultiTrack || enable) {
1161         pCore->window()->getMainTimeline()->controller()->slotMultitrackView(enable, true);
1162         return true;
1163     }
1164     return false;
1165 }
1166 
1167 int Core::audioChannels()
1168 {
1169     if (m_projectManager && m_projectManager->current()) {
1170         return m_projectManager->current()->audioChannels();
1171     }
1172     return 2;
1173 }
1174 
1175 void Core::addGuides(const QList<int> &guides)
1176 {
1177     QMap<GenTime, QString> markers;
1178     for (int pos : guides) {
1179         GenTime p(pos, pCore->getCurrentFps());
1180         markers.insert(p, pCore->currentDoc()->timecode().getDisplayTimecode(p, false));
1181     }
1182     currentDoc()->getGuideModel()->addMarkers(markers);
1183 }
1184 
1185 void Core::temporaryUnplug(const QList<int> &clipIds, bool hide)
1186 {
1187     window()->getMainTimeline()->controller()->temporaryUnplug(clipIds, hide);
1188 }
1189 
1190 void Core::transcodeFile(const QString &url)
1191 {
1192     qDebug() << "=== TRANSCODING: " << url;
1193     window()->slotTranscode({url});
1194 }
1195 
1196 void Core::transcodeFriendlyFile(const QString &binId, bool checkProfile)
1197 {
1198     window()->slotFriendlyTranscode(binId, checkProfile);
1199 }
1200 
1201 void Core::setWidgetKeyBinding(const QString &mess)
1202 {
1203     window()->setWidgetKeyBinding(mess);
1204 }
1205 
1206 void Core::showEffectZone(ObjectId id, QPair<int, int> inOut, bool checked)
1207 {
1208     if (m_guiConstructed && m_mainWindow->getCurrentTimeline()->controller() && id.first != ObjectType::BinClip) {
1209         m_mainWindow->getCurrentTimeline()->controller()->showRulerEffectZone(inOut, checked);
1210     }
1211 }
1212 
1213 void Core::updateMasterZones()
1214 {
1215     if (m_guiConstructed && m_mainWindow->getCurrentTimeline()->controller()) {
1216         m_mainWindow->getCurrentTimeline()->controller()->updateMasterZones(m_mainWindow->getCurrentTimeline()->model()->getMasterEffectZones());
1217     }
1218 }
1219 
1220 void Core::testProxies()
1221 {
1222     QScopedPointer<ProxyTest> dialog(new ProxyTest(QApplication::activeWindow()));
1223     dialog->exec();
1224 }
1225 
1226 void Core::resizeMix(int cid, int duration, MixAlignment align, int leftFrames)
1227 {
1228     m_mainWindow->getCurrentTimeline()->controller()->resizeMix(cid, duration, align, leftFrames);
1229 }
1230 
1231 MixAlignment Core::getMixAlign(int cid) const
1232 {
1233     return m_mainWindow->getCurrentTimeline()->controller()->getMixAlign(cid);
1234 }
1235 
1236 int Core::getMixCutPos(int cid) const
1237 {
1238     return m_mainWindow->getCurrentTimeline()->controller()->getMixCutPos(cid);
1239 }
1240 
1241 void Core::cleanup()
1242 {
1243     audioThumbCache.clear();
1244     taskManager.slotCancelJobs();
1245     if (timeRemapWidget()) {
1246         timeRemapWidget()->selectedClip(-1);
1247     }
1248     if (m_mainWindow && m_mainWindow->getMainTimeline()) {
1249         disconnect(m_mainWindow->getMainTimeline()->controller(), &TimelineController::durationChanged, m_projectManager,
1250                    &ProjectManager::adjustProjectDuration);
1251         m_mainWindow->getMainTimeline()->controller()->clipActions.clear();
1252     }
1253 }
1254 
1255 int Core::getNewStuff(const QString &config)
1256 {
1257     return m_mainWindow->getNewStuff(config);
1258 }
1259 
1260 void Core::addBin(const QString &id)
1261 {
1262     Bin *bin = new Bin(m_projectItemModel, m_mainWindow, false);
1263     bin->setupMenu();
1264     bin->setMonitor(m_monitorManager->clipMonitor());
1265     const QString folderName = bin->setDocument(pCore->currentDoc(), id);
1266     m_mainWindow->addBin(bin, folderName);
1267 }
1268 
1269 void Core::loadTimelinePreview(const QString &chunks, const QString &dirty, int enablePreview, Mlt::Playlist &playlist)
1270 {
1271     pCore->window()->getMainTimeline()->controller()->loadPreview(chunks, dirty, enablePreview, playlist);
1272 }