fix(mpv): auto-select subtitle and audio tracks on file open

This commit is contained in:
tri committed 2026-11-13 20:49:19 +07:00
1 parent 32c686e0e3
commit 23cf7e31a9
13 files changed
+437 -1

No files matched your search

+5
View File
@@ -19,3 +19,8 @@ ecm_add_test(playlistmodeltest.cpp ../src/playlist/playlistmodel.cpp
LINK_LIBRARIES Qt6::Qml Qt6::Test LINK_LIBRARIES Qt6::Qml Qt6::Test
) )
target_include_directories(playlistmodeltest PRIVATE ../src/playlist) target_include_directories(playlistmodeltest PRIVATE ../src/playlist)
ecm_add_test(trackselectiontest.cpp
TEST_NAME trackselectiontest
LINK_LIBRARIES qtpotato_core Qt6::Test
)
+90
View File
@@ -0,0 +1,90 @@
#include "trackselection.h"
#include <QTest>
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<Track> 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<Track> 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<Track> 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<Track> 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<Track> 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"
+2
View File
@@ -3,6 +3,8 @@
add_library(qtpotato_core STATIC add_library(qtpotato_core STATIC
mpv/mpvuserconfig.cpp mpv/mpvuserconfig.cpp
mpv/mpvuserconfig.h mpv/mpvuserconfig.h
mpv/trackselection.cpp
mpv/trackselection.h
subtitles/subtitleparser.cpp subtitles/subtitleparser.cpp
subtitles/subtitleparser.h subtitles/subtitleparser.h
subtitles/subtitleextractor.cpp subtitles/subtitleextractor.cpp
+66
View File
@@ -1,11 +1,13 @@
#include "mpvitem.h" #include "mpvitem.h"
#include "mpvuserconfig.h" #include "mpvuserconfig.h"
#include "trackselection.h"
#include <MpvController> #include <MpvController>
#include <QDir> #include <QDir>
#include <QFile> #include <QFile>
#include <QLocale>
#include <QSaveFile> #include <QSaveFile>
#include <QStandardPaths> #include <QStandardPaths>
@@ -25,6 +27,33 @@ QVariant trackIdValue(int id)
{ {
return id > 0 ? QVariant(id) : QVariant(QStringLiteral("no")); return id > 0 ? QVariant(id) : QVariant(QStringLiteral("no"));
} }
QList<TrackSelection::Track> selectionTracks(const QVariantList &tracks)
{
QList<TrackSelection::Track> 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) MpvItem::MpvItem(QQuickItem *parent)
@@ -64,6 +93,7 @@ MpvItem::MpvItem(QQuickItem *parent)
this, this,
[this] { [this] {
setFileLoaded(true); setFileLoaded(true);
selectInitialTracks();
}, },
Qt::QueuedConnection); Qt::QueuedConnection);
connect( connect(
@@ -338,6 +368,37 @@ void MpvItem::showTranslatedSubtitleFile(const QUrl &url, const QString &title)
commandAsync(QStringList{QStringLiteral("sub-add"), path, QStringLiteral("auto"), 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) void MpvItem::showOnlySubtitle(int id)
{ {
setSubtitleId(id); setSubtitleId(id);
@@ -484,6 +545,8 @@ void MpvItem::updateTracks(const QVariantList &trackList)
{QStringLiteral("title"), track.value(QStringLiteral("title")).toString()}, {QStringLiteral("title"), track.value(QStringLiteral("title")).toString()},
{QStringLiteral("lang"), track.value(QStringLiteral("lang")).toString()}, {QStringLiteral("lang"), track.value(QStringLiteral("lang")).toString()},
{QStringLiteral("codec"), track.value(QStringLiteral("codec")).toString()}, {QStringLiteral("codec"), track.value(QStringLiteral("codec")).toString()},
{QStringLiteral("forced"), track.value(QStringLiteral("forced")).toBool()},
{QStringLiteral("default"), track.value(QStringLiteral("default")).toBool()},
}); });
continue; continue;
} }
@@ -505,6 +568,9 @@ void MpvItem::updateTracks(const QVariantList &trackList)
{QStringLiteral("title"), track.value(QStringLiteral("title")).toString()}, {QStringLiteral("title"), track.value(QStringLiteral("title")).toString()},
{QStringLiteral("lang"), track.value(QStringLiteral("lang")).toString()}, {QStringLiteral("lang"), track.value(QStringLiteral("lang")).toString()},
{QStringLiteral("codec"), track.value(QStringLiteral("codec")).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("external"), track.value(QStringLiteral("external")).toBool()},
{QStringLiteral("externalFilename"), track.value(QStringLiteral("external-filename")).toString()}, {QStringLiteral("externalFilename"), track.value(QStringLiteral("external-filename")).toString()},
{QStringLiteral("ffIndex"), track.value(QStringLiteral("ff-index"), -1).toInt()}, {QStringLiteral("ffIndex"), track.value(QStringLiteral("ff-index"), -1).toInt()},
+15
View File
@@ -51,6 +51,13 @@ class MpvItem : public MpvAbstractItem
Q_PROPERTY(QStringList loadedUserConfigFiles READ loadedUserConfigFiles NOTIFY userConfigApplied) Q_PROPERTY(QStringList loadedUserConfigFiles READ loadedUserConfigFiles NOTIFY userConfigApplied)
Q_PROPERTY(QStringList skippedUserOptions READ skippedUserOptions 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: public:
explicit MpvItem(QQuickItem *parent = nullptr); explicit MpvItem(QQuickItem *parent = nullptr);
~MpvItem() override = default; ~MpvItem() override = default;
@@ -106,6 +113,9 @@ public:
// Makes \a url the only subtitle shown, adding it first (named \a title) unless it is // Makes \a url the only subtitle shown, adding it first (named \a title) unless it is
// already loaded. // already loaded.
Q_INVOKABLE void showTranslatedSubtitleFile(const QUrl &url, const QString &title); 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: Q_SIGNALS:
void currentUrlChanged(); void currentUrlChanged();
@@ -130,6 +140,7 @@ Q_SIGNALS:
void loadUserConfigChanged(); void loadUserConfigChanged();
// mpv.conf was queued for loading; options set after this take precedence over it. // mpv.conf was queued for loading; options set after this take precedence over it.
void userConfigApplied(); void userConfigApplied();
void trackPreferencesChanged();
private: private:
void onPropertyChanged(const QString &property, const QVariant &value); void onPropertyChanged(const QString &property, const QVariant &value);
@@ -137,6 +148,7 @@ private:
void updateTracks(const QVariantList &trackList); void updateTracks(const QVariantList &trackList);
void applyUserConfig(); void applyUserConfig();
void showOnlySubtitle(int id); void showOnlySubtitle(int id);
void selectInitialTracks();
bool m_ready = false; bool m_ready = false;
QUrl m_pendingUrl; QUrl m_pendingUrl;
@@ -165,4 +177,7 @@ private:
bool m_loadUserConfig = false; bool m_loadUserConfig = false;
QStringList m_loadedUserConfigFiles; QStringList m_loadedUserConfigFiles;
QStringList m_skippedUserOptions; QStringList m_skippedUserOptions;
QStringList m_preferredAudioLanguages;
QStringList m_preferredSubtitleLanguages;
bool m_autoSelectSubtitle = true;
}; };
+134
View File
@@ -0,0 +1,134 @@
#include "trackselection.h"
#include <QLocale>
#include <QRegularExpression>
extern "C" {
#include <libavcodec/avcodec.h>
}
#include <functional>
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<QLocale::Language>(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<Track> &tracks, const std::function<bool(const Track &)> &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<Track> &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<Track> &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<Track> &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<Track> &tracks, const QStringList &sourceLanguages, int currentId)
{
QList<Track> 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;
}
}
+41
View File
@@ -0,0 +1,41 @@
#pragma once
#include <QList>
#include <QString>
#include <QStringList>
// 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<Track> &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<Track> &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<Track> &tracks, const QStringList &sourceLanguages, int currentId);
}
+1 -1
View File
@@ -389,7 +389,7 @@ Item {
} }
property Action translateSubtitle: Action { 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) text: KI18n.i18nc("@action", "Translate Subtitle into %1", QtPotatoSettings.targetLanguage)
icon.name: "translate" icon.name: "translate"
shortcut: "Ctrl+T" shortcut: "Ctrl+T"
+5
View File
@@ -51,6 +51,11 @@ ApplicationWindow {
if (track && translatedSubtitleUrls.includes(track.externalFilename) && lastTranslationTrack) { if (track && translatedSubtitleUrls.includes(track.externalFilename) && lastTranslationTrack) {
track = video.subtitleTracks.find(track => track.id === lastTranslationTrack.id) 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) { if (track) {
lastTranslationTrack = track lastTranslationTrack = track
lastTranslationVideo = video.currentUrl lastTranslationVideo = video.currentUrl
+3
View File
@@ -34,6 +34,9 @@ MpvItem {
Kirigami.Theme.inherit: false Kirigami.Theme.inherit: false
loadUserConfig: QtPotatoSettings.loadMpvConfig 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. // Set once the saved volume is applied; mpv reports its default volume before that.
property bool volumeRestored: false property bool volumeRestored: false
+20
View File
@@ -72,5 +72,25 @@ SettingsBasePage {
QtPotatoSettings.save() 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.")
}
}
} }
} }
+43
View File
@@ -13,6 +13,49 @@ SettingsBasePage {
Kirigami.FormLayout { 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 { RowLayout {
spacing: Kirigami.Units.smallSpacing spacing: Kirigami.Units.smallSpacing
Kirigami.FormData.label: KI18n.i18nc("@label:spinbox", "Font size") Kirigami.FormData.label: KI18n.i18nc("@label:spinbox", "Font size")
+12
View File
@@ -64,6 +64,18 @@
<min>1</min> <min>1</min>
<max>600</max> <max>600</max>
</entry> </entry>
<entry name="PreferredAudioLanguages" type="StringList">
<label>Audio languages to turn on when a file opens, in order of preference (ISO 639 codes or English names); empty keeps mpv's choice</label>
<default></default>
</entry>
<entry name="PreferredSubtitleLanguages" type="StringList">
<label>Subtitle languages to turn on when a file opens, in order of preference (ISO 639 codes or English names)</label>
<default></default>
</entry>
<entry name="AutoSelectSubtitle" type="Bool">
<label>Turn on a subtitle when a file would open with none, preferring the audio language</label>
<default>true</default>
</entry>
</group> </group>
<group name="Subtitles"> <group name="Subtitles">
<entry name="SubtitleFontSize" type="Int"> <entry name="SubtitleFontSize" type="Int">