diff --git a/CMakeLists.txt b/CMakeLists.txt index 2345732e59..8a5d737532 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -77,6 +77,12 @@ if(UNIX AND NOT APPLE AND BUILD_MINIMAL_MEDIA_BACKEND) add_subdirectory(MinimalMediaBackend) endif() +option(SHOTCUT_BUILD_TESTS "Build unit tests" OFF) +if(SHOTCUT_BUILD_TESTS) + enable_testing() + add_subdirectory(tests) +endif() + feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) add_custom_target(codespell COMMAND diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d7a802906e..b69e9833ca 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -66,6 +66,7 @@ add_executable(shotcut WIN32 MACOSX_BUNDLE docks/subtitlesdock.cpp docks/subtitlesdock.h docks/timelinedock.cpp docks/timelinedock.h FlatpakWrapperGenerator.cpp FlatpakWrapperGenerator.h + gpuinfo.cpp gpuinfo.h htmlgenerator.h htmlgenerator.cpp jobqueue.cpp jobqueue.h jobs/abstractjob.cpp jobs/abstractjob.h @@ -340,7 +341,7 @@ if(WIN32) target_sources(shotcut PRIVATE windowstools.cpp windowstools.h) target_sources(shotcut PRIVATE widgets/d3dvideowidget.h widgets/d3dvideowidget.cpp) target_sources(shotcut PRIVATE widgets/openglvideowidget.h widgets/openglvideowidget.cpp) - target_link_libraries(shotcut PRIVATE d3d11 d3dcompiler ole32) + target_link_libraries(shotcut PRIVATE d3d11 d3dcompiler dxgi dxguid ole32) # Runtime exception handler for debug only if(CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64") diff --git a/src/docks/encodedock.cpp b/src/docks/encodedock.cpp index 3975b1f93b..6a8991e9e7 100644 --- a/src/docks/encodedock.cpp +++ b/src/docks/encodedock.cpp @@ -23,6 +23,7 @@ #include "dialogs/listselectiondialog.h" #include "dialogs/multifileexportdialog.h" #include "findanalysisfilterparser.h" +#include "gpuinfo.h" #include "jobqueue.h" #include "jobs/encodejob.h" #include "mainwindow.h" @@ -211,16 +212,18 @@ void EncodeDock::loadPresetFromProperties(Mlt::Properties &preset) ui->metaLanguageLineEdit->clear(); if (ui->hwencodeCheckBox->isChecked()) { + // Prefer the hardware encoder family that matches the user-selected GPU vendor + // (NVIDIA -> *_nvenc, AMD -> *_amf, Intel -> *_qsv) so that selecting the + // discrete NVIDIA GPU drives export through NVENC even when an AMD (AMF) encoder + // also passed detection and happens to come first. 10-bit video skips H.264 + // hardware encoders, which do not support it. See gpuinfo.cpp. const bool is10bit = QString::fromLatin1(preset.get("pix_fmt")).contains("p10le"); - for (const QString &hw : Settings.encodeHardware()) { - if ((vcodec == "libx264" && hw.startsWith("h264") && !is10bit) - || (vcodec == "libx265" && hw.startsWith("hevc")) - || (vcodec == "libvpx-vp9" && hw.startsWith("vp9")) - || (vcodec == "libsvtav1" && hw.startsWith("av1"))) { - vcodec = hw; - break; - } - } + const QString chosen = preferredHardwareVcodec(Settings.encodeHardware(), + vcodec, + Settings.gpuAdapterVendorId(), + is10bit); + if (!chosen.isEmpty()) + vcodec = chosen; } ui->disableAudioCheckbox->setChecked(preset.get_int("an")); diff --git a/src/gpuinfo.cpp b/src/gpuinfo.cpp new file mode 100644 index 0000000000..49b65494a1 --- /dev/null +++ b/src/gpuinfo.cpp @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2026 Meltytech, LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gpuinfo.h" + +#include +#include + +// Platform-independent: pick the hardware encoder matching the GPU vendor, with a +// fallback to the first type-compatible encoder. Kept out of the Windows-only block +// so it builds and is unit-testable everywhere. +QString preferredHardwareVcodec(const QStringList &hardwareCodecs, + const QString &softwareVcodec, + uint vendorId, + bool is10bit) +{ + auto matchesType = [&softwareVcodec, is10bit](const QString &hw) { + // No known H.264 hardware encoder supports 10-bit, so skip it in that case. + return (softwareVcodec == QLatin1String("libx264") + && hw.startsWith(QLatin1String("h264")) && !is10bit) + || (softwareVcodec == QLatin1String("libx265") + && hw.startsWith(QLatin1String("hevc"))) + || (softwareVcodec == QLatin1String("libvpx-vp9") + && hw.startsWith(QLatin1String("vp9"))) + || (softwareVcodec == QLatin1String("libsvtav1") + && hw.startsWith(QLatin1String("av1"))); + }; + QString preferredSuffix; + switch (vendorId) { + case kGpuVendorNvidia: + preferredSuffix = QStringLiteral("_nvenc"); + break; + case kGpuVendorAmd: + preferredSuffix = QStringLiteral("_amf"); + break; + case kGpuVendorIntel: + preferredSuffix = QStringLiteral("_qsv"); + break; + default: + break; + } + if (!preferredSuffix.isEmpty()) { + for (const QString &hw : hardwareCodecs) { + if (matchesType(hw) && hw.endsWith(preferredSuffix)) + return hw; + } + } + for (const QString &hw : hardwareCodecs) { + if (matchesType(hw)) + return hw; + } + return QString(); +} + +#ifdef Q_OS_WIN +#include + +QList enumerateGpuAdapters() +{ + QList result; + IDXGIFactory1 *factory = nullptr; + // IID_IDXGIFactory1 is provided by the dxguid import library (linked in CMake), + // which keeps this portable across MinGW/GCC and MSVC. + if (FAILED(CreateDXGIFactory1(IID_IDXGIFactory1, reinterpret_cast(&factory))) + || !factory) + return result; + + // Some drivers (notably AMD integrated graphics) enumerate the same physical GPU + // many times with identical hardware ids but different LUIDs. De-duplicate by a + // stable hardware key so each GPU appears once in the menu, while preserving the + // true DXGI adapter index (which is what QT_D3D_ADAPTER_INDEX expects). + QSet seen; + IDXGIAdapter1 *adapter = nullptr; + for (UINT i = 0; factory->EnumAdapters1(i, &adapter) != DXGI_ERROR_NOT_FOUND; ++i) { + if (adapter) { + DXGI_ADAPTER_DESC1 desc; + if (SUCCEEDED(adapter->GetDesc1(&desc)) + // Hide the Microsoft Basic Render Driver (software rasterizer). + && !(desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)) { + const QString key = QStringLiteral("%1:%2:%3:%4") + .arg(desc.VendorId) + .arg(desc.DeviceId) + .arg(desc.SubSysId) + .arg(desc.Revision); + if (!seen.contains(key)) { + seen.insert(key); + GpuAdapterInfo info; + info.index = static_cast(i); + info.name = QString::fromWCharArray(desc.Description); + info.vendorId = desc.VendorId; + info.deviceId = desc.DeviceId; + result.append(info); + } + } + adapter->Release(); + adapter = nullptr; + } + } + factory->Release(); + return result; +} + +int gpuAdapterIndexFor(uint vendorId, uint deviceId) +{ + if (vendorId == 0) + return -1; + IDXGIFactory1 *factory = nullptr; + if (FAILED(CreateDXGIFactory1(IID_IDXGIFactory1, reinterpret_cast(&factory))) + || !factory) + return -1; + int found = -1; + IDXGIAdapter1 *adapter = nullptr; + for (UINT i = 0; factory->EnumAdapters1(i, &adapter) != DXGI_ERROR_NOT_FOUND; ++i) { + if (adapter) { + DXGI_ADAPTER_DESC1 desc; + if (found < 0 && SUCCEEDED(adapter->GetDesc1(&desc)) + && !(desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) && desc.VendorId == vendorId + && desc.DeviceId == deviceId) { + found = static_cast(i); + } + adapter->Release(); + adapter = nullptr; + } + } + factory->Release(); + return found; +} + +#else // !Q_OS_WIN + +QList enumerateGpuAdapters() +{ + // Index-based GPU selection is currently only implemented for the Windows + // Direct3D RHI backend. Returning empty hides the selection UI elsewhere. + return QList(); +} + +int gpuAdapterIndexFor(uint, uint) +{ + return -1; +} + +#endif diff --git a/src/gpuinfo.h b/src/gpuinfo.h new file mode 100644 index 0000000000..ceaa2b349b --- /dev/null +++ b/src/gpuinfo.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2026 Meltytech, LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef GPUINFO_H +#define GPUINFO_H + +#include +#include +#include + +// PCI vendor ids of the common GPU vendors. +enum GpuVendorId { + kGpuVendorNvidia = 0x10DE, + kGpuVendorAmd = 0x1002, + kGpuVendorIntel = 0x8086, +}; + +struct GpuAdapterInfo +{ + int index = -1; // current DXGI adapter index; matches QT_D3D_ADAPTER_INDEX + QString name; // human-readable description, e.g. "NVIDIA GeForce RTX 3090" + uint vendorId = 0; // PCI vendor id (see GpuVendorId) + uint deviceId = 0; // PCI device id; together with vendorId identifies the GPU +}; + +// Enumerate the physical GPU adapters usable by the renderer. +// On Windows this uses DXGI (the same enumeration order Qt's D3D RHI backend uses). +// Returns an empty list on platforms/backends where index-based selection is not +// supported, in which case the caller should hide the selection UI. +QList enumerateGpuAdapters(); + +// Resolve the CURRENT DXGI adapter index (as QT_D3D_ADAPTER_INDEX expects) of the GPU +// identified by vendorId+deviceId, or -1 if not found. Some drivers enumerate adapters +// in an unstable order across runs, so the index must be resolved live at startup from +// the GPU's stable identity rather than persisted from a previous session. +int gpuAdapterIndexFor(uint vendorId, uint deviceId); + +// Choose the hardware video encoder for a given software codec, preferring the encoder +// family matching the selected GPU vendor (NVIDIA->*_nvenc, AMD->*_amf, Intel->*_qsv) +// and otherwise falling back to the first type-compatible encoder in hardwareCodecs. +// When is10bit is true, H.264 hardware encoders are skipped because they do not support +// 10-bit. Returns an empty string when no compatible hardware encoder is available. +// This is a pure, platform-independent function so it can be unit tested. +QString preferredHardwareVcodec(const QStringList &hardwareCodecs, + const QString &softwareVcodec, + uint vendorId, + bool is10bit); + +#endif // GPUINFO_H diff --git a/src/main.cpp b/src/main.cpp index 013d110a85..c76e333227 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -18,6 +18,7 @@ #include "ConsoleAppender.h" #include "FileAppender.h" #include "Logger.h" +#include "gpuinfo.h" #include "mainwindow.h" #include "settings.h" @@ -432,6 +433,30 @@ int main(int argc, char **argv) Application a(argc, argv); int result = EXIT_SUCCESS; +#ifdef Q_OS_WIN + // Direct the user-selected physical GPU to be used by the Qt RHI (Direct3D) + // preview/UI via QT_D3D_ADAPTER_INDEX and by the FFmpeg hardware decoder via + // MLT_AVFORMAT_HWACCEL_DEVICE. These must be set before the RHI is initialized (the + // adapter index is read lazily when the first scene-graph window is created). In + // release builds the watchdog child process, and the melt export subprocess, inherit + // this environment. Values already set by the user take precedence. The adapter index + // is resolved live from the GPU's stable vendor+device identity because some drivers + // enumerate adapters in an unstable order across runs. See gpuinfo.cpp and Settings. + { + const uint gpuVendorId = Settings.gpuAdapterVendorId(); + if (gpuVendorId != 0) { + const int gpuAdapterIndex = gpuAdapterIndexFor(gpuVendorId, + Settings.gpuAdapterDeviceId()); + if (gpuAdapterIndex >= 0) { + const QByteArray index = QByteArray::number(gpuAdapterIndex); + if (!qEnvironmentVariableIsSet("QT_D3D_ADAPTER_INDEX")) + ::qputenv("QT_D3D_ADAPTER_INDEX", index); + if (!qEnvironmentVariableIsSet("MLT_AVFORMAT_HWACCEL_DEVICE")) + ::qputenv("MLT_AVFORMAT_HWACCEL_DEVICE", index); + } + } + } +#endif #ifdef QT_DEBUG ::qputenv(kWatchdogEnvVar, "1"); #endif diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 39191e5a38..73db33d3f5 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -47,6 +47,7 @@ #include "docks/recentdock.h" #include "docks/subtitlesdock.h" #include "docks/timelinedock.h" +#include "gpuinfo.h" #include "hdrpreviewwindow.h" #include "jobqueue.h" #include "jobs/screencapturejob.h" @@ -1626,6 +1627,43 @@ void MainWindow::setupSettingsMenu() ui->menuDrawingMethod = 0; #endif + // Setup the Graphics Adapter (GPU) selection menu. This is currently only + // populated where index-based GPU selection is supported (Windows D3D RHI); + // enumerateGpuAdapters() returns empty elsewhere and the menu is hidden. + { + const QList adapters = enumerateGpuAdapters(); + if (adapters.isEmpty()) { + delete ui->menuGpuAdapter; + ui->menuGpuAdapter = nullptr; + } else { + QActionGroup *gpuGroup = new QActionGroup(this); + const uint currentVendor = Settings.gpuAdapterVendorId(); + const uint currentDevice = Settings.gpuAdapterDeviceId(); + QAction *autoAction = ui->menuGpuAdapter->addAction(tr("Automatic")); + autoAction->setCheckable(true); + autoAction->setProperty("vendorId", 0u); + autoAction->setProperty("deviceId", 0u); + autoAction->setChecked(currentVendor == 0); + gpuGroup->addAction(autoAction); + for (const GpuAdapterInfo &gpu : adapters) { + QAction *action = ui->menuGpuAdapter->addAction(gpu.name); + action->setCheckable(true); + action->setProperty("vendorId", gpu.vendorId); + action->setProperty("deviceId", gpu.deviceId); + action->setChecked(currentVendor == gpu.vendorId && currentDevice == gpu.deviceId); + gpuGroup->addAction(action); + LOG_INFO() << "GPU adapter" << gpu.index << gpu.name + << QString::asprintf("vendor=0x%04X device=0x%04X", + gpu.vendorId, + gpu.deviceId); + } + connect(gpuGroup, + SIGNAL(triggered(QAction *)), + this, + SLOT(onGpuAdapterTriggered(QAction *))); + } + } + // Setup the job priority actions group = new QActionGroup(this); group->addAction(ui->actionJobPriorityLow); @@ -5383,6 +5421,25 @@ void MainWindow::onDrawingMethodTriggered(QAction *action) } #endif +void MainWindow::onGpuAdapterTriggered(QAction *action) +{ + Settings.setGpuAdapterVendorId(action->property("vendorId").toUInt()); + Settings.setGpuAdapterDeviceId(action->property("deviceId").toUInt()); + QMessageBox dialog(QMessageBox::Information, + qApp->applicationName(), + tr("You must restart Shotcut to change the graphics adapter.\n" + "Do you want to restart now?"), + QMessageBox::No | QMessageBox::Yes, + this); + dialog.setDefaultButton(QMessageBox::Yes); + dialog.setEscapeButton(QMessageBox::No); + dialog.setWindowModality(QmlApplication::dialogModality()); + if (dialog.exec() == QMessageBox::Yes) { + m_exitCode = EXIT_RESTART; + QApplication::closeAllWindows(); + } +} + void MainWindow::on_actionResources_triggered() { ResourceDialog dialog(this); diff --git a/src/mainwindow.h b/src/mainwindow.h index f37c45624f..e855fe2268 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -346,6 +346,7 @@ private slots: #if !defined(Q_OS_MAC) void onDrawingMethodTriggered(QAction *); #endif + void onGpuAdapterTriggered(QAction *); void on_actionResources_triggered(); void on_actionApplicationLog_triggered(); void on_actionClose_triggered(); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index b340cd2981..7f09ef8b99 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -196,6 +196,11 @@ + + + Graphics Adapter + + Job Priority @@ -314,6 +319,7 @@ + diff --git a/src/settings.cpp b/src/settings.cpp index 79e026c51c..74162996b7 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1,2160 +1,2184 @@ -/* - * Copyright (c) 2013-2026 Meltytech, LLC - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "settings.h" - -#include "Logger.h" -#include "qmltypes/qmlapplication.h" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static const QString APP_DATA_DIR_KEY("appdatadir"); -static const QString SHOTCUT_INI_FILENAME("/shotcut.ini"); -static const QString RECENT_INI_FILENAME("recent.ini"); -static QScopedPointer instance; -static QString appDataForSession; -static const int kMaximumTrackHeight = 125; -static const QString kRecentKey("recent"); -static const QString kProjectsKey("projects"); - -namespace { -struct ModeMap -{ - ShotcutSettings::ProcessingMode id; - const char *name; -}; -static constexpr ModeMap kModeMap[] = { - {ShotcutSettings::Native8Cpu, "Native8Cpu"}, - {ShotcutSettings::Linear8Cpu, "Linear8Cpu"}, - {ShotcutSettings::Native10Cpu, "Native10Cpu"}, - {ShotcutSettings::Linear10Cpu, "Linear10Cpu"}, - {ShotcutSettings::Linear10GpuCpu, "Linear10GpuCpu"}, -}; -} // anonymous namespace - -ShotcutSettings &ShotcutSettings::singleton() -{ - if (!instance) { - if (appDataForSession.isEmpty()) { - instance.reset(new ShotcutSettings); - if (instance->settings.value(APP_DATA_DIR_KEY).isValid() - && QFile::exists(instance->settings.value(APP_DATA_DIR_KEY).toString() - + SHOTCUT_INI_FILENAME)) - instance.reset( - new ShotcutSettings(instance->settings.value(APP_DATA_DIR_KEY).toString())); - } else { - instance.reset(new ShotcutSettings(appDataForSession)); - } - } - return *instance; -} - -/*! - \qmltype Settings - \inqmlmodule org.shotcut.qml - \brief Persistent application settings, accessed via the \c settings context property. - - \c settings is an uncreatable QML type — use the global \c settings identifier - available in every Shotcut QML view. Settings are backed by QSettings and persist - across sessions. - - \code - if (settings.timelineSnap) { ... } - settings.timelineSnap = true - \endcode -*/ - -/*! - \qmlproperty Settings::TimelineScrolling Settings::timelineScrolling - \brief The timeline auto-scroll mode. - One of \c NoScrolling, \c CenterPlayhead, \c PageScrolling, or \c SmoothScrolling. -*/ - -ShotcutSettings::ShotcutSettings() - : QObject() - , m_recent(QDir(appDataLocation()).filePath(RECENT_INI_FILENAME), QSettings::IniFormat) -{ - migrateLayout(); - migrateRecent(); -} - -ShotcutSettings::ShotcutSettings(const QString &appDataLocation) - : QObject() - , settings(appDataLocation + SHOTCUT_INI_FILENAME, QSettings::IniFormat) - , m_appDataLocation(appDataLocation) - , m_recent(QDir(appDataLocation).filePath(RECENT_INI_FILENAME), QSettings::IniFormat) -{ - migrateLayout(); - migrateRecent(); -} - -void ShotcutSettings::migrateRecent() -{ - // Migrate recent to separate INI file - auto oldRecents = settings.value(kRecentKey).toStringList(); - if (recent().isEmpty() && !oldRecents.isEmpty()) { - auto newRecents = recent(); - for (const auto &a : oldRecents) { - if (a.size() < ShotcutSettings::MaxPath && !newRecents.contains(a)) { - while (newRecents.size() > 100) { - newRecents.removeFirst(); - } - newRecents.append(a); - } - } - setRecent(newRecents); - m_recent.sync(); - // settings.remove("recent"); - settings.sync(); - } -} - -void ShotcutSettings::migrateLayout() -{ - // Migrate old startup layout to a custom layout and start fresh - if (!settings.contains("geometry2")) { - auto geometry = settings.value("geometry").toByteArray(); - auto windowState = settings.value("windowState").toByteArray(); - setLayout(tr("Old (before v23) Layout"), geometry, windowState); - setLayoutMode(2); - settings.sync(); - } -} - -void ShotcutSettings::log() -{ - LOG_INFO() << "language" << language(); - LOG_INFO() << "deinterlacer" << playerDeinterlacer(); - LOG_INFO() << "external monitor" << playerExternal(); - LOG_INFO() << "GPU processing" << playerGPU(); - LOG_INFO() << "interpolation" << playerInterpolation(); - LOG_INFO() << "video mode" << playerProfile(); - LOG_INFO() << "realtime" << playerRealtime(); - LOG_INFO() << "audio channels" << playerAudioChannels(); -#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) - if (::qEnvironmentVariableIsSet("SDL_AUDIODRIVER")) { - LOG_INFO() << "audio driver" << ::qgetenv("SDL_AUDIODRIVER"); - } else { - LOG_INFO() << "audio driver" << playerAudioDriver(); - } -#endif -} - -QString ShotcutSettings::language() const -{ - QString language = settings.value("language", QLocale().name()).toString(); - if (language == "en") - language = "en_US"; - return language; -} - -void ShotcutSettings::setLanguage(const QString &s) -{ - settings.setValue("language", s); -} - -double ShotcutSettings::imageDuration() const -{ - return settings.value("imageDuration", 4.0).toDouble(); -} - -void ShotcutSettings::setImageDuration(double d) -{ - settings.setValue("imageDuration", d); -} - -/*! - \qmlproperty string Settings::openPath - \brief The last directory used for opening files. -*/ - -QString ShotcutSettings::openPath() const -{ - return settings - .value("openPath", QStandardPaths::standardLocations(QStandardPaths::MoviesLocation)) - .toString(); -} - -void ShotcutSettings::setOpenPath(const QString &s) -{ - settings.setValue("openPath", s); - emit savePathChanged(); -} - -/*! - \qmlproperty string Settings::savePath - \brief The last directory used for saving files. -*/ - -QString ShotcutSettings::savePath() const -{ - return settings - .value("savePath", QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation)) - .toString(); -} - -void ShotcutSettings::setSavePath(const QString &s) -{ - settings.setValue("savePath", s); - emit savePathChanged(); -} - -QStringList ShotcutSettings::recent() const -{ - return m_recent.value(kRecentKey).toStringList(); -} - -void ShotcutSettings::setRecent(const QStringList &ls) -{ - if (ls.isEmpty()) - m_recent.remove(kRecentKey); - else if (!clearRecent()) - m_recent.setValue(kRecentKey, ls); -} - -QStringList ShotcutSettings::projects() -{ - auto ls = m_recent.value(kProjectsKey).toStringList(); - if (ls.isEmpty()) { - for (auto &r : recent()) { - if (r.endsWith(".mlt")) - ls << r; - } - // Prevent entering this block repeatedly - if (ls.isEmpty()) - ls << QString(); - setProjects(ls); - } - return ls; -} - -void ShotcutSettings::setProjects(const QStringList &ls) -{ - if (ls.isEmpty()) - m_recent.remove(kProjectsKey); - else if (!clearRecent()) - m_recent.setValue(kProjectsKey, ls); -} - -QString ShotcutSettings::theme() const -{ - return settings.value("theme", "dark").toString(); -} - -void ShotcutSettings::setTheme(const QString &s) -{ - settings.setValue("theme", s); -} - -QThread::Priority ShotcutSettings::jobPriority() const -{ - const auto priority = settings.value("jobPriority", "low").toString(); - if (priority == "low") { - return QThread::LowPriority; - } - return QThread::NormalPriority; -} - -void ShotcutSettings::setJobPriority(const QString &s) -{ - settings.setValue("jobPriority", s); -} - -bool ShotcutSettings::showTitleBars() const -{ - return settings.value("titleBars", true).toBool(); -} - -void ShotcutSettings::setShowTitleBars(bool b) -{ - settings.setValue("titleBars", b); -} - -bool ShotcutSettings::showToolBar() const -{ - return settings.value("toolBar", true).toBool(); -} - -void ShotcutSettings::setShowToolBar(bool b) -{ - settings.setValue("toolBar", b); -} - -bool ShotcutSettings::textUnderIcons() const -{ - return settings.value("textUnderIcons", true).toBool(); -} - -void ShotcutSettings::setTextUnderIcons(bool b) -{ - settings.setValue("textUnderIcons", b); -} - -/*! - \qmlproperty bool Settings::smallIcons - \brief Whether the toolbar uses small icons. -*/ - -bool ShotcutSettings::smallIcons() const -{ - return settings.value("smallIcons", false).toBool(); -} - -void ShotcutSettings::setSmallIcons(bool b) -{ - settings.setValue("smallIcons", b); - emit smallIconsChanged(); -} - -QByteArray ShotcutSettings::windowGeometry() const -{ - return settings.value("geometry2").toByteArray(); -} - -void ShotcutSettings::setWindowGeometry(const QByteArray &a) -{ - settings.setValue("geometry2", a); -} - -QByteArray ShotcutSettings::windowGeometryDefault() const -{ - return settings.value("geometryDefault").toByteArray(); -} - -void ShotcutSettings::setWindowGeometryDefault(const QByteArray &a) -{ - settings.setValue("geometryDefault", a); -} - -QByteArray ShotcutSettings::windowState() const -{ - return settings.value("windowState2").toByteArray(); -} - -void ShotcutSettings::setWindowState(const QByteArray &a) -{ - settings.setValue("windowState2", a); -} - -QByteArray ShotcutSettings::windowStateDefault() const -{ - return settings.value("windowStateDefault").toByteArray(); -} - -void ShotcutSettings::setWindowStateDefault(const QByteArray &a) -{ - settings.setValue("windowStateDefault", a); -} - -/*! - \qmlproperty string Settings::viewMode - \brief The current view mode of the Playlist panel (e.g. \c "details", \c "icons"). -*/ - -QString ShotcutSettings::viewMode() const -{ - return settings.value("playlist/viewMode").toString(); -} - -void ShotcutSettings::setViewMode(const QString &viewMode) -{ - settings.setValue("playlist/viewMode", viewMode); - emit viewModeChanged(); -} - -QString ShotcutSettings::filesViewMode() const -{ - return settings.value("files/viewMode", QLatin1String("tiled")).toString(); -} - -void ShotcutSettings::setFilesViewMode(const QString &viewMode) -{ - settings.setValue("files/viewMode", viewMode); - emit filesViewModeChanged(); -} - -QStringList ShotcutSettings::filesLocations() const -{ - QStringList result; - for (const auto &s : settings.value("files/locations").toStringList()) { - if (!s.startsWith("__")) - result << s; - } - return result; -} - -QString ShotcutSettings::filesLocationPath(const QString &name) const -{ - QString key = QStringLiteral("files/location/%1").arg(name); - return settings.value(key).toString(); -} - -bool ShotcutSettings::setFilesLocation(const QString &name, const QString &path) -{ - bool isNew = false; - QStringList locations = filesLocations(); - if (!locations.contains(name)) { - isNew = true; - locations.append(name); - settings.setValue("files/locations", locations); - } - settings.setValue("files/location/" + name, path); - return isNew; -} - -bool ShotcutSettings::removeFilesLocation(const QString &name) -{ - QStringList list = filesLocations(); - int index = list.indexOf(name); - if (index > -1) { - list.removeAt(index); - if (list.isEmpty()) - settings.remove("files/locations"); - else - settings.setValue("files/locations", list); - settings.remove("files/location/" + name); - return true; - } - return false; -} - -QStringList ShotcutSettings::filesOpenOther(const QString &type) const -{ - return settings.value("files/openOther/" + type).toStringList(); -} - -void ShotcutSettings::setFilesOpenOther(const QString &type, const QString &filePath) -{ - QStringList filePaths = filesOpenOther(type); - filePaths.removeAll(filePath); - filePaths.append(filePath); - settings.setValue("files/openOther/" + type, filePaths); -} - -bool ShotcutSettings::removeFilesOpenOther(const QString &type, const QString &filePath) -{ - QStringList list = filesOpenOther(type); - int index = list.indexOf(filePath); - if (index > -1) { - list.removeAt(index); - if (list.isEmpty()) - settings.remove("files/openOther/" + type); - else - settings.setValue("files/openOther/" + type, list); - return true; - } - return false; -} - -QString ShotcutSettings::filesCurrentDir() const -{ - const auto ls = QStandardPaths::standardLocations(QStandardPaths::HomeLocation); - auto path = settings.value("files/currentDir", ls.first()).toString(); - if (!QFile::exists(path)) { - LOG_DEBUG() << "dir does not exist:" << QDir::toNativeSeparators(path); - path = ls.first(); - } - return path; -} - -void ShotcutSettings::setFilesCurrentDir(const QString &s) -{ - settings.setValue("files/currentDir", s); -} - -bool ShotcutSettings::filesFoldersOpen() const -{ - return settings.value("files/foldersOpen", true).toBool(); -} - -void ShotcutSettings::setFilesFoldersOpen(bool b) -{ - settings.setValue("files/foldersOpen", b); -} - -QString ShotcutSettings::exportFrameSuffix() const -{ - return settings.value("exportFrameSuffix", ".png").toString(); -} - -void ShotcutSettings::setExportFrameSuffix(const QString &exportFrameSuffix) -{ - settings.setValue("exportFrameSuffix", exportFrameSuffix); -} - -QString ShotcutSettings::encodePath() const -{ - return settings - .value("encode/path", QStandardPaths::standardLocations(QStandardPaths::MoviesLocation)) - .toString(); -} - -void ShotcutSettings::setEncodePath(const QString &s) -{ - settings.setValue("encode/path", s); -} - -bool ShotcutSettings::encodeFreeSpaceCheck() const -{ - return settings.value("encode/freeSpaceCheck", true).toBool(); -} - -void ShotcutSettings::setEncodeFreeSpaceCheck(bool b) -{ - settings.setValue("encode/freeSpaceCheck", b); -} - -bool ShotcutSettings::encodeUseHardware() const -{ - return settings.value("encode/useHardware").toBool(); -} - -void ShotcutSettings::setEncodeUseHardware(bool b) -{ - settings.setValue("encode/useHardware", b); -} - -QStringList ShotcutSettings::encodeHardware() const -{ - return settings.value("encode/hardware").toStringList(); -} - -void ShotcutSettings::setEncodeHardware(const QStringList &ls) -{ - if (ls.isEmpty()) - settings.remove("encode/hardware"); - else - settings.setValue("encode/hardware", ls); -} - -bool ShotcutSettings::encodeHardwareDecoder() const -{ - return settings.value("encode/hardwareDecoder", false).toBool(); -} - -void ShotcutSettings::setEncodeHardwareDecoder(bool b) -{ - settings.setValue("encode/hardwareDecoder", b); -} - -bool ShotcutSettings::encodeAdvanced() const -{ - return settings.value("encode/advanced", false).toBool(); -} - -void ShotcutSettings::setEncodeAdvanced(bool b) -{ - settings.setValue("encode/advanced", b); -} - -bool ShotcutSettings::convertAdvanced() const -{ - return settings.value("convertAdvanced", false).toBool(); -} - -void ShotcutSettings::setConvertAdvanced(bool b) -{ - settings.setValue("convertAdvanced", b); -} - -ShotcutSettings::ProcessingMode ShotcutSettings::processingMode() -{ - if (settings.contains("processingMode")) { - auto result = (ShotcutSettings::ProcessingMode) settings.value("processingMode").toInt(); - if (result == Linear8Cpu) { - // No longer supported but kept to prevent unexpected processing behavior going from - // beta to release - result = Native8Cpu; - } - return result; - } else if (settings.contains("player/gpu2")) { - // Legacy GPU Mode - if (settings.value("player/gpu2").toBool()) { - return ShotcutSettings::Linear10GpuCpu; - } - } - return ShotcutSettings::Native8Cpu; -} - -void ShotcutSettings::setProcessingMode(ProcessingMode mode) -{ - settings.setValue("processingMode", mode); - emit playerGpuChanged(); -} - -QString ShotcutSettings::processingModeStr(ShotcutSettings::ProcessingMode mode) -{ - for (const auto &m : kModeMap) { - if (m.id == mode) - return QString::fromLatin1(m.name); - } - LOG_ERROR() << "Unknown processing mode" << mode; - return QStringLiteral("Native8Cpu"); -} - -ShotcutSettings::ProcessingMode ShotcutSettings::processingModeId(const QString &mode) -{ - for (const auto &m : kModeMap) { - if (mode == QLatin1String(m.name)) - return m.id; - } - LOG_ERROR() << "Unknown processing mode" << mode; - return Native8Cpu; -} - -bool ShotcutSettings::showConvertClipDialog() const -{ - return settings.value("showConvertClipDialog", true).toBool(); -} - -void ShotcutSettings::setShowConvertClipDialog(bool b) -{ - settings.setValue("showConvertClipDialog", b); -} - -bool ShotcutSettings::showHdrPlayerWarning() const -{ - return settings.value("showHdrPlayerWarning", true).toBool(); -} - -void ShotcutSettings::setShowHdrPlayerWarning(bool b) -{ - settings.setValue("showHdrPlayerWarning", b); -} - -bool ShotcutSettings::encodeParallelProcessing() const -{ - return settings.value("encode/parallelProcessing", false).toBool(); -} - -void ShotcutSettings::setEncodeParallelProcessing(bool b) -{ - settings.setValue("encode/parallelProcessing", b); -} - -/*! - \qmlproperty int Settings::playerAudioChannels - \brief The number of audio channels used by the player (e.g. 2 or 6). -*/ - -int ShotcutSettings::playerAudioChannels() const -{ - return settings.value("player/audioChannels", 2).toInt(); -} - -void ShotcutSettings::setPlayerAudioChannels(int i) -{ - settings.setValue("player/audioChannels", i); - emit playerAudioChannelsChanged(i); -} - -QString ShotcutSettings::playerDeinterlacer() const -{ - QString result = settings.value("player/deinterlacer", "onefield").toString(); - //XXX workaround yadif crashing with mlt_transition - if (result == "yadif" || result == "yadif-nospatial") - result = "onefield"; - return result; -} - -void ShotcutSettings::setPlayerDeinterlacer(const QString &s) -{ - settings.setValue("player/deinterlacer", s); -} - -QString ShotcutSettings::playerExternal() const -{ - auto result = settings.value("player/external", "").toString(); - // "sdi" is no longer supported DVEO VidPort - return result == "sdi" ? "" : result; -} - -void ShotcutSettings::setPlayerExternal(const QString &s) -{ - settings.setValue("player/external", s); -} - -bool ShotcutSettings::playerJACK() const -{ - return settings.value("player/jack", false).toBool(); -} - -QString ShotcutSettings::playerInterpolation() const -{ - return settings.value("player/interpolation", "bilinear").toString(); -} - -void ShotcutSettings::setPlayerInterpolation(const QString &s) -{ - settings.setValue("player/interpolation", s); -} - -/*! - \qmlproperty bool Settings::playerGPU - \brief Whether GPU processing (GLSL) is enabled for the video player. -*/ - -bool ShotcutSettings::playerGPU() const -{ - // This is the legacy function for the old GPU mode. - if (settings.contains("processingMode")) { - ProcessingMode mode = (ProcessingMode) settings.value("processingMode").toInt(); - return mode == Linear10GpuCpu; - } else if (settings.contains("player/gpu2")) { - // Legacy GPU Mode - return settings.value("player/gpu2").toBool(); - } - return false; -} - -bool ShotcutSettings::playerWarnGPU() const -{ - return false; //settings.value("player/warnGPU", false).toBool(); -} - -void ShotcutSettings::setPlayerJACK(bool b) -{ - settings.setValue("player/jack", b); -} - -int ShotcutSettings::playerDecklinkHdrMaxCll() const -{ - return settings.value("player/decklinkHdrMaxCll", 1000).toInt(); -} - -void ShotcutSettings::setPlayerDecklinkHdrMaxCll(int nits) -{ - settings.setValue("player/decklinkHdrMaxCll", nits); -} - -int ShotcutSettings::playerDecklinkHdrMaxFall() const -{ - return settings.value("player/decklinkHdrMaxFall", 400).toInt(); -} - -void ShotcutSettings::setPlayerDecklinkHdrMaxFall(int nits) -{ - settings.setValue("player/decklinkHdrMaxFall", nits); -} - -int ShotcutSettings::playerDecklinkHdrMasterPreset() const -{ - return settings.value("player/decklinkHdrMasterPreset", 0).toInt(); -} - -void ShotcutSettings::setPlayerDecklinkHdrMasterPreset(int preset) -{ - settings.setValue("player/decklinkHdrMasterPreset", preset); -} - -int ShotcutSettings::playerDecklinkHdrMaxLuminance() const -{ - return settings.value("player/decklinkHdrMaxLuminance", 1000).toInt(); -} - -void ShotcutSettings::setPlayerDecklinkHdrMaxLuminance(int nits) -{ - settings.setValue("player/decklinkHdrMaxLuminance", nits); -} - -double ShotcutSettings::playerDecklinkHdrMinLuminance() const -{ - return settings.value("player/decklinkHdrMinLuminance", 0.01).toDouble(); -} - -void ShotcutSettings::setPlayerDecklinkHdrMinLuminance(double nits) -{ - settings.setValue("player/decklinkHdrMinLuminance", nits); -} - -int ShotcutSettings::playerKeyerMode() const -{ - return settings.value("player/keyer", 0).toInt(); -} - -void ShotcutSettings::setPlayerKeyerMode(int i) -{ - settings.setValue("player/keyer", i); -} - -bool ShotcutSettings::playerMuted() const -{ - return settings.value("player/muted", false).toBool(); -} - -void ShotcutSettings::setPlayerMuted(bool b) -{ - settings.setValue("player/muted", b); -} - -QString ShotcutSettings::playerProfile() const -{ - return settings.value("player/profile", "").toString(); -} - -void ShotcutSettings::setPlayerProfile(const QString &s) -{ - settings.setValue("player/profile", s); -} - -bool ShotcutSettings::playerProgressive() const -{ - return settings.value("player/progressive", true).toBool(); -} - -void ShotcutSettings::setPlayerProgressive(bool b) -{ - settings.setValue("player/progressive", b); -} - -bool ShotcutSettings::playerRealtime() const -{ - return settings.value("player/realtime", true).toBool(); -} - -void ShotcutSettings::setPlayerRealtime(bool b) -{ - settings.setValue("player/realtime", b); -} - -bool ShotcutSettings::playerScrubAudio() const -{ - return settings.value("player/scrubAudio", true).toBool(); -} - -void ShotcutSettings::setPlayerScrubAudio(bool b) -{ - settings.setValue("player/scrubAudio", b); -} - -int ShotcutSettings::playerVolume() const -{ - return settings.value("player/volume", 88).toInt(); -} - -void ShotcutSettings::setPlayerVolume(int i) -{ - settings.setValue("player/volume", i); -} - -float ShotcutSettings::playerZoom() const -{ - return settings.value("player/zoom", 0.0f).toFloat(); -} - -void ShotcutSettings::setPlayerZoom(float f) -{ - settings.setValue("player/zoom", f); -} - -int ShotcutSettings::playerPreviewScale() const -{ - return settings.value("player/previewScale", 0).toInt(); -} - -void ShotcutSettings::setPlayerPreviewScale(int i) -{ - settings.setValue("player/previewScale", i); -} - -bool ShotcutSettings::playerPreviewHardwareDecoder() const -{ - return settings.value("player/previewHardwareDecoder", true).toBool(); -} - -bool ShotcutSettings::playerPreviewHardwareDecoderIsSet() const -{ - return settings.contains("player/previewHardwareDecoder"); -} - -void ShotcutSettings::setPlayerPreviewHardwareDecoder(bool b) -{ - settings.setValue("player/previewHardwareDecoder", b); -} - -int ShotcutSettings::playerVideoDelayMs() const -{ - return settings.value("player/videoDelayMs", 0).toInt(); -} - -void ShotcutSettings::setPlayerVideoDelayMs(int i) -{ - settings.setValue("player/videoDelayMs", i); -} - -double ShotcutSettings::playerJumpSeconds() const -{ - return settings.value("player/jumpSeconds", 60.0).toDouble(); -} - -void ShotcutSettings::setPlayerJumpSeconds(double i) -{ - settings.setValue("player/jumpSeconds", i); -} - -QString ShotcutSettings::playerAudioDriver() const -{ -#if defined(Q_OS_WIN) - auto s = playerAudioChannels() > 2 ? "directsound" : "winmm"; -#else - auto s = "pulseaudio"; -#endif - if (::qEnvironmentVariableIsSet("SDL_AUDIODRIVER")) { - return ::qgetenv("SDL_AUDIODRIVER"); - } else { - return settings.value("player/audioDriver", s).toString(); - } -} - -void ShotcutSettings::setPlayerAudioDriver(const QString &s) -{ - settings.setValue("player/audioDriver", s); -} - -bool ShotcutSettings::playerPauseAfterSeek() const -{ - return settings.value("player/pauseAfterSeek", true).toBool(); -} - -void ShotcutSettings::setPlayerPauseAfterSeek(bool b) -{ - settings.setValue("player/pauseAfterSeek", b); -} - -bool ShotcutSettings::playerOldVideoOutput() const -{ - return settings.value("player/oldVideoOutput", false).toBool(); -} - -void ShotcutSettings::setPlayerOldVideoOutput(bool b) -{ - settings.setValue("player/oldVideoOutput", b); -} - -bool ShotcutSettings::playerHdrPreview() const -{ - return settings.value("player/hdrPreview", false).toBool(); -} - -void ShotcutSettings::setPlayerHdrPreview(bool b) -{ - settings.setValue("player/hdrPreview", b); -} - -QRect ShotcutSettings::playerHdrPreviewGeometry() const -{ - return settings.value("player/hdrPreviewGeometry").toRect(); -} - -void ShotcutSettings::setPlayerHdrPreviewGeometry(const QRect &r) -{ - settings.setValue("player/hdrPreviewGeometry", r); -} - -bool ShotcutSettings::playerHdrPreviewFullScreen() const -{ - return settings.value("player/hdrPreviewFullScreen", false).toBool(); -} - -void ShotcutSettings::setPlayerHdrPreviewFullScreen(bool b) -{ - settings.setValue("player/hdrPreviewFullScreen", b); -} - -int ShotcutSettings::playerHdrDisplayPeakNits() const -{ - return settings.value("player/hdrDisplayPeakNits", 0).toInt(); -} - -void ShotcutSettings::setPlayerHdrDisplayPeakNits(int nits) -{ - settings.setValue("player/hdrDisplayPeakNits", nits); -} - -int ShotcutSettings::playerHdrContentPeakNits() const -{ - return settings.value("player/hdrContentPeakNits", 0).toInt(); -} - -void ShotcutSettings::setPlayerHdrContentPeakNits(int nits) -{ - settings.setValue("player/hdrContentPeakNits", nits); -} - -bool ShotcutSettings::playerHdrToneMapping() const -{ - return settings.value("player/hdrToneMapping", true).toBool(); -} - -void ShotcutSettings::setPlayerHdrToneMapping(bool b) -{ - settings.setValue("player/hdrToneMapping", b); -} - -/*! - \qmlproperty string Settings::playlistThumbnails - \brief The thumbnail display mode for the Playlist panel. -*/ - -QString ShotcutSettings::playlistThumbnails() const -{ - return settings.value("playlist/thumbnails", "small").toString(); -} - -void ShotcutSettings::setPlaylistThumbnails(const QString &s) -{ - settings.setValue("playlist/thumbnails", s); - emit playlistThumbnailsChanged(); -} - -bool ShotcutSettings::playlistAutoplay() const -{ - return settings.value("playlist/autoplay", true).toBool(); -} - -void ShotcutSettings::setPlaylistAutoplay(bool b) -{ - settings.setValue("playlist/autoplay", b); -} - -bool ShotcutSettings::playlistShowColumn(const QString &column) -{ - return settings.value("playlist/columns/" + column, true).toBool(); -} - -void ShotcutSettings::setPlaylistShowColumn(const QString &column, bool b) -{ - settings.setValue("playlist/columns/" + column, b); -} - -/*! - \qmlproperty bool Settings::timelineDragScrub - \brief Whether scrubbing occurs while dragging clips on the timeline. -*/ - -bool ShotcutSettings::timelineDragScrub() const -{ - return settings.value("timeline/dragScrub", false).toBool(); -} - -void ShotcutSettings::setTimelineDragScrub(bool b) -{ - settings.setValue("timeline/dragScrub", b); - emit timelineDragScrubChanged(); -} - -/*! - \qmlproperty bool Settings::timelineShowWaveforms - \brief Whether audio waveforms are shown on timeline clips. -*/ - -bool ShotcutSettings::timelineShowWaveforms() const -{ - return settings.value("timeline/waveforms", true).toBool(); -} - -void ShotcutSettings::setTimelineShowWaveforms(bool b) -{ - settings.setValue("timeline/waveforms", b); - emit timelineShowWaveformsChanged(); -} - -/*! - \qmlproperty bool Settings::timelineShowThumbnails - \brief Whether video thumbnails are shown on timeline clips. -*/ - -bool ShotcutSettings::timelineShowThumbnails() const -{ - return settings.value("timeline/thumbnails", true).toBool(); -} - -void ShotcutSettings::setTimelineShowThumbnails(bool b) -{ - settings.setValue("timeline/thumbnails", b); - emit timelineShowThumbnailsChanged(); -} - -/*! - \qmlproperty bool Settings::timelineRipple - \brief Whether ripple editing is enabled on the timeline. -*/ - -bool ShotcutSettings::timelineRipple() const -{ - return settings.value("timeline/ripple", false).toBool(); -} - -void ShotcutSettings::setTimelineRipple(bool b) -{ - settings.setValue("timeline/ripple", b); - emit timelineRippleChanged(); -} - -/*! - \qmlproperty bool Settings::timelineRippleAllTracks - \brief Whether ripple editing affects all tracks simultaneously. -*/ - -bool ShotcutSettings::timelineRippleAllTracks() const -{ - return settings.value("timeline/rippleAllTracks", false).toBool(); -} - -void ShotcutSettings::setTimelineRippleAllTracks(bool b) -{ - settings.setValue("timeline/rippleAllTracks", b); - emit timelineRippleAllTracksChanged(); -} - -/*! - \qmlproperty bool Settings::timelineRippleMarkers - \brief Whether markers are moved along with ripple edits. -*/ - -bool ShotcutSettings::timelineRippleMarkers() const -{ - return settings.value("timeline/rippleMarkers", false).toBool(); -} - -void ShotcutSettings::setTimelineRippleMarkers(bool b) -{ - settings.setValue("timeline/rippleMarkers", b); - emit timelineRippleMarkersChanged(); -} - -/*! - \qmlproperty bool Settings::timelineSnap - \brief Whether clip snapping is enabled on the timeline. -*/ - -bool ShotcutSettings::timelineSnap() const -{ - return settings.value("timeline/snap", true).toBool(); -} - -void ShotcutSettings::setTimelineSnap(bool b) -{ - settings.setValue("timeline/snap", b); - emit timelineSnapChanged(); -} - -int ShotcutSettings::timelineTrackHeight() const -{ - return qMin(settings.value("timeline/trackHeight", 50).toInt(), kMaximumTrackHeight); -} - -void ShotcutSettings::setTimelineTrackHeight(int n) -{ - settings.setValue("timeline/trackHeight", qMin(n, kMaximumTrackHeight)); -} - -/*! - \qmlproperty bool Settings::timelineScrollZoom - \brief Whether the scroll wheel zooms the timeline (instead of scrolling). -*/ - -bool ShotcutSettings::timelineScrollZoom() const -{ - return settings.value("timeline/scrollZoom", true).toBool(); -} - -void ShotcutSettings::setTimelineScrollZoom(bool b) -{ - settings.setValue("timeline/scrollZoom", b); - emit timelineScrollZoomChanged(); -} - -/*! - \qmlproperty bool Settings::timelineFramebufferWaveform - \brief Whether waveforms are rendered using a framebuffer (GPU) path. -*/ - -bool ShotcutSettings::timelineFramebufferWaveform() const -{ - return settings.value("timeline/framebufferWaveform", true).toBool(); -} - -void ShotcutSettings::setTimelineFramebufferWaveform(bool b) -{ - settings.setValue("timeline/framebufferWaveform", b); - emit timelineFramebufferWaveformChanged(); -} - -int ShotcutSettings::audioReferenceTrack() const -{ - return settings.value("timeline/audioReferenceTrack", 0).toInt(); -} -void ShotcutSettings::setAudioReferenceTrack(int track) -{ - settings.setValue("timeline/audioReferenceTrack", track); -} - -double ShotcutSettings::audioReferenceSpeedRange() const -{ - return settings.value("timeline/audioReferenceSpeedRange", 0).toDouble(); -} -void ShotcutSettings::setAudioReferenceSpeedRange(double range) -{ - settings.setValue("timeline/audioReferenceSpeedRange", range); -} - -bool ShotcutSettings::timelinePreviewTransition() const -{ - return settings.value("timeline/previewTransition", true).toBool(); -} - -void ShotcutSettings::setTimelinePreviewTransition(bool b) -{ - settings.setValue("timeline/previewTransition", b); -} - -void ShotcutSettings::setTimelineScrolling(ShotcutSettings::TimelineScrolling value) -{ - settings.remove("timeline/centerPlayhead"); - settings.setValue("timeline/scrolling", value); - emit timelineScrollingChanged(); -} - -ShotcutSettings::TimelineScrolling ShotcutSettings::timelineScrolling() const -{ - if (settings.contains("timeline/centerPlayhead") - && settings.value("timeline/centerPlayhead").toBool()) - return ShotcutSettings::TimelineScrolling::CenterPlayhead; - else - return ShotcutSettings::TimelineScrolling( - settings.value("timeline/scrolling", PageScrolling).toInt()); -} - -bool ShotcutSettings::timelineAutoAddTracks() const -{ - return settings.value("timeline/autoAddTracks", false).toBool(); -} - -void ShotcutSettings::setTimelineAutoAddTracks(bool b) -{ - if (b != timelineAutoAddTracks()) { - settings.setValue("timeline/autoAddTracks", b); - emit timelineAutoAddTracksChanged(); - } -} - -/*! - \qmlproperty bool Settings::timelineRectangleSelect - \brief Whether rectangle (rubber-band) selection is enabled on the timeline. -*/ - -bool ShotcutSettings::timelineRectangleSelect() const -{ - return settings.value("timeline/rectangleSelect", true).toBool(); -} - -void ShotcutSettings::setTimelineRectangleSelect(bool b) -{ - settings.setValue("timeline/rectangleSelect", b); - emit timelineRectangleSelectChanged(); -} - -/*! - \qmlproperty bool Settings::timelineAdjustGain - \brief Whether dragging the gain handle on audio clips adjusts volume inline. -*/ - -bool ShotcutSettings::timelineAdjustGain() const -{ - return settings.value("timeline/adjustGain", false).toBool(); -} - -void ShotcutSettings::setTimelineAdjustGain(bool b) -{ - settings.setValue("timeline/adjustGain", b); - emit timelineAdjustGainChanged(); -} - -/*! - \qmlproperty bool Settings::timelineAllowTransitions - \brief Whether overlapping clips on the timeline automatically create transitions. -*/ - -bool ShotcutSettings::timelineAllowTransitions() const -{ - return settings.value("timeline/allowTransitions", true).toBool(); -} - -void ShotcutSettings::setTimelineAllowTransitions(bool b) -{ - if (b != timelineAllowTransitions()) { - settings.setValue("timeline/allowTransitions", b); - emit timelineAllowTransitionsChanged(); - } -} - -QString ShotcutSettings::filterFavorite(const QString &filterName) -{ - return settings.value("filter/favorite/" + filterName, "").toString(); -} - -void ShotcutSettings::setFilterFavorite(const QString &filterName, const QString &value) -{ - settings.setValue("filter/favorite/" + filterName, value); -} - -QStringList ShotcutSettings::addOnFilterServices() const -{ - return settings.value("filter/addOnServices").toStringList(); -} - -void ShotcutSettings::setAddOnFilterServices(const QStringList &services) -{ - settings.setValue("filter/addOnServices", services); -} - -/*! - \qmlproperty real Settings::audioInDuration - \brief The default duration in seconds for audio fade-in transitions. -*/ - -double ShotcutSettings::audioInDuration() const -{ - return settings.value("filter/audioInDuration", 1.0).toDouble(); -} - -void ShotcutSettings::setAudioInDuration(double d) -{ - settings.setValue("filter/audioInDuration", d); - emit audioInDurationChanged(); -} - -/*! - \qmlproperty real Settings::audioOutDuration - \brief The default duration in seconds for audio fade-out transitions. -*/ - -double ShotcutSettings::audioOutDuration() const -{ - return settings.value("filter/audioOutDuration", 1.0).toDouble(); -} - -void ShotcutSettings::setAudioOutDuration(double d) -{ - settings.setValue("filter/audioOutDuration", d); - emit audioOutDurationChanged(); -} - -/*! - \qmlproperty real Settings::videoInDuration - \brief The default duration in seconds for video fade-in transitions. -*/ - -double ShotcutSettings::videoInDuration() const -{ - return settings.value("filter/videoInDuration", 1.0).toDouble(); -} - -void ShotcutSettings::setVideoInDuration(double d) -{ - settings.setValue("filter/videoInDuration", d); - emit videoInDurationChanged(); -} - -/*! - \qmlproperty real Settings::videoOutDuration - \brief The default duration in seconds for video fade-out transitions. -*/ - -double ShotcutSettings::videoOutDuration() const -{ - return settings.value("filter/videoOutDuration", 1.0).toDouble(); -} - -void ShotcutSettings::setVideoOutDuration(double d) -{ - settings.setValue("filter/videoOutDuration", d); - emit videoOutDurationChanged(); -} - -/*! - \qmlproperty int Settings::audioInCurve - \brief The curve type for audio fade-in (0 = linear, higher = more exponential). -*/ - -int ShotcutSettings::audioInCurve() const -{ - return settings.value("filter/audioInCurve", mlt_keyframe_linear).toInt(); -} - -void ShotcutSettings::setAudioInCurve(int c) -{ - settings.setValue("filter/audioInCurve", c); - emit audioInCurveChanged(); -} - -/*! - \qmlproperty int Settings::audioOutCurve - \brief The curve type for audio fade-out (0 = linear, higher = more exponential). -*/ - -int ShotcutSettings::audioOutCurve() const -{ - return settings.value("filter/audioOutCurve", mlt_keyframe_linear).toInt(); -} - -void ShotcutSettings::setAudioOutCurve(int c) -{ - settings.setValue("filter/audioOutCurve", c); - emit audioOutCurveChanged(); -} - -/*! - \qmlproperty bool Settings::askOutputFilter - \brief Whether Shotcut should prompt before applying a filter to the output node. -*/ - -bool ShotcutSettings::askOutputFilter() const -{ - return settings.value("filter/askOutput", true).toBool(); -} - -void ShotcutSettings::setAskOutputFilter(bool b) -{ - settings.setValue("filter/askOutput", b); - emit askOutputFilterChanged(); -} - -bool ShotcutSettings::loudnessScopeShowMeter(const QString &meter) const -{ - return settings.value("scope/loudness/" + meter, true).toBool(); -} - -void ShotcutSettings::setLoudnessScopeShowMeter(const QString &meter, bool b) -{ - settings.setValue("scope/loudness/" + meter, b); -} - -void ShotcutSettings::setMarkerColor(const QColor &color) -{ - settings.setValue("markers/color", color.name()); -} - -QColor ShotcutSettings::markerColor() const -{ - return QColor(settings.value("markers/color", "green").toString()); -} - -void ShotcutSettings::setMarkersShowColumn(const QString &column, bool b) -{ - settings.setValue("markers/columns/" + column, b); -} - -bool ShotcutSettings::markersShowColumn(const QString &column) const -{ - return settings.value("markers/columns/" + column, true).toBool(); -} - -void ShotcutSettings::setMarkerSort(int column, Qt::SortOrder order) -{ - settings.setValue("markers/sortColumn", column); - settings.setValue("markers/sortOrder", order); -} - -int ShotcutSettings::getMarkerSortColumn() -{ - return settings.value("markers/sortColumn", -1).toInt(); -} - -Qt::SortOrder ShotcutSettings::getMarkerSortOrder() -{ - return (Qt::SortOrder) settings.value("markers/sortOrder", Qt::AscendingOrder).toInt(); -} - -int ShotcutSettings::drawMethod() const -{ -#ifdef Q_OS_WIN - return settings.value("opengl", Qt::AA_UseOpenGLES).toInt(); -#else - return settings.value("opengl", Qt::AA_UseDesktopOpenGL).toInt(); -#endif -} - -void ShotcutSettings::setDrawMethod(int i) -{ - settings.setValue("opengl", i); -} - -bool ShotcutSettings::safeMode() const -{ - return settings.value("safeMode", false).toBool(); -} - -void ShotcutSettings::setSafeMode(bool value) -{ - settings.setValue("safeMode", value); -} - -bool ShotcutSettings::noUpgrade() const -{ - return settings.value("noupgrade", false).toBool(); -} - -void ShotcutSettings::setNoUpgrade(bool value) -{ - settings.setValue("noupgrade", value); -} - -bool ShotcutSettings::checkUpgradeAutomatic() -{ - return settings.value("checkUpgradeAutomatic", false).toBool(); -} - -void ShotcutSettings::setCheckUpgradeAutomatic(bool b) -{ - settings.setValue("checkUpgradeAutomatic", b); -} - -bool ShotcutSettings::askUpgradeAutomatic() -{ - return settings.value("askUpgradeAutmatic", true).toBool(); -} - -void ShotcutSettings::setAskUpgradeAutomatic(bool b) -{ - settings.setValue("askUpgradeAutmatic", b); -} - -bool ShotcutSettings::askChangeVideoMode() -{ - return settings.value("askChangeVideoMode", true).toBool(); -} - -void ShotcutSettings::setAskChangeVideoMode(bool b) -{ - settings.setValue("askChangeVideoMode", b); -} - -void ShotcutSettings::sync() -{ - settings.sync(); -} - -/*! - \qmlproperty string Settings::appDataLocation - \brief The path to the application data directory (read-only). -*/ - -QString ShotcutSettings::appDataLocation() const -{ - if (!m_appDataLocation.isEmpty()) - return m_appDataLocation; - else - return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation); -} - -void ShotcutSettings::setAppDataForSession(const QString &location) -{ - // This is intended to be called when using a command line option - // to set the AppData location. - appDataForSession = location; - if (instance) - instance.reset(new ShotcutSettings(location)); -} - -void ShotcutSettings::setAppDataLocally(const QString &location) -{ - // This is intended to be called when using a GUI action to set the - // the new AppData location. - - // Copy the existing settings if they exist. - if (!QFile::exists(location + SHOTCUT_INI_FILENAME)) { - QSettings newSettings(location + SHOTCUT_INI_FILENAME, QSettings::IniFormat); - foreach (const QString &key, settings.allKeys()) - newSettings.setValue(key, settings.value(key)); - newSettings.sync(); - } - - // Set the new location. - QSettings localSettings; - localSettings.setValue(APP_DATA_DIR_KEY, location); - localSettings.sync(); -} - -QStringList ShotcutSettings::layouts() const -{ - QStringList result; - for (const auto &s : settings.value("layout/layouts").toStringList()) { - if (!s.startsWith("__")) - result << s; - } - return result; -} - -bool ShotcutSettings::setLayout(const QString &name, - const QByteArray &geometry, - const QByteArray &state) -{ - bool isNew = false; - QStringList layouts = this->layouts(); - if (layouts.indexOf(name) == -1) { - isNew = true; - layouts.append(name); - settings.setValue("layout/layouts", layouts); - } - settings.setValue(QStringLiteral("layout/%1_%2").arg(name, "geometry"), geometry); - settings.setValue(QStringLiteral("layout/%1_%2").arg(name, "state"), state); - return isNew; -} - -QByteArray ShotcutSettings::layoutGeometry(const QString &name) -{ - QString key = QStringLiteral("layout/%1_geometry").arg(name); - return settings.value(key).toByteArray(); -} - -QByteArray ShotcutSettings::layoutState(const QString &name) -{ - QString key = QStringLiteral("layout/%1_state").arg(name); - return settings.value(key).toByteArray(); -} - -bool ShotcutSettings::removeLayout(const QString &name) -{ - QStringList list = layouts(); - int index = list.indexOf(name); - if (index > -1) { - list.removeAt(index); - if (list.isEmpty()) - settings.remove("layout/layouts"); - else - settings.setValue("layout/layouts", list); - settings.remove(QStringLiteral("layout/%1_%2").arg(name, "geometry")); - settings.remove(QStringLiteral("layout/%1_%2").arg(name, "state")); - return true; - } - return false; -} - -int ShotcutSettings::layoutMode() const -{ - return settings.value("layout/mode", -1).toInt(); -} - -void ShotcutSettings::setLayoutMode(int mode) -{ - settings.setValue("layout/mode", mode); -} - -bool ShotcutSettings::clearRecent() const -{ - return settings.value("clearRecent", false).toBool(); -} - -void ShotcutSettings::setClearRecent(bool b) -{ - settings.setValue("clearRecent", b); -} - -QString ShotcutSettings::projectsFolder() const -{ - return settings - .value("projectsFolder", QStandardPaths::standardLocations(QStandardPaths::MoviesLocation)) - .toString(); -} - -void ShotcutSettings::setProjectsFolder(const QString &path) -{ - settings.setValue("projectsFolder", path); -} - -QString ShotcutSettings::audioInput() const -{ - QString defaultValue = "default"; -#if defined(Q_OS_MAC) || defined(Q_OS_WIN) - for (const auto &deviceInfo : QMediaDevices::audioInputs()) { - defaultValue = deviceInfo.description(); - } -#endif - return settings.value("audioInput", defaultValue).toString(); -} - -void ShotcutSettings::setAudioInput(const QString &name) -{ - settings.setValue("audioInput", name); -} - -QString ShotcutSettings::videoInput() const -{ - return settings.value("videoInput").toString(); -} - -void ShotcutSettings::setVideoInput(const QString &name) -{ - settings.setValue("videoInput", name); -} - -QString ShotcutSettings::glaxnimatePath() const -{ - QDir dir(qApp->applicationDirPath()); - return settings.value("glaxnimatePath", dir.absoluteFilePath("glaxnimate")).toString(); -} - -void ShotcutSettings::setGlaxnimatePath(const QString &path) -{ - settings.setValue("glaxnimatePath", path); -} - -void ShotcutSettings::resetGlaxnimatePath() -{ - settings.remove("glaxnimatePath"); -} - -bool ShotcutSettings::exportRangeMarkers() const -{ - return settings.value("exportRangeMarkers", true).toBool(); -} - -void ShotcutSettings::setExportRangeMarkers(bool b) -{ - settings.setValue("exportRangeMarkers", b); -} - -bool ShotcutSettings::proxyEnabled() const -{ - return settings.value("proxy/enabled", false).toBool(); -} - -void ShotcutSettings::setProxyEnabled(bool b) -{ - settings.setValue("proxy/enabled", b); -} - -QString ShotcutSettings::proxyFolder() const -{ - QDir dir(appDataLocation()); - const char *subfolder = "proxies"; - if (!dir.cd(subfolder)) { - if (dir.mkdir(subfolder)) - dir.cd(subfolder); - } - return settings.value("proxy/folder", dir.path()).toString(); -} - -void ShotcutSettings::setProxyFolder(const QString &path) -{ - settings.setValue("proxy/folder", path); -} - -bool ShotcutSettings::proxyUseProjectFolder() const -{ - return settings.value("proxy/useProjectFolder", true).toBool(); -} - -void ShotcutSettings::setProxyUseProjectFolder(bool b) -{ - settings.setValue("proxy/useProjectFolder", b); -} - -bool ShotcutSettings::proxyUseHardware() const -{ - return settings.value("proxy/useHardware", false).toBool(); -} - -void ShotcutSettings::setProxyUseHardware(bool b) -{ - settings.setValue("proxy/useHardware", b); -} - -void ShotcutSettings::clearShortcuts(const QString &name) -{ - QString key = "shortcuts/" + name; - settings.remove(key); -} - -void ShotcutSettings::setShortcuts(const QString &name, const QList &shortcuts) -{ - QString key = "shortcuts/" + name; - QString shortcutSetting; - if (shortcuts.size() > 0) - shortcutSetting += shortcuts[0].toString(); - shortcutSetting += "||"; - if (shortcuts.size() > 1) - shortcutSetting += shortcuts[1].toString(); - settings.setValue(key, shortcutSetting); -} - -QList ShotcutSettings::shortcuts(const QString &name) -{ - QString key = "shortcuts/" + name; - QList shortcuts; - QString shortcutSetting = settings.value(key, "").toString(); - if (!shortcutSetting.isEmpty()) { - for (const QString &s : shortcutSetting.split("||")) - shortcuts << QKeySequence::fromString(s); - } - return shortcuts; -} - -double ShotcutSettings::slideshowImageDuration(double defaultSeconds) const -{ - return settings.value("slideshow/clipDuration", defaultSeconds).toDouble(); -} - -void ShotcutSettings::setSlideshowImageDuration(double seconds) -{ - settings.setValue("slideshow/clipDuration", seconds); -} - -double ShotcutSettings::slideshowAudioVideoDuration(double defaultSeconds) const -{ - return settings.value("slideshow/audioVideoDuration", defaultSeconds).toDouble(); -} - -void ShotcutSettings::setSlideshowAudioVideoDuration(double seconds) -{ - settings.setValue("slideshow/audioVideoDuration", seconds); -} - -int ShotcutSettings::slideshowAspectConversion(int defaultAspectConversion) const -{ - return settings.value("slideshow/aspectConversion", defaultAspectConversion).toInt(); -} - -void ShotcutSettings::setSlideshowAspectConversion(int aspectConversion) -{ - settings.setValue("slideshow/aspectConversion", aspectConversion); -} - -int ShotcutSettings::slideshowZoomPercent(int defaultZoomPercent) const -{ - return settings.value("slideshow/zoomPercent", defaultZoomPercent).toInt(); -} - -void ShotcutSettings::setSlideshowZoomPercent(int zoomPercent) -{ - settings.setValue("slideshow/zoomPercent", zoomPercent); -} - -double ShotcutSettings::slideshowTransitionDuration(double defaultTransitionDuration) const -{ - return settings.value("slideshow/transitionDuration", defaultTransitionDuration).toDouble(); -} - -void ShotcutSettings::setSlideshowTransitionDuration(double transitionDuration) -{ - settings.setValue("slideshow/transitionDuration", transitionDuration); -} - -int ShotcutSettings::slideshowTransitionStyle(int defaultTransitionStyle) const -{ - return settings.value("slideshow/transitionStyle", defaultTransitionStyle).toInt(); -} - -void ShotcutSettings::setSlideshowTransitionStyle(int transitionStyle) -{ - settings.setValue("slideshow/transitionStyle", transitionStyle); -} - -int ShotcutSettings::slideshowTransitionSoftness(int defaultTransitionStyle) const -{ - return settings.value("slideshow/transitionSoftness", defaultTransitionStyle).toInt(); -} - -void ShotcutSettings::setSlideshowTransitionSoftness(int transitionSoftness) -{ - settings.setValue("slideshow/transitionSoftness", transitionSoftness); -} - -/*! - \qmlproperty bool Settings::keyframesDragScrub - \brief Whether scrubbing occurs while dragging keyframes. -*/ - -bool ShotcutSettings::keyframesDragScrub() const -{ - return settings.value("keyframes/dragScrub", false).toBool(); -} - -void ShotcutSettings::setKeyframesDragScrub(bool b) -{ - settings.setValue("keyframes/dragScrub", b); - emit keyframesDragScrubChanged(); -} - -void ShotcutSettings::setSubtitlesShowColumn(const QString &column, bool b) -{ - settings.setValue("subtitles/columns/" + column, b); -} - -bool ShotcutSettings::subtitlesShowColumn(const QString &column) const -{ - return settings.value("subtitles/columns/" + column, true).toBool(); -} - -void ShotcutSettings::setSubtitlesTrackTimeline(bool b) -{ - settings.setValue("subtitles/trackTimeline", b); -} - -bool ShotcutSettings::subtitlesTrackTimeline() const -{ - return settings.value("subtitles/trackTimeline", true).toBool(); -} - -void ShotcutSettings::setSubtitlesShowPrevNext(bool b) -{ - settings.setValue("subtitles/showPrevNext", b); -} - -bool ShotcutSettings::subtitlesShowPrevNext() const -{ - return settings.value("subtitles/showPrevNext", true).toBool(); -} - -QString ShotcutSettings::speechLanguage() const -{ - return settings.value("speech/language", QStringLiteral("a")).toString(); -} - -void ShotcutSettings::setSpeechLanguage(const QString &code) -{ - settings.setValue("speech/language", code); -} - -QString ShotcutSettings::speechVoice() const -{ - return settings.value("speech/voice", QString()).toString(); -} - -void ShotcutSettings::setSpeechVoice(const QString &voiceId) -{ - settings.setValue("speech/voice", voiceId); -} - -double ShotcutSettings::speechSpeed() const -{ - return settings.value("speech/speed", 1.0).toDouble(); -} - -void ShotcutSettings::setSpeechSpeed(double speed) -{ - settings.setValue("speech/speed", speed); -} - -void ShotcutSettings::saveCustomColors() -{ - // QColorDialog supports up to 48 custom colors (16 in older versions) - QStringList colorList; - for (int i = 0; i < QColorDialog::customCount(); ++i) { - QColor color = QColorDialog::customColor(i); - if (color.isValid()) { - colorList.append(color.name(QColor::HexArgb)); - } else { - colorList.append(QString()); - } - } - settings.setValue("colorDialog/customColors", colorList); -} - -void ShotcutSettings::restoreCustomColors() -{ - QStringList colorList = settings.value("colorDialog/customColors").toStringList(); - for (int i = 0; i < colorList.size() && i < QColorDialog::customCount(); ++i) { - const QString &colorName = colorList.at(i); - if (!colorName.isEmpty()) { - QColor color(colorName); - if (color.isValid()) { - // Use rgba() to preserve alpha channel - QColorDialog::setCustomColor(i, color.rgba()); - } - } - } -} - -void ShotcutSettings::setWhisperExe(const QString &path) -{ - settings.setValue("subtitles/whisperExe", path); -} - -QString ShotcutSettings::whisperExe() -{ - QDir dir(qApp->applicationDirPath()); -#if defined(Q_OS_WIN) - auto exe = "whisper-cli.exe"; -#else - auto exe = "whisper-cli"; -#endif - return settings.value("subtitles/whisperExe", dir.absoluteFilePath(exe)).toString(); -} - -void ShotcutSettings::setWhisperModel(const QString &path) -{ - settings.setValue("subtitles/whisperModel", path); -} - -QString ShotcutSettings::whisperModel() -{ - QDir dataPath = QmlApplication::dataDir(); - dataPath.cd("shotcut/whisper_models"); - return settings.value("subtitles/whisperModel", "").toString(); -} - -void ShotcutSettings::setWhisperUseGpu(bool b) -{ - settings.setValue("subtitles/whisperUseGpu", b); -} - -bool ShotcutSettings::whisperUseGpu() const -{ - return settings.value("subtitles/whisperUseGpu", true).toBool(); -} - -void ShotcutSettings::setNotesZoom(int zoom) -{ - settings.setValue("notes/zoom", zoom); -} - -int ShotcutSettings::notesZoom() const -{ - return settings.value("notes/zoom", 0).toInt(); -} - -void ShotcutSettings::reset() -{ - for (auto &key : settings.allKeys()) { - settings.remove(key); - } -} - -int ShotcutSettings::undoLimit() const -{ - return settings.value("undoLimit", 50).toInt(); -} - -bool ShotcutSettings::warnLowMemory() const -{ - return settings.value("warnLowMemory", true).toBool(); -} - -int ShotcutSettings::backupPeriod() const -{ - return settings.value("backupPeriod", 24 * 60).toInt(); -} - -void ShotcutSettings::setBackupPeriod(int minutes) -{ - settings.setValue("backupPeriod", minutes); -} - -QDateTime ShotcutSettings::lastBackupDateTime(const QString &filePath) const -{ - return settings.value("lastBackupDateTimeMap").toMap().value(filePath).toDateTime(); -} - -void ShotcutSettings::setLastBackupDateTime(const QString &filePath, const QDateTime &dt) -{ - static const int kMaxBackupEntries = 100; - auto map = settings.value("lastBackupDateTimeMap").toMap(); - if (dt.isValid()) - map[filePath] = dt; - else - map.remove(filePath); - // Prune entries for files that no longer exist. - for (const auto &path : map.keys()) - if (!QFile::exists(path)) - map.remove(path); - // If still over the limit, remove the oldest entries. - while (map.size() > kMaxBackupEntries) { - map.erase(std::min_element(map.begin(), map.end(), [](const QVariant &a, const QVariant &b) { - return a.toDateTime() < b.toDateTime(); - })); - } - settings.setValue("lastBackupDateTimeMap", map); -} - -mlt_time_format ShotcutSettings::timeFormat() const -{ - return (mlt_time_format) settings.value("timeFormat", mlt_time_clock).toInt(); -} - -void ShotcutSettings::setTimeFormat(int format) -{ - settings.setValue("timeFormat", format); - emit timeFormatChanged(); -} - -bool ShotcutSettings::askFlatpakWrappers() -{ - return settings.value("flatpakWrappers", true).toBool(); -} - -void ShotcutSettings::setAskFlatpakWrappers(bool b) -{ - settings.setValue("flatpakWrappers", b); -} - -QString ShotcutSettings::dockerPath() const -{ -#if defined(Q_OS_MAC) - return settings.value("dockerPath", "/usr/local/bin/docker").toString(); -#elif defined(Q_OS_WIN) - return settings.value("dockerPath", "C:/Program Files/Docker/Docker/resources/bin/docker.exe") - .toString(); -#else - return settings.value("dockerPath", "docker").toString(); -#endif -} - -void ShotcutSettings::setDockerPath(const QString &path) -{ - settings.setValue("dockerPath", path); -} - -QString ShotcutSettings::chromiumPath() const -{ -#if defined(Q_OS_MAC) - return settings.value("chromiumPath", "/Applications/Google Chrome.app").toString(); -#elif defined(Q_OS_WIN) - return settings.value("chromiumPath", "C:/Program Files/Google/Chrome/Application/chrome.exe") - .toString(); -#else - return settings.value("chromiumPath", "/usr/bin/chromium-browser").toString(); -#endif -} - -void ShotcutSettings::setChromiumPath(const QString &path) -{ - settings.setValue("chromiumPath", path); -} - -QString ShotcutSettings::screenRecorderPath() const -{ - return settings.value("screenRecorderPath", "obs").toString(); -} - -void ShotcutSettings::setScreenRecorderPath(const QString &path) -{ - settings.setValue("screenRecorderPath", path); -} +/* + * Copyright (c) 2013-2026 Meltytech, LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "settings.h" + +#include "Logger.h" +#include "qmltypes/qmlapplication.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const QString APP_DATA_DIR_KEY("appdatadir"); +static const QString SHOTCUT_INI_FILENAME("/shotcut.ini"); +static const QString RECENT_INI_FILENAME("recent.ini"); +static QScopedPointer instance; +static QString appDataForSession; +static const int kMaximumTrackHeight = 125; +static const QString kRecentKey("recent"); +static const QString kProjectsKey("projects"); + +namespace { +struct ModeMap +{ + ShotcutSettings::ProcessingMode id; + const char *name; +}; +static constexpr ModeMap kModeMap[] = { + {ShotcutSettings::Native8Cpu, "Native8Cpu"}, + {ShotcutSettings::Linear8Cpu, "Linear8Cpu"}, + {ShotcutSettings::Native10Cpu, "Native10Cpu"}, + {ShotcutSettings::Linear10Cpu, "Linear10Cpu"}, + {ShotcutSettings::Linear10GpuCpu, "Linear10GpuCpu"}, +}; +} // anonymous namespace + +ShotcutSettings &ShotcutSettings::singleton() +{ + if (!instance) { + if (appDataForSession.isEmpty()) { + instance.reset(new ShotcutSettings); + if (instance->settings.value(APP_DATA_DIR_KEY).isValid() + && QFile::exists(instance->settings.value(APP_DATA_DIR_KEY).toString() + + SHOTCUT_INI_FILENAME)) + instance.reset( + new ShotcutSettings(instance->settings.value(APP_DATA_DIR_KEY).toString())); + } else { + instance.reset(new ShotcutSettings(appDataForSession)); + } + } + return *instance; +} + +/*! + \qmltype Settings + \inqmlmodule org.shotcut.qml + \brief Persistent application settings, accessed via the \c settings context property. + + \c settings is an uncreatable QML type — use the global \c settings identifier + available in every Shotcut QML view. Settings are backed by QSettings and persist + across sessions. + + \code + if (settings.timelineSnap) { ... } + settings.timelineSnap = true + \endcode +*/ + +/*! + \qmlproperty Settings::TimelineScrolling Settings::timelineScrolling + \brief The timeline auto-scroll mode. + One of \c NoScrolling, \c CenterPlayhead, \c PageScrolling, or \c SmoothScrolling. +*/ + +ShotcutSettings::ShotcutSettings() + : QObject() + , m_recent(QDir(appDataLocation()).filePath(RECENT_INI_FILENAME), QSettings::IniFormat) +{ + migrateLayout(); + migrateRecent(); +} + +ShotcutSettings::ShotcutSettings(const QString &appDataLocation) + : QObject() + , settings(appDataLocation + SHOTCUT_INI_FILENAME, QSettings::IniFormat) + , m_appDataLocation(appDataLocation) + , m_recent(QDir(appDataLocation).filePath(RECENT_INI_FILENAME), QSettings::IniFormat) +{ + migrateLayout(); + migrateRecent(); +} + +void ShotcutSettings::migrateRecent() +{ + // Migrate recent to separate INI file + auto oldRecents = settings.value(kRecentKey).toStringList(); + if (recent().isEmpty() && !oldRecents.isEmpty()) { + auto newRecents = recent(); + for (const auto &a : oldRecents) { + if (a.size() < ShotcutSettings::MaxPath && !newRecents.contains(a)) { + while (newRecents.size() > 100) { + newRecents.removeFirst(); + } + newRecents.append(a); + } + } + setRecent(newRecents); + m_recent.sync(); + // settings.remove("recent"); + settings.sync(); + } +} + +void ShotcutSettings::migrateLayout() +{ + // Migrate old startup layout to a custom layout and start fresh + if (!settings.contains("geometry2")) { + auto geometry = settings.value("geometry").toByteArray(); + auto windowState = settings.value("windowState").toByteArray(); + setLayout(tr("Old (before v23) Layout"), geometry, windowState); + setLayoutMode(2); + settings.sync(); + } +} + +void ShotcutSettings::log() +{ + LOG_INFO() << "language" << language(); + LOG_INFO() << "deinterlacer" << playerDeinterlacer(); + LOG_INFO() << "external monitor" << playerExternal(); + LOG_INFO() << "GPU processing" << playerGPU(); + LOG_INFO() << "interpolation" << playerInterpolation(); + LOG_INFO() << "video mode" << playerProfile(); + LOG_INFO() << "realtime" << playerRealtime(); + LOG_INFO() << "audio channels" << playerAudioChannels(); +#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) + if (::qEnvironmentVariableIsSet("SDL_AUDIODRIVER")) { + LOG_INFO() << "audio driver" << ::qgetenv("SDL_AUDIODRIVER"); + } else { + LOG_INFO() << "audio driver" << playerAudioDriver(); + } +#endif +} + +QString ShotcutSettings::language() const +{ + QString language = settings.value("language", QLocale().name()).toString(); + if (language == "en") + language = "en_US"; + return language; +} + +void ShotcutSettings::setLanguage(const QString &s) +{ + settings.setValue("language", s); +} + +double ShotcutSettings::imageDuration() const +{ + return settings.value("imageDuration", 4.0).toDouble(); +} + +void ShotcutSettings::setImageDuration(double d) +{ + settings.setValue("imageDuration", d); +} + +/*! + \qmlproperty string Settings::openPath + \brief The last directory used for opening files. +*/ + +QString ShotcutSettings::openPath() const +{ + return settings + .value("openPath", QStandardPaths::standardLocations(QStandardPaths::MoviesLocation)) + .toString(); +} + +void ShotcutSettings::setOpenPath(const QString &s) +{ + settings.setValue("openPath", s); + emit savePathChanged(); +} + +/*! + \qmlproperty string Settings::savePath + \brief The last directory used for saving files. +*/ + +QString ShotcutSettings::savePath() const +{ + return settings + .value("savePath", QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation)) + .toString(); +} + +void ShotcutSettings::setSavePath(const QString &s) +{ + settings.setValue("savePath", s); + emit savePathChanged(); +} + +QStringList ShotcutSettings::recent() const +{ + return m_recent.value(kRecentKey).toStringList(); +} + +void ShotcutSettings::setRecent(const QStringList &ls) +{ + if (ls.isEmpty()) + m_recent.remove(kRecentKey); + else if (!clearRecent()) + m_recent.setValue(kRecentKey, ls); +} + +QStringList ShotcutSettings::projects() +{ + auto ls = m_recent.value(kProjectsKey).toStringList(); + if (ls.isEmpty()) { + for (auto &r : recent()) { + if (r.endsWith(".mlt")) + ls << r; + } + // Prevent entering this block repeatedly + if (ls.isEmpty()) + ls << QString(); + setProjects(ls); + } + return ls; +} + +void ShotcutSettings::setProjects(const QStringList &ls) +{ + if (ls.isEmpty()) + m_recent.remove(kProjectsKey); + else if (!clearRecent()) + m_recent.setValue(kProjectsKey, ls); +} + +QString ShotcutSettings::theme() const +{ + return settings.value("theme", "dark").toString(); +} + +void ShotcutSettings::setTheme(const QString &s) +{ + settings.setValue("theme", s); +} + +QThread::Priority ShotcutSettings::jobPriority() const +{ + const auto priority = settings.value("jobPriority", "low").toString(); + if (priority == "low") { + return QThread::LowPriority; + } + return QThread::NormalPriority; +} + +void ShotcutSettings::setJobPriority(const QString &s) +{ + settings.setValue("jobPriority", s); +} + +bool ShotcutSettings::showTitleBars() const +{ + return settings.value("titleBars", true).toBool(); +} + +void ShotcutSettings::setShowTitleBars(bool b) +{ + settings.setValue("titleBars", b); +} + +bool ShotcutSettings::showToolBar() const +{ + return settings.value("toolBar", true).toBool(); +} + +void ShotcutSettings::setShowToolBar(bool b) +{ + settings.setValue("toolBar", b); +} + +bool ShotcutSettings::textUnderIcons() const +{ + return settings.value("textUnderIcons", true).toBool(); +} + +void ShotcutSettings::setTextUnderIcons(bool b) +{ + settings.setValue("textUnderIcons", b); +} + +/*! + \qmlproperty bool Settings::smallIcons + \brief Whether the toolbar uses small icons. +*/ + +bool ShotcutSettings::smallIcons() const +{ + return settings.value("smallIcons", false).toBool(); +} + +void ShotcutSettings::setSmallIcons(bool b) +{ + settings.setValue("smallIcons", b); + emit smallIconsChanged(); +} + +QByteArray ShotcutSettings::windowGeometry() const +{ + return settings.value("geometry2").toByteArray(); +} + +void ShotcutSettings::setWindowGeometry(const QByteArray &a) +{ + settings.setValue("geometry2", a); +} + +QByteArray ShotcutSettings::windowGeometryDefault() const +{ + return settings.value("geometryDefault").toByteArray(); +} + +void ShotcutSettings::setWindowGeometryDefault(const QByteArray &a) +{ + settings.setValue("geometryDefault", a); +} + +QByteArray ShotcutSettings::windowState() const +{ + return settings.value("windowState2").toByteArray(); +} + +void ShotcutSettings::setWindowState(const QByteArray &a) +{ + settings.setValue("windowState2", a); +} + +QByteArray ShotcutSettings::windowStateDefault() const +{ + return settings.value("windowStateDefault").toByteArray(); +} + +void ShotcutSettings::setWindowStateDefault(const QByteArray &a) +{ + settings.setValue("windowStateDefault", a); +} + +/*! + \qmlproperty string Settings::viewMode + \brief The current view mode of the Playlist panel (e.g. \c "details", \c "icons"). +*/ + +QString ShotcutSettings::viewMode() const +{ + return settings.value("playlist/viewMode").toString(); +} + +void ShotcutSettings::setViewMode(const QString &viewMode) +{ + settings.setValue("playlist/viewMode", viewMode); + emit viewModeChanged(); +} + +QString ShotcutSettings::filesViewMode() const +{ + return settings.value("files/viewMode", QLatin1String("tiled")).toString(); +} + +void ShotcutSettings::setFilesViewMode(const QString &viewMode) +{ + settings.setValue("files/viewMode", viewMode); + emit filesViewModeChanged(); +} + +QStringList ShotcutSettings::filesLocations() const +{ + QStringList result; + for (const auto &s : settings.value("files/locations").toStringList()) { + if (!s.startsWith("__")) + result << s; + } + return result; +} + +QString ShotcutSettings::filesLocationPath(const QString &name) const +{ + QString key = QStringLiteral("files/location/%1").arg(name); + return settings.value(key).toString(); +} + +bool ShotcutSettings::setFilesLocation(const QString &name, const QString &path) +{ + bool isNew = false; + QStringList locations = filesLocations(); + if (!locations.contains(name)) { + isNew = true; + locations.append(name); + settings.setValue("files/locations", locations); + } + settings.setValue("files/location/" + name, path); + return isNew; +} + +bool ShotcutSettings::removeFilesLocation(const QString &name) +{ + QStringList list = filesLocations(); + int index = list.indexOf(name); + if (index > -1) { + list.removeAt(index); + if (list.isEmpty()) + settings.remove("files/locations"); + else + settings.setValue("files/locations", list); + settings.remove("files/location/" + name); + return true; + } + return false; +} + +QStringList ShotcutSettings::filesOpenOther(const QString &type) const +{ + return settings.value("files/openOther/" + type).toStringList(); +} + +void ShotcutSettings::setFilesOpenOther(const QString &type, const QString &filePath) +{ + QStringList filePaths = filesOpenOther(type); + filePaths.removeAll(filePath); + filePaths.append(filePath); + settings.setValue("files/openOther/" + type, filePaths); +} + +bool ShotcutSettings::removeFilesOpenOther(const QString &type, const QString &filePath) +{ + QStringList list = filesOpenOther(type); + int index = list.indexOf(filePath); + if (index > -1) { + list.removeAt(index); + if (list.isEmpty()) + settings.remove("files/openOther/" + type); + else + settings.setValue("files/openOther/" + type, list); + return true; + } + return false; +} + +QString ShotcutSettings::filesCurrentDir() const +{ + const auto ls = QStandardPaths::standardLocations(QStandardPaths::HomeLocation); + auto path = settings.value("files/currentDir", ls.first()).toString(); + if (!QFile::exists(path)) { + LOG_DEBUG() << "dir does not exist:" << QDir::toNativeSeparators(path); + path = ls.first(); + } + return path; +} + +void ShotcutSettings::setFilesCurrentDir(const QString &s) +{ + settings.setValue("files/currentDir", s); +} + +bool ShotcutSettings::filesFoldersOpen() const +{ + return settings.value("files/foldersOpen", true).toBool(); +} + +void ShotcutSettings::setFilesFoldersOpen(bool b) +{ + settings.setValue("files/foldersOpen", b); +} + +QString ShotcutSettings::exportFrameSuffix() const +{ + return settings.value("exportFrameSuffix", ".png").toString(); +} + +void ShotcutSettings::setExportFrameSuffix(const QString &exportFrameSuffix) +{ + settings.setValue("exportFrameSuffix", exportFrameSuffix); +} + +QString ShotcutSettings::encodePath() const +{ + return settings + .value("encode/path", QStandardPaths::standardLocations(QStandardPaths::MoviesLocation)) + .toString(); +} + +void ShotcutSettings::setEncodePath(const QString &s) +{ + settings.setValue("encode/path", s); +} + +bool ShotcutSettings::encodeFreeSpaceCheck() const +{ + return settings.value("encode/freeSpaceCheck", true).toBool(); +} + +void ShotcutSettings::setEncodeFreeSpaceCheck(bool b) +{ + settings.setValue("encode/freeSpaceCheck", b); +} + +bool ShotcutSettings::encodeUseHardware() const +{ + return settings.value("encode/useHardware").toBool(); +} + +void ShotcutSettings::setEncodeUseHardware(bool b) +{ + settings.setValue("encode/useHardware", b); +} + +QStringList ShotcutSettings::encodeHardware() const +{ + return settings.value("encode/hardware").toStringList(); +} + +void ShotcutSettings::setEncodeHardware(const QStringList &ls) +{ + if (ls.isEmpty()) + settings.remove("encode/hardware"); + else + settings.setValue("encode/hardware", ls); +} + +bool ShotcutSettings::encodeHardwareDecoder() const +{ + return settings.value("encode/hardwareDecoder", false).toBool(); +} + +void ShotcutSettings::setEncodeHardwareDecoder(bool b) +{ + settings.setValue("encode/hardwareDecoder", b); +} + +bool ShotcutSettings::encodeAdvanced() const +{ + return settings.value("encode/advanced", false).toBool(); +} + +void ShotcutSettings::setEncodeAdvanced(bool b) +{ + settings.setValue("encode/advanced", b); +} + +bool ShotcutSettings::convertAdvanced() const +{ + return settings.value("convertAdvanced", false).toBool(); +} + +void ShotcutSettings::setConvertAdvanced(bool b) +{ + settings.setValue("convertAdvanced", b); +} + +ShotcutSettings::ProcessingMode ShotcutSettings::processingMode() +{ + if (settings.contains("processingMode")) { + auto result = (ShotcutSettings::ProcessingMode) settings.value("processingMode").toInt(); + if (result == Linear8Cpu) { + // No longer supported but kept to prevent unexpected processing behavior going from + // beta to release + result = Native8Cpu; + } + return result; + } else if (settings.contains("player/gpu2")) { + // Legacy GPU Mode + if (settings.value("player/gpu2").toBool()) { + return ShotcutSettings::Linear10GpuCpu; + } + } + return ShotcutSettings::Native8Cpu; +} + +void ShotcutSettings::setProcessingMode(ProcessingMode mode) +{ + settings.setValue("processingMode", mode); + emit playerGpuChanged(); +} + +QString ShotcutSettings::processingModeStr(ShotcutSettings::ProcessingMode mode) +{ + for (const auto &m : kModeMap) { + if (m.id == mode) + return QString::fromLatin1(m.name); + } + LOG_ERROR() << "Unknown processing mode" << mode; + return QStringLiteral("Native8Cpu"); +} + +ShotcutSettings::ProcessingMode ShotcutSettings::processingModeId(const QString &mode) +{ + for (const auto &m : kModeMap) { + if (mode == QLatin1String(m.name)) + return m.id; + } + LOG_ERROR() << "Unknown processing mode" << mode; + return Native8Cpu; +} + +bool ShotcutSettings::showConvertClipDialog() const +{ + return settings.value("showConvertClipDialog", true).toBool(); +} + +void ShotcutSettings::setShowConvertClipDialog(bool b) +{ + settings.setValue("showConvertClipDialog", b); +} + +bool ShotcutSettings::showHdrPlayerWarning() const +{ + return settings.value("showHdrPlayerWarning", true).toBool(); +} + +void ShotcutSettings::setShowHdrPlayerWarning(bool b) +{ + settings.setValue("showHdrPlayerWarning", b); +} + +bool ShotcutSettings::encodeParallelProcessing() const +{ + return settings.value("encode/parallelProcessing", false).toBool(); +} + +void ShotcutSettings::setEncodeParallelProcessing(bool b) +{ + settings.setValue("encode/parallelProcessing", b); +} + +/*! + \qmlproperty int Settings::playerAudioChannels + \brief The number of audio channels used by the player (e.g. 2 or 6). +*/ + +int ShotcutSettings::playerAudioChannels() const +{ + return settings.value("player/audioChannels", 2).toInt(); +} + +void ShotcutSettings::setPlayerAudioChannels(int i) +{ + settings.setValue("player/audioChannels", i); + emit playerAudioChannelsChanged(i); +} + +QString ShotcutSettings::playerDeinterlacer() const +{ + QString result = settings.value("player/deinterlacer", "onefield").toString(); + //XXX workaround yadif crashing with mlt_transition + if (result == "yadif" || result == "yadif-nospatial") + result = "onefield"; + return result; +} + +void ShotcutSettings::setPlayerDeinterlacer(const QString &s) +{ + settings.setValue("player/deinterlacer", s); +} + +QString ShotcutSettings::playerExternal() const +{ + auto result = settings.value("player/external", "").toString(); + // "sdi" is no longer supported DVEO VidPort + return result == "sdi" ? "" : result; +} + +void ShotcutSettings::setPlayerExternal(const QString &s) +{ + settings.setValue("player/external", s); +} + +bool ShotcutSettings::playerJACK() const +{ + return settings.value("player/jack", false).toBool(); +} + +QString ShotcutSettings::playerInterpolation() const +{ + return settings.value("player/interpolation", "bilinear").toString(); +} + +void ShotcutSettings::setPlayerInterpolation(const QString &s) +{ + settings.setValue("player/interpolation", s); +} + +/*! + \qmlproperty bool Settings::playerGPU + \brief Whether GPU processing (GLSL) is enabled for the video player. +*/ + +bool ShotcutSettings::playerGPU() const +{ + // This is the legacy function for the old GPU mode. + if (settings.contains("processingMode")) { + ProcessingMode mode = (ProcessingMode) settings.value("processingMode").toInt(); + return mode == Linear10GpuCpu; + } else if (settings.contains("player/gpu2")) { + // Legacy GPU Mode + return settings.value("player/gpu2").toBool(); + } + return false; +} + +bool ShotcutSettings::playerWarnGPU() const +{ + return false; //settings.value("player/warnGPU", false).toBool(); +} + +void ShotcutSettings::setPlayerJACK(bool b) +{ + settings.setValue("player/jack", b); +} + +int ShotcutSettings::playerDecklinkHdrMaxCll() const +{ + return settings.value("player/decklinkHdrMaxCll", 1000).toInt(); +} + +void ShotcutSettings::setPlayerDecklinkHdrMaxCll(int nits) +{ + settings.setValue("player/decklinkHdrMaxCll", nits); +} + +int ShotcutSettings::playerDecklinkHdrMaxFall() const +{ + return settings.value("player/decklinkHdrMaxFall", 400).toInt(); +} + +void ShotcutSettings::setPlayerDecklinkHdrMaxFall(int nits) +{ + settings.setValue("player/decklinkHdrMaxFall", nits); +} + +int ShotcutSettings::playerDecklinkHdrMasterPreset() const +{ + return settings.value("player/decklinkHdrMasterPreset", 0).toInt(); +} + +void ShotcutSettings::setPlayerDecklinkHdrMasterPreset(int preset) +{ + settings.setValue("player/decklinkHdrMasterPreset", preset); +} + +int ShotcutSettings::playerDecklinkHdrMaxLuminance() const +{ + return settings.value("player/decklinkHdrMaxLuminance", 1000).toInt(); +} + +void ShotcutSettings::setPlayerDecklinkHdrMaxLuminance(int nits) +{ + settings.setValue("player/decklinkHdrMaxLuminance", nits); +} + +double ShotcutSettings::playerDecklinkHdrMinLuminance() const +{ + return settings.value("player/decklinkHdrMinLuminance", 0.01).toDouble(); +} + +void ShotcutSettings::setPlayerDecklinkHdrMinLuminance(double nits) +{ + settings.setValue("player/decklinkHdrMinLuminance", nits); +} + +int ShotcutSettings::playerKeyerMode() const +{ + return settings.value("player/keyer", 0).toInt(); +} + +void ShotcutSettings::setPlayerKeyerMode(int i) +{ + settings.setValue("player/keyer", i); +} + +bool ShotcutSettings::playerMuted() const +{ + return settings.value("player/muted", false).toBool(); +} + +void ShotcutSettings::setPlayerMuted(bool b) +{ + settings.setValue("player/muted", b); +} + +QString ShotcutSettings::playerProfile() const +{ + return settings.value("player/profile", "").toString(); +} + +void ShotcutSettings::setPlayerProfile(const QString &s) +{ + settings.setValue("player/profile", s); +} + +bool ShotcutSettings::playerProgressive() const +{ + return settings.value("player/progressive", true).toBool(); +} + +void ShotcutSettings::setPlayerProgressive(bool b) +{ + settings.setValue("player/progressive", b); +} + +bool ShotcutSettings::playerRealtime() const +{ + return settings.value("player/realtime", true).toBool(); +} + +void ShotcutSettings::setPlayerRealtime(bool b) +{ + settings.setValue("player/realtime", b); +} + +bool ShotcutSettings::playerScrubAudio() const +{ + return settings.value("player/scrubAudio", true).toBool(); +} + +void ShotcutSettings::setPlayerScrubAudio(bool b) +{ + settings.setValue("player/scrubAudio", b); +} + +int ShotcutSettings::playerVolume() const +{ + return settings.value("player/volume", 88).toInt(); +} + +void ShotcutSettings::setPlayerVolume(int i) +{ + settings.setValue("player/volume", i); +} + +float ShotcutSettings::playerZoom() const +{ + return settings.value("player/zoom", 0.0f).toFloat(); +} + +void ShotcutSettings::setPlayerZoom(float f) +{ + settings.setValue("player/zoom", f); +} + +int ShotcutSettings::playerPreviewScale() const +{ + return settings.value("player/previewScale", 0).toInt(); +} + +void ShotcutSettings::setPlayerPreviewScale(int i) +{ + settings.setValue("player/previewScale", i); +} + +bool ShotcutSettings::playerPreviewHardwareDecoder() const +{ + return settings.value("player/previewHardwareDecoder", true).toBool(); +} + +bool ShotcutSettings::playerPreviewHardwareDecoderIsSet() const +{ + return settings.contains("player/previewHardwareDecoder"); +} + +void ShotcutSettings::setPlayerPreviewHardwareDecoder(bool b) +{ + settings.setValue("player/previewHardwareDecoder", b); +} + +int ShotcutSettings::playerVideoDelayMs() const +{ + return settings.value("player/videoDelayMs", 0).toInt(); +} + +void ShotcutSettings::setPlayerVideoDelayMs(int i) +{ + settings.setValue("player/videoDelayMs", i); +} + +double ShotcutSettings::playerJumpSeconds() const +{ + return settings.value("player/jumpSeconds", 60.0).toDouble(); +} + +void ShotcutSettings::setPlayerJumpSeconds(double i) +{ + settings.setValue("player/jumpSeconds", i); +} + +QString ShotcutSettings::playerAudioDriver() const +{ +#if defined(Q_OS_WIN) + auto s = playerAudioChannels() > 2 ? "directsound" : "winmm"; +#else + auto s = "pulseaudio"; +#endif + if (::qEnvironmentVariableIsSet("SDL_AUDIODRIVER")) { + return ::qgetenv("SDL_AUDIODRIVER"); + } else { + return settings.value("player/audioDriver", s).toString(); + } +} + +void ShotcutSettings::setPlayerAudioDriver(const QString &s) +{ + settings.setValue("player/audioDriver", s); +} + +bool ShotcutSettings::playerPauseAfterSeek() const +{ + return settings.value("player/pauseAfterSeek", true).toBool(); +} + +void ShotcutSettings::setPlayerPauseAfterSeek(bool b) +{ + settings.setValue("player/pauseAfterSeek", b); +} + +bool ShotcutSettings::playerOldVideoOutput() const +{ + return settings.value("player/oldVideoOutput", false).toBool(); +} + +void ShotcutSettings::setPlayerOldVideoOutput(bool b) +{ + settings.setValue("player/oldVideoOutput", b); +} + +bool ShotcutSettings::playerHdrPreview() const +{ + return settings.value("player/hdrPreview", false).toBool(); +} + +void ShotcutSettings::setPlayerHdrPreview(bool b) +{ + settings.setValue("player/hdrPreview", b); +} + +QRect ShotcutSettings::playerHdrPreviewGeometry() const +{ + return settings.value("player/hdrPreviewGeometry").toRect(); +} + +void ShotcutSettings::setPlayerHdrPreviewGeometry(const QRect &r) +{ + settings.setValue("player/hdrPreviewGeometry", r); +} + +bool ShotcutSettings::playerHdrPreviewFullScreen() const +{ + return settings.value("player/hdrPreviewFullScreen", false).toBool(); +} + +void ShotcutSettings::setPlayerHdrPreviewFullScreen(bool b) +{ + settings.setValue("player/hdrPreviewFullScreen", b); +} + +int ShotcutSettings::playerHdrDisplayPeakNits() const +{ + return settings.value("player/hdrDisplayPeakNits", 0).toInt(); +} + +void ShotcutSettings::setPlayerHdrDisplayPeakNits(int nits) +{ + settings.setValue("player/hdrDisplayPeakNits", nits); +} + +int ShotcutSettings::playerHdrContentPeakNits() const +{ + return settings.value("player/hdrContentPeakNits", 0).toInt(); +} + +void ShotcutSettings::setPlayerHdrContentPeakNits(int nits) +{ + settings.setValue("player/hdrContentPeakNits", nits); +} + +bool ShotcutSettings::playerHdrToneMapping() const +{ + return settings.value("player/hdrToneMapping", true).toBool(); +} + +void ShotcutSettings::setPlayerHdrToneMapping(bool b) +{ + settings.setValue("player/hdrToneMapping", b); +} + +/*! + \qmlproperty string Settings::playlistThumbnails + \brief The thumbnail display mode for the Playlist panel. +*/ + +QString ShotcutSettings::playlistThumbnails() const +{ + return settings.value("playlist/thumbnails", "small").toString(); +} + +void ShotcutSettings::setPlaylistThumbnails(const QString &s) +{ + settings.setValue("playlist/thumbnails", s); + emit playlistThumbnailsChanged(); +} + +bool ShotcutSettings::playlistAutoplay() const +{ + return settings.value("playlist/autoplay", true).toBool(); +} + +void ShotcutSettings::setPlaylistAutoplay(bool b) +{ + settings.setValue("playlist/autoplay", b); +} + +bool ShotcutSettings::playlistShowColumn(const QString &column) +{ + return settings.value("playlist/columns/" + column, true).toBool(); +} + +void ShotcutSettings::setPlaylistShowColumn(const QString &column, bool b) +{ + settings.setValue("playlist/columns/" + column, b); +} + +/*! + \qmlproperty bool Settings::timelineDragScrub + \brief Whether scrubbing occurs while dragging clips on the timeline. +*/ + +bool ShotcutSettings::timelineDragScrub() const +{ + return settings.value("timeline/dragScrub", false).toBool(); +} + +void ShotcutSettings::setTimelineDragScrub(bool b) +{ + settings.setValue("timeline/dragScrub", b); + emit timelineDragScrubChanged(); +} + +/*! + \qmlproperty bool Settings::timelineShowWaveforms + \brief Whether audio waveforms are shown on timeline clips. +*/ + +bool ShotcutSettings::timelineShowWaveforms() const +{ + return settings.value("timeline/waveforms", true).toBool(); +} + +void ShotcutSettings::setTimelineShowWaveforms(bool b) +{ + settings.setValue("timeline/waveforms", b); + emit timelineShowWaveformsChanged(); +} + +/*! + \qmlproperty bool Settings::timelineShowThumbnails + \brief Whether video thumbnails are shown on timeline clips. +*/ + +bool ShotcutSettings::timelineShowThumbnails() const +{ + return settings.value("timeline/thumbnails", true).toBool(); +} + +void ShotcutSettings::setTimelineShowThumbnails(bool b) +{ + settings.setValue("timeline/thumbnails", b); + emit timelineShowThumbnailsChanged(); +} + +/*! + \qmlproperty bool Settings::timelineRipple + \brief Whether ripple editing is enabled on the timeline. +*/ + +bool ShotcutSettings::timelineRipple() const +{ + return settings.value("timeline/ripple", false).toBool(); +} + +void ShotcutSettings::setTimelineRipple(bool b) +{ + settings.setValue("timeline/ripple", b); + emit timelineRippleChanged(); +} + +/*! + \qmlproperty bool Settings::timelineRippleAllTracks + \brief Whether ripple editing affects all tracks simultaneously. +*/ + +bool ShotcutSettings::timelineRippleAllTracks() const +{ + return settings.value("timeline/rippleAllTracks", false).toBool(); +} + +void ShotcutSettings::setTimelineRippleAllTracks(bool b) +{ + settings.setValue("timeline/rippleAllTracks", b); + emit timelineRippleAllTracksChanged(); +} + +/*! + \qmlproperty bool Settings::timelineRippleMarkers + \brief Whether markers are moved along with ripple edits. +*/ + +bool ShotcutSettings::timelineRippleMarkers() const +{ + return settings.value("timeline/rippleMarkers", false).toBool(); +} + +void ShotcutSettings::setTimelineRippleMarkers(bool b) +{ + settings.setValue("timeline/rippleMarkers", b); + emit timelineRippleMarkersChanged(); +} + +/*! + \qmlproperty bool Settings::timelineSnap + \brief Whether clip snapping is enabled on the timeline. +*/ + +bool ShotcutSettings::timelineSnap() const +{ + return settings.value("timeline/snap", true).toBool(); +} + +void ShotcutSettings::setTimelineSnap(bool b) +{ + settings.setValue("timeline/snap", b); + emit timelineSnapChanged(); +} + +int ShotcutSettings::timelineTrackHeight() const +{ + return qMin(settings.value("timeline/trackHeight", 50).toInt(), kMaximumTrackHeight); +} + +void ShotcutSettings::setTimelineTrackHeight(int n) +{ + settings.setValue("timeline/trackHeight", qMin(n, kMaximumTrackHeight)); +} + +/*! + \qmlproperty bool Settings::timelineScrollZoom + \brief Whether the scroll wheel zooms the timeline (instead of scrolling). +*/ + +bool ShotcutSettings::timelineScrollZoom() const +{ + return settings.value("timeline/scrollZoom", true).toBool(); +} + +void ShotcutSettings::setTimelineScrollZoom(bool b) +{ + settings.setValue("timeline/scrollZoom", b); + emit timelineScrollZoomChanged(); +} + +/*! + \qmlproperty bool Settings::timelineFramebufferWaveform + \brief Whether waveforms are rendered using a framebuffer (GPU) path. +*/ + +bool ShotcutSettings::timelineFramebufferWaveform() const +{ + return settings.value("timeline/framebufferWaveform", true).toBool(); +} + +void ShotcutSettings::setTimelineFramebufferWaveform(bool b) +{ + settings.setValue("timeline/framebufferWaveform", b); + emit timelineFramebufferWaveformChanged(); +} + +int ShotcutSettings::audioReferenceTrack() const +{ + return settings.value("timeline/audioReferenceTrack", 0).toInt(); +} +void ShotcutSettings::setAudioReferenceTrack(int track) +{ + settings.setValue("timeline/audioReferenceTrack", track); +} + +double ShotcutSettings::audioReferenceSpeedRange() const +{ + return settings.value("timeline/audioReferenceSpeedRange", 0).toDouble(); +} +void ShotcutSettings::setAudioReferenceSpeedRange(double range) +{ + settings.setValue("timeline/audioReferenceSpeedRange", range); +} + +bool ShotcutSettings::timelinePreviewTransition() const +{ + return settings.value("timeline/previewTransition", true).toBool(); +} + +void ShotcutSettings::setTimelinePreviewTransition(bool b) +{ + settings.setValue("timeline/previewTransition", b); +} + +void ShotcutSettings::setTimelineScrolling(ShotcutSettings::TimelineScrolling value) +{ + settings.remove("timeline/centerPlayhead"); + settings.setValue("timeline/scrolling", value); + emit timelineScrollingChanged(); +} + +ShotcutSettings::TimelineScrolling ShotcutSettings::timelineScrolling() const +{ + if (settings.contains("timeline/centerPlayhead") + && settings.value("timeline/centerPlayhead").toBool()) + return ShotcutSettings::TimelineScrolling::CenterPlayhead; + else + return ShotcutSettings::TimelineScrolling( + settings.value("timeline/scrolling", PageScrolling).toInt()); +} + +bool ShotcutSettings::timelineAutoAddTracks() const +{ + return settings.value("timeline/autoAddTracks", false).toBool(); +} + +void ShotcutSettings::setTimelineAutoAddTracks(bool b) +{ + if (b != timelineAutoAddTracks()) { + settings.setValue("timeline/autoAddTracks", b); + emit timelineAutoAddTracksChanged(); + } +} + +/*! + \qmlproperty bool Settings::timelineRectangleSelect + \brief Whether rectangle (rubber-band) selection is enabled on the timeline. +*/ + +bool ShotcutSettings::timelineRectangleSelect() const +{ + return settings.value("timeline/rectangleSelect", true).toBool(); +} + +void ShotcutSettings::setTimelineRectangleSelect(bool b) +{ + settings.setValue("timeline/rectangleSelect", b); + emit timelineRectangleSelectChanged(); +} + +/*! + \qmlproperty bool Settings::timelineAdjustGain + \brief Whether dragging the gain handle on audio clips adjusts volume inline. +*/ + +bool ShotcutSettings::timelineAdjustGain() const +{ + return settings.value("timeline/adjustGain", false).toBool(); +} + +void ShotcutSettings::setTimelineAdjustGain(bool b) +{ + settings.setValue("timeline/adjustGain", b); + emit timelineAdjustGainChanged(); +} + +/*! + \qmlproperty bool Settings::timelineAllowTransitions + \brief Whether overlapping clips on the timeline automatically create transitions. +*/ + +bool ShotcutSettings::timelineAllowTransitions() const +{ + return settings.value("timeline/allowTransitions", true).toBool(); +} + +void ShotcutSettings::setTimelineAllowTransitions(bool b) +{ + if (b != timelineAllowTransitions()) { + settings.setValue("timeline/allowTransitions", b); + emit timelineAllowTransitionsChanged(); + } +} + +QString ShotcutSettings::filterFavorite(const QString &filterName) +{ + return settings.value("filter/favorite/" + filterName, "").toString(); +} + +void ShotcutSettings::setFilterFavorite(const QString &filterName, const QString &value) +{ + settings.setValue("filter/favorite/" + filterName, value); +} + +QStringList ShotcutSettings::addOnFilterServices() const +{ + return settings.value("filter/addOnServices").toStringList(); +} + +void ShotcutSettings::setAddOnFilterServices(const QStringList &services) +{ + settings.setValue("filter/addOnServices", services); +} + +/*! + \qmlproperty real Settings::audioInDuration + \brief The default duration in seconds for audio fade-in transitions. +*/ + +double ShotcutSettings::audioInDuration() const +{ + return settings.value("filter/audioInDuration", 1.0).toDouble(); +} + +void ShotcutSettings::setAudioInDuration(double d) +{ + settings.setValue("filter/audioInDuration", d); + emit audioInDurationChanged(); +} + +/*! + \qmlproperty real Settings::audioOutDuration + \brief The default duration in seconds for audio fade-out transitions. +*/ + +double ShotcutSettings::audioOutDuration() const +{ + return settings.value("filter/audioOutDuration", 1.0).toDouble(); +} + +void ShotcutSettings::setAudioOutDuration(double d) +{ + settings.setValue("filter/audioOutDuration", d); + emit audioOutDurationChanged(); +} + +/*! + \qmlproperty real Settings::videoInDuration + \brief The default duration in seconds for video fade-in transitions. +*/ + +double ShotcutSettings::videoInDuration() const +{ + return settings.value("filter/videoInDuration", 1.0).toDouble(); +} + +void ShotcutSettings::setVideoInDuration(double d) +{ + settings.setValue("filter/videoInDuration", d); + emit videoInDurationChanged(); +} + +/*! + \qmlproperty real Settings::videoOutDuration + \brief The default duration in seconds for video fade-out transitions. +*/ + +double ShotcutSettings::videoOutDuration() const +{ + return settings.value("filter/videoOutDuration", 1.0).toDouble(); +} + +void ShotcutSettings::setVideoOutDuration(double d) +{ + settings.setValue("filter/videoOutDuration", d); + emit videoOutDurationChanged(); +} + +/*! + \qmlproperty int Settings::audioInCurve + \brief The curve type for audio fade-in (0 = linear, higher = more exponential). +*/ + +int ShotcutSettings::audioInCurve() const +{ + return settings.value("filter/audioInCurve", mlt_keyframe_linear).toInt(); +} + +void ShotcutSettings::setAudioInCurve(int c) +{ + settings.setValue("filter/audioInCurve", c); + emit audioInCurveChanged(); +} + +/*! + \qmlproperty int Settings::audioOutCurve + \brief The curve type for audio fade-out (0 = linear, higher = more exponential). +*/ + +int ShotcutSettings::audioOutCurve() const +{ + return settings.value("filter/audioOutCurve", mlt_keyframe_linear).toInt(); +} + +void ShotcutSettings::setAudioOutCurve(int c) +{ + settings.setValue("filter/audioOutCurve", c); + emit audioOutCurveChanged(); +} + +/*! + \qmlproperty bool Settings::askOutputFilter + \brief Whether Shotcut should prompt before applying a filter to the output node. +*/ + +bool ShotcutSettings::askOutputFilter() const +{ + return settings.value("filter/askOutput", true).toBool(); +} + +void ShotcutSettings::setAskOutputFilter(bool b) +{ + settings.setValue("filter/askOutput", b); + emit askOutputFilterChanged(); +} + +bool ShotcutSettings::loudnessScopeShowMeter(const QString &meter) const +{ + return settings.value("scope/loudness/" + meter, true).toBool(); +} + +void ShotcutSettings::setLoudnessScopeShowMeter(const QString &meter, bool b) +{ + settings.setValue("scope/loudness/" + meter, b); +} + +void ShotcutSettings::setMarkerColor(const QColor &color) +{ + settings.setValue("markers/color", color.name()); +} + +QColor ShotcutSettings::markerColor() const +{ + return QColor(settings.value("markers/color", "green").toString()); +} + +void ShotcutSettings::setMarkersShowColumn(const QString &column, bool b) +{ + settings.setValue("markers/columns/" + column, b); +} + +bool ShotcutSettings::markersShowColumn(const QString &column) const +{ + return settings.value("markers/columns/" + column, true).toBool(); +} + +void ShotcutSettings::setMarkerSort(int column, Qt::SortOrder order) +{ + settings.setValue("markers/sortColumn", column); + settings.setValue("markers/sortOrder", order); +} + +int ShotcutSettings::getMarkerSortColumn() +{ + return settings.value("markers/sortColumn", -1).toInt(); +} + +Qt::SortOrder ShotcutSettings::getMarkerSortOrder() +{ + return (Qt::SortOrder) settings.value("markers/sortOrder", Qt::AscendingOrder).toInt(); +} + +int ShotcutSettings::drawMethod() const +{ +#ifdef Q_OS_WIN + return settings.value("opengl", Qt::AA_UseOpenGLES).toInt(); +#else + return settings.value("opengl", Qt::AA_UseDesktopOpenGL).toInt(); +#endif +} + +void ShotcutSettings::setDrawMethod(int i) +{ + settings.setValue("opengl", i); +} + +uint ShotcutSettings::gpuAdapterVendorId() const +{ + // PCI vendor id of the selected GPU (0x10DE NVIDIA, 0x1002 AMD, 0x8086 Intel). + // 0 means Automatic / system default. The vendor+device pair is the stable identity + // of the chosen GPU; the live DXGI adapter index is resolved from it at startup. + return settings.value("player/gpuAdapterVendorId", 0).toUInt(); +} + +void ShotcutSettings::setGpuAdapterVendorId(uint id) +{ + settings.setValue("player/gpuAdapterVendorId", id); +} + +uint ShotcutSettings::gpuAdapterDeviceId() const +{ + // PCI device id of the selected GPU; pairs with the vendor id to identify it. + return settings.value("player/gpuAdapterDeviceId", 0).toUInt(); +} + +void ShotcutSettings::setGpuAdapterDeviceId(uint id) +{ + settings.setValue("player/gpuAdapterDeviceId", id); +} + +bool ShotcutSettings::safeMode() const +{ + return settings.value("safeMode", false).toBool(); +} + +void ShotcutSettings::setSafeMode(bool value) +{ + settings.setValue("safeMode", value); +} + +bool ShotcutSettings::noUpgrade() const +{ + return settings.value("noupgrade", false).toBool(); +} + +void ShotcutSettings::setNoUpgrade(bool value) +{ + settings.setValue("noupgrade", value); +} + +bool ShotcutSettings::checkUpgradeAutomatic() +{ + return settings.value("checkUpgradeAutomatic", false).toBool(); +} + +void ShotcutSettings::setCheckUpgradeAutomatic(bool b) +{ + settings.setValue("checkUpgradeAutomatic", b); +} + +bool ShotcutSettings::askUpgradeAutomatic() +{ + return settings.value("askUpgradeAutmatic", true).toBool(); +} + +void ShotcutSettings::setAskUpgradeAutomatic(bool b) +{ + settings.setValue("askUpgradeAutmatic", b); +} + +bool ShotcutSettings::askChangeVideoMode() +{ + return settings.value("askChangeVideoMode", true).toBool(); +} + +void ShotcutSettings::setAskChangeVideoMode(bool b) +{ + settings.setValue("askChangeVideoMode", b); +} + +void ShotcutSettings::sync() +{ + settings.sync(); +} + +/*! + \qmlproperty string Settings::appDataLocation + \brief The path to the application data directory (read-only). +*/ + +QString ShotcutSettings::appDataLocation() const +{ + if (!m_appDataLocation.isEmpty()) + return m_appDataLocation; + else + return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation); +} + +void ShotcutSettings::setAppDataForSession(const QString &location) +{ + // This is intended to be called when using a command line option + // to set the AppData location. + appDataForSession = location; + if (instance) + instance.reset(new ShotcutSettings(location)); +} + +void ShotcutSettings::setAppDataLocally(const QString &location) +{ + // This is intended to be called when using a GUI action to set the + // the new AppData location. + + // Copy the existing settings if they exist. + if (!QFile::exists(location + SHOTCUT_INI_FILENAME)) { + QSettings newSettings(location + SHOTCUT_INI_FILENAME, QSettings::IniFormat); + foreach (const QString &key, settings.allKeys()) + newSettings.setValue(key, settings.value(key)); + newSettings.sync(); + } + + // Set the new location. + QSettings localSettings; + localSettings.setValue(APP_DATA_DIR_KEY, location); + localSettings.sync(); +} + +QStringList ShotcutSettings::layouts() const +{ + QStringList result; + for (const auto &s : settings.value("layout/layouts").toStringList()) { + if (!s.startsWith("__")) + result << s; + } + return result; +} + +bool ShotcutSettings::setLayout(const QString &name, + const QByteArray &geometry, + const QByteArray &state) +{ + bool isNew = false; + QStringList layouts = this->layouts(); + if (layouts.indexOf(name) == -1) { + isNew = true; + layouts.append(name); + settings.setValue("layout/layouts", layouts); + } + settings.setValue(QStringLiteral("layout/%1_%2").arg(name, "geometry"), geometry); + settings.setValue(QStringLiteral("layout/%1_%2").arg(name, "state"), state); + return isNew; +} + +QByteArray ShotcutSettings::layoutGeometry(const QString &name) +{ + QString key = QStringLiteral("layout/%1_geometry").arg(name); + return settings.value(key).toByteArray(); +} + +QByteArray ShotcutSettings::layoutState(const QString &name) +{ + QString key = QStringLiteral("layout/%1_state").arg(name); + return settings.value(key).toByteArray(); +} + +bool ShotcutSettings::removeLayout(const QString &name) +{ + QStringList list = layouts(); + int index = list.indexOf(name); + if (index > -1) { + list.removeAt(index); + if (list.isEmpty()) + settings.remove("layout/layouts"); + else + settings.setValue("layout/layouts", list); + settings.remove(QStringLiteral("layout/%1_%2").arg(name, "geometry")); + settings.remove(QStringLiteral("layout/%1_%2").arg(name, "state")); + return true; + } + return false; +} + +int ShotcutSettings::layoutMode() const +{ + return settings.value("layout/mode", -1).toInt(); +} + +void ShotcutSettings::setLayoutMode(int mode) +{ + settings.setValue("layout/mode", mode); +} + +bool ShotcutSettings::clearRecent() const +{ + return settings.value("clearRecent", false).toBool(); +} + +void ShotcutSettings::setClearRecent(bool b) +{ + settings.setValue("clearRecent", b); +} + +QString ShotcutSettings::projectsFolder() const +{ + return settings + .value("projectsFolder", QStandardPaths::standardLocations(QStandardPaths::MoviesLocation)) + .toString(); +} + +void ShotcutSettings::setProjectsFolder(const QString &path) +{ + settings.setValue("projectsFolder", path); +} + +QString ShotcutSettings::audioInput() const +{ + QString defaultValue = "default"; +#if defined(Q_OS_MAC) || defined(Q_OS_WIN) + for (const auto &deviceInfo : QMediaDevices::audioInputs()) { + defaultValue = deviceInfo.description(); + } +#endif + return settings.value("audioInput", defaultValue).toString(); +} + +void ShotcutSettings::setAudioInput(const QString &name) +{ + settings.setValue("audioInput", name); +} + +QString ShotcutSettings::videoInput() const +{ + return settings.value("videoInput").toString(); +} + +void ShotcutSettings::setVideoInput(const QString &name) +{ + settings.setValue("videoInput", name); +} + +QString ShotcutSettings::glaxnimatePath() const +{ + QDir dir(qApp->applicationDirPath()); + return settings.value("glaxnimatePath", dir.absoluteFilePath("glaxnimate")).toString(); +} + +void ShotcutSettings::setGlaxnimatePath(const QString &path) +{ + settings.setValue("glaxnimatePath", path); +} + +void ShotcutSettings::resetGlaxnimatePath() +{ + settings.remove("glaxnimatePath"); +} + +bool ShotcutSettings::exportRangeMarkers() const +{ + return settings.value("exportRangeMarkers", true).toBool(); +} + +void ShotcutSettings::setExportRangeMarkers(bool b) +{ + settings.setValue("exportRangeMarkers", b); +} + +bool ShotcutSettings::proxyEnabled() const +{ + return settings.value("proxy/enabled", false).toBool(); +} + +void ShotcutSettings::setProxyEnabled(bool b) +{ + settings.setValue("proxy/enabled", b); +} + +QString ShotcutSettings::proxyFolder() const +{ + QDir dir(appDataLocation()); + const char *subfolder = "proxies"; + if (!dir.cd(subfolder)) { + if (dir.mkdir(subfolder)) + dir.cd(subfolder); + } + return settings.value("proxy/folder", dir.path()).toString(); +} + +void ShotcutSettings::setProxyFolder(const QString &path) +{ + settings.setValue("proxy/folder", path); +} + +bool ShotcutSettings::proxyUseProjectFolder() const +{ + return settings.value("proxy/useProjectFolder", true).toBool(); +} + +void ShotcutSettings::setProxyUseProjectFolder(bool b) +{ + settings.setValue("proxy/useProjectFolder", b); +} + +bool ShotcutSettings::proxyUseHardware() const +{ + return settings.value("proxy/useHardware", false).toBool(); +} + +void ShotcutSettings::setProxyUseHardware(bool b) +{ + settings.setValue("proxy/useHardware", b); +} + +void ShotcutSettings::clearShortcuts(const QString &name) +{ + QString key = "shortcuts/" + name; + settings.remove(key); +} + +void ShotcutSettings::setShortcuts(const QString &name, const QList &shortcuts) +{ + QString key = "shortcuts/" + name; + QString shortcutSetting; + if (shortcuts.size() > 0) + shortcutSetting += shortcuts[0].toString(); + shortcutSetting += "||"; + if (shortcuts.size() > 1) + shortcutSetting += shortcuts[1].toString(); + settings.setValue(key, shortcutSetting); +} + +QList ShotcutSettings::shortcuts(const QString &name) +{ + QString key = "shortcuts/" + name; + QList shortcuts; + QString shortcutSetting = settings.value(key, "").toString(); + if (!shortcutSetting.isEmpty()) { + for (const QString &s : shortcutSetting.split("||")) + shortcuts << QKeySequence::fromString(s); + } + return shortcuts; +} + +double ShotcutSettings::slideshowImageDuration(double defaultSeconds) const +{ + return settings.value("slideshow/clipDuration", defaultSeconds).toDouble(); +} + +void ShotcutSettings::setSlideshowImageDuration(double seconds) +{ + settings.setValue("slideshow/clipDuration", seconds); +} + +double ShotcutSettings::slideshowAudioVideoDuration(double defaultSeconds) const +{ + return settings.value("slideshow/audioVideoDuration", defaultSeconds).toDouble(); +} + +void ShotcutSettings::setSlideshowAudioVideoDuration(double seconds) +{ + settings.setValue("slideshow/audioVideoDuration", seconds); +} + +int ShotcutSettings::slideshowAspectConversion(int defaultAspectConversion) const +{ + return settings.value("slideshow/aspectConversion", defaultAspectConversion).toInt(); +} + +void ShotcutSettings::setSlideshowAspectConversion(int aspectConversion) +{ + settings.setValue("slideshow/aspectConversion", aspectConversion); +} + +int ShotcutSettings::slideshowZoomPercent(int defaultZoomPercent) const +{ + return settings.value("slideshow/zoomPercent", defaultZoomPercent).toInt(); +} + +void ShotcutSettings::setSlideshowZoomPercent(int zoomPercent) +{ + settings.setValue("slideshow/zoomPercent", zoomPercent); +} + +double ShotcutSettings::slideshowTransitionDuration(double defaultTransitionDuration) const +{ + return settings.value("slideshow/transitionDuration", defaultTransitionDuration).toDouble(); +} + +void ShotcutSettings::setSlideshowTransitionDuration(double transitionDuration) +{ + settings.setValue("slideshow/transitionDuration", transitionDuration); +} + +int ShotcutSettings::slideshowTransitionStyle(int defaultTransitionStyle) const +{ + return settings.value("slideshow/transitionStyle", defaultTransitionStyle).toInt(); +} + +void ShotcutSettings::setSlideshowTransitionStyle(int transitionStyle) +{ + settings.setValue("slideshow/transitionStyle", transitionStyle); +} + +int ShotcutSettings::slideshowTransitionSoftness(int defaultTransitionStyle) const +{ + return settings.value("slideshow/transitionSoftness", defaultTransitionStyle).toInt(); +} + +void ShotcutSettings::setSlideshowTransitionSoftness(int transitionSoftness) +{ + settings.setValue("slideshow/transitionSoftness", transitionSoftness); +} + +/*! + \qmlproperty bool Settings::keyframesDragScrub + \brief Whether scrubbing occurs while dragging keyframes. +*/ + +bool ShotcutSettings::keyframesDragScrub() const +{ + return settings.value("keyframes/dragScrub", false).toBool(); +} + +void ShotcutSettings::setKeyframesDragScrub(bool b) +{ + settings.setValue("keyframes/dragScrub", b); + emit keyframesDragScrubChanged(); +} + +void ShotcutSettings::setSubtitlesShowColumn(const QString &column, bool b) +{ + settings.setValue("subtitles/columns/" + column, b); +} + +bool ShotcutSettings::subtitlesShowColumn(const QString &column) const +{ + return settings.value("subtitles/columns/" + column, true).toBool(); +} + +void ShotcutSettings::setSubtitlesTrackTimeline(bool b) +{ + settings.setValue("subtitles/trackTimeline", b); +} + +bool ShotcutSettings::subtitlesTrackTimeline() const +{ + return settings.value("subtitles/trackTimeline", true).toBool(); +} + +void ShotcutSettings::setSubtitlesShowPrevNext(bool b) +{ + settings.setValue("subtitles/showPrevNext", b); +} + +bool ShotcutSettings::subtitlesShowPrevNext() const +{ + return settings.value("subtitles/showPrevNext", true).toBool(); +} + +QString ShotcutSettings::speechLanguage() const +{ + return settings.value("speech/language", QStringLiteral("a")).toString(); +} + +void ShotcutSettings::setSpeechLanguage(const QString &code) +{ + settings.setValue("speech/language", code); +} + +QString ShotcutSettings::speechVoice() const +{ + return settings.value("speech/voice", QString()).toString(); +} + +void ShotcutSettings::setSpeechVoice(const QString &voiceId) +{ + settings.setValue("speech/voice", voiceId); +} + +double ShotcutSettings::speechSpeed() const +{ + return settings.value("speech/speed", 1.0).toDouble(); +} + +void ShotcutSettings::setSpeechSpeed(double speed) +{ + settings.setValue("speech/speed", speed); +} + +void ShotcutSettings::saveCustomColors() +{ + // QColorDialog supports up to 48 custom colors (16 in older versions) + QStringList colorList; + for (int i = 0; i < QColorDialog::customCount(); ++i) { + QColor color = QColorDialog::customColor(i); + if (color.isValid()) { + colorList.append(color.name(QColor::HexArgb)); + } else { + colorList.append(QString()); + } + } + settings.setValue("colorDialog/customColors", colorList); +} + +void ShotcutSettings::restoreCustomColors() +{ + QStringList colorList = settings.value("colorDialog/customColors").toStringList(); + for (int i = 0; i < colorList.size() && i < QColorDialog::customCount(); ++i) { + const QString &colorName = colorList.at(i); + if (!colorName.isEmpty()) { + QColor color(colorName); + if (color.isValid()) { + // Use rgba() to preserve alpha channel + QColorDialog::setCustomColor(i, color.rgba()); + } + } + } +} + +void ShotcutSettings::setWhisperExe(const QString &path) +{ + settings.setValue("subtitles/whisperExe", path); +} + +QString ShotcutSettings::whisperExe() +{ + QDir dir(qApp->applicationDirPath()); +#if defined(Q_OS_WIN) + auto exe = "whisper-cli.exe"; +#else + auto exe = "whisper-cli"; +#endif + return settings.value("subtitles/whisperExe", dir.absoluteFilePath(exe)).toString(); +} + +void ShotcutSettings::setWhisperModel(const QString &path) +{ + settings.setValue("subtitles/whisperModel", path); +} + +QString ShotcutSettings::whisperModel() +{ + QDir dataPath = QmlApplication::dataDir(); + dataPath.cd("shotcut/whisper_models"); + return settings.value("subtitles/whisperModel", "").toString(); +} + +void ShotcutSettings::setWhisperUseGpu(bool b) +{ + settings.setValue("subtitles/whisperUseGpu", b); +} + +bool ShotcutSettings::whisperUseGpu() const +{ + return settings.value("subtitles/whisperUseGpu", true).toBool(); +} + +void ShotcutSettings::setNotesZoom(int zoom) +{ + settings.setValue("notes/zoom", zoom); +} + +int ShotcutSettings::notesZoom() const +{ + return settings.value("notes/zoom", 0).toInt(); +} + +void ShotcutSettings::reset() +{ + for (auto &key : settings.allKeys()) { + settings.remove(key); + } +} + +int ShotcutSettings::undoLimit() const +{ + return settings.value("undoLimit", 50).toInt(); +} + +bool ShotcutSettings::warnLowMemory() const +{ + return settings.value("warnLowMemory", true).toBool(); +} + +int ShotcutSettings::backupPeriod() const +{ + return settings.value("backupPeriod", 24 * 60).toInt(); +} + +void ShotcutSettings::setBackupPeriod(int minutes) +{ + settings.setValue("backupPeriod", minutes); +} + +QDateTime ShotcutSettings::lastBackupDateTime(const QString &filePath) const +{ + return settings.value("lastBackupDateTimeMap").toMap().value(filePath).toDateTime(); +} + +void ShotcutSettings::setLastBackupDateTime(const QString &filePath, const QDateTime &dt) +{ + static const int kMaxBackupEntries = 100; + auto map = settings.value("lastBackupDateTimeMap").toMap(); + if (dt.isValid()) + map[filePath] = dt; + else + map.remove(filePath); + // Prune entries for files that no longer exist. + for (const auto &path : map.keys()) + if (!QFile::exists(path)) + map.remove(path); + // If still over the limit, remove the oldest entries. + while (map.size() > kMaxBackupEntries) { + map.erase(std::min_element(map.begin(), map.end(), [](const QVariant &a, const QVariant &b) { + return a.toDateTime() < b.toDateTime(); + })); + } + settings.setValue("lastBackupDateTimeMap", map); +} + +mlt_time_format ShotcutSettings::timeFormat() const +{ + return (mlt_time_format) settings.value("timeFormat", mlt_time_clock).toInt(); +} + +void ShotcutSettings::setTimeFormat(int format) +{ + settings.setValue("timeFormat", format); + emit timeFormatChanged(); +} + +bool ShotcutSettings::askFlatpakWrappers() +{ + return settings.value("flatpakWrappers", true).toBool(); +} + +void ShotcutSettings::setAskFlatpakWrappers(bool b) +{ + settings.setValue("flatpakWrappers", b); +} + +QString ShotcutSettings::dockerPath() const +{ +#if defined(Q_OS_MAC) + return settings.value("dockerPath", "/usr/local/bin/docker").toString(); +#elif defined(Q_OS_WIN) + return settings.value("dockerPath", "C:/Program Files/Docker/Docker/resources/bin/docker.exe") + .toString(); +#else + return settings.value("dockerPath", "docker").toString(); +#endif +} + +void ShotcutSettings::setDockerPath(const QString &path) +{ + settings.setValue("dockerPath", path); +} + +QString ShotcutSettings::chromiumPath() const +{ +#if defined(Q_OS_MAC) + return settings.value("chromiumPath", "/Applications/Google Chrome.app").toString(); +#elif defined(Q_OS_WIN) + return settings.value("chromiumPath", "C:/Program Files/Google/Chrome/Application/chrome.exe") + .toString(); +#else + return settings.value("chromiumPath", "/usr/bin/chromium-browser").toString(); +#endif +} + +void ShotcutSettings::setChromiumPath(const QString &path) +{ + settings.setValue("chromiumPath", path); +} + +QString ShotcutSettings::screenRecorderPath() const +{ + return settings.value("screenRecorderPath", "obs").toString(); +} + +void ShotcutSettings::setScreenRecorderPath(const QString &path) +{ + settings.setValue("screenRecorderPath", path); +} diff --git a/src/settings.h b/src/settings.h index f7a68fc66c..d73e43434f 100644 --- a/src/settings.h +++ b/src/settings.h @@ -302,6 +302,10 @@ class ShotcutSettings : public QObject // general continued int drawMethod() const; void setDrawMethod(int); + uint gpuAdapterVendorId() const; + void setGpuAdapterVendorId(uint); + uint gpuAdapterDeviceId() const; + void setGpuAdapterDeviceId(uint); bool safeMode() const; void setSafeMode(bool value); bool noUpgrade() const; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000000..1aadb88969 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,12 @@ +find_package(Qt6 6.4 REQUIRED COMPONENTS Core Test) + +add_executable(test_gpuinfo + test_gpuinfo.cpp + ${CMAKE_SOURCE_DIR}/src/gpuinfo.cpp +) +target_include_directories(test_gpuinfo PRIVATE ${CMAKE_SOURCE_DIR}/src) +target_link_libraries(test_gpuinfo PRIVATE Qt6::Core Qt6::Test) +if(WIN32) + target_link_libraries(test_gpuinfo PRIVATE dxgi dxguid) +endif() +add_test(NAME gpuinfo COMMAND test_gpuinfo) diff --git a/tests/test_gpuinfo.cpp b/tests/test_gpuinfo.cpp new file mode 100644 index 0000000000..65e68b8efb --- /dev/null +++ b/tests/test_gpuinfo.cpp @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2026 Meltytech, LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gpuinfo.h" + +#include +#include + +class TestGpuInfo : public QObject +{ + Q_OBJECT + +private slots: + // ---- preferredHardwareVcodec (pure logic) ---------------------------------- + + void prefersVendorFamily() + { + const QStringList hw{"h264_nvenc", "h264_amf", "hevc_nvenc", "hevc_amf"}; + QCOMPARE(preferredHardwareVcodec(hw, "libx264", kGpuVendorNvidia, false), + QString("h264_nvenc")); + QCOMPARE(preferredHardwareVcodec(hw, "libx265", kGpuVendorNvidia, false), + QString("hevc_nvenc")); + QCOMPARE(preferredHardwareVcodec(hw, "libx264", kGpuVendorAmd, false), + QString("h264_amf")); + QCOMPARE(preferredHardwareVcodec(hw, "libx265", kGpuVendorAmd, false), + QString("hevc_amf")); + } + + void prefersQsvForIntel() + { + const QStringList hw{"h264_nvenc", "h264_qsv"}; + QCOMPARE(preferredHardwareVcodec(hw, "libx264", kGpuVendorIntel, false), + QString("h264_qsv")); + } + + void orderIndependent() + { + // The NVIDIA encoder must win even when the AMD one comes first in the list. + const QStringList hw{"h264_amf", "h264_nvenc"}; + QCOMPARE(preferredHardwareVcodec(hw, "libx264", kGpuVendorNvidia, false), + QString("h264_nvenc")); + } + + void fallsBackToFirstWhenFamilyMissing() + { + // NVIDIA selected but only an AMF encoder is available -> use it. + const QStringList hw{"h264_amf"}; + QCOMPARE(preferredHardwareVcodec(hw, "libx264", kGpuVendorNvidia, false), + QString("h264_amf")); + } + + void fallsBackToFirstWhenAutomatic() + { + // vendorId 0 (Automatic) -> first type-compatible encoder. + const QStringList hw{"h264_amf", "h264_nvenc"}; + QCOMPARE(preferredHardwareVcodec(hw, "libx264", 0u, false), QString("h264_amf")); + } + + void matchesCodecType() + { + const QStringList hw{"h264_nvenc", "hevc_nvenc", "av1_nvenc", "vp9_qsv"}; + QCOMPARE(preferredHardwareVcodec(hw, "libsvtav1", kGpuVendorNvidia, false), + QString("av1_nvenc")); + // No vp9 NVENC exists -> fall back to the first vp9 encoder. + QCOMPARE(preferredHardwareVcodec(hw, "libvpx-vp9", kGpuVendorNvidia, false), + QString("vp9_qsv")); + } + + void skipsH264HardwareFor10bit() + { + // No H.264 hardware encoder supports 10-bit, so it must not be selected; HEVC + // hardware encoders are unaffected (they do support 10-bit). + const QStringList hw{"h264_nvenc", "hevc_nvenc"}; + QVERIFY(preferredHardwareVcodec(hw, "libx264", kGpuVendorNvidia, true).isEmpty()); + QCOMPARE(preferredHardwareVcodec(hw, "libx265", kGpuVendorNvidia, true), + QString("hevc_nvenc")); + } + + void returnsEmptyWhenNoneCompatible() + { + const QStringList hw{"h264_nvenc", "hevc_nvenc"}; + QVERIFY(preferredHardwareVcodec(hw, "libvpx-vp9", kGpuVendorNvidia, false).isEmpty()); + QVERIFY(preferredHardwareVcodec(QStringList(), "libx264", kGpuVendorNvidia, false).isEmpty()); + } + + // ---- enumerateGpuAdapters / gpuAdapterIndexFor (hardware dependent) --------- + // These assert invariants only; on a machine/CI without a usable GPU the list is + // empty and the loops are simply skipped. + + void enumerationInvariants() + { + const QList adapters = enumerateGpuAdapters(); + QSet identities; + for (const GpuAdapterInfo &g : adapters) { + QVERIFY2(g.index >= 0, "adapter index must be non-negative"); + QVERIFY2(g.vendorId != 0, "adapter vendorId must be set"); + QVERIFY2(!g.name.isEmpty(), "adapter name must be set"); + // De-duplication: each physical GPU appears at most once. + const QString id = QStringLiteral("%1:%2").arg(g.vendorId).arg(g.deviceId); + QVERIFY2(!identities.contains(id), "adapters must be de-duplicated by identity"); + identities.insert(id); + } + } + + void indexResolution() + { + const QList adapters = enumerateGpuAdapters(); + for (const GpuAdapterInfo &g : adapters) { + // A present GPU resolves to a valid live index... + QVERIFY(gpuAdapterIndexFor(g.vendorId, g.deviceId) >= 0); + } + // ...an absent GPU and the "automatic" sentinel resolve to -1. + QCOMPARE(gpuAdapterIndexFor(0xFFFFu, 0xFFFFu), -1); + QCOMPARE(gpuAdapterIndexFor(0u, 0u), -1); + } +}; + +QTEST_MAIN(TestGpuInfo) +#include "test_gpuinfo.moc"