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

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 #pragma once
0006 
0007 #include <QObject>
0008 #include <QThread>
0009 #include <QFuture>
0010 #include <QFutureWatcher>
0011 
0012 #include <QCoroTask>
0013 #include <QCoroFuture>
0014 
0015 #include <iostream>
0016 #include <vector>
0017 
0018 #include <ytmusic.h>
0019 
0020 constexpr QStringView YTMUSIC_WEB_BASE_URL = u"https://music.youtube.com/";
0021 
0022 Q_DECLARE_METATYPE(std::vector<artist::Artist::Album>);
0023 Q_DECLARE_METATYPE(std::vector<search::SearchResultItem>)
0024 Q_DECLARE_METATYPE(artist::Artist)
0025 Q_DECLARE_METATYPE(album::Album)
0026 Q_DECLARE_METATYPE(song::Song)
0027 Q_DECLARE_METATYPE(playlist::Playlist)
0028 Q_DECLARE_METATYPE(video_info::VideoInfo)
0029 Q_DECLARE_METATYPE(watch::Playlist)
0030 Q_DECLARE_METATYPE(std::optional<QString>)
0031 Q_DECLARE_METATYPE(std::vector<meta::Artist>)
0032 Q_DECLARE_METATYPE(meta::Artist)
0033 
0034 ///
0035 /// Lazy initialized unique_ptr
0036 ///
0037 template <typename T>
0038 class Lazy {
0039 public:
0040     T *operator->() {
0041         return get().operator->();
0042     }
0043 
0044     inline std::unique_ptr<T> &get() {
0045         if (!m_item) {
0046             m_item = std::make_unique<T>();
0047         }
0048         Q_ASSERT(m_item);
0049         return m_item;
0050     }
0051 
0052 private:
0053     std::unique_ptr<T> m_item = nullptr;
0054 };
0055 
0056 class AsyncYTMusic : public QObject
0057 {
0058     friend class YTMusicThread;
0059 
0060     Q_OBJECT
0061 
0062 public:
0063     // public functions need to be thread safe
0064     QFuture<std::vector<search::SearchResultItem>> search(const QString &query);
0065 
0066     QFuture<artist::Artist> fetchArtist(const QString &channelId);
0067 
0068     QFuture<album::Album> fetchAlbum(const QString &browseId);
0069 
0070     QFuture<std::optional<song::Song> > fetchSong(const QString &videoId);
0071 
0072     QFuture<playlist::Playlist> fetchPlaylist(const QString &playlistId);
0073 
0074     QFuture<std::vector<artist::Artist::Album>> fetchArtistAlbums(const QString &channelId, const QString &params);
0075 
0076     QFuture<video_info::VideoInfo> extractVideoInfo(const QString &videoId);
0077 
0078     QFuture<watch::Playlist> fetchWatchPlaylist(const std::optional<QString> &videoId = std::nullopt ,
0079                             const std::optional<QString> &playlistId = std::nullopt);
0080 
0081     QFuture<Lyrics> fetchLyrics(const QString &browseId);
0082 
0083     QFuture<QString> version();
0084 
0085     Q_SIGNAL void errorOccurred(const QString &error);
0086 
0087 protected:
0088     explicit AsyncYTMusic(QObject *parent = nullptr);
0089 
0090 private:
0091     /// Invokes the given function on the thread of the YTMusic object, and handles exceptions that occur while invoking it.
0092     template <typename Func>
0093     QFuture<std::invoke_result_t<Func>> invokeAndCatchOnThread(Func fun) {
0094         using ReturnType = std::invoke_result_t<Func>;
0095         auto interface = std::make_shared<QFutureInterface<ReturnType>>();
0096         QMetaObject::invokeMethod(this, [=, this]() {
0097             ReturnType val;
0098             try {
0099                 val = fun();
0100             } catch (const std::exception &err) {
0101                 Q_EMIT errorOccurred(QString::fromLocal8Bit(err.what()));
0102             }
0103 
0104             interface->reportResult(val);
0105             interface->reportFinished();
0106         });
0107         return interface->future();
0108     }
0109 
0110     // Python interpreter will be initialized from the thread calling the methods
0111     Lazy<YTMusic> m_ytm;
0112 };
0113 
0114 class YTMusicThread : private QThread {
0115 public:
0116     static YTMusicThread &instance();
0117     ~YTMusicThread() override;
0118 
0119     AsyncYTMusic *operator->();
0120     AsyncYTMusic &get();
0121 
0122 private:
0123     YTMusicThread();
0124 
0125     AsyncYTMusic *m_ytm;
0126 };