feat: add subtitle track selection, secondary subtitle and delay
This commit is contained in:
1 parent
1eb67b829c
commit
48cac86d60
5 files changed
+320
-4
No files matched your search
@@ -11,6 +11,7 @@ ecm_add_qml_module(qtpotato
|
|||||||
ecm_target_qml_sources(qtpotato SOURCES
|
ecm_target_qml_sources(qtpotato SOURCES
|
||||||
qml/Main.qml
|
qml/Main.qml
|
||||||
qml/PlayerPage.qml
|
qml/PlayerPage.qml
|
||||||
|
qml/SubtitlePopup.qml
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(qtpotato PRIVATE mpv)
|
target_include_directories(qtpotato PRIVATE mpv)
|
||||||
|
|||||||
+122
-2
@@ -2,19 +2,38 @@
|
|||||||
|
|
||||||
#include <MpvController>
|
#include <MpvController>
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
#include <utility>
|
#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)
|
MpvItem::MpvItem(QQuickItem *parent)
|
||||||
: MpvAbstractItem(parent)
|
: MpvAbstractItem(parent)
|
||||||
{
|
{
|
||||||
// Leave the player on the last frame at end of file instead of unloading it.
|
// Leave the player on the last frame at end of file instead of unloading it.
|
||||||
setPropertyAsync(QStringLiteral("keep-open"), QStringLiteral("yes"));
|
setPropertyAsync(QStringLiteral("keep-open"), QStringLiteral("yes"));
|
||||||
setPropertyAsync(QStringLiteral("hwdec"), QStringLiteral("auto-safe"));
|
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("media-title"), MPV_FORMAT_STRING);
|
||||||
observeProperty(QStringLiteral("time-pos"), MPV_FORMAT_DOUBLE);
|
observeProperty(QStringLiteral("time-pos"), MPV_FORMAT_DOUBLE);
|
||||||
observeProperty(QStringLiteral("duration"), MPV_FORMAT_DOUBLE);
|
observeProperty(QStringLiteral("duration"), MPV_FORMAT_DOUBLE);
|
||||||
observeProperty(QStringLiteral("pause"), MPV_FORMAT_FLAG);
|
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.
|
// MpvController lives on a worker thread; queue its signals back to the GUI thread.
|
||||||
connect(mpvController(), &MpvController::propertyChanged, this, &MpvItem::onPropertyChanged, Qt::QueuedConnection);
|
connect(mpvController(), &MpvController::propertyChanged, this, &MpvItem::onPropertyChanged, Qt::QueuedConnection);
|
||||||
@@ -98,9 +117,10 @@ void MpvItem::loadFile(const QUrl &url)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString target = url.isLocalFile() ? url.toLocalFile() : url.toString();
|
commandAsync(QStringList{QStringLiteral("loadfile"), localPathOrUrl(url)});
|
||||||
commandAsync(QStringList{QStringLiteral("loadfile"), target});
|
|
||||||
setPropertyAsync(QStringLiteral("pause"), false);
|
setPropertyAsync(QStringLiteral("pause"), false);
|
||||||
|
// sub-delay is global in mpv; start every file in sync.
|
||||||
|
setSubtitleDelay(0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MpvItem::togglePause()
|
void MpvItem::togglePause()
|
||||||
@@ -113,6 +133,63 @@ void MpvItem::seek(double seconds)
|
|||||||
commandAsync(QStringList{QStringLiteral("seek"), QString::number(seconds), QStringLiteral("absolute")});
|
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)
|
void MpvItem::onPropertyChanged(const QString &property, const QVariant &value)
|
||||||
{
|
{
|
||||||
if (property == u"media-title") {
|
if (property == u"media-title") {
|
||||||
@@ -127,9 +204,52 @@ void MpvItem::onPropertyChanged(const QString &property, const QVariant &value)
|
|||||||
} else if (property == u"pause") {
|
} else if (property == u"pause") {
|
||||||
m_paused = value.toBool();
|
m_paused = value.toBool();
|
||||||
Q_EMIT pausedChanged();
|
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)
|
void MpvItem::setFileLoaded(bool loaded)
|
||||||
{
|
{
|
||||||
if (loaded == m_fileLoaded) {
|
if (loaded == m_fileLoaded) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <MpvAbstractItem>
|
#include <MpvAbstractItem>
|
||||||
|
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
#include <QVariantList>
|
||||||
#include <QtQml/qqmlregistration.h>
|
#include <QtQml/qqmlregistration.h>
|
||||||
|
|
||||||
// QML video surface backed by libmpv (through MpvQt).
|
// QML video surface backed by libmpv (through MpvQt).
|
||||||
@@ -19,6 +20,14 @@ class MpvItem : public MpvAbstractItem
|
|||||||
Q_PROPERTY(bool paused READ paused WRITE setPaused NOTIFY pausedChanged)
|
Q_PROPERTY(bool paused READ paused WRITE setPaused NOTIFY pausedChanged)
|
||||||
Q_PROPERTY(bool fileLoaded READ isFileLoaded NOTIFY fileLoadedChanged)
|
Q_PROPERTY(bool fileLoaded READ isFileLoaded NOTIFY fileLoadedChanged)
|
||||||
|
|
||||||
|
// Subtitle tracks of the current file, each a map with id, title, lang, codec,
|
||||||
|
// external and externalFilename. Track id 0 means "no subtitle".
|
||||||
|
Q_PROPERTY(QVariantList subtitleTracks READ subtitleTracks NOTIFY subtitleTracksChanged)
|
||||||
|
Q_PROPERTY(int subtitleId READ subtitleId WRITE setSubtitleId NOTIFY subtitleTracksChanged)
|
||||||
|
Q_PROPERTY(int secondarySubtitleId READ secondarySubtitleId WRITE setSecondarySubtitleId NOTIFY subtitleTracksChanged)
|
||||||
|
// In seconds; applied to both subtitles so a translation stays in sync with its source.
|
||||||
|
Q_PROPERTY(double subtitleDelay READ subtitleDelay WRITE setSubtitleDelay NOTIFY subtitleDelayChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit MpvItem(QQuickItem *parent = nullptr);
|
explicit MpvItem(QQuickItem *parent = nullptr);
|
||||||
~MpvItem() override = default;
|
~MpvItem() override = default;
|
||||||
@@ -31,9 +40,21 @@ public:
|
|||||||
void setPaused(bool paused);
|
void setPaused(bool paused);
|
||||||
bool isFileLoaded() const;
|
bool isFileLoaded() const;
|
||||||
|
|
||||||
|
QVariantList subtitleTracks() const;
|
||||||
|
int subtitleId() const;
|
||||||
|
void setSubtitleId(int id);
|
||||||
|
int secondarySubtitleId() const;
|
||||||
|
void setSecondarySubtitleId(int id);
|
||||||
|
double subtitleDelay() const;
|
||||||
|
void setSubtitleDelay(double seconds);
|
||||||
|
|
||||||
Q_INVOKABLE void loadFile(const QUrl &url);
|
Q_INVOKABLE void loadFile(const QUrl &url);
|
||||||
Q_INVOKABLE void togglePause();
|
Q_INVOKABLE void togglePause();
|
||||||
Q_INVOKABLE void seek(double seconds);
|
Q_INVOKABLE void seek(double seconds);
|
||||||
|
// Adds an external subtitle file and makes it the primary subtitle.
|
||||||
|
Q_INVOKABLE void addSubtitleFile(const QUrl &url);
|
||||||
|
// Shows a short message on mpv's on-screen display.
|
||||||
|
Q_INVOKABLE void showText(const QString &text);
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void currentUrlChanged();
|
void currentUrlChanged();
|
||||||
@@ -42,10 +63,13 @@ Q_SIGNALS:
|
|||||||
void durationChanged();
|
void durationChanged();
|
||||||
void pausedChanged();
|
void pausedChanged();
|
||||||
void fileLoadedChanged();
|
void fileLoadedChanged();
|
||||||
|
void subtitleTracksChanged();
|
||||||
|
void subtitleDelayChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void onPropertyChanged(const QString &property, const QVariant &value);
|
void onPropertyChanged(const QString &property, const QVariant &value);
|
||||||
void setFileLoaded(bool loaded);
|
void setFileLoaded(bool loaded);
|
||||||
|
void updateTracks(const QVariantList &trackList);
|
||||||
|
|
||||||
bool m_ready = false;
|
bool m_ready = false;
|
||||||
QUrl m_pendingUrl;
|
QUrl m_pendingUrl;
|
||||||
@@ -55,4 +79,8 @@ private:
|
|||||||
double m_duration = 0.0;
|
double m_duration = 0.0;
|
||||||
bool m_paused = false;
|
bool m_paused = false;
|
||||||
bool m_fileLoaded = false;
|
bool m_fileLoaded = false;
|
||||||
|
QVariantList m_subtitleTracks;
|
||||||
|
int m_subtitleId = 0;
|
||||||
|
int m_secondarySubtitleId = 0;
|
||||||
|
double m_subtitleDelay = 0.0;
|
||||||
};
|
};
|
||||||
+53
-2
@@ -65,6 +65,27 @@ Kirigami.Page {
|
|||||||
sequence: "Right"
|
sequence: "Right"
|
||||||
onActivated: mpv.seek(mpv.position + 5)
|
onActivated: mpv.seek(mpv.position + 5)
|
||||||
}
|
}
|
||||||
|
// Same keys as mpv: z shows subtitles earlier, x later.
|
||||||
|
Shortcut {
|
||||||
|
sequence: "Z"
|
||||||
|
enabled: mpv.fileLoaded
|
||||||
|
onActivated: page.nudgeSubtitleDelay(-0.1)
|
||||||
|
}
|
||||||
|
Shortcut {
|
||||||
|
sequence: "X"
|
||||||
|
enabled: mpv.fileLoaded
|
||||||
|
onActivated: page.nudgeSubtitleDelay(0.1)
|
||||||
|
}
|
||||||
|
|
||||||
|
function nudgeSubtitleDelay(seconds: real) {
|
||||||
|
const delay = mpv.subtitleDelay + seconds
|
||||||
|
mpv.subtitleDelay = delay
|
||||||
|
mpv.showText(KI18n.i18nc("@info:osd", "Subtitle delay: %1 ms", Math.round(delay * 1000)))
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSubtitleFile(url: url): bool {
|
||||||
|
return /\.(srt|ass|ssa|vtt|sub|sup)$/i.test(url.toString())
|
||||||
|
}
|
||||||
|
|
||||||
FileDialog {
|
FileDialog {
|
||||||
id: fileDialog
|
id: fileDialog
|
||||||
@@ -111,8 +132,14 @@ Kirigami.Page {
|
|||||||
DropArea {
|
DropArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
onDropped: drop => {
|
onDropped: drop => {
|
||||||
if (drop.hasUrls) {
|
if (!drop.hasUrls) {
|
||||||
mpv.loadFile(drop.urls[0])
|
return
|
||||||
|
}
|
||||||
|
const url = drop.urls[0]
|
||||||
|
if (mpv.fileLoaded && page.isSubtitleFile(url)) {
|
||||||
|
mpv.addSubtitleFile(url)
|
||||||
|
} else {
|
||||||
|
mpv.loadFile(url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -165,6 +192,30 @@ Kirigami.Page {
|
|||||||
text: page.formatTime(mpv.duration)
|
text: page.formatTime(mpv.duration)
|
||||||
font.features: { "tnum": 1 }
|
font.features: { "tnum": 1 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QQC2.ToolButton {
|
||||||
|
id: subtitleButton
|
||||||
|
enabled: mpv.fileLoaded
|
||||||
|
focusPolicy: Qt.NoFocus
|
||||||
|
checkable: true
|
||||||
|
checked: subtitlePopup.visible
|
||||||
|
icon.name: "media-view-subtitles-symbolic"
|
||||||
|
text: KI18n.i18nc("@action:button", "Subtitles")
|
||||||
|
display: QQC2.AbstractButton.IconOnly
|
||||||
|
onClicked: subtitlePopup.visible ? subtitlePopup.close() : subtitlePopup.open()
|
||||||
|
|
||||||
|
QQC2.ToolTip.text: text
|
||||||
|
QQC2.ToolTip.visible: hovered && !subtitlePopup.visible
|
||||||
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
|
|
||||||
|
SubtitlePopup {
|
||||||
|
id: subtitlePopup
|
||||||
|
objectName: "subtitlePopup"
|
||||||
|
mpv: mpv
|
||||||
|
x: subtitleButton.width - width
|
||||||
|
y: -height - Kirigami.Units.smallSpacing
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls as QQC2
|
||||||
|
import QtQuick.Dialogs
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import org.kde.kirigami as Kirigami
|
||||||
|
import org.kde.ki18n
|
||||||
|
|
||||||
|
QQC2.Popup {
|
||||||
|
id: popup
|
||||||
|
|
||||||
|
required property MpvItem mpv
|
||||||
|
|
||||||
|
// [{ id, text }] with "Off" first, shared by both track pickers.
|
||||||
|
readonly property var trackModel: [{ id: 0, text: KI18n.i18nc("@item:inlistbox no subtitle", "Off") }]
|
||||||
|
.concat(mpv.subtitleTracks.map(track => ({ id: track.id, text: trackLabel(track) })))
|
||||||
|
|
||||||
|
function trackLabel(track: var): string {
|
||||||
|
const name = track.title || (track.external ? track.externalFilename.split("/").pop() : "")
|
||||||
|
const lang = track.lang ? `[${track.lang}]` : ""
|
||||||
|
const description = [name, lang].filter(part => part.length > 0).join(" ")
|
||||||
|
return KI18n.i18nc("@item:inlistbox subtitle track number and description", "%1: %2",
|
||||||
|
track.id, description || KI18n.i18nc("@item:inlistbox", "Untitled"))
|
||||||
|
}
|
||||||
|
|
||||||
|
function indexOfTrack(id: int): int {
|
||||||
|
return Math.max(0, trackModel.findIndex(entry => entry.id === id))
|
||||||
|
}
|
||||||
|
|
||||||
|
padding: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
|
contentItem: Kirigami.FormLayout {
|
||||||
|
QQC2.ComboBox {
|
||||||
|
id: primaryCombo
|
||||||
|
Kirigami.FormData.label: KI18n.i18nc("@label:listbox", "Subtitle:")
|
||||||
|
Layout.preferredWidth: Kirigami.Units.gridUnit * 16
|
||||||
|
model: popup.trackModel
|
||||||
|
textRole: "text"
|
||||||
|
valueRole: "id"
|
||||||
|
onActivated: popup.mpv.subtitleId = currentValue
|
||||||
|
|
||||||
|
// Always reflect what mpv actually selected, also after the user picks an entry.
|
||||||
|
Binding on currentIndex {
|
||||||
|
value: popup.indexOfTrack(popup.mpv.subtitleId)
|
||||||
|
restoreMode: Binding.RestoreNone
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.ComboBox {
|
||||||
|
id: secondaryCombo
|
||||||
|
Kirigami.FormData.label: KI18n.i18nc("@label:listbox", "Secondary subtitle:")
|
||||||
|
Layout.preferredWidth: Kirigami.Units.gridUnit * 16
|
||||||
|
model: popup.trackModel
|
||||||
|
textRole: "text"
|
||||||
|
valueRole: "id"
|
||||||
|
onActivated: popup.mpv.secondarySubtitleId = currentValue
|
||||||
|
|
||||||
|
Binding on currentIndex {
|
||||||
|
value: popup.indexOfTrack(popup.mpv.secondarySubtitleId)
|
||||||
|
restoreMode: Binding.RestoreNone
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Kirigami.FormData.label: KI18n.i18nc("@label:spinbox", "Delay:")
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
QQC2.SpinBox {
|
||||||
|
id: delaySpin
|
||||||
|
from: -600000
|
||||||
|
to: 600000
|
||||||
|
stepSize: 100
|
||||||
|
editable: true
|
||||||
|
textFromValue: (value, locale) => KI18n.i18nc("@item:valuesuffix milliseconds", "%1 ms", value)
|
||||||
|
valueFromText: (text, locale) => parseInt(text) || 0
|
||||||
|
onValueModified: popup.mpv.subtitleDelay = value / 1000
|
||||||
|
|
||||||
|
Binding on value {
|
||||||
|
value: Math.round(popup.mpv.subtitleDelay * 1000)
|
||||||
|
restoreMode: Binding.RestoreNone
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.ToolButton {
|
||||||
|
enabled: popup.mpv.subtitleDelay !== 0
|
||||||
|
icon.name: "edit-undo"
|
||||||
|
text: KI18n.i18nc("@action:button", "Reset delay")
|
||||||
|
display: QQC2.AbstractButton.IconOnly
|
||||||
|
onClicked: popup.mpv.subtitleDelay = 0
|
||||||
|
|
||||||
|
QQC2.ToolTip.text: text
|
||||||
|
QQC2.ToolTip.visible: hovered
|
||||||
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.Button {
|
||||||
|
icon.name: "add-subtitle"
|
||||||
|
text: KI18n.i18nc("@action:button", "Load Subtitle File…")
|
||||||
|
onClicked: subtitleDialog.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileDialog {
|
||||||
|
id: subtitleDialog
|
||||||
|
title: KI18n.i18nc("@title:window", "Load Subtitle")
|
||||||
|
currentFolder: {
|
||||||
|
const url = popup.mpv.currentUrl.toString()
|
||||||
|
return url.substring(0, url.lastIndexOf("/"))
|
||||||
|
}
|
||||||
|
nameFilters: [
|
||||||
|
KI18n.i18nc("@item:inlistbox file filter", "Subtitle files (*.srt *.ass *.ssa *.vtt *.sub *.sup)"),
|
||||||
|
KI18n.i18nc("@item:inlistbox file filter", "All files (*)")
|
||||||
|
]
|
||||||
|
onAccepted: popup.mpv.addSubtitleFile(selectedFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user