From 23cf7e31a9b5c2eec533d76ee3bf85e73613ba19 Mon Sep 17 00:00:00 2001 From: Tri Vo Khac Date: Fri, 13 Nov 2026 20:49:19 +0700 Subject: [PATCH] fix(mpv): auto-select subtitle and audio tracks on file open --- autotests/CMakeLists.txt | 5 ++ autotests/trackselectiontest.cpp | 90 +++++++++++++++++++ src/CMakeLists.txt | 2 + src/mpv/mpvitem.cpp | 66 ++++++++++++++ src/mpv/mpvitem.h | 15 ++++ src/mpv/trackselection.cpp | 134 +++++++++++++++++++++++++++++ src/mpv/trackselection.h | 41 +++++++++ src/qml/Actions.qml | 2 +- src/qml/Main.qml | 5 ++ src/qml/MpvVideo.qml | 3 + src/qml/PlaybackSettings.qml | 20 +++++ src/qml/SubtitlesSettings.qml | 43 +++++++++ src/settings/qtpotatosettings.kcfg | 12 +++ 13 files changed, 437 insertions(+), 1 deletion(-) create mode 100644 autotests/trackselectiontest.cpp create mode 100644 src/mpv/trackselection.cpp create mode 100644 src/mpv/trackselection.h diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index 3b170b6..8b350d8 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -19,3 +19,8 @@ ecm_add_test(playlistmodeltest.cpp ../src/playlist/playlistmodel.cpp LINK_LIBRARIES Qt6::Qml Qt6::Test ) target_include_directories(playlistmodeltest PRIVATE ../src/playlist) + +ecm_add_test(trackselectiontest.cpp + TEST_NAME trackselectiontest + LINK_LIBRARIES qtpotato_core Qt6::Test +) diff --git a/autotests/trackselectiontest.cpp b/autotests/trackselectiontest.cpp new file mode 100644 index 0000000..daff88f --- /dev/null +++ b/autotests/trackselectiontest.cpp @@ -0,0 +1,90 @@ +#include "trackselection.h" + +#include + +using namespace Qt::Literals::StringLiterals; +using TrackSelection::Track; + +namespace +{ +Track sub(int id, const QString &lang, const QString &codec = u"subrip"_s, bool forced = false, bool isDefault = false) +{ + return {.id = id, .lang = lang, .codec = codec, .forced = forced, .isDefault = isDefault}; +} +} + +class TrackSelectionTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void matchesLanguageTags() + { + QVERIFY(TrackSelection::sameLanguage(u"en"_s, u"eng"_s)); + QVERIFY(TrackSelection::sameLanguage(u"en-US"_s, u"English"_s)); + QVERIFY(TrackSelection::sameLanguage(u"ger"_s, u"de"_s)); + QVERIFY(TrackSelection::sameLanguage(u"vie"_s, u"vietnamese"_s)); + QVERIFY(!TrackSelection::sameLanguage(u"en"_s, u"ru"_s)); + QVERIFY(!TrackSelection::sameLanguage(u""_s, u""_s)); + QVERIFY(!TrackSelection::sameLanguage(u"en"_s, u""_s)); + } + + void detectsBitmapSubtitles() + { + QVERIFY(TrackSelection::isBitmapSubtitle(u"hdmv_pgs_subtitle"_s)); + QVERIFY(TrackSelection::isBitmapSubtitle(u"dvd_subtitle"_s)); + QVERIFY(!TrackSelection::isBitmapSubtitle(u"subrip"_s)); + QVERIFY(!TrackSelection::isBitmapSubtitle(u"ass"_s)); + QVERIFY(!TrackSelection::isBitmapSubtitle(u""_s)); + } + + // VisionQuest release: Russian dub by default, two Russian subtitles before the + // English one, no default subtitle, so mpv turns none on. + void fallsBackInLanguageOrder() + { + const QList tracks{sub(1, u"rus"_s), sub(2, u"rus"_s), sub(3, u"eng"_s)}; + QCOMPARE(TrackSelection::pickSubtitle(tracks, {}, {u"vi"_s, u"en"_s, u"rus"_s}, 0, true), 3); + QCOMPARE(TrackSelection::pickSubtitle(tracks, {}, {u"vi"_s, u"en"_s, u"rus"_s}, 0, false), 0); + QCOMPARE(TrackSelection::pickSubtitle(tracks, {}, {u"ru"_s, u"en"_s}, 0, true), 1); + QCOMPARE(TrackSelection::pickSubtitle(tracks, {}, {u"ja"_s}, 0, true), 1); + } + + void fallbackPrefersDefaultFullTextTracks() + { + const QList tracks{sub(1, u"en"_s, u"subrip"_s, true), sub(2, u"en"_s, u"hdmv_pgs_subtitle"_s), sub(3, u"en"_s), sub(4, u"fr"_s, u"subrip"_s, false, true)}; + QCOMPARE(TrackSelection::pickSubtitle(tracks, {}, {u"en"_s}, 0, true), 3); + QCOMPARE(TrackSelection::pickSubtitle(tracks, {}, {u"ja"_s}, 0, true), 4); + // Only forced subtitles: leave subtitles off. + QCOMPARE(TrackSelection::pickSubtitle({sub(1, u"en"_s, u"subrip"_s, true)}, {}, {u"en"_s}, 0, true), 0); + } + + void preferredLanguagesWinOverMpvChoice() + { + const QList tracks{sub(1, u"ru"_s), sub(2, u"vi"_s), sub(3, u"en"_s)}; + QCOMPARE(TrackSelection::pickSubtitle(tracks, {u"vi"_s, u"en"_s}, {u"en"_s}, 1, true), 2); + QCOMPARE(TrackSelection::pickSubtitle(tracks, {u"de"_s, u"English"_s}, {u"en"_s}, 1, true), 3); + // No preferred language present: keep mpv's choice. + QCOMPARE(TrackSelection::pickSubtitle(tracks, {u"de"_s}, {u"en"_s}, 1, true), 1); + } + + void picksAudioByPreference() + { + const QList tracks{sub(1, u"en"_s, u"aac"_s), sub(2, u"jpn"_s, u"aac"_s)}; + QCOMPARE(TrackSelection::pickAudio(tracks, {u"ja"_s}, 1), 2); + QCOMPARE(TrackSelection::pickAudio(tracks, {u"de"_s}, 1), 1); + QCOMPARE(TrackSelection::pickAudio(tracks, {}, 1), 1); + } + + void picksTextTranslationSource() + { + const QList tracks{sub(1, u"en"_s, u"hdmv_pgs_subtitle"_s), sub(2, u"ru"_s), sub(3, u"en"_s)}; + QCOMPARE(TrackSelection::pickTranslationSource(tracks, {u"en"_s}, 2), 2); + QCOMPARE(TrackSelection::pickTranslationSource(tracks, {u"en"_s}, 1), 3); + QCOMPARE(TrackSelection::pickTranslationSource(tracks, {u"en"_s}, 0), 3); + QCOMPARE(TrackSelection::pickTranslationSource(tracks, {u"ru"_s}, 0), 2); + QCOMPARE(TrackSelection::pickTranslationSource({sub(1, u"en"_s, u"dvd_subtitle"_s)}, {u"en"_s}, 0), 0); + } +}; + +QTEST_GUILESS_MAIN(TrackSelectionTest) +#include "trackselectiontest.moc" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b335513..da3aad5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,6 +3,8 @@ add_library(qtpotato_core STATIC mpv/mpvuserconfig.cpp mpv/mpvuserconfig.h + mpv/trackselection.cpp + mpv/trackselection.h subtitles/subtitleparser.cpp subtitles/subtitleparser.h subtitles/subtitleextractor.cpp diff --git a/src/mpv/mpvitem.cpp b/src/mpv/mpvitem.cpp index 6aaf7d0..fd6e409 100644 --- a/src/mpv/mpvitem.cpp +++ b/src/mpv/mpvitem.cpp @@ -1,11 +1,13 @@ #include "mpvitem.h" #include "mpvuserconfig.h" +#include "trackselection.h" #include #include #include +#include #include #include @@ -25,6 +27,33 @@ QVariant trackIdValue(int id) { return id > 0 ? QVariant(id) : QVariant(QStringLiteral("no")); } + +QList selectionTracks(const QVariantList &tracks) +{ + QList result; + for (const QVariant &entry : tracks) { + const QVariantMap track = entry.toMap(); + result.append({ + .id = track.value(QStringLiteral("id")).toInt(), + .lang = track.value(QStringLiteral("lang")).toString(), + .codec = track.value(QStringLiteral("codec")).toString(), + .forced = track.value(QStringLiteral("forced")).toBool(), + .isDefault = track.value(QStringLiteral("default")).toBool(), + }); + } + return result; +} + +QString trackLanguage(const QVariantList &tracks, int id) +{ + for (const QVariant &entry : tracks) { + const QVariantMap track = entry.toMap(); + if (track.value(QStringLiteral("id")).toInt() == id) { + return track.value(QStringLiteral("lang")).toString(); + } + } + return {}; +} } MpvItem::MpvItem(QQuickItem *parent) @@ -64,6 +93,7 @@ MpvItem::MpvItem(QQuickItem *parent) this, [this] { setFileLoaded(true); + selectInitialTracks(); }, Qt::QueuedConnection); connect( @@ -338,6 +368,37 @@ void MpvItem::showTranslatedSubtitleFile(const QUrl &url, const QString &title) commandAsync(QStringList{QStringLiteral("sub-add"), path, QStringLiteral("auto"), title}); } +int MpvItem::translationSourceId() const +{ + // English first: it is the usual original, and dubbed releases often default to + // subtitles translated from it. + return TrackSelection::pickTranslationSource(selectionTracks(m_subtitleTracks), + {QStringLiteral("en"), trackLanguage(m_audioTracks, m_audioId)}, + m_subtitleId); +} + +void MpvItem::selectInitialTracks() +{ + // The observed track-list may still describe the previous file; read it directly. + updateTracks(getProperty(QStringLiteral("track-list")).toList()); + + const int audioId = TrackSelection::pickAudio(selectionTracks(m_audioTracks), m_preferredAudioLanguages, m_audioId); + if (audioId != m_audioId) { + setAudioId(audioId); + } + // Subtitles are for reading: the user's own language, then English, before the + // audio language. Dubbed releases often default to a dub with matching subtitles. + const QStringList fallbackLanguages{QLocale::languageToCode(QLocale::system().language()), QStringLiteral("en"), trackLanguage(m_audioTracks, audioId)}; + const int subtitleId = TrackSelection::pickSubtitle(selectionTracks(m_subtitleTracks), + m_preferredSubtitleLanguages, + fallbackLanguages, + m_subtitleId, + m_autoSelectSubtitle); + if (subtitleId != m_subtitleId) { + setSubtitleId(subtitleId); + } +} + void MpvItem::showOnlySubtitle(int id) { setSubtitleId(id); @@ -484,6 +545,8 @@ void MpvItem::updateTracks(const QVariantList &trackList) {QStringLiteral("title"), track.value(QStringLiteral("title")).toString()}, {QStringLiteral("lang"), track.value(QStringLiteral("lang")).toString()}, {QStringLiteral("codec"), track.value(QStringLiteral("codec")).toString()}, + {QStringLiteral("forced"), track.value(QStringLiteral("forced")).toBool()}, + {QStringLiteral("default"), track.value(QStringLiteral("default")).toBool()}, }); continue; } @@ -505,6 +568,9 @@ void MpvItem::updateTracks(const QVariantList &trackList) {QStringLiteral("title"), track.value(QStringLiteral("title")).toString()}, {QStringLiteral("lang"), track.value(QStringLiteral("lang")).toString()}, {QStringLiteral("codec"), track.value(QStringLiteral("codec")).toString()}, + {QStringLiteral("bitmap"), TrackSelection::isBitmapSubtitle(track.value(QStringLiteral("codec")).toString())}, + {QStringLiteral("forced"), track.value(QStringLiteral("forced")).toBool()}, + {QStringLiteral("default"), track.value(QStringLiteral("default")).toBool()}, {QStringLiteral("external"), track.value(QStringLiteral("external")).toBool()}, {QStringLiteral("externalFilename"), track.value(QStringLiteral("external-filename")).toString()}, {QStringLiteral("ffIndex"), track.value(QStringLiteral("ff-index"), -1).toInt()}, diff --git a/src/mpv/mpvitem.h b/src/mpv/mpvitem.h index f37a590..94fbc8b 100644 --- a/src/mpv/mpvitem.h +++ b/src/mpv/mpvitem.h @@ -51,6 +51,13 @@ class MpvItem : public MpvAbstractItem Q_PROPERTY(QStringList loadedUserConfigFiles READ loadedUserConfigFiles NOTIFY userConfigApplied) Q_PROPERTY(QStringList skippedUserOptions READ skippedUserOptions NOTIFY userConfigApplied) + // Tracks turned on when a file opens; see TrackSelection. Languages are ISO 639 codes + // or English names, in order of preference. + Q_PROPERTY(QStringList preferredAudioLanguages MEMBER m_preferredAudioLanguages NOTIFY trackPreferencesChanged) + Q_PROPERTY(QStringList preferredSubtitleLanguages MEMBER m_preferredSubtitleLanguages NOTIFY trackPreferencesChanged) + // Turn on a subtitle when mpv would open the file with none. + Q_PROPERTY(bool autoSelectSubtitle MEMBER m_autoSelectSubtitle NOTIFY trackPreferencesChanged) + public: explicit MpvItem(QQuickItem *parent = nullptr); ~MpvItem() override = default; @@ -106,6 +113,9 @@ public: // Makes \a url the only subtitle shown, adding it first (named \a title) unless it is // already loaded. Q_INVOKABLE void showTranslatedSubtitleFile(const QUrl &url, const QString &title); + // Id of the subtitle to translate: the shown one if it is text, else the best text + // subtitle. 0 when the file has no text subtitle. + Q_INVOKABLE int translationSourceId() const; Q_SIGNALS: void currentUrlChanged(); @@ -130,6 +140,7 @@ Q_SIGNALS: void loadUserConfigChanged(); // mpv.conf was queued for loading; options set after this take precedence over it. void userConfigApplied(); + void trackPreferencesChanged(); private: void onPropertyChanged(const QString &property, const QVariant &value); @@ -137,6 +148,7 @@ private: void updateTracks(const QVariantList &trackList); void applyUserConfig(); void showOnlySubtitle(int id); + void selectInitialTracks(); bool m_ready = false; QUrl m_pendingUrl; @@ -165,4 +177,7 @@ private: bool m_loadUserConfig = false; QStringList m_loadedUserConfigFiles; QStringList m_skippedUserOptions; + QStringList m_preferredAudioLanguages; + QStringList m_preferredSubtitleLanguages; + bool m_autoSelectSubtitle = true; }; diff --git a/src/mpv/trackselection.cpp b/src/mpv/trackselection.cpp new file mode 100644 index 0000000..3fd4827 --- /dev/null +++ b/src/mpv/trackselection.cpp @@ -0,0 +1,134 @@ +#include "trackselection.h" + +#include +#include + +extern "C" { +#include +} + +#include + +namespace +{ +using TrackSelection::Track; + +QLocale::Language languageOf(QString tag) +{ + tag = tag.trimmed(); + if (const auto separator = tag.indexOf(QRegularExpression(QStringLiteral("[-_]"))); separator > 0) { + tag.truncate(separator); + } + if (tag.isEmpty()) { + return QLocale::AnyLanguage; + } + if (const auto language = QLocale::codeToLanguage(tag, QLocale::AnyLanguageCode); language != QLocale::AnyLanguage) { + return language; + } + for (int i = QLocale::C + 1; i <= QLocale::LastLanguage; ++i) { + const auto candidate = static_cast(i); + if (QLocale::languageToString(candidate).compare(tag, Qt::CaseInsensitive) == 0) { + return candidate; + } + } + return QLocale::AnyLanguage; +} + +// First full (not forced) subtitle matching \a matches, a text one before a bitmap one. +int firstFull(const QList &tracks, const std::function &matches) +{ + for (const bool textOnly : {true, false}) { + for (const Track &track : tracks) { + if (!track.forced && matches(track) && !(textOnly && TrackSelection::isBitmapSubtitle(track.codec))) { + return track.id; + } + } + } + return 0; +} + +int fallbackSubtitle(const QList &tracks, const QStringList &languages) +{ + for (const QString &language : languages) { + if (const int id = firstFull(tracks, [&](const Track &track) { + return TrackSelection::sameLanguage(track.lang, language); + })) { + return id; + } + } + if (const int id = firstFull(tracks, [](const Track &track) { + return track.isDefault; + })) { + return id; + } + return firstFull(tracks, [](const Track &) { + return true; + }); +} +} + +namespace TrackSelection +{ +bool isBitmapSubtitle(const QString &codec) +{ + const AVCodecDescriptor *descriptor = avcodec_descriptor_get_by_name(codec.toLatin1().constData()); + return descriptor && (descriptor->props & AV_CODEC_PROP_BITMAP_SUB); +} + +bool sameLanguage(const QString &a, const QString &b) +{ + const QLocale::Language first = languageOf(a); + const QLocale::Language second = languageOf(b); + if (first != QLocale::AnyLanguage || second != QLocale::AnyLanguage) { + return first == second; + } + // Tags QLocale doesn't know ("und", "mis", made-up ones) only match themselves. + return !a.trimmed().isEmpty() && a.trimmed().compare(b.trimmed(), Qt::CaseInsensitive) == 0; +} + +int pickAudio(const QList &tracks, const QStringList &preferredLanguages, int currentId) +{ + for (const QString &language : preferredLanguages) { + for (const Track &track : tracks) { + if (sameLanguage(track.lang, language)) { + return track.id; + } + } + } + return currentId; +} + +int pickSubtitle(const QList &tracks, const QStringList &preferredLanguages, const QStringList &fallbackLanguages, int currentId, bool fallback) +{ + for (const QString &language : preferredLanguages) { + if (const int id = firstFull(tracks, [&](const Track &track) { + return sameLanguage(track.lang, language); + })) { + return id; + } + } + if (currentId > 0 || !fallback) { + return currentId; + } + return fallbackSubtitle(tracks, fallbackLanguages); +} + +int pickTranslationSource(const QList &tracks, const QStringList &sourceLanguages, int currentId) +{ + QList text; + for (const Track &track : tracks) { + if (isBitmapSubtitle(track.codec)) { + continue; + } + if (track.id == currentId) { + return currentId; + } + text.append(track); + } + if (const int id = fallbackSubtitle(text, sourceLanguages)) { + return id; + } + // Only forced text subtitles left; still better than nothing to translate. + return text.isEmpty() ? 0 : text.constFirst().id; +} +} diff --git a/src/mpv/trackselection.h b/src/mpv/trackselection.h new file mode 100644 index 0000000..36de0ff --- /dev/null +++ b/src/mpv/trackselection.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include +#include + +// Which audio and subtitle tracks to turn on when a file opens. mpv only turns on a +// subtitle that is flagged default or matches --slang, so many files open with none. +namespace TrackSelection +{ +struct Track { + int id = 0; + QString lang; + QString codec; + bool forced = false; + bool isDefault = false; +}; + +// True for picture-based subtitles (PGS, VobSub, DVB), which can't be translated. +// \a codec is the FFmpeg codec name that mpv reports. +bool isBitmapSubtitle(const QString &codec); + +// Compares language tags written as ISO 639-1/2 codes ("en", "eng"), with a region +// ("en-US") or as English names ("English"). +bool sameLanguage(const QString &a, const QString &b); + +// The first audio track in a \a preferredLanguages language, else \a currentId. +int pickAudio(const QList &tracks, const QStringList &preferredLanguages, int currentId); + +// The subtitle to turn on when the file opens. A track in one of \a preferredLanguages +// wins over mpv's own choice \a currentId; without one, \a currentId is kept. When mpv +// chose nothing and \a fallback is set, picks a full (not forced) subtitle in the first +// of \a fallbackLanguages that has one, then one flagged default, then the first one. +// Text subtitles win over bitmap ones in the same step. Returns 0 for no subtitle. +int pickSubtitle(const QList &tracks, const QStringList &preferredLanguages, const QStringList &fallbackLanguages, int currentId, bool fallback); + +// The subtitle to translate: \a currentId when it is a text subtitle, else a full text +// subtitle in the first of \a sourceLanguages that has one, then one flagged default, +// then the first one. 0 when there is no text subtitle. +int pickTranslationSource(const QList &tracks, const QStringList &sourceLanguages, int currentId); +} diff --git a/src/qml/Actions.qml b/src/qml/Actions.qml index 0b0e524..b09a49e 100644 --- a/src/qml/Actions.qml +++ b/src/qml/Actions.qml @@ -389,7 +389,7 @@ Item { } property Action translateSubtitle: Action { - enabled: root.shortcutsEnabled && root.hasFile && root.mpv.subtitleId > 0 && !root.translator.busy + enabled: root.shortcutsEnabled && root.hasFile && root.mpv.subtitleTracks.some(track => !track.bitmap) && !root.translator.busy text: KI18n.i18nc("@action", "Translate Subtitle into %1", QtPotatoSettings.targetLanguage) icon.name: "translate" shortcut: "Ctrl+T" diff --git a/src/qml/Main.qml b/src/qml/Main.qml index 6dca9a7..4949163 100644 --- a/src/qml/Main.qml +++ b/src/qml/Main.qml @@ -51,6 +51,11 @@ ApplicationWindow { if (track && translatedSubtitleUrls.includes(track.externalFilename) && lastTranslationTrack) { track = video.subtitleTracks.find(track => track.id === lastTranslationTrack.id) } + // No subtitle shown, or a picture-based one: translate the best text subtitle. + if (!track || track.bitmap) { + const sourceId = video.translationSourceId() + track = video.subtitleTracks.find(track => track.id === sourceId) + } if (track) { lastTranslationTrack = track lastTranslationVideo = video.currentUrl diff --git a/src/qml/MpvVideo.qml b/src/qml/MpvVideo.qml index e11ed8b..f0c366a 100644 --- a/src/qml/MpvVideo.qml +++ b/src/qml/MpvVideo.qml @@ -34,6 +34,9 @@ MpvItem { Kirigami.Theme.inherit: false loadUserConfig: QtPotatoSettings.loadMpvConfig + preferredAudioLanguages: QtPotatoSettings.preferredAudioLanguages + preferredSubtitleLanguages: QtPotatoSettings.preferredSubtitleLanguages + autoSelectSubtitle: QtPotatoSettings.autoSelectSubtitle // Set once the saved volume is applied; mpv reports its default volume before that. property bool volumeRestored: false diff --git a/src/qml/PlaybackSettings.qml b/src/qml/PlaybackSettings.qml index 84883c6..0f27f97 100644 --- a/src/qml/PlaybackSettings.qml +++ b/src/qml/PlaybackSettings.qml @@ -72,5 +72,25 @@ SettingsBasePage { QtPotatoSettings.save() } } + + RowLayout { + spacing: Kirigami.Units.smallSpacing + Kirigami.FormData.label: KI18n.i18nc("@label:textbox", "Preferred languages") + Kirigami.FormData.buddyFor: audioLanguagesField + + TextField { + id: audioLanguagesField + text: QtPotatoSettings.preferredAudioLanguages.join(", ") + placeholderText: KI18n.i18nc("@info:placeholder language codes", "e.g. ja, en") + onEditingFinished: { + QtPotatoSettings.preferredAudioLanguages = text.split(/[,;\s]+/).filter(Boolean) + QtPotatoSettings.save() + } + } + + ToolTipButton { + toolTipText: KI18n.i18nc("@info:tooltip", "Audio track turned on when a file opens, first match wins. Use language codes or English names, separated by commas. Leave empty to keep the file's default track.") + } + } } } diff --git a/src/qml/SubtitlesSettings.qml b/src/qml/SubtitlesSettings.qml index 43c7fee..b525e95 100644 --- a/src/qml/SubtitlesSettings.qml +++ b/src/qml/SubtitlesSettings.qml @@ -13,6 +13,49 @@ SettingsBasePage { Kirigami.FormLayout { + SettingsHeader { + text: KI18n.i18nc("@title", "Track Selection") + Layout.fillWidth: true + } + + RowLayout { + spacing: Kirigami.Units.smallSpacing + Kirigami.FormData.label: KI18n.i18nc("@label:textbox", "Preferred languages") + Kirigami.FormData.buddyFor: subtitleLanguagesField + + TextField { + id: subtitleLanguagesField + text: QtPotatoSettings.preferredSubtitleLanguages.join(", ") + placeholderText: KI18n.i18nc("@info:placeholder language codes", "e.g. vi, en") + onEditingFinished: { + QtPotatoSettings.preferredSubtitleLanguages = text.split(/[,;\s]+/).filter(Boolean) + QtPotatoSettings.save() + } + } + + ToolTipButton { + toolTipText: KI18n.i18nc("@info:tooltip", "Subtitle turned on when a file opens, first match wins. Use language codes or English names, separated by commas. Subtitle files next to the video count too.") + } + } + + CheckBox { + text: KI18n.i18nc("@option:check", "Turn on a subtitle when none matches") + checked: QtPotatoSettings.autoSelectSubtitle + onToggled: { + QtPotatoSettings.autoSelectSubtitle = checked + QtPotatoSettings.save() + } + + ToolTip.text: KI18n.i18nc("@info:tooltip", "Many files have no default subtitle, so they open with subtitles off. Picks one in the system language instead, then English, then the audio language.") + ToolTip.visible: hovered + ToolTip.delay: Kirigami.Units.toolTipDelay + } + + SettingsHeader { + text: KI18n.i18nc("@title", "Appearance") + Layout.fillWidth: true + } + RowLayout { spacing: Kirigami.Units.smallSpacing Kirigami.FormData.label: KI18n.i18nc("@label:spinbox", "Font size") diff --git a/src/settings/qtpotatosettings.kcfg b/src/settings/qtpotatosettings.kcfg index b537f37..3750a8b 100644 --- a/src/settings/qtpotatosettings.kcfg +++ b/src/settings/qtpotatosettings.kcfg @@ -64,6 +64,18 @@ 1 600 + + + + + + + + + + + true +