File indexing completed on 2024-04-28 04:49:19

0001 /*
0002  * SPDX-FileCopyrightText: 2020 George Florea Bănuș <georgefb899@gmail.com>
0003  *
0004  * SPDX-License-Identifier: GPL-3.0-or-later
0005  */
0006 
0007 #include "mpvitem.h"
0008 
0009 #include <QCommandLineParser>
0010 #include <QCryptographicHash>
0011 #include <QDir>
0012 #include <QTimer>
0013 
0014 #include <KFileMetaData/ExtractorCollection>
0015 #include <KFileMetaData/SimpleExtractionResult>
0016 #include <KLocalizedString>
0017 #include <KShell>
0018 #include <MpvController>
0019 
0020 #include "application.h"
0021 #include "audiosettings.h"
0022 #include "chaptersmodel.h"
0023 #include "generalsettings.h"
0024 #include "global.h"
0025 #include "informationsettings.h"
0026 #include "mpvproperties.h"
0027 #include "playbacksettings.h"
0028 #include "playlistmodel.h"
0029 #include "playlistsettings.h"
0030 #include "subtitlessettings.h"
0031 #include "tracksmodel.h"
0032 #include "videosettings.h"
0033 #include "worker.h"
0034 
0035 #if defined(Q_OS_UNIX)
0036 #include "lockmanager.h"
0037 #include "mediaplayer2.h"
0038 #include "mediaplayer2player.h"
0039 #include <QDBusConnection>
0040 #endif
0041 
0042 MpvItem::MpvItem(QQuickItem *parent)
0043     : MpvAbstractItem(parent)
0044     , m_audioTracksModel{new TracksModel}
0045     , m_subtitleTracksModel{new TracksModel}
0046     , m_playlistModel{new PlaylistModel}
0047     , m_playlistProxyModel{new PlaylistProxyModel}
0048     , m_chaptersModel{new ChaptersModel}
0049     , m_watchLaterPath{QString(Global::instance()->appConfigDirPath()).append(QStringLiteral("/watch-later/"))}
0050 {
0051     m_playlistProxyModel->setSourceModel(m_playlistModel);
0052     observeProperty(MpvProperties::self()->MediaTitle, MPV_FORMAT_STRING);
0053     observeProperty(MpvProperties::self()->Position, MPV_FORMAT_DOUBLE);
0054     observeProperty(MpvProperties::self()->Remaining, MPV_FORMAT_DOUBLE);
0055     observeProperty(MpvProperties::self()->Duration, MPV_FORMAT_DOUBLE);
0056     observeProperty(MpvProperties::self()->Pause, MPV_FORMAT_FLAG);
0057     observeProperty(MpvProperties::self()->Volume, MPV_FORMAT_INT64);
0058     observeProperty(MpvProperties::self()->VolumeMax, MPV_FORMAT_INT64);
0059     observeProperty(MpvProperties::self()->Mute, MPV_FORMAT_FLAG);
0060     observeProperty(MpvProperties::self()->AudioId, MPV_FORMAT_INT64);
0061     observeProperty(MpvProperties::self()->SubtitleId, MPV_FORMAT_INT64);
0062     observeProperty(MpvProperties::self()->Width, MPV_FORMAT_NODE);
0063     observeProperty(MpvProperties::self()->Height, MPV_FORMAT_NODE);
0064     observeProperty(MpvProperties::self()->SecondarySubtitleId, MPV_FORMAT_INT64);
0065     observeProperty(MpvProperties::self()->Chapter, MPV_FORMAT_INT64);
0066     observeProperty(MpvProperties::self()->ChapterList, MPV_FORMAT_NODE);
0067 
0068     initProperties();
0069     setupConnections();
0070 
0071     auto *timer = new QTimer();
0072     timer->setInterval(PlaybackSettings::savePositionInterval() * 1000);
0073     timer->start();
0074 
0075     connect(timer, &QTimer::timeout, this, [=]() {
0076         if (finishedLoading() && duration() > 0 && !pause()) {
0077             if (position() < duration() - 10) {
0078                 saveTimePosition();
0079             } else {
0080                 resetTimePosition();
0081             }
0082         }
0083     });
0084 
0085     connect(QApplication::instance(), &QApplication::aboutToQuit, this, [=]() {
0086         if (position() < duration() - 10) {
0087             saveTimePosition();
0088         } else {
0089             resetTimePosition();
0090         }
0091     });
0092 
0093     connect(this, &MpvAbstractItem::ready, this, &MpvItem::onReady);
0094 
0095     // run user commands
0096     KSharedConfig::Ptr m_customPropsConfig;
0097     QString ccConfig = Global::instance()->appConfigFilePath(Global::ConfigFile::CustomCommands);
0098     m_customPropsConfig = KSharedConfig::openConfig(ccConfig, KConfig::SimpleConfig);
0099     QStringList groups = m_customPropsConfig->groupList();
0100     for (const QString &_group : std::as_const(groups)) {
0101         auto configGroup = m_customPropsConfig->group(_group);
0102         QString type = configGroup.readEntry("Type", QString());
0103         bool setOnStartup = configGroup.readEntry("SetOnStartup", true);
0104         if (type == QStringLiteral("startup") && setOnStartup) {
0105             userCommand(configGroup.readEntry("Command", QString()));
0106         }
0107     }
0108 }
0109 
0110 void MpvItem::initProperties()
0111 {
0112     //    setProperty(QStringLiteral("terminal"), InformationSettings::mpvLogging());
0113     //    setProperty(QStringLiteral("msg-level"), QStringLiteral("all=v"));
0114 
0115     QString hwdec = PlaybackSettings::useHWDecoding() ? PlaybackSettings::hWDecoding() : QStringLiteral("no");
0116     setProperty(MpvProperties::self()->HardwareDecoding, hwdec);
0117     setProperty(MpvProperties::self()->Volume, AudioSettings::volume());
0118     setProperty(MpvProperties::self()->VolumeMax, 100);
0119 
0120     // set ytdl_path to yt-dlp or fallback to youtube-dl
0121     setProperty(MpvProperties::self()->ScriptOpts, QStringLiteral("ytdl_hook-ytdl_path=%1").arg(Application::youtubeDlExecutable()));
0122     QCommandLineParser *cmdParser = Application::instance()->parser();
0123     QString ytdlFormat = PlaybackSettings::ytdlFormat();
0124     if (cmdParser->isSet(QStringLiteral("ytdl-format-selection"))) {
0125         ytdlFormat = cmdParser->value(QStringLiteral("ytdl-format-selection"));
0126     }
0127     setProperty(MpvProperties::self()->YtdlFormat, ytdlFormat);
0128 
0129     setProperty(MpvProperties::self()->SubtitleAuto, QStringLiteral("exact"));
0130     setProperty(MpvProperties::self()->SubtitleUseMargins, SubtitlesSettings::allowOnBlackBorders());
0131     setProperty(MpvProperties::self()->SubtitleAssForceMargins, SubtitlesSettings::allowOnBlackBorders());
0132     setProperty(MpvProperties::self()->SubtitleFont, SubtitlesSettings::fontFamily());
0133     setProperty(MpvProperties::self()->SubtitleFontSize, SubtitlesSettings::fontSize());
0134     setProperty(MpvProperties::self()->SubtitleColor, SubtitlesSettings::fontColor());
0135     setProperty(MpvProperties::self()->SubtitleShadowColor, SubtitlesSettings::shadowColor());
0136     setProperty(MpvProperties::self()->SubtitleShadowOffset, SubtitlesSettings::shadowOffset());
0137     setProperty(MpvProperties::self()->SubtitleBorderColor, SubtitlesSettings::borderColor());
0138     setProperty(MpvProperties::self()->SubtitleBorderSize, SubtitlesSettings::borderSize());
0139     setProperty(MpvProperties::self()->SubtitleBold, SubtitlesSettings::isBold());
0140     setProperty(MpvProperties::self()->SubtitleItalic, SubtitlesSettings::isItalic());
0141 
0142     setProperty(MpvProperties::self()->ScreenshotTemplate, VideoSettings::screenshotTemplate());
0143     setProperty(MpvProperties::self()->ScreenshotFormat, VideoSettings::screenshotFormat());
0144 
0145     setProperty(MpvProperties::self()->AudioClientName, QStringLiteral("haruna"));
0146     const QVariant preferredAudioTrack = AudioSettings::preferredTrack();
0147     setProperty(MpvProperties::self()->AudioId, preferredAudioTrack == 0 ? QStringLiteral("auto") : preferredAudioTrack);
0148     setProperty(MpvProperties::self()->AudioLanguage, AudioSettings::preferredLanguage().remove(QStringLiteral(" ")));
0149 
0150     const QVariant preferredSubTrack = SubtitlesSettings::preferredTrack();
0151     setProperty(MpvProperties::self()->SubtitleId, preferredSubTrack == 0 ? QStringLiteral("auto") : preferredSubTrack);
0152     setProperty(MpvProperties::self()->SubtitleLanguage, SubtitlesSettings::preferredLanguage().remove(QStringLiteral(" ")));
0153     setProperty(MpvProperties::self()->SubtitleFilePaths, SubtitlesSettings::subtitlesFolders().join(QStringLiteral(":")));
0154 }
0155 
0156 void MpvItem::setupConnections()
0157 {
0158     // clang-format off
0159     connect(mpvController(), &MpvController::propertyChanged,
0160             this, &MpvItem::onPropertyChanged, Qt::QueuedConnection);
0161 
0162     connect(mpvController(), &MpvController::fileStarted,
0163             this, &MpvItem::fileStarted, Qt::QueuedConnection);
0164 
0165     connect(mpvController(), &MpvController::endFile,
0166             this, &MpvItem::onEndFile, Qt::QueuedConnection);
0167 
0168     connect(mpvController(), &MpvController::videoReconfig,
0169             this, &MpvItem::videoReconfig, Qt::QueuedConnection);
0170 
0171     connect(mpvController(), &MpvController::asyncReply,
0172             this, &MpvItem::onAsyncReply, Qt::QueuedConnection);
0173 
0174     connect(this, &MpvItem::currentUrlChanged, this, [=]() {
0175         setFinishedLoading(false);
0176     });
0177 
0178     connect(mpvController(), &MpvController::fileLoaded, this, [=]() {
0179         getPropertyAsync(MpvProperties::self()->ChapterList, static_cast<int>(AsyncIds::ChapterList));
0180         getPropertyAsync(MpvProperties::self()->VideoId, static_cast<int>(AsyncIds::VideoId));
0181         getPropertyAsync(MpvProperties::self()->TrackList, static_cast<int>(AsyncIds::TrackList));
0182 
0183         if (m_playlistModel->rowCount() <= 1 && PlaylistSettings::repeat()) {
0184             setProperty(MpvProperties::self()->LoopFile, QStringLiteral("inf"));
0185         }
0186 
0187         setProperty(MpvProperties::self()->ABLoopA, QStringLiteral("no"));
0188         setProperty(MpvProperties::self()->ABLoopB, QStringLiteral("no"));
0189 
0190         // this is only run when reloading the last file in the playlist
0191         // due to the playlist repeat setting being turned off
0192         if (isFileReloaded()) {
0193             setPause(true);
0194             setPosition(0);
0195             setIsFileReloaded(false);
0196             setFinishedLoading(true);
0197             return;
0198         }
0199 
0200         setFinishedLoading(true);
0201         Q_EMIT fileLoaded();
0202 
0203     }, Qt::QueuedConnection);
0204 
0205     connect(this, &MpvItem::positionChanged, this, [this]() {
0206         auto pos = static_cast<int>(position());
0207         if (!m_secondsWatched.contains(pos)) {
0208             m_secondsWatched << pos;
0209             if (m_duration != 0) {
0210                 setWatchPercentage(m_secondsWatched.count() * 100 / m_duration);
0211             }
0212         }
0213     });
0214 
0215     connect(this, &MpvItem::chapterChanged,
0216             this, &MpvItem::onChapterChanged);
0217 
0218     connect(m_playlistModel, &PlaylistModel::playingItemChanged, this, [=]() {
0219         loadFile(m_playlistModel->m_playlist[m_playlistModel->m_playingItem].url.toString());
0220     });
0221 
0222 
0223 #if defined(Q_OS_UNIX)
0224     // register mpris dbus service
0225     QString mspris2Name(QStringLiteral("org.mpris.MediaPlayer2.haruna"));
0226     QDBusConnection::sessionBus().registerService(mspris2Name);
0227     QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/mpris/MediaPlayer2"), this, QDBusConnection::ExportAdaptors);
0228     // org.mpris.MediaPlayer2 mpris2 interface
0229     auto mp2 = new MediaPlayer2(this);
0230     connect(mp2, &MediaPlayer2::raise, this, &MpvItem::raise);
0231     auto mp2Player = new MediaPlayer2Player(this);
0232     connect(mp2Player, &MediaPlayer2Player::playpause, this, [=]() {
0233         setPause(!pause());
0234     });
0235     connect(mp2Player, &MediaPlayer2Player::play, this, [=]() {
0236         setPause(false);
0237     });
0238     connect(mp2Player, &MediaPlayer2Player::pause, this, [=]() {
0239         setPause(true);
0240     });
0241     connect(mp2Player, &MediaPlayer2Player::stop, this, [=]() {
0242         setPosition(0);
0243         setPause(true);
0244     });
0245     connect(mp2Player, &MediaPlayer2Player::next, this, [=]() {
0246         Q_EMIT playNext();
0247     });
0248     connect(mp2Player, &MediaPlayer2Player::previous, this, [=]() {
0249         Q_EMIT playPrevious();
0250     });
0251     connect(mp2Player, &MediaPlayer2Player::seek, this, [=](int offset) {
0252         command(QStringList() << QStringLiteral("add") << QStringLiteral("time-pos") << QString::number(offset));
0253     });
0254     connect(mp2Player, &MediaPlayer2Player::openUri, this, [=](const QString &uri) {
0255         Q_EMIT openUri(uri);
0256     });
0257 #endif
0258 
0259 #if defined(Q_OS_UNIX)
0260     auto lockManager = new LockManager(this);
0261     connect(this, &MpvItem::pauseChanged, this, [=]() {
0262         if (pause()) {
0263             lockManager->setInhibitionOff();
0264         } else {
0265             lockManager->setInhibitionOn();
0266         }
0267     });
0268 #endif
0269 
0270     connect(this, &MpvItem::syncConfigValue, Worker::instance(), &Worker::syncConfigValue, Qt::QueuedConnection);
0271     // clang-format on
0272 }
0273 
0274 void MpvItem::onEndFile(const QString &reason)
0275 {
0276     if (reason == u"error"_qs) {
0277         if (playlistModel()->rowCount() == 0) {
0278             return;
0279         }
0280 
0281         const auto index = playlistProxyModel()->index(playlistProxyModel()->getPlayingItem(), 0);
0282         const auto title = playlistModel()->data(playlistProxyModel()->mapToSource(index), PlaylistModel::TitleRole);
0283 
0284         Q_EMIT osdMessage(i18nc("@info:tooltip", "Could not play: %1", title.toString()));
0285     }
0286 
0287     if (playlistProxyModel()->getPlayingItem() + 1 < playlistModel()->rowCount()) {
0288         playlistProxyModel()->playNext();
0289     } else {
0290         // Last file in playlist
0291         if (PlaylistSettings::repeat()) {
0292             playlistProxyModel()->setPlayingItem(0);
0293         } else {
0294             setIsFileReloaded(true);
0295             playlistProxyModel()->setPlayingItem(playlistProxyModel()->getPlayingItem());
0296         }
0297     }
0298 }
0299 
0300 void MpvItem::onReady()
0301 {
0302     setIsReady(true);
0303 
0304     QUrl url{Application::instance()->url(0)};
0305     if (!url.isEmpty() && url.isValid()) {
0306         playlistModel()->addItem(Application::instance()->url(0), PlaylistModel::Clear);
0307         Q_EMIT addToRecentFiles(url);
0308     } else {
0309         if (PlaybackSettings::openLastPlayedFile()) {
0310             // if both lastPlaylist and lastPlayedFile are set the playlist is loaded
0311             // and the lastPlayedFile is searched in the playlist
0312             if (!GeneralSettings::lastPlaylist().isEmpty()) {
0313                 playlistModel()->addItem(GeneralSettings::lastPlaylist(), PlaylistModel::Clear);
0314             } else {
0315                 playlistModel()->addItem(GeneralSettings::lastPlayedFile(), PlaylistModel::Clear);
0316             }
0317         }
0318     }
0319 }
0320 
0321 void MpvItem::onPropertyChanged(const QString &property, const QVariant &value)
0322 {
0323     if (property == MpvProperties::self()->MediaTitle) {
0324         m_mediaTitle = value.toString();
0325         Q_EMIT mediaTitleChanged();
0326 
0327     } else if (property == MpvProperties::self()->Position) {
0328         m_position = value.toDouble();
0329         m_formattedPosition = Application::formatTime(m_position);
0330         Q_EMIT positionChanged();
0331 
0332     } else if (property == MpvProperties::self()->Remaining) {
0333         m_remaining = value.toDouble();
0334         m_formattedRemaining = Application::formatTime(m_remaining);
0335         Q_EMIT remainingChanged();
0336 
0337     } else if (property == MpvProperties::self()->Duration) {
0338         m_duration = value.toDouble();
0339         m_formattedDuration = Application::formatTime(m_duration);
0340         Q_EMIT durationChanged();
0341 
0342     } else if (property == MpvProperties::self()->Pause) {
0343         m_pause = value.toBool();
0344         Q_EMIT pauseChanged();
0345 
0346     } else if (property == MpvProperties::self()->Volume) {
0347         m_volume = value.toInt();
0348         Q_EMIT volumeChanged();
0349 
0350         AudioSettings::setVolume(m_volume);
0351         AudioSettings::self()->save();
0352 
0353     } else if (property == MpvProperties::self()->VolumeMax) {
0354         m_volumeMax = value.toInt();
0355         Q_EMIT volumeMaxChanged();
0356 
0357     } else if (property == MpvProperties::self()->Mute) {
0358         m_mute = value.toBool();
0359         Q_EMIT muteChanged();
0360 
0361     } else if (property == MpvProperties::self()->Chapter) {
0362         m_chapter = value.toInt();
0363         Q_EMIT chapterChanged();
0364 
0365     } else if (property == MpvProperties::self()->ChapterList) {
0366         Q_EMIT chapterListChanged();
0367 
0368     } else if (property == MpvProperties::self()->AudioId) {
0369         m_audioId = value.toInt();
0370         Q_EMIT audioIdChanged();
0371 
0372     } else if (property == MpvProperties::self()->SubtitleId) {
0373         m_subtitleId = value.toInt();
0374         Q_EMIT subtitleIdChanged();
0375 
0376     } else if (property == MpvProperties::self()->SecondarySubtitleId) {
0377         m_secondarySubtitleId = value.toInt();
0378         Q_EMIT secondarySubtitleIdChanged();
0379 
0380     } else if (property == MpvProperties::self()->Width) {
0381         m_videoWidth = value.toInt();
0382         Q_EMIT videoWidthChanged();
0383 
0384     } else if (property == MpvProperties::self()->Height) {
0385         m_videoHeight = value.toInt();
0386         Q_EMIT videoHeightChanged();
0387     }
0388 }
0389 
0390 void MpvItem::loadFile(const QString &file)
0391 {
0392     auto url = QUrl::fromUserInput(file);
0393     if (m_currentUrl != url) {
0394         m_currentUrl = url;
0395         Q_EMIT currentUrlChanged();
0396     }
0397 
0398     setPause(false);
0399     setWatchLaterPosition(loadTimePosition());
0400     if (PlaybackSettings::seekToLastPosition()) {
0401         setPause(!PlaybackSettings::playOnResume() && watchLaterPosition() > 0);
0402         setProperty(u"start"_qs, QVariant(u"+"_qs + QString::number(m_watchLaterPosition)));
0403     }
0404     command(QStringList() << QStringLiteral("loadfile") << m_currentUrl.toString());
0405 
0406     GeneralSettings::setLastPlayedFile(m_currentUrl.toString());
0407     GeneralSettings::self()->save();
0408 }
0409 
0410 void MpvItem::loadTracks(QList<QVariant> tracks)
0411 {
0412     m_subtitleTracks.clear();
0413     m_audioTracks.clear();
0414 
0415     QMap<QString, QVariant> none = {
0416         {QStringLiteral("id"), QVariant(0)},
0417         {QStringLiteral("title"), QVariant(i18nc("@action The \"None\" subtitle track is used to clear/unset selected track", "None"))},
0418     };
0419     m_subtitleTracks.append(none);
0420 
0421     for (const auto &track : tracks) {
0422         const auto trackMap = track.toMap();
0423         if (trackMap[QStringLiteral("type")] == QStringLiteral("sub")) {
0424             m_subtitleTracks.append(track);
0425         }
0426         if (trackMap[QStringLiteral("type")] == QStringLiteral("audio")) {
0427             m_audioTracks.append(track);
0428         }
0429     }
0430     m_subtitleTracksModel->setTracks(std::move(m_subtitleTracks));
0431     m_audioTracksModel->setTracks(std::move(m_audioTracks));
0432 
0433     Q_EMIT audioTracksModelChanged();
0434     Q_EMIT subtitleTracksModelChanged();
0435 }
0436 
0437 void MpvItem::onAsyncReply(const QVariant &data, mpv_event event)
0438 {
0439     switch (static_cast<AsyncIds>(event.reply_userdata)) {
0440     case AsyncIds::None: {
0441         break;
0442     }
0443     case AsyncIds::SavePosition: {
0444         auto hash = md5(currentUrl().toString());
0445         auto watchLaterConfig = QString(m_watchLaterPath).append(hash);
0446         Q_EMIT syncConfigValue(watchLaterConfig, QString(), QStringLiteral("TimePosition"), data.toString());
0447         break;
0448     }
0449     case AsyncIds::Screenshot: {
0450         if (event.error < 0) {
0451             osdMessage(i18nc("@info:tooltip osd", "Screenshot failed"));
0452             break;
0453         }
0454         auto filename = data.toMap().value(QStringLiteral("filename")).toString();
0455         if (filename.isEmpty()) {
0456             osdMessage(i18nc("@info:tooltip osd", "Screenshot taken"));
0457         } else {
0458             osdMessage(i18nc("@info:tooltip osd", "Screenshot: %1", filename));
0459         }
0460         break;
0461     }
0462     case AsyncIds::TrackList: {
0463         loadTracks(data.toList());
0464         break;
0465     }
0466     case AsyncIds::ChapterList: {
0467         m_chaptersList = data.toList();
0468         QList<Chapter> chaptersList;
0469         for (const auto &chapter : m_chaptersList) {
0470             Chapter c;
0471             c.title = chapter.toMap()[QStringLiteral("title")].toString();
0472             c.startTime = chapter.toMap()[QStringLiteral("time")].toDouble();
0473             chaptersList.append(c);
0474         }
0475         m_chaptersModel->setChapters(chaptersList);
0476         break;
0477     }
0478     case AsyncIds::VideoId: {
0479         if (!data.toBool()) {
0480             command(QStringList{QStringLiteral("video-add"), VideoSettings::defaultCover()});
0481         }
0482         break;
0483     }
0484     }
0485 }
0486 
0487 void MpvItem::onChapterChanged()
0488 {
0489     if (!finishedLoading() || !PlaybackSettings::skipChapters()) {
0490         return;
0491     }
0492 
0493     const QString chaptersToSkip = PlaybackSettings::chaptersToSkip();
0494     if (m_chaptersList.count() == 0 || chaptersToSkip == QString()) {
0495         return;
0496     }
0497 
0498     const QStringList words = chaptersToSkip.split(QStringLiteral(","));
0499     auto ch = m_chaptersList.value(chapter()).toMap();
0500     auto title = ch.value(QStringLiteral("title")).toString();
0501     for (int i = 0; i < words.count(); ++i) {
0502         QString word = words.at(i).toLower().simplified();
0503         if (!ch.isEmpty() && title.toLower().contains(word)) {
0504             command({QStringLiteral("add"), QStringLiteral("chapter"), QStringLiteral("1")});
0505             if (PlaybackSettings::showOsdOnSkipChapters()) {
0506                 Q_EMIT osdMessage(i18nc("@info:tooltip osd", "Skipped chapter: %1", title));
0507             }
0508             return;
0509         }
0510     }
0511 }
0512 
0513 void MpvItem::saveTimePosition()
0514 {
0515     // saving position is disabled
0516     if (PlaybackSettings::minDurationToSavePosition() == -1) {
0517         return;
0518     }
0519     // position is saved only for files longer than PlaybackSettings::minDurationToSavePosition()
0520     if (getProperty(MpvProperties::self()->Duration).toInt() < PlaybackSettings::minDurationToSavePosition() * 60) {
0521         return;
0522     }
0523 
0524     getPropertyAsync(MpvProperties::self()->Position, static_cast<int>(AsyncIds::SavePosition));
0525 }
0526 
0527 double MpvItem::loadTimePosition()
0528 {
0529     // saving position is disabled
0530     if (PlaybackSettings::minDurationToSavePosition() == -1) {
0531         return 0;
0532     }
0533     double duration{0};
0534 
0535     // KFileMetaData is faster than getProperty
0536     QString mimeType = Application::mimeType(m_currentUrl);
0537     KFileMetaData::ExtractorCollection exCol;
0538     QList<KFileMetaData::Extractor *> extractors = exCol.fetchExtractors(mimeType);
0539     KFileMetaData::SimpleExtractionResult result(m_currentUrl.toLocalFile(), mimeType, KFileMetaData::ExtractionResult::ExtractMetaData);
0540     if (extractors.size() > 0) {
0541         KFileMetaData::Extractor *ex = extractors.first();
0542         ex->extract(&result);
0543         auto properties = result.properties();
0544         duration = properties.value(KFileMetaData::Property::Duration).toDouble();
0545     } else {
0546         duration = getProperty(MpvProperties::self()->Duration).toInt();
0547     }
0548 
0549     // position for files with a duration lower than
0550     // PlaybackSettings::minDurationToSavePosition() is not restored
0551     if (duration < PlaybackSettings::minDurationToSavePosition() * 60) {
0552         return 0;
0553     }
0554 
0555     auto hash = md5(currentUrl().toString());
0556     auto watchLaterConfig = QString(m_watchLaterPath).append(hash);
0557     KConfig config(watchLaterConfig);
0558     auto pos = config.group(QString()).readEntry("TimePosition", QString::number(0)).toDouble();
0559 
0560     return pos;
0561 }
0562 
0563 void MpvItem::resetTimePosition()
0564 {
0565     auto hash = md5(currentUrl().toString());
0566     auto watchLaterConfig = QString(m_watchLaterPath).append(hash);
0567     QFile f(watchLaterConfig);
0568 
0569     if (f.exists()) {
0570         f.remove();
0571     }
0572     f.close();
0573 }
0574 
0575 void MpvItem::userCommand(const QString &commandString)
0576 {
0577     QStringList args = KShell::splitArgs(commandString.simplified());
0578     command(args);
0579 }
0580 
0581 QString MpvItem::md5(const QString &str)
0582 {
0583     auto md5 = QCryptographicHash::hash((str.toUtf8()), QCryptographicHash::Md5);
0584 
0585     return QString::fromUtf8(md5.toHex());
0586 }
0587 
0588 PlaylistModel *MpvItem::playlistModel()
0589 {
0590     return m_playlistModel;
0591 }
0592 
0593 void MpvItem::setPlaylistModel(PlaylistModel *model)
0594 {
0595     m_playlistModel = model;
0596 }
0597 
0598 PlaylistProxyModel *MpvItem::playlistProxyModel()
0599 {
0600     return m_playlistProxyModel;
0601 }
0602 
0603 void MpvItem::setPlaylistProxyModel(PlaylistProxyModel *model)
0604 {
0605     m_playlistProxyModel = model;
0606 }
0607 
0608 ChaptersModel *MpvItem::chaptersModel() const
0609 {
0610     return m_chaptersModel;
0611 }
0612 
0613 void MpvItem::setChaptersModel(ChaptersModel *_chaptersModel)
0614 {
0615     if (m_chaptersModel == _chaptersModel) {
0616         return;
0617     }
0618     m_chaptersModel = _chaptersModel;
0619     Q_EMIT chaptersModelChanged();
0620 }
0621 
0622 TracksModel *MpvItem::subtitleTracksModel() const
0623 {
0624     return m_subtitleTracksModel;
0625 }
0626 
0627 TracksModel *MpvItem::audioTracksModel() const
0628 {
0629     return m_audioTracksModel;
0630 }
0631 
0632 bool MpvItem::isFileReloaded() const
0633 {
0634     return m_isFileReloaded;
0635 }
0636 
0637 void MpvItem::setIsFileReloaded(bool _isFileReloaded)
0638 {
0639     if (m_isFileReloaded == _isFileReloaded) {
0640         return;
0641     }
0642     m_isFileReloaded = _isFileReloaded;
0643     Q_EMIT isFileReloadedChanged();
0644 }
0645 
0646 QString MpvItem::mediaTitle()
0647 {
0648     return m_mediaTitle;
0649 }
0650 
0651 double MpvItem::position()
0652 {
0653     return m_position;
0654 }
0655 
0656 void MpvItem::setPosition(double value)
0657 {
0658     if (qFuzzyCompare(value, position())) {
0659         return;
0660     }
0661     setProperty(MpvProperties::self()->Position, value);
0662 }
0663 
0664 double MpvItem::remaining()
0665 {
0666     return m_remaining;
0667 }
0668 
0669 double MpvItem::duration()
0670 {
0671     return m_duration;
0672 }
0673 
0674 bool MpvItem::pause()
0675 {
0676     return m_pause;
0677 }
0678 
0679 void MpvItem::setPause(bool value)
0680 {
0681     if (value == pause()) {
0682         return;
0683     }
0684     setProperty(MpvProperties::self()->Pause, value);
0685 }
0686 
0687 int MpvItem::volume()
0688 {
0689     return m_volume;
0690 }
0691 
0692 void MpvItem::setVolume(int value)
0693 {
0694     if (value == volume()) {
0695         return;
0696     }
0697     setProperty(MpvProperties::self()->Volume, value);
0698 }
0699 
0700 int MpvItem::volumeMax()
0701 {
0702     return m_volumeMax;
0703 }
0704 
0705 void MpvItem::setVolumeMax(int value)
0706 {
0707     if (volumeMax() == value) {
0708         return;
0709     }
0710 
0711     setProperty(MpvProperties::self()->VolumeMax, value);
0712 }
0713 
0714 bool MpvItem::mute()
0715 {
0716     return m_mute;
0717 }
0718 
0719 void MpvItem::setMute(bool value)
0720 {
0721     if (value == mute()) {
0722         return;
0723     }
0724     setProperty(MpvProperties::self()->Mute, value);
0725 }
0726 
0727 int MpvItem::chapter()
0728 {
0729     return m_chapter;
0730 }
0731 
0732 void MpvItem::setChapter(int value)
0733 {
0734     if (value == chapter()) {
0735         return;
0736     }
0737     setProperty(MpvProperties::self()->Chapter, value);
0738 }
0739 
0740 int MpvItem::audioId()
0741 {
0742     return m_audioId;
0743 }
0744 
0745 void MpvItem::setAudioId(int value)
0746 {
0747     if (value == audioId()) {
0748         return;
0749     }
0750     setProperty(MpvProperties::self()->AudioId, value);
0751 }
0752 
0753 int MpvItem::subtitleId()
0754 {
0755     return m_subtitleId;
0756 }
0757 
0758 void MpvItem::setSubtitleId(int value)
0759 {
0760     if (value == subtitleId()) {
0761         return;
0762     }
0763     setProperty(MpvProperties::self()->SubtitleId, value);
0764 }
0765 
0766 int MpvItem::secondarySubtitleId()
0767 {
0768     return m_secondarySubtitleId;
0769 }
0770 
0771 void MpvItem::setSecondarySubtitleId(int value)
0772 {
0773     if (value == secondarySubtitleId()) {
0774         return;
0775     }
0776     setProperty(MpvProperties::self()->SecondarySubtitleId, value);
0777 }
0778 
0779 QString MpvItem::formattedDuration() const
0780 {
0781     return m_formattedDuration;
0782 }
0783 
0784 QString MpvItem::formattedRemaining() const
0785 {
0786     return m_formattedRemaining;
0787 }
0788 
0789 QString MpvItem::formattedPosition() const
0790 {
0791     return m_formattedPosition;
0792 }
0793 
0794 QUrl MpvItem::currentUrl() const
0795 {
0796     return m_currentUrl;
0797 }
0798 
0799 bool MpvItem::isReady() const
0800 {
0801     return m_isReady;
0802 }
0803 
0804 inline void MpvItem::setIsReady(bool _isReady)
0805 {
0806     if (m_isReady == _isReady) {
0807         return;
0808     }
0809     m_isReady = _isReady;
0810     Q_EMIT isReadyChanged();
0811 }
0812 
0813 double MpvItem::watchLaterPosition() const
0814 {
0815     return m_watchLaterPosition;
0816 }
0817 
0818 void MpvItem::setWatchLaterPosition(double _watchLaterPosition)
0819 {
0820     if (qFuzzyCompare(m_watchLaterPosition + 1, _watchLaterPosition + 1)) {
0821         return;
0822     }
0823     m_watchLaterPosition = _watchLaterPosition;
0824     Q_EMIT watchLaterPositionChanged();
0825 }
0826 
0827 bool MpvItem::finishedLoading() const
0828 {
0829     return m_finishedLoading;
0830 }
0831 
0832 void MpvItem::setFinishedLoading(bool _finishedLoading)
0833 {
0834     if (m_finishedLoading == _finishedLoading) {
0835         return;
0836     }
0837     m_finishedLoading = _finishedLoading;
0838     Q_EMIT finishedLoadingChanged();
0839 }
0840 
0841 double MpvItem::watchPercentage()
0842 {
0843     return m_watchPercentage;
0844 }
0845 
0846 void MpvItem::setWatchPercentage(double value)
0847 {
0848     if (qFuzzyCompare(m_watchPercentage, value)) {
0849         return;
0850     }
0851     m_watchPercentage = value;
0852     Q_EMIT watchPercentageChanged();
0853 }
0854 
0855 int MpvItem::videoWidth()
0856 {
0857     return m_videoWidth;
0858 }
0859 
0860 int MpvItem::videoHeight()
0861 {
0862     return m_videoHeight;
0863 }
0864 
0865 #include "moc_mpvitem.cpp"