667 lines
21 KiB
C++
667 lines
21 KiB
C++
#include "mpvitem.h"
|
|
|
|
#include "mpvuserconfig.h"
|
|
#include "trackselection.h"
|
|
|
|
#include <MpvController>
|
|
|
|
#include <QDir>
|
|
#include <QFile>
|
|
#include <QFileInfo>
|
|
#include <QLocale>
|
|
#include <QSaveFile>
|
|
#include <QStandardPaths>
|
|
|
|
#include <algorithm>
|
|
#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"));
|
|
}
|
|
|
|
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 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) {
|
|
const QVariantMap track = entry.toMap();
|
|
if (track.value(QStringLiteral("id")).toInt() == id) {
|
|
return track.value(QStringLiteral("lang")).toString();
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
}
|
|
|
|
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"));
|
|
// mpvqt renders with the default blocking timing, so each new video frame stalls
|
|
// the Qt Quick render thread for up to this offset (50 ms) and animations drawn
|
|
// over a playing video stutter. Hand frames over at their display time instead.
|
|
setPropertyAsync(QStringLiteral("video-timing-offset"), 0);
|
|
// 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);
|
|
observeProperty(QStringLiteral("volume"), MPV_FORMAT_INT64);
|
|
observeProperty(QStringLiteral("volume-max"), MPV_FORMAT_INT64);
|
|
observeProperty(QStringLiteral("mute"), MPV_FORMAT_FLAG);
|
|
observeProperty(QStringLiteral("sub-visibility"), MPV_FORMAT_FLAG);
|
|
observeProperty(QStringLiteral("deinterlace"), MPV_FORMAT_FLAG);
|
|
observeProperty(QStringLiteral("speed"), MPV_FORMAT_DOUBLE);
|
|
observeProperty(QStringLiteral("chapter-list"), MPV_FORMAT_NODE);
|
|
observeProperty(QStringLiteral("chapter"), MPV_FORMAT_INT64);
|
|
observeProperty(QStringLiteral("eof-reached"), MPV_FORMAT_FLAG);
|
|
|
|
// 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::fileLoaded,
|
|
this,
|
|
[this] {
|
|
setFileLoaded(true);
|
|
selectInitialTracks();
|
|
},
|
|
Qt::QueuedConnection);
|
|
connect(
|
|
mpvController(),
|
|
&MpvController::endFile,
|
|
this,
|
|
[this] {
|
|
setFileLoaded(false);
|
|
},
|
|
Qt::QueuedConnection);
|
|
|
|
// The render context only exists once Qt Quick has rendered the item for the first
|
|
// time; a file loaded before that plays without video. Hold it until ready() fires.
|
|
connect(this, &MpvAbstractItem::ready, this, [this] {
|
|
m_ready = true;
|
|
// Before the first file, so its options apply from the start.
|
|
applyUserConfig();
|
|
if (m_pendingUrl.isValid()) {
|
|
loadFile(std::exchange(m_pendingUrl, {}), QString(m_openingTranslatedPath), QString(m_openingTranslatedTitle));
|
|
}
|
|
});
|
|
}
|
|
|
|
QUrl MpvItem::currentUrl() const
|
|
{
|
|
return m_currentUrl;
|
|
}
|
|
|
|
QString MpvItem::mediaTitle() const
|
|
{
|
|
return m_mediaTitle;
|
|
}
|
|
|
|
double MpvItem::position() const
|
|
{
|
|
return m_position;
|
|
}
|
|
|
|
double MpvItem::duration() const
|
|
{
|
|
return m_duration;
|
|
}
|
|
|
|
bool MpvItem::paused() const
|
|
{
|
|
return m_paused;
|
|
}
|
|
|
|
void MpvItem::setPaused(bool paused)
|
|
{
|
|
if (paused == m_paused) {
|
|
return;
|
|
}
|
|
// m_paused is updated when mpv reports the "pause" property back.
|
|
setPropertyAsync(QStringLiteral("pause"), paused);
|
|
}
|
|
|
|
bool MpvItem::isFileLoaded() const
|
|
{
|
|
return m_fileLoaded;
|
|
}
|
|
|
|
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();
|
|
|
|
if (!m_ready) {
|
|
m_pendingUrl = url;
|
|
return;
|
|
}
|
|
|
|
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::stop()
|
|
{
|
|
commandAsync(QStringList{QStringLiteral("stop")});
|
|
m_currentUrl.clear();
|
|
Q_EMIT currentUrlChanged();
|
|
}
|
|
|
|
void MpvItem::togglePause()
|
|
{
|
|
setPaused(!m_paused);
|
|
}
|
|
|
|
void MpvItem::seek(double seconds)
|
|
{
|
|
commandAsync(QStringList{QStringLiteral("seek"), QString::number(seconds), QStringLiteral("absolute")});
|
|
}
|
|
|
|
void MpvItem::seekBy(double seconds)
|
|
{
|
|
commandAsync(QStringList{QStringLiteral("seek"), QString::number(seconds), QStringLiteral("relative")});
|
|
}
|
|
|
|
void MpvItem::frameStep(bool forward)
|
|
{
|
|
commandAsync(QStringList{forward ? QStringLiteral("frame-step") : QStringLiteral("frame-back-step")});
|
|
}
|
|
|
|
void MpvItem::screenshot()
|
|
{
|
|
commandAsync(QStringList{QStringLiteral("screenshot")});
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
QVariantList MpvItem::audioTracks() const
|
|
{
|
|
return m_audioTracks;
|
|
}
|
|
|
|
int MpvItem::audioId() const
|
|
{
|
|
return m_audioId;
|
|
}
|
|
|
|
void MpvItem::setAudioId(int id)
|
|
{
|
|
setPropertyAsync(QStringLiteral("aid"), trackIdValue(id));
|
|
}
|
|
|
|
int MpvItem::volume() const
|
|
{
|
|
return m_volume;
|
|
}
|
|
|
|
void MpvItem::setVolume(int volume)
|
|
{
|
|
volume = std::clamp(volume, 0, m_volumeMax);
|
|
// Like sub-delay: update right away so repeated steps build on each other. Always
|
|
// sent, since mpv may not have reported a volume from mpv.conf yet.
|
|
if (volume != m_volume) {
|
|
m_volume = volume;
|
|
Q_EMIT volumeChanged();
|
|
}
|
|
setPropertyAsync(QStringLiteral("volume"), volume);
|
|
}
|
|
|
|
int MpvItem::volumeMax() const
|
|
{
|
|
return m_volumeMax;
|
|
}
|
|
|
|
bool MpvItem::mute() const
|
|
{
|
|
return m_mute;
|
|
}
|
|
|
|
bool MpvItem::subtitleVisible() const
|
|
{
|
|
return m_subtitleVisible;
|
|
}
|
|
|
|
bool MpvItem::deinterlace() const
|
|
{
|
|
return m_deinterlace;
|
|
}
|
|
|
|
void MpvItem::setMute(bool mute)
|
|
{
|
|
setPropertyAsync(QStringLiteral("mute"), mute);
|
|
}
|
|
|
|
double MpvItem::speed() const
|
|
{
|
|
return m_speed;
|
|
}
|
|
|
|
void MpvItem::setSpeed(double speed)
|
|
{
|
|
const double rounded = std::round(std::clamp(speed, 0.1, 10.0) * 100.0) / 100.0;
|
|
if (rounded != m_speed) {
|
|
m_speed = rounded;
|
|
Q_EMIT speedChanged();
|
|
}
|
|
setPropertyAsync(QStringLiteral("speed"), rounded);
|
|
}
|
|
|
|
QVariantList MpvItem::chapters() const
|
|
{
|
|
return m_chapters;
|
|
}
|
|
|
|
int MpvItem::chapter() const
|
|
{
|
|
return m_chapter;
|
|
}
|
|
|
|
void MpvItem::setChapter(int chapter)
|
|
{
|
|
if (chapter >= 0 && chapter < m_chapters.size()) {
|
|
setPropertyAsync(QStringLiteral("chapter"), chapter);
|
|
}
|
|
}
|
|
|
|
void MpvItem::addSubtitleFile(const QUrl &url)
|
|
{
|
|
if (!url.isValid()) {
|
|
return;
|
|
}
|
|
commandAsync(QStringList{QStringLiteral("sub-add"), localPathOrUrl(url), QStringLiteral("select")});
|
|
}
|
|
|
|
void MpvItem::showTranslatedSubtitleFile(const QUrl &url, const QString &title)
|
|
{
|
|
if (!url.isValid()) {
|
|
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) {
|
|
commandAsync(QStringList{QStringLiteral("sub-reload"), QString::number(track.value(QStringLiteral("id")).toInt())});
|
|
showOnlySubtitle(track.value(QStringLiteral("id")).toInt());
|
|
return;
|
|
}
|
|
}
|
|
// "auto" adds the track without selecting it; updateTracks() selects it once mpv lists it.
|
|
m_pendingTranslatedPath = path;
|
|
commandAsync(QStringList{QStringLiteral("sub-add"), path, QStringLiteral("auto"), 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(sources),
|
|
{QStringLiteral("en"), trackLanguage(m_audioTracks, m_audioId)},
|
|
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()
|
|
{
|
|
// 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);
|
|
}
|
|
// 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,
|
|
translationId);
|
|
if (subtitleId != m_subtitleId) {
|
|
setSubtitleId(subtitleId);
|
|
}
|
|
}
|
|
|
|
void MpvItem::showOnlySubtitle(int id)
|
|
{
|
|
setSubtitleId(id);
|
|
setSecondarySubtitleId(0);
|
|
}
|
|
|
|
bool MpvItem::loadUserConfig() const
|
|
{
|
|
return m_loadUserConfig;
|
|
}
|
|
|
|
void MpvItem::setLoadUserConfig(bool load)
|
|
{
|
|
if (load != m_loadUserConfig) {
|
|
m_loadUserConfig = load;
|
|
Q_EMIT loadUserConfigChanged();
|
|
}
|
|
}
|
|
|
|
QString MpvItem::userConfigDir() const
|
|
{
|
|
return MpvUserConfig::configDir();
|
|
}
|
|
|
|
QStringList MpvItem::loadedUserConfigFiles() const
|
|
{
|
|
return m_loadedUserConfigFiles;
|
|
}
|
|
|
|
QStringList MpvItem::skippedUserOptions() const
|
|
{
|
|
return m_skippedUserOptions;
|
|
}
|
|
|
|
void MpvItem::applyUserConfig()
|
|
{
|
|
if (m_loadUserConfig) {
|
|
const QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QStringLiteral("/mpv");
|
|
QDir().mkpath(cacheDir);
|
|
|
|
const QStringList files = MpvUserConfig::configFiles();
|
|
for (qsizetype i = 0; i < files.size(); ++i) {
|
|
QFile source(files.at(i));
|
|
if (!source.open(QIODevice::ReadOnly)) {
|
|
continue;
|
|
}
|
|
const MpvUserConfig::Prepared prepared = MpvUserConfig::prepare(QString::fromUtf8(source.readAll()), MpvUserConfig::configDir());
|
|
|
|
// mpv only loads config from files, so the adapted copy goes to the cache.
|
|
QSaveFile copy(QStringLiteral("%1/mpv-%2.conf").arg(cacheDir).arg(i));
|
|
if (!copy.open(QIODevice::WriteOnly) || copy.write(prepared.content.toUtf8()) < 0 || !copy.commit()) {
|
|
continue;
|
|
}
|
|
commandAsync(QStringList{QStringLiteral("load-config-file"), copy.fileName()});
|
|
m_loadedUserConfigFiles.append(files.at(i));
|
|
for (const QString &option : prepared.skippedOptions) {
|
|
if (!m_skippedUserOptions.contains(option)) {
|
|
m_skippedUserOptions.append(option);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Q_EMIT userConfigApplied();
|
|
}
|
|
|
|
void MpvItem::onPropertyChanged(const QString &property, const QVariant &value)
|
|
{
|
|
if (property == u"media-title") {
|
|
m_mediaTitle = value.toString();
|
|
Q_EMIT mediaTitleChanged();
|
|
} else if (property == u"time-pos") {
|
|
m_position = value.toDouble();
|
|
Q_EMIT positionChanged();
|
|
} else if (property == u"duration") {
|
|
m_duration = value.toDouble();
|
|
Q_EMIT durationChanged();
|
|
} 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();
|
|
} else if (property == u"volume") {
|
|
m_volume = value.toInt();
|
|
Q_EMIT volumeChanged();
|
|
} else if (property == u"volume-max") {
|
|
m_volumeMax = value.toInt();
|
|
Q_EMIT volumeMaxChanged();
|
|
} else if (property == u"mute") {
|
|
m_mute = value.toBool();
|
|
Q_EMIT muteChanged();
|
|
} else if (property == u"sub-visibility") {
|
|
m_subtitleVisible = value.toBool();
|
|
Q_EMIT subtitleVisibleChanged();
|
|
} else if (property == u"deinterlace") {
|
|
m_deinterlace = value.toBool();
|
|
Q_EMIT deinterlaceChanged();
|
|
} else if (property == u"speed") {
|
|
m_speed = value.toDouble();
|
|
Q_EMIT speedChanged();
|
|
} else if (property == u"chapter-list") {
|
|
QVariantList chapters;
|
|
for (const QVariant &entry : value.toList()) {
|
|
const QVariantMap chapter = entry.toMap();
|
|
chapters.append(QVariantMap{
|
|
{QStringLiteral("title"), chapter.value(QStringLiteral("title")).toString()},
|
|
{QStringLiteral("time"), chapter.value(QStringLiteral("time")).toDouble()},
|
|
});
|
|
}
|
|
m_chapters = chapters;
|
|
Q_EMIT chaptersChanged();
|
|
} else if (property == u"chapter") {
|
|
// Unavailable (no chapters) arrives as an invalid value.
|
|
m_chapter = value.isValid() ? value.toInt() : -1;
|
|
Q_EMIT chapterChanged();
|
|
} else if (property == u"eof-reached") {
|
|
if (value.toBool()) {
|
|
Q_EMIT endOfFile();
|
|
}
|
|
}
|
|
}
|
|
|
|
void MpvItem::updateTracks(const QVariantList &trackList)
|
|
{
|
|
QVariantList subtitles;
|
|
QVariantList audio;
|
|
int primary = 0;
|
|
int secondary = 0;
|
|
int audioId = 0;
|
|
|
|
for (const QVariant &entry : trackList) {
|
|
const QVariantMap track = entry.toMap();
|
|
const QString type = track.value(QStringLiteral("type")).toString();
|
|
const int id = track.value(QStringLiteral("id")).toInt();
|
|
|
|
if (type == u"audio") {
|
|
if (track.value(QStringLiteral("selected")).toBool()) {
|
|
audioId = id;
|
|
}
|
|
audio.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("forced"), track.value(QStringLiteral("forced")).toBool()},
|
|
{QStringLiteral("default"), track.value(QStringLiteral("default")).toBool()},
|
|
});
|
|
continue;
|
|
}
|
|
if (type != u"sub") {
|
|
continue;
|
|
}
|
|
|
|
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("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()},
|
|
});
|
|
|
|
if (!m_pendingTranslatedPath.isEmpty() && track.value(QStringLiteral("external-filename")).toString() == m_pendingTranslatedPath) {
|
|
m_pendingTranslatedPath.clear();
|
|
showOnlySubtitle(id);
|
|
}
|
|
}
|
|
|
|
m_subtitleTracks = subtitles;
|
|
m_subtitleId = primary;
|
|
m_secondarySubtitleId = secondary;
|
|
Q_EMIT subtitleTracksChanged();
|
|
|
|
m_audioTracks = audio;
|
|
m_audioId = audioId;
|
|
Q_EMIT audioTracksChanged();
|
|
}
|
|
|
|
void MpvItem::setFileLoaded(bool loaded)
|
|
{
|
|
if (loaded == m_fileLoaded) {
|
|
return;
|
|
}
|
|
m_fileLoaded = loaded;
|
|
Q_EMIT fileLoadedChanged();
|
|
}
|