File indexing completed on 2024-05-12 16:21:15

0001 // SPDX-FileCopyrightText: 2021 Jonah Brüchert <jbb@kaidan.im>
0002 //
0003 // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0004 
0005 #include "asyncytmusic.h"
0006 
0007 #include <QThread>
0008 #include <QDebug>
0009 #include <QJsonObject>
0010 #include <QJsonArray>
0011 #include <QFutureInterface>
0012 #include <QCoreApplication>
0013 #include <QTimer>
0014 
0015 #include <KLocalizedString>
0016 
0017 #include <pybind11/embed.h>
0018 
0019 #include <iostream>
0020 
0021 namespace py = pybind11;
0022 
0023 #include <iostream>
0024 #include <algorithm>
0025 #include <unordered_map>
0026 #include <ranges>
0027 #include <type_traits>
0028 #include <memory>
0029 
0030 template <typename T, typename OP>
0031 std::optional<std::invoke_result_t<OP, T>> mapOptional(const std::optional<T> &optional, OP op) {
0032     if (optional.has_value()) {
0033         if constexpr (std::is_member_function_pointer<OP>::value) {
0034             return (&optional.value()->*op)();
0035         } else {
0036             return op(optional.value());
0037         }
0038     }
0039 
0040     return std::nullopt;
0041 }
0042 
0043 AsyncYTMusic::AsyncYTMusic(QObject *parent)
0044     : QObject(parent)
0045 {
0046     qRegisterMetaType<std::vector<artist::Artist::Album>>();
0047     qRegisterMetaType<std::vector<search::SearchResultItem>>();
0048     qRegisterMetaType<artist::Artist>();
0049     qRegisterMetaType<album::Album>();
0050     qRegisterMetaType<song::Song>();
0051     qRegisterMetaType<playlist::Playlist>();
0052     qRegisterMetaType<video_info::VideoInfo>();
0053     qRegisterMetaType<watch::Playlist>();
0054     qRegisterMetaType<std::optional<QString>>();
0055     qRegisterMetaType<std::vector<meta::Artist>>();
0056     qRegisterMetaType<meta::Artist>();
0057 
0058     connect(this, &AsyncYTMusic::errorOccurred, this, [](const QString &err) {
0059         std::cerr << qPrintable(err) << std::endl;
0060     });
0061 
0062     QTimer::singleShot(0, this, [this]() {
0063         QCoro::connect(version(), this, [this](auto &&version) {
0064             if (version != TESTED_YTMUSICAPI_VERSION) {
0065                 Q_EMIT errorOccurred(i18n("Running with untested version of ytmusicapi %1. "
0066                                           "If you experience errors, please report them to your distribution.", version));
0067             }
0068         });
0069     });
0070 }
0071 
0072 //
0073 // search
0074 //
0075 QFuture<std::vector<search::SearchResultItem>> AsyncYTMusic::search(const QString &query)
0076 {
0077     return invokeAndCatchOnThread([=, this]() {
0078         return m_ytm->search(query.toStdString());
0079     });
0080 }
0081 
0082 //
0083 // fetchArtist
0084 //
0085 QFuture<artist::Artist> AsyncYTMusic::fetchArtist(const QString &channelId)
0086 {
0087     return invokeAndCatchOnThread([=, this]() {
0088         return m_ytm->get_artist(channelId.toStdString());
0089     });
0090 }
0091 
0092 //
0093 // fetchAlbum
0094 //
0095 QFuture<album::Album> AsyncYTMusic::fetchAlbum(const QString &browseId)
0096 {
0097     return invokeAndCatchOnThread([=, this]() {
0098         return m_ytm->get_album(browseId.toStdString());
0099     });
0100 }
0101 
0102 //
0103 // fetchSong
0104 //
0105 QFuture<std::optional<song::Song>> AsyncYTMusic::fetchSong(const QString &videoId)
0106 {
0107     return invokeAndCatchOnThread([=, this]() -> std::optional<song::Song> {
0108         if (videoId.isEmpty()) {
0109             return {};
0110         }
0111 
0112         return m_ytm->get_song(videoId.toStdString());
0113     });
0114 }
0115 
0116 //
0117 // fetchPlaylist
0118 //
0119 QFuture<playlist::Playlist> AsyncYTMusic::fetchPlaylist(const QString &playlistId) {
0120     return invokeAndCatchOnThread([=, this]() {
0121         return m_ytm->get_playlist(playlistId.toStdString());
0122     });
0123 }
0124 
0125 //
0126 // fetchArtistAlbum
0127 //
0128 QFuture<std::vector<artist::Artist::Album>> AsyncYTMusic::fetchArtistAlbums(const QString &channelId, const QString &params)
0129 {
0130     return invokeAndCatchOnThread([=, this]() {
0131         return m_ytm->get_artist_albums(channelId.toStdString(), params.toStdString());
0132     });
0133 }
0134 
0135 //
0136 // extractVideoInfo
0137 //
0138 QFuture<video_info::VideoInfo> AsyncYTMusic::extractVideoInfo(const QString &videoId)
0139 {
0140     return invokeAndCatchOnThread([=, this]() {
0141         return m_ytm->extract_video_info(videoId.toStdString());
0142     });
0143 }
0144 
0145 //
0146 // fetchWatchPlaylist
0147 //
0148 QFuture<watch::Playlist> AsyncYTMusic::fetchWatchPlaylist(const std::optional<QString> &videoId, const std::optional<QString> &playlistId)
0149 {
0150     return invokeAndCatchOnThread([=, this]() {
0151         return m_ytm->get_watch_playlist(
0152             mapOptional(videoId, &QString::toStdString),
0153             mapOptional(playlistId,  &QString::toStdString)
0154         );
0155     });
0156 }
0157 
0158 QFuture<Lyrics> AsyncYTMusic::fetchLyrics(const QString &browseId)
0159 {
0160     return invokeAndCatchOnThread([=, this]() {
0161         return m_ytm->get_lyrics(
0162             browseId.toStdString()
0163         );
0164     });
0165 }
0166 
0167 QFuture<QString> AsyncYTMusic::version()
0168 {
0169     return invokeAndCatchOnThread([this]() {
0170         return QString::fromStdString(m_ytm->get_version());
0171     });
0172 }
0173 
0174 YTMusicThread &YTMusicThread::instance()
0175 {
0176     static YTMusicThread thread;
0177     return thread;
0178 }
0179 
0180 YTMusicThread::~YTMusicThread()
0181 {
0182     quit();
0183     wait();
0184 }
0185 
0186 AsyncYTMusic *YTMusicThread::operator->()
0187 {
0188     return m_ytm;
0189 }
0190 
0191 AsyncYTMusic &YTMusicThread::get()
0192 {
0193     return *m_ytm;
0194 }
0195 
0196 YTMusicThread::YTMusicThread()
0197     : m_ytm(new AsyncYTMusic())
0198 {
0199     connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, m_ytm, &QObject::deleteLater);
0200     setObjectName(QStringLiteral("YTMusicAPI"));
0201     m_ytm->moveToThread(this);
0202     start();
0203 }