feat(translate): keep translated subtitles next to the video so they load on reopen (#14)

Co-authored-by: Tri Vo Khac <git@trivk.dev>
This commit was merged in pull request #14.
This commit is contained in:
tri authored and tri committed 2026-11-13 22:49:56 +07:00
1 parent b34a01a92f
commit 76b12d6632
13 files changed
+716 -65

No files matched your search

+69 -5
View File
@@ -7,6 +7,7 @@
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QLocale>
#include <QSaveFile>
#include <QStandardPaths>
@@ -44,6 +45,38 @@ QList<TrackSelection::Track> selectionTracks(const QVariantList &tracks)
return result;
}
QString subtitlePath(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("externalFilename")).toString();
}
}
return {};
}
int subtitleIdForPath(const QVariantList &tracks, const QString &path)
{
if (path.isEmpty()) {
return 0;
}
for (const QVariant &entry : tracks) {
const QVariantMap track = entry.toMap();
if (track.value(QStringLiteral("externalFilename")).toString() == path) {
return track.value(QStringLiteral("id")).toInt();
}
}
return 0;
}
// "movie.abc123.vi.srt" -> "vi", the way mpv tags subtitles it finds next to a video.
QString filenameLanguage(const QString &path)
{
const QStringList parts = QFileInfo(path).completeBaseName().split(QLatin1Char('.'));
return parts.size() > 1 ? parts.constLast() : QString();
}
QString trackLanguage(const QVariantList &tracks, int id)
{
for (const QVariant &entry : tracks) {
@@ -112,7 +145,7 @@ MpvItem::MpvItem(QQuickItem *parent)
// Before the first file, so its options apply from the start.
applyUserConfig();
if (m_pendingUrl.isValid()) {
loadFile(std::exchange(m_pendingUrl, {}));
loadFile(std::exchange(m_pendingUrl, {}), QString(m_openingTranslatedPath), QString(m_openingTranslatedTitle));
}
});
}
@@ -156,12 +189,18 @@ bool MpvItem::isFileLoaded() const
return m_fileLoaded;
}
void MpvItem::loadFile(const QUrl &url)
void MpvItem::loadFile(const QUrl &url, const QString &translatedSubtitle, const QString &translatedTitle)
{
if (!url.isValid()) {
return;
}
m_translatedPaths.clear();
m_openingTranslatedPath = translatedSubtitle;
m_openingTranslatedTitle = translatedTitle;
if (!translatedSubtitle.isEmpty()) {
m_translatedPaths.append(translatedSubtitle);
}
m_currentUrl = url;
Q_EMIT currentUrlChanged();
@@ -355,6 +394,9 @@ void MpvItem::showTranslatedSubtitleFile(const QUrl &url, const QString &title)
return;
}
const QString path = localPathOrUrl(url);
if (!m_translatedPaths.contains(path)) {
m_translatedPaths.append(path);
}
for (const QVariant &entry : std::as_const(m_subtitleTracks)) {
const QVariantMap track = entry.toMap();
if (track.value(QStringLiteral("externalFilename")).toString() == path) {
@@ -370,11 +412,22 @@ void MpvItem::showTranslatedSubtitleFile(const QUrl &url, const QString &title)
int MpvItem::translationSourceId() const
{
QVariantList sources;
for (const QVariant &entry : std::as_const(m_subtitleTracks)) {
if (!isTranslatedSubtitle(entry.toMap().value(QStringLiteral("id")).toInt())) {
sources.append(entry);
}
}
// English first: it is the usual original, and dubbed releases often default to
// subtitles translated from it.
return TrackSelection::pickTranslationSource(selectionTracks(m_subtitleTracks),
return TrackSelection::pickTranslationSource(selectionTracks(sources),
{QStringLiteral("en"), trackLanguage(m_audioTracks, m_audioId)},
m_subtitleId);
isTranslatedSubtitle(m_subtitleId) ? 0 : m_subtitleId);
}
bool MpvItem::isTranslatedSubtitle(int id) const
{
return id > 0 && m_translatedPaths.contains(subtitlePath(m_subtitleTracks, id));
}
void MpvItem::selectInitialTracks()
@@ -382,6 +435,16 @@ void MpvItem::selectInitialTracks()
// The observed track-list may still describe the previous file; read it directly.
updateTracks(getProperty(QStringLiteral("track-list")).toList());
// mpv finds a translation next to the video by itself (sub-auto); one kept in the
// cache is added here, before choosing, so it can be chosen.
const QString translated = std::exchange(m_openingTranslatedPath, {});
int translationId = subtitleIdForPath(m_subtitleTracks, translated);
if (!translated.isEmpty() && translationId == 0 && QFileInfo::exists(translated)) {
commandBlocking(QStringList{QStringLiteral("sub-add"), translated, QStringLiteral("auto"), m_openingTranslatedTitle, filenameLanguage(translated)});
updateTracks(getProperty(QStringLiteral("track-list")).toList());
translationId = subtitleIdForPath(m_subtitleTracks, translated);
}
const int audioId = TrackSelection::pickAudio(selectionTracks(m_audioTracks), m_preferredAudioLanguages, m_audioId);
if (audioId != m_audioId) {
setAudioId(audioId);
@@ -393,7 +456,8 @@ void MpvItem::selectInitialTracks()
m_preferredSubtitleLanguages,
fallbackLanguages,
m_subtitleId,
m_autoSelectSubtitle);
m_autoSelectSubtitle,
translationId);
if (subtitleId != m_subtitleId) {
setSubtitleId(subtitleId);
}