fix(mpv): auto-select subtitle and audio tracks on file open
This commit is contained in:
1 parent
32c686e0e3
commit
23cf7e31a9
13 files changed
+437
-1
No files matched your search
@@ -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
|
||||
)
|
||||
@@ -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"
|
||||
@@ -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
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#include "mpvitem.h"
|
||||
|
||||
#include "mpvuserconfig.h"
|
||||
#include "trackselection.h"
|
||||
|
||||
#include <MpvController>
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QLocale>
|
||||
#include <QSaveFile>
|
||||
#include <QStandardPaths>
|
||||
|
||||
@@ -25,6 +27,33 @@ QVariant trackIdValue(int id)
|
||||
{
|
||||
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)
|
||||
@@ -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()},
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
@@ -64,6 +64,18 @@
|
||||
<min>1</min>
|
||||
<max>600</max>
|
||||
</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 name="Subtitles">
|
||||
<entry name="SubtitleFontSize" type="Int">
|
||||
|
||||
Reference in new issue
Block a user