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 ¶ms) 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 }