feat: add subtitle track selection, secondary subtitle and delay

This commit is contained in:
tri committed 2026-10-27 22:18:36 +07:00
1 parent 1eb67b829c
commit 48cac86d60
5 files changed
+320 -4

No files matched your search

+122 -2
View File
@@ -2,19 +2,38 @@
#include <MpvController>
#include <cmath>
#include <utility>
namespace
{
QString localPathOrUrl(const QUrl &url)
{
return url.isLocalFile() ? url.toLocalFile() : url.toString();
}
// mpv takes "no" to deselect a track.
QVariant trackIdValue(int id)
{
return id > 0 ? QVariant(id) : QVariant(QStringLiteral("no"));
}
}
MpvItem::MpvItem(QQuickItem *parent)
: MpvAbstractItem(parent)
{
// Leave the player on the last frame at end of file instead of unloading it.
setPropertyAsync(QStringLiteral("keep-open"), QStringLiteral("yes"));
setPropertyAsync(QStringLiteral("hwdec"), QStringLiteral("auto-safe"));
// Also pick up "movie.en.srt" style files next to "movie.mkv".
setPropertyAsync(QStringLiteral("sub-auto"), QStringLiteral("fuzzy"));
observeProperty(QStringLiteral("media-title"), MPV_FORMAT_STRING);
observeProperty(QStringLiteral("time-pos"), MPV_FORMAT_DOUBLE);
observeProperty(QStringLiteral("duration"), MPV_FORMAT_DOUBLE);
observeProperty(QStringLiteral("pause"), MPV_FORMAT_FLAG);
observeProperty(QStringLiteral("track-list"), MPV_FORMAT_NODE);
observeProperty(QStringLiteral("sub-delay"), MPV_FORMAT_DOUBLE);
// MpvController lives on a worker thread; queue its signals back to the GUI thread.
connect(mpvController(), &MpvController::propertyChanged, this, &MpvItem::onPropertyChanged, Qt::QueuedConnection);
@@ -98,9 +117,10 @@ void MpvItem::loadFile(const QUrl &url)
return;
}
const QString target = url.isLocalFile() ? url.toLocalFile() : url.toString();
commandAsync(QStringList{QStringLiteral("loadfile"), target});
commandAsync(QStringList{QStringLiteral("loadfile"), localPathOrUrl(url)});
setPropertyAsync(QStringLiteral("pause"), false);
// sub-delay is global in mpv; start every file in sync.
setSubtitleDelay(0.0);
}
void MpvItem::togglePause()
@@ -113,6 +133,63 @@ void MpvItem::seek(double seconds)
commandAsync(QStringList{QStringLiteral("seek"), QString::number(seconds), QStringLiteral("absolute")});
}
QVariantList MpvItem::subtitleTracks() const
{
return m_subtitleTracks;
}
int MpvItem::subtitleId() const
{
return m_subtitleId;
}
void MpvItem::setSubtitleId(int id)
{
setPropertyAsync(QStringLiteral("sid"), trackIdValue(id));
}
int MpvItem::secondarySubtitleId() const
{
return m_secondarySubtitleId;
}
void MpvItem::setSecondarySubtitleId(int id)
{
setPropertyAsync(QStringLiteral("secondary-sid"), trackIdValue(id));
}
double MpvItem::subtitleDelay() const
{
return m_subtitleDelay;
}
void MpvItem::setSubtitleDelay(double seconds)
{
// Keep whole milliseconds so repeated +/-0.1 steps don't drift.
const double rounded = std::round(seconds * 1000.0) / 1000.0;
// Update right away so quick repeated steps build on each other instead of on the
// last value mpv reported; mpv confirms it through the observed property.
if (rounded != m_subtitleDelay) {
m_subtitleDelay = rounded;
Q_EMIT subtitleDelayChanged();
}
setPropertyAsync(QStringLiteral("sub-delay"), rounded);
setPropertyAsync(QStringLiteral("secondary-sub-delay"), rounded);
}
void MpvItem::addSubtitleFile(const QUrl &url)
{
if (!url.isValid()) {
return;
}
commandAsync(QStringList{QStringLiteral("sub-add"), localPathOrUrl(url), QStringLiteral("select")});
}
void MpvItem::showText(const QString &text)
{
commandAsync(QStringList{QStringLiteral("show-text"), text});
}
void MpvItem::onPropertyChanged(const QString &property, const QVariant &value)
{
if (property == u"media-title") {
@@ -127,9 +204,52 @@ void MpvItem::onPropertyChanged(const QString &property, const QVariant &value)
} else if (property == u"pause") {
m_paused = value.toBool();
Q_EMIT pausedChanged();
} else if (property == u"track-list") {
updateTracks(value.toList());
} else if (property == u"sub-delay") {
m_subtitleDelay = value.toDouble();
Q_EMIT subtitleDelayChanged();
}
}
void MpvItem::updateTracks(const QVariantList &trackList)
{
QVariantList subtitles;
int primary = 0;
int secondary = 0;
for (const QVariant &entry : trackList) {
const QVariantMap track = entry.toMap();
if (track.value(QStringLiteral("type")).toString() != u"sub") {
continue;
}
const int id = track.value(QStringLiteral("id")).toInt();
if (track.value(QStringLiteral("selected")).toBool()) {
// main-selection is 0 for the primary subtitle and 1 for the secondary one.
if (track.value(QStringLiteral("main-selection")).toInt() == 0) {
primary = id;
} else {
secondary = id;
}
}
subtitles.append(QVariantMap{
{QStringLiteral("id"), id},
{QStringLiteral("title"), track.value(QStringLiteral("title")).toString()},
{QStringLiteral("lang"), track.value(QStringLiteral("lang")).toString()},
{QStringLiteral("codec"), track.value(QStringLiteral("codec")).toString()},
{QStringLiteral("external"), track.value(QStringLiteral("external")).toBool()},
{QStringLiteral("externalFilename"), track.value(QStringLiteral("external-filename")).toString()},
});
}
m_subtitleTracks = subtitles;
m_subtitleId = primary;
m_secondarySubtitleId = secondary;
Q_EMIT subtitleTracksChanged();
}
void MpvItem::setFileLoaded(bool loaded)
{
if (loaded == m_fileLoaded) {