File indexing completed on 2024-12-08 04:27:17

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