Compare commits
23
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5112d0bd1b | ||
|
|
2b60ae8745 | ||
|
|
15f439c38e | ||
|
|
ca4d5d8b77 | ||
|
|
06338900dc | ||
|
|
8ec674fbef | ||
|
|
58eefc29eb | ||
|
|
9312331755 | ||
|
|
1d25f46f52 | ||
|
|
a775cd58be | ||
|
|
ae5ed3b10b | ||
|
|
8aab91568e | ||
|
|
6647346314 | ||
|
|
bfb41310ad | ||
|
|
c3371adda4 | ||
|
|
9bcf017c8d | ||
|
|
2c0b13ff9f | ||
|
|
abfd901a82 | ||
|
|
135b8859d6 | ||
|
|
f8637b686c | ||
|
|
53f466c976 | ||
|
|
762976159e | ||
|
|
de1a872f74 |
No files matched your search
@@ -6,3 +6,4 @@
|
||||
*.kdev4
|
||||
CMakeLists.txt.user
|
||||
flatpak/flatpak-builder-tools/
|
||||
build/
|
||||
+21
@@ -2,3 +2,24 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: AudioTube
|
||||
Upstream-Contact: Jonah Brüchert <[email protected]>
|
||||
Source: https://invent.kde.org/plasma-mobile/audiotube
|
||||
|
||||
# Sample paragraph, commented out:
|
||||
#
|
||||
# Files: ...
|
||||
# Copyright: $YEAR $NAME <$CONTACT>
|
||||
# License: ...
|
||||
# Comment: ...
|
||||
|
||||
Files: README.md
|
||||
Copyright: 2021 Jonah Brüchert <[email protected]>
|
||||
License: CC0-1.0
|
||||
|
||||
Files: org.kde.audiotube.desktop
|
||||
org.kde.audiotube.json
|
||||
Copyright: 2021 Jonah Brüchert <[email protected]>
|
||||
License: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
Files: logo.png
|
||||
org.kde.audiotube.svg
|
||||
Copyright: 2021 Mathis Brüchert <[email protected]>
|
||||
License: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
+2
-2
@@ -8,11 +8,11 @@ project(audiotube)
|
||||
|
||||
include(FeatureSummary)
|
||||
|
||||
set(PROJECT_VERSION "21.08")
|
||||
set(PROJECT_VERSION "22.02")
|
||||
set(QT5_MIN_VERSION 5.15)
|
||||
set(KF5_MIN_VERSION 5.81)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE)
|
||||
|
||||
@@ -6,7 +6,7 @@ AudioTube is a client for YouTube Music.
|
||||
|
||||
It depends on Qt, Kirigami, KI18n, KCoreAddons and KCrash in addition to a few python packages:
|
||||
* ytmusicapi
|
||||
* youtube_dl
|
||||
* yt_dlp
|
||||
|
||||
YouTube uses webp images for the cover art, so you will need to install qtimageformats.
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
SPDX-FileCopyrightText: 2021 Jonah Brüchert <[email protected]>
|
||||
|
||||
SPDX-License-Identifier: CC0-1.0
|
||||
@@ -7,8 +7,13 @@
|
||||
"sources": [
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/5e/2a/3cbe2e29926784d8718eb2f92357d7e22caf393f24e19287746076ffc9f2/yt_dlp-2021.11.10.1-py2.py3-none-any.whl",
|
||||
"sha256": "9c3d85fdeeac3d61cfc85fd72d55fe6b63fcf1d19d05e2841cf2e544922ed157"
|
||||
"url": "https://files.pythonhosted.org/packages/8e/10/7b43c0a779d4c6331088d64af7e4a2da27b47817f558e97ebbadfc41869f/yt_dlp-2022.1.21-py2.py3-none-any.whl",
|
||||
"sha256": "182903b78a4645edd504dcd9f2603f76f9fdaf8bd279fd45d01992f7637b6d8a"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/f6/06/e2ad9e93210790be86d36c6e2d5524ba54928c3ed27dd0be9b2ced7c57f1/pycryptodomex-3.12.0.zip",
|
||||
"sha256": "922e9dac0166e4617e5c7980d2cff6912a6eb5cb5c13e7ece222438650bd7f66"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
@@ -19,11 +24,6 @@
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/16/b3/f7aa8edf2ff4495116f95fd442b2a346aa55d1d46313143c8814886dbcdb/mutagen-1.45.1-py3-none-any.whl",
|
||||
"sha256": "9c9f243fcec7f410f138cb12c21c84c64fde4195481a30c9bfb05b5f003adfed"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/47/14/dd9ad29cd29ea4cc521286f2cb401ca7ac6fd5db0791c5e9bacaf2c9ac78/pycryptodomex-3.11.0.tar.gz",
|
||||
"sha256": "0398366656bb55ebdb1d1d493a7175fc48ade449283086db254ac44c7d318d6d"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -7,24 +7,24 @@
|
||||
"sources": [
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/7b/77/f341b20e7307f18ede4625867cbb8e18c20199117e0c23f7c6b53fe6597d/ytmusicapi-0.19.4-py3-none-any.whl",
|
||||
"sha256": "d0e980b35cf290eae409dcdc6ee31c5c358b28baebf1052e9fc9f446fc7901b7"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/92/96/144f70b972a9c0eabbd4391ef93ccd49d0f2747f4f6a2a2738e99e5adc65/requests-2.26.0-py2.py3-none-any.whl",
|
||||
"sha256": "6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/c8/27/141554fc0f42c05dd318fbb0be0e3e018da686544a3ff452762e49ccac58/charset_normalizer-2.0.8-py3-none-any.whl",
|
||||
"sha256": "83fcdeb225499d6344c8f7f34684c2981270beacc32ede2e669e94f7fa544405"
|
||||
"url": "https://files.pythonhosted.org/packages/33/d9/d18f3827dfd54acc48f29337059197514ad656ffc88e02a51e0457c5117a/ytmusicapi-0.20.0-py3-none-any.whl",
|
||||
"sha256": "abdb90426baa14a7929ec810d65c1df228661b61d580678fc48c087909a9aea2"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/37/45/946c02767aabb873146011e665728b680884cd8fe70dde973c640e45b775/certifi-2021.10.8-py2.py3-none-any.whl",
|
||||
"sha256": "d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/84/3e/1037abe6498e65d645ce7a22d3402605d49a3b2c7f20c3abb027760da4f0/charset_normalizer-2.0.10-py3-none-any.whl",
|
||||
"sha256": "cb957888737fc0bbcd78e3df769addb41fd1ff8cf950dc9e7ad7793f1bf44455"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/2d/61/08076519c80041bc0ffa1a8af0cbd3bf3e2b62af10435d269a9d0f40564d/requests-2.27.1-py2.py3-none-any.whl",
|
||||
"sha256": "f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/04/a2/d918dcd22354d8958fe113e1a3630137e0fc8b44859ade3063982eacd2a4/idna-3.3-py3-none-any.whl",
|
||||
@@ -32,8 +32,8 @@
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/af/f4/524415c0744552cce7d8bf3669af78e8a069514405ea4fcbd0cc44733744/urllib3-1.26.7-py2.py3-none-any.whl",
|
||||
"sha256": "c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"
|
||||
"url": "https://files.pythonhosted.org/packages/4e/b8/f5a25b22e803f0578e668daa33ba3701bb37858ec80e08a150bd7d2cf1b1/urllib3-1.26.8-py2.py3-none-any.whl",
|
||||
"sha256": "000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
SPDX-FileCopyrightText: 2021 Mathis Brüchert <[email protected]>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
@@ -7,6 +7,7 @@ SPDX-License-Identifier: CC0-1.0
|
||||
<component type="desktop-application">
|
||||
<id>org.kde.audiotube</id>
|
||||
<name>AudioTube</name>
|
||||
<name xml:lang="ar">تيوب الصوتي</name>
|
||||
<name xml:lang="ca">AudioTube</name>
|
||||
<name xml:lang="ca-valencia">AudioTube</name>
|
||||
<name xml:lang="cs">AudioTube</name>
|
||||
@@ -24,12 +25,14 @@ SPDX-License-Identifier: CC0-1.0
|
||||
<name xml:lang="pl">AudioTube</name>
|
||||
<name xml:lang="pt">AudioTube</name>
|
||||
<name xml:lang="pt-BR">AudioTube</name>
|
||||
<name xml:lang="ro">AudioTube</name>
|
||||
<name xml:lang="sk">AudioTube</name>
|
||||
<name xml:lang="sl">AudioTube</name>
|
||||
<name xml:lang="sv">AudioTube</name>
|
||||
<name xml:lang="uk">AudioTube</name>
|
||||
<name xml:lang="x-test">xxAudioTubexx</name>
|
||||
<summary>Convergent YouTube Music client</summary>
|
||||
<summary xml:lang="ar">عميل موسيقى يوتيوب متعدد المنصات</summary>
|
||||
<summary xml:lang="ca">Client convergent de música de YouTube</summary>
|
||||
<summary xml:lang="ca-valencia">Client convergent de música de YouTube</summary>
|
||||
<summary xml:lang="de">Konvergentes YouTube-Musikprogramm</summary>
|
||||
@@ -45,6 +48,7 @@ SPDX-License-Identifier: CC0-1.0
|
||||
<summary xml:lang="pl">Adaptacyjny program do YouTube Music</summary>
|
||||
<summary xml:lang="pt">Cliente de música do YouTube convergente</summary>
|
||||
<summary xml:lang="pt-BR">Um cliente convergente para o YouTube Music</summary>
|
||||
<summary xml:lang="ro">Client convergent de muzică pentru YouTube</summary>
|
||||
<summary xml:lang="sl">Konvergentni odjemalec glasbe YouTube</summary>
|
||||
<summary xml:lang="sv">Konvergent YouTube-musikprogram</summary>
|
||||
<summary xml:lang="uk">Зручний музичний клієнт YouTube</summary>
|
||||
@@ -52,6 +56,7 @@ SPDX-License-Identifier: CC0-1.0
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>GPL-2.0+</project_license>
|
||||
<developer_name>KDE Community</developer_name>
|
||||
<developer_name xml:lang="ar">مجتمع كدي</developer_name>
|
||||
<developer_name xml:lang="ca">La comunitat KDE</developer_name>
|
||||
<developer_name xml:lang="ca-valencia">La comunitat KDE</developer_name>
|
||||
<developer_name xml:lang="cs">Komunita KDE</developer_name>
|
||||
@@ -69,6 +74,7 @@ SPDX-License-Identifier: CC0-1.0
|
||||
<developer_name xml:lang="pl">Społeczność KDE</developer_name>
|
||||
<developer_name xml:lang="pt">Comunidade do KDE</developer_name>
|
||||
<developer_name xml:lang="pt-BR">A Comunidade KDE</developer_name>
|
||||
<developer_name xml:lang="ro">Comunitatea KDE</developer_name>
|
||||
<developer_name xml:lang="sk">KDE komunita</developer_name>
|
||||
<developer_name xml:lang="sl">Skupnost KDE</developer_name>
|
||||
<developer_name xml:lang="sv">KDE-gemenskapen</developer_name>
|
||||
@@ -77,6 +83,17 @@ SPDX-License-Identifier: CC0-1.0
|
||||
<content_rating type="oars-1.0"/>
|
||||
<url type="homepage">https://apps.kde.org/en/audiotube</url>
|
||||
<releases>
|
||||
<release version="22.02" date="2022-02-09">
|
||||
<url>https://www.plasma-mobile.org/2022/02/09/plasma-mobile-gear-22-02/</url>
|
||||
<description>
|
||||
<ul>
|
||||
<li>Update the version of ytmusicapi</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="21.12" date="2021-12-07">
|
||||
<url>https://www.plasma-mobile.org/2021/12/07/plasma-mobile-gear-21-12/</url>
|
||||
</release>
|
||||
<release version="21.08" date="2021-08-31">
|
||||
<url>https://www.plasma-mobile.org/2021/08/31/plasma-mobile-gear-21-08/</url>
|
||||
</release>
|
||||
@@ -97,6 +114,7 @@ SPDX-License-Identifier: CC0-1.0
|
||||
</recommends>
|
||||
<description>
|
||||
<p>AudioTube can search YouTube Music, list albums and artists, play automatically generated playlists, albums and allows to put your own playlist together.</p>
|
||||
<p xml:lang="ar">يمكن لتيوب الصوتي البحث في موسيقى يوتيوب، وعرض الألبومات والفنانين، وتشغيل القوائم والألبومات المولدة تلقائيًا، ويسمح لك بوضع قائمة التشغيل الخاصة بك معًا.</p>
|
||||
<p xml:lang="ca">L'AudioTube pot cercar música a YouTube, llistar àlbums i artistes, reproduir llistes de reproducció generades automàticament, àlbums i permet crear la vostra pròpia llista de reproducció.</p>
|
||||
<p xml:lang="ca-valencia">L'AudioTube pot cercar música a YouTube, llistar àlbums i artistes, reproduir llistes de reproducció generades automàticament, àlbums i permet crear la vostra pròpia llista de reproducció.</p>
|
||||
<p xml:lang="de">AudioTube kann Musik auf YouTube durchsuchen, Alben und Interpreten auflisten, automatisch generierte Wiedergabelisten und Alben abspielen und erlaubt es, eine eigene Wiedergabeliste zusammenzustellen.</p>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
[Desktop Entry]
|
||||
Name=AudioTube
|
||||
Name[ar]=تيوب الصوتي
|
||||
Name[ca]=AudioTube
|
||||
Name[ca@valencia]=AudioTube
|
||||
Name[cs]=AudioTube
|
||||
@@ -24,6 +25,7 @@ Name[sv]=AudioTube
|
||||
Name[uk]=AudioTube
|
||||
Name[x-test]=xxAudioTubexx
|
||||
Comment=YouTube Music client
|
||||
Comment[ar]=عميل يوتيوب ميوزيك
|
||||
Comment[ca]=Client de música de YouTube
|
||||
Comment[ca@valencia]=Client de música de YouTube
|
||||
Comment[cs]=Hudební klient YouTube
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
SPDX-FileCopyrightText: 2021 Jonah Brüchert <[email protected]>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
@@ -28,7 +28,7 @@
|
||||
"config-opts": ["-DPYBIND11_FINDPYTHON=ON", "-DPYBIND11_TEST=OFF"],
|
||||
"buildsystem": "cmake-ninja",
|
||||
"builddir": true,
|
||||
"sources": [ { "type": "git", "url": "https://github.com/pybind/pybind11", "tag": "v2.6.2" } ]
|
||||
"sources": [ { "type": "git", "url": "https://github.com/pybind/pybind11", "tag": "v2.9.1" } ]
|
||||
},
|
||||
{
|
||||
"name": "org.kde.audiotube",
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
SPDX-FileCopyrightText: 2021 Jonah Brüchert <[email protected]>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
@@ -1,3 +0,0 @@
|
||||
SPDX-FileCopyrightText: 2021 Mathis Brüchert <[email protected]>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
+2
-2
@@ -3,5 +3,5 @@
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
ytmusicapi==0.19.4
|
||||
ytdlp
|
||||
ytmusicapi==0.20.0
|
||||
yt-dlp
|
||||
+3
-3
@@ -9,11 +9,11 @@
|
||||
AlbumModel::AlbumModel(QObject *parent)
|
||||
: AbstractYTMusicModel(parent)
|
||||
{
|
||||
connect(this, &AlbumModel::browseIdChanged, this, [=] {
|
||||
connect(this, &AlbumModel::browseIdChanged, this, [this] {
|
||||
setLoading(true);
|
||||
YTMusicThread::instance()->fetchAlbum(m_browseId);
|
||||
});
|
||||
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::fetchAlbumFinished, this, [=](const album::Album &album) {
|
||||
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::fetchAlbumFinished, this, [this](const album::Album &album) {
|
||||
setLoading(false);
|
||||
|
||||
beginResetModel();
|
||||
@@ -25,7 +25,7 @@ AlbumModel::AlbumModel(QObject *parent)
|
||||
Q_EMIT thumbnailUrlChanged();
|
||||
Q_EMIT playlistIdChanged();
|
||||
});
|
||||
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::errorOccurred, this, [=] {
|
||||
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::errorOccurred, this, [this] {
|
||||
setLoading(false);
|
||||
});
|
||||
}
|
||||
|
||||
+28
-36
@@ -8,8 +8,9 @@
|
||||
|
||||
ArtistModel::ArtistModel(QObject *parent)
|
||||
: AbstractYTMusicModel(parent)
|
||||
, m_view(albums, singles, songs, videos)
|
||||
{
|
||||
connect(this, &ArtistModel::channelIdChanged, this, [=] {
|
||||
connect(this, &ArtistModel::channelIdChanged, this, [this] {
|
||||
if (m_channelId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
@@ -18,44 +19,37 @@ ArtistModel::ArtistModel(QObject *parent)
|
||||
|
||||
YTMusicThread::instance()->fetchArtist(m_channelId);
|
||||
});
|
||||
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::fetchArtistFinished, this, [=](const artist::Artist &artist) {
|
||||
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::fetchArtistFinished, this, [this](const artist::Artist &artist) {
|
||||
setLoading(false);
|
||||
|
||||
beginResetModel();
|
||||
m_artist = artist;
|
||||
std::sort(m_artist.thumbnails.begin(), m_artist.thumbnails.end());
|
||||
|
||||
auto albums = m_artist.albums ? m_artist.albums->results : std::vector<artist::Artist::Album>();
|
||||
auto singles = m_artist.singles ? m_artist.singles->results : std::vector<artist::Artist::Single>();
|
||||
auto songs = m_artist.songs ? m_artist.songs->results : std::vector<artist::Artist::Song>();
|
||||
auto videos = m_artist.videos ? m_artist.videos->results : std::vector<artist::Artist::Video>();
|
||||
albums = m_artist.albums ? m_artist.albums->results : std::vector<artist::Artist::Album>();
|
||||
singles = m_artist.singles ? m_artist.singles->results : std::vector<artist::Artist::Single>();
|
||||
songs = m_artist.songs ? m_artist.songs->results : std::vector<artist::Artist::Song>();
|
||||
videos = m_artist.videos ? m_artist.videos->results : std::vector<artist::Artist::Video>();
|
||||
|
||||
m_view = std::optional(MultiIterableView(std::move(albums),
|
||||
std::move(singles),
|
||||
std::move(songs),
|
||||
std::move(videos)));
|
||||
// std::span can't know if the data pointer underneath it was changed, so re-create
|
||||
m_view = MultiIterableView(albums, singles, songs, videos);
|
||||
endResetModel();
|
||||
|
||||
|
||||
Q_EMIT titleChanged();
|
||||
Q_EMIT thumbnailUrlChanged();
|
||||
});
|
||||
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::errorOccurred, this, [=] {
|
||||
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::errorOccurred, this, [this] {
|
||||
setLoading(false);
|
||||
});
|
||||
}
|
||||
|
||||
int ArtistModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
return parent.isValid() ? 0 : m_view ? int(m_view->size()) : 0;
|
||||
return parent.isValid() ? 0 : m_view.size();
|
||||
}
|
||||
|
||||
QVariant ArtistModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!m_view) {
|
||||
return {};
|
||||
}
|
||||
|
||||
switch (role) {
|
||||
case Title:
|
||||
return QString::fromStdString(std::visit([&](auto&& item) {
|
||||
@@ -71,7 +65,7 @@ QVariant ArtistModel::data(const QModelIndex &index, int role) const
|
||||
}
|
||||
|
||||
Q_UNREACHABLE();
|
||||
}, m_view.value()[index.row()]));
|
||||
}, m_view[index.row()]));
|
||||
case Type:
|
||||
return std::visit([&](auto&& item) {
|
||||
using T = std::decay_t<decltype(item)>;
|
||||
@@ -86,7 +80,7 @@ QVariant ArtistModel::data(const QModelIndex &index, int role) const
|
||||
}
|
||||
|
||||
Q_UNREACHABLE();
|
||||
}, m_view.value()[index.row()]);
|
||||
}, m_view[index.row()]);
|
||||
case Artists:
|
||||
return QVariant::fromValue(std::vector<meta::Artist> {
|
||||
{
|
||||
@@ -108,7 +102,7 @@ QVariant ArtistModel::data(const QModelIndex &index, int role) const
|
||||
}
|
||||
|
||||
Q_UNREACHABLE();
|
||||
}, m_view.value()[index.row()]);
|
||||
}, m_view[index.row()]);
|
||||
}
|
||||
|
||||
Q_UNREACHABLE();
|
||||
@@ -153,20 +147,18 @@ QUrl ArtistModel::thumbnailUrl() const
|
||||
|
||||
void ArtistModel::triggerItem(int row)
|
||||
{
|
||||
if (m_view) {
|
||||
std::visit([&](auto&& item) {
|
||||
using T = std::decay_t<decltype(item)>;
|
||||
if constexpr(std::is_same_v<T, artist::Artist::Album>) {
|
||||
Q_EMIT openAlbum(QString::fromStdString(item.browse_id));
|
||||
} else if constexpr(std::is_same_v<T, artist::Artist::Single>) {
|
||||
Q_EMIT openAlbum(QString::fromStdString(item.browse_id));
|
||||
} else if constexpr(std::is_same_v<T, artist::Artist::Song>) {
|
||||
Q_EMIT openSong(QString::fromStdString(item.video_id));
|
||||
} else if constexpr(std::is_same_v<T, artist::Artist::Video>) {
|
||||
Q_EMIT openVideo(QString::fromStdString(item.video_id), QString::fromStdString(item.title));
|
||||
} else {
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
}, m_view.value()[row]);
|
||||
}
|
||||
std::visit([&](auto&& item) {
|
||||
using T = std::decay_t<decltype(item)>;
|
||||
if constexpr(std::is_same_v<T, artist::Artist::Album>) {
|
||||
Q_EMIT openAlbum(QString::fromStdString(item.browse_id));
|
||||
} else if constexpr(std::is_same_v<T, artist::Artist::Single>) {
|
||||
Q_EMIT openAlbum(QString::fromStdString(item.browse_id));
|
||||
} else if constexpr(std::is_same_v<T, artist::Artist::Song>) {
|
||||
Q_EMIT openSong(QString::fromStdString(item.video_id));
|
||||
} else if constexpr(std::is_same_v<T, artist::Artist::Video>) {
|
||||
Q_EMIT openVideo(QString::fromStdString(item.video_id), QString::fromStdString(item.title));
|
||||
} else {
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
}, m_view[row]);
|
||||
}
|
||||
+8
-2
@@ -66,7 +66,13 @@ private:
|
||||
QString m_channelId;
|
||||
|
||||
artist::Artist m_artist;
|
||||
std::optional<MultiIterableView<
|
||||
|
||||
std::vector<artist::Artist::Album> albums;
|
||||
std::vector<artist::Artist::Single> singles;
|
||||
std::vector<artist::Artist::Song> songs;
|
||||
std::vector<artist::Artist::Video> videos;
|
||||
|
||||
MultiIterableView<
|
||||
artist::Artist::Album, artist::Artist::Single, artist::Artist::Song, artist::Artist::Video
|
||||
>> m_view;
|
||||
> m_view;
|
||||
};
|
||||
@@ -51,7 +51,7 @@ AsyncYTMusic::AsyncYTMusic(QObject *parent)
|
||||
//
|
||||
void AsyncYTMusic::search(const QString &query)
|
||||
{
|
||||
invokeAndCatchOnThread([=]() {
|
||||
invokeAndCatchOnThread([=, this]() {
|
||||
Q_EMIT searchFinished(m_ytm->search(query.toStdString()));
|
||||
});
|
||||
}
|
||||
@@ -61,7 +61,7 @@ void AsyncYTMusic::search(const QString &query)
|
||||
//
|
||||
void AsyncYTMusic::fetchArtist(const QString &channelId)
|
||||
{
|
||||
invokeAndCatchOnThread([=]() {
|
||||
invokeAndCatchOnThread([=, this]() {
|
||||
Q_EMIT fetchArtistFinished(m_ytm->get_artist(channelId.toStdString()));
|
||||
});
|
||||
}
|
||||
@@ -71,7 +71,7 @@ void AsyncYTMusic::fetchArtist(const QString &channelId)
|
||||
//
|
||||
void AsyncYTMusic::fetchAlbum(const QString &browseId)
|
||||
{
|
||||
invokeAndCatchOnThread([=]() {
|
||||
invokeAndCatchOnThread([=, this]() {
|
||||
Q_EMIT fetchAlbumFinished(m_ytm->get_album(browseId.toStdString()));
|
||||
});
|
||||
}
|
||||
@@ -81,7 +81,7 @@ void AsyncYTMusic::fetchAlbum(const QString &browseId)
|
||||
//
|
||||
void AsyncYTMusic::fetchSong(const QString &videoId)
|
||||
{
|
||||
invokeAndCatchOnThread([=]() {
|
||||
invokeAndCatchOnThread([=, this]() {
|
||||
auto maybeSong = m_ytm->get_song(videoId.toStdString());
|
||||
if (maybeSong.has_value()) {
|
||||
Q_EMIT fetchSongFinished(*maybeSong);
|
||||
@@ -93,7 +93,7 @@ void AsyncYTMusic::fetchSong(const QString &videoId)
|
||||
// fetchPlaylist
|
||||
//
|
||||
void AsyncYTMusic::fetchPlaylist(const QString &playlistId) {
|
||||
invokeAndCatchOnThread([=]() {
|
||||
invokeAndCatchOnThread([=, this]() {
|
||||
Q_EMIT fetchPlaylistFinished(m_ytm->get_playlist(playlistId.toStdString()));
|
||||
});
|
||||
}
|
||||
@@ -103,7 +103,7 @@ void AsyncYTMusic::fetchPlaylist(const QString &playlistId) {
|
||||
//
|
||||
void AsyncYTMusic::fetchArtistAlbums(const QString &channelId, const QString ¶ms)
|
||||
{
|
||||
invokeAndCatchOnThread([=]() {
|
||||
invokeAndCatchOnThread([=, this]() {
|
||||
Q_EMIT fetchArtistAlbumsFinished(m_ytm->get_artist_albums(channelId.toStdString(), params.toStdString()));
|
||||
});
|
||||
}
|
||||
@@ -113,7 +113,7 @@ void AsyncYTMusic::fetchArtistAlbums(const QString &channelId, const QString &pa
|
||||
//
|
||||
void AsyncYTMusic::extractVideoInfo(const QString &videoId)
|
||||
{
|
||||
invokeAndCatchOnThread([=]() {
|
||||
invokeAndCatchOnThread([=, this]() {
|
||||
Q_EMIT extractVideoInfoFinished(m_ytm->extract_video_info(videoId.toStdString()));
|
||||
});
|
||||
}
|
||||
@@ -123,7 +123,7 @@ void AsyncYTMusic::extractVideoInfo(const QString &videoId)
|
||||
//
|
||||
void AsyncYTMusic::fetchWatchPlaylist(const std::optional<QString> &videoId, const std::optional<QString> &playlistId)
|
||||
{
|
||||
invokeAndCatchOnThread([=]() {
|
||||
invokeAndCatchOnThread([=, this]() {
|
||||
Q_EMIT fetchWatchPlaylistFinished(m_ytm->get_watch_playlist(
|
||||
map_optional<std::string>(videoId, &QString::toStdString),
|
||||
map_optional<std::string>(playlistId, &QString::toStdString)
|
||||
@@ -132,7 +132,7 @@ void AsyncYTMusic::fetchWatchPlaylist(const std::optional<QString> &videoId, con
|
||||
}
|
||||
|
||||
void AsyncYTMusic::invokeAndCatchOnThread(const std::function<void()> &fun) {
|
||||
QMetaObject::invokeMethod(this, [=]() {
|
||||
QMetaObject::invokeMethod(this, [=, this]() {
|
||||
try {
|
||||
fun();
|
||||
} catch (const py::error_already_set &err) {
|
||||
|
||||
+49
-52
@@ -6,71 +6,68 @@
|
||||
|
||||
#include <variant>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
#include <span>
|
||||
#include <QtGlobal>
|
||||
|
||||
// MultiIterableView is usually used on a small number of vectors.
|
||||
// The number is already known, so we can convert this into an if else if else etc tree
|
||||
// instead of recursively calling the function
|
||||
#ifdef __GNUC__
|
||||
#define REMOVE_RECURSION __attribute__((always_inline))
|
||||
#else
|
||||
#define REMOVE_RECURSION
|
||||
#endif
|
||||
template <typename Tuple, typename Func, std::size_t i>
|
||||
inline constexpr void iterate_impl(Tuple &tup, Func fun)
|
||||
{
|
||||
if constexpr(i >= std::tuple_size_v<std::decay_t<decltype(tup)>>) {
|
||||
return;
|
||||
} else {
|
||||
fun(std::get<i>(tup));
|
||||
return iterate_impl<Tuple, Func, i + 1>(tup, fun);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Tuple, typename Func>
|
||||
inline constexpr void iterate_tuple(Tuple &tup, Func fun)
|
||||
{
|
||||
iterate_impl<Tuple, Func, 0>(tup, fun);
|
||||
}
|
||||
|
||||
/// MultiIterableView is a view over multiple contigous memory arrays, like std::vector or std::array.
|
||||
/// The elements of the arrays do not need to be of the same type.
|
||||
template <typename... Arguments>
|
||||
class MultiIterableView {
|
||||
private:
|
||||
template<typename T, size_t i, size_t len>
|
||||
REMOVE_RECURSION inline size_t count(const T &tuple, int initial_size = 0) const {
|
||||
if constexpr(i < len) {
|
||||
return count<T, i + 1, len>(tuple, initial_size + std::get<i>(tuple).size());
|
||||
} else {
|
||||
return initial_size;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, size_t tuple_i, size_t tuple_len>
|
||||
REMOVE_RECURSION inline std::variant<Arguments...> get_item(const T &tuple, size_t i) const {
|
||||
if constexpr(tuple_i < tuple_len) {
|
||||
if (i < std::get<tuple_i>(tuple).size()) {
|
||||
return std::get<tuple_i>(tuple)[i];
|
||||
} else {
|
||||
return get_item<T, tuple_i + 1, tuple_len>(tuple, i - std::get<tuple_i>(tuple).size());
|
||||
}
|
||||
} else {
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, size_t i, size_t len>
|
||||
REMOVE_RECURSION inline bool everything_empty(const T &tuple, bool initial_state = true) const {
|
||||
if constexpr(i < len) {
|
||||
return everything_empty<T, i + 1, len>(tuple, initial_state && std::get<i>(tuple).empty());
|
||||
} else {
|
||||
return initial_state;
|
||||
}
|
||||
}
|
||||
public:
|
||||
explicit MultiIterableView(std::vector<Arguments> &&...lists)
|
||||
: m_vectors(std::make_tuple(lists...))
|
||||
explicit MultiIterableView(std::span<Arguments>... lists)
|
||||
: m_vectors(std::forward_as_tuple(lists...))
|
||||
{
|
||||
}
|
||||
|
||||
size_t size() const {
|
||||
constexpr size_t nvecs = std::tuple_size_v<decltype(m_vectors)>;
|
||||
return count<decltype(m_vectors), 0, nvecs>(m_vectors);
|
||||
explicit MultiIterableView(std::vector<Arguments> &...lists)
|
||||
: MultiIterableView(std::span{lists}...)
|
||||
{
|
||||
}
|
||||
|
||||
std::variant<Arguments...> operator[](size_t i) const {
|
||||
constexpr size_t nvecs = std::tuple_size_v<decltype(m_vectors)>;
|
||||
return get_item<decltype(m_vectors), 0, nvecs>(m_vectors, i);
|
||||
[[nodiscard]] constexpr size_t size() const {
|
||||
size_t s = 0;
|
||||
iterate_tuple(m_vectors, [&](auto &vec) {
|
||||
s += vec.size();
|
||||
});
|
||||
return s;
|
||||
}
|
||||
|
||||
bool empty() const {
|
||||
constexpr size_t nvecs = std::tuple_size_v<decltype(m_vectors)>;
|
||||
return everything_empty<decltype(m_vectors), 0, nvecs>(m_vectors);
|
||||
[[nodiscard]] constexpr std::variant<Arguments...> operator[](size_t i) const {
|
||||
size_t s = 0;
|
||||
std::variant<Arguments...> out;
|
||||
iterate_tuple(m_vectors, [&](auto &vec) {
|
||||
if (i >= s && i < s + vec.size()) {
|
||||
out = vec[i - s];
|
||||
}
|
||||
s += vec.size();
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool empty() const {
|
||||
bool empty = true;
|
||||
iterate_tuple(m_vectors, [&](auto &elem) {
|
||||
empty &= elem.empty();
|
||||
});
|
||||
return empty;
|
||||
}
|
||||
private:
|
||||
std::tuple<std::vector<Arguments>...> m_vectors;
|
||||
std::tuple<std::span<Arguments>...> m_vectors;
|
||||
};
|
||||
@@ -13,11 +13,11 @@
|
||||
PlaylistModel::PlaylistModel(QObject *parent)
|
||||
: AbstractYTMusicModel(parent)
|
||||
{
|
||||
connect(this, &PlaylistModel::playlistIdChanged, this, [=] {
|
||||
connect(this, &PlaylistModel::playlistIdChanged, this, [=, this] {
|
||||
setLoading(true);
|
||||
YTMusicThread::instance()->fetchPlaylist(m_playlistId);
|
||||
});
|
||||
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::fetchPlaylistFinished, this, [=](const playlist::Playlist &playlist) {
|
||||
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::fetchPlaylistFinished, this, [this](const playlist::Playlist &playlist) {
|
||||
setLoading(false);
|
||||
beginResetModel();
|
||||
m_playlist = playlist;
|
||||
|
||||
@@ -15,11 +15,12 @@ namespace PlaylistUtils {
|
||||
QString artistsToString(const std::vector<meta::Artist> &artists)
|
||||
{
|
||||
if (!artists.empty()) {
|
||||
return std::accumulate(artists.begin() + 1, artists.end(),
|
||||
QString::fromStdString(artists.front().name),
|
||||
[](QString &string, const meta::Artist &artist) {
|
||||
return string.append(QStringLiteral(", %1").arg(QString::fromStdString(artist.name)));
|
||||
QString string = QString::fromStdString(artists.front().name);
|
||||
std::for_each(artists.begin() + 1, artists.end(),
|
||||
[&string](const meta::Artist &artist) {
|
||||
string.append(QStringLiteral(", %1").arg(QString::fromStdString(artist.name)));
|
||||
});
|
||||
return string;
|
||||
}
|
||||
|
||||
return {};
|
||||
|
||||
+28
-3
@@ -7,7 +7,7 @@
|
||||
SearchModel::SearchModel(QObject *parent)
|
||||
: AbstractYTMusicModel(parent)
|
||||
{
|
||||
connect(this, &SearchModel::searchQueryChanged, this, [=] {
|
||||
connect(this, &SearchModel::searchQueryChanged, this, [this] {
|
||||
if (m_searchQuery.isEmpty()) {
|
||||
beginResetModel();
|
||||
m_searchResults.clear();
|
||||
@@ -18,13 +18,17 @@ SearchModel::SearchModel(QObject *parent)
|
||||
setLoading(true);
|
||||
YTMusicThread::instance()->search(m_searchQuery);
|
||||
});
|
||||
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::searchFinished, this, [=](const std::vector<search::SearchResultItem> &results) {
|
||||
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::searchFinished, this, [this](const std::vector<search::SearchResultItem> &results) {
|
||||
beginResetModel();
|
||||
setLoading(false);
|
||||
m_searchResults = results;
|
||||
std::sort(m_searchResults.begin(), m_searchResults.end(),
|
||||
[](search::SearchResultItem const & a, search::SearchResultItem const & b) {
|
||||
return SearchModel::itemType(a) < SearchModel::itemType(b);
|
||||
});
|
||||
endResetModel();
|
||||
});
|
||||
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::errorOccurred, this, [=] {
|
||||
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::errorOccurred, this, [this] {
|
||||
setLoading(false);
|
||||
});
|
||||
}
|
||||
@@ -165,3 +169,24 @@ void SearchModel::triggerItem(int row)
|
||||
}
|
||||
}, m_searchResults.at(row));
|
||||
}
|
||||
|
||||
|
||||
int SearchModel::itemType(search::SearchResultItem const &item)
|
||||
{
|
||||
return std::visit([&](auto &&arg) {
|
||||
using T = std::decay_t<decltype(arg)>;
|
||||
if constexpr(std::is_same_v<T, search::Album>) {
|
||||
return SearchModel::Type::Album;
|
||||
} else if constexpr(std::is_same_v<T, search::Artist>) {
|
||||
return SearchModel::Type::Artist;
|
||||
} else if constexpr(std::is_same_v<T, search::Playlist>) {
|
||||
return SearchModel::Type::Playlist;
|
||||
} else if constexpr(std::is_same_v<T, search::Song>) {
|
||||
return SearchModel::Type::Song;
|
||||
} else if constexpr(std::is_same_v<T, search::Video>) {
|
||||
return SearchModel::Type::Video;
|
||||
} else {
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
}, item);
|
||||
}
|
||||
@@ -55,4 +55,5 @@ public:
|
||||
private:
|
||||
QString m_searchQuery;
|
||||
std::vector<search::SearchResultItem> m_searchResults;
|
||||
static int itemType(search::SearchResultItem const &item);
|
||||
};
|
||||
@@ -15,7 +15,7 @@
|
||||
UserPlaylistModel::UserPlaylistModel(QObject *parent)
|
||||
: AbstractYTMusicModel(parent)
|
||||
{
|
||||
connect(this, &UserPlaylistModel::initialVideoIdChanged, this, [=] {
|
||||
connect(this, &UserPlaylistModel::initialVideoIdChanged, this, [this] {
|
||||
if (m_initialVideoId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
@@ -23,7 +23,7 @@ UserPlaylistModel::UserPlaylistModel(QObject *parent)
|
||||
setLoading(true);
|
||||
YTMusicThread::instance()->fetchWatchPlaylist(m_initialVideoId);
|
||||
});
|
||||
connect(this, &UserPlaylistModel::playlistIdChanged, this, [=] {
|
||||
connect(this, &UserPlaylistModel::playlistIdChanged, this, [this] {
|
||||
if (m_playlistId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
@@ -31,7 +31,7 @@ UserPlaylistModel::UserPlaylistModel(QObject *parent)
|
||||
setLoading(true);
|
||||
YTMusicThread::instance()->fetchWatchPlaylist(std::nullopt, m_playlistId);
|
||||
});
|
||||
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::fetchWatchPlaylistFinished, this, [=](const watch::Playlist &playlist) {
|
||||
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::fetchWatchPlaylistFinished, this, [this](const watch::Playlist &playlist) {
|
||||
setLoading(false);
|
||||
|
||||
beginResetModel();
|
||||
@@ -49,7 +49,7 @@ UserPlaylistModel::UserPlaylistModel(QObject *parent)
|
||||
setCurrentVideoId({});
|
||||
}
|
||||
});
|
||||
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::errorOccurred, this, [=] {
|
||||
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::errorOccurred, this, [this] {
|
||||
setLoading(false);
|
||||
});
|
||||
}
|
||||
@@ -101,7 +101,7 @@ void UserPlaylistModel::setInitialVideoId(const QString &videoId)
|
||||
QString UserPlaylistModel::nextVideoId() const
|
||||
{
|
||||
auto currentTrack = std::find_if(m_playlist.tracks.begin(), m_playlist.tracks.end(),
|
||||
[=](const watch::Playlist::Track &track) {
|
||||
[this](const watch::Playlist::Track &track) {
|
||||
return track.video_id == m_currentVideoId.toStdString();
|
||||
});
|
||||
|
||||
@@ -129,7 +129,7 @@ void UserPlaylistModel::setCurrentVideoId(const QString &videoId)
|
||||
bool UserPlaylistModel::canSkip() const
|
||||
{
|
||||
const auto currentTrackIt = std::find_if(m_playlist.tracks.begin(), m_playlist.tracks.end(),
|
||||
[=](const watch::Playlist::Track &track) {
|
||||
[this](const watch::Playlist::Track &track) {
|
||||
return track.video_id == m_currentVideoId.toStdString();
|
||||
});
|
||||
|
||||
@@ -157,7 +157,7 @@ void UserPlaylistModel::skipTo(const QString &videoId)
|
||||
void UserPlaylistModel::playNext(const QString &videoId, const QString &title, const std::vector<meta::Artist> &artists)
|
||||
{
|
||||
const auto currentIt = std::find_if(m_playlist.tracks.begin(), m_playlist.tracks.end(),
|
||||
[=](const watch::Playlist::Track &track) {
|
||||
[this](const watch::Playlist::Track &track) {
|
||||
return track.video_id == m_currentVideoId.toStdString();
|
||||
});
|
||||
|
||||
@@ -239,7 +239,7 @@ void UserPlaylistModel::shufflePlaylist()
|
||||
// Only shuffle playlist after current track
|
||||
if (!m_currentVideoId.isEmpty()) {
|
||||
const auto currentIt = std::find_if(m_playlist.tracks.begin(), m_playlist.tracks.end(),
|
||||
[=](const watch::Playlist::Track &track) {
|
||||
[this](const watch::Playlist::Track &track) {
|
||||
return track.video_id == m_currentVideoId.toStdString();
|
||||
});
|
||||
|
||||
@@ -257,7 +257,7 @@ void UserPlaylistModel::emitCurrentVideoChanged(const QString &oldVideoId)
|
||||
return track.video_id == oldVideoId.toStdString();
|
||||
});
|
||||
const auto currentVideoIt = std::find_if(m_playlist.tracks.begin(), m_playlist.tracks.end(),
|
||||
[=](const watch::Playlist::Track &track) {
|
||||
[this](const watch::Playlist::Track &track) {
|
||||
return track.video_id == m_currentVideoId.toStdString();
|
||||
});
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
VideoInfoExtractor::VideoInfoExtractor(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
connect(this, &VideoInfoExtractor::videoIdChanged, this, [=] {
|
||||
connect(this, &VideoInfoExtractor::videoIdChanged, this, [this] {
|
||||
if (m_videoId.isEmpty()) {
|
||||
m_videoInfo = {};
|
||||
Q_EMIT audioUrlChanged();
|
||||
@@ -23,7 +23,7 @@ VideoInfoExtractor::VideoInfoExtractor(QObject *parent)
|
||||
YTMusicThread::instance()->extractVideoInfo(QString::fromStdString(m_videoId.toStdString()));
|
||||
});
|
||||
|
||||
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::extractVideoInfoFinished, this, [=](const video_info::VideoInfo &info) {
|
||||
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::extractVideoInfoFinished, this, [this](const video_info::VideoInfo &info) {
|
||||
m_videoInfo = info;
|
||||
setLoading(false);
|
||||
Q_EMIT audioUrlChanged();
|
||||
@@ -36,7 +36,7 @@ VideoInfoExtractor::VideoInfoExtractor(QObject *parent)
|
||||
QUrl VideoInfoExtractor::audioUrl() const
|
||||
{
|
||||
if (m_videoInfo.formats.empty()) {
|
||||
return QUrl();
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<video_info::Format> audioFormats;
|
||||
@@ -48,17 +48,13 @@ QUrl VideoInfoExtractor::audioUrl() const
|
||||
});
|
||||
|
||||
if (audioFormats.empty()) {
|
||||
return QUrl();
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
struct {
|
||||
bool operator()(const video_info::Format &a, const video_info::Format &b) {
|
||||
return a.quality > b.quality;
|
||||
}
|
||||
} qualitySort;
|
||||
|
||||
std::sort(audioFormats.begin(), audioFormats.end(), qualitySort);
|
||||
std::sort(audioFormats.begin(), audioFormats.end(),
|
||||
[](const video_info::Format &a, const video_info::Format &b) {
|
||||
return a.quality > b.quality;
|
||||
});
|
||||
|
||||
return QUrl(QString::fromStdString(audioFormats.front().url));
|
||||
}
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ void pyPrintPretty(py::handle obj) {
|
||||
|
||||
#define UNEXPORT __attribute__ ((visibility("hidden")))
|
||||
|
||||
constexpr auto TESTED_YTMUSICAPI_VERSION = "0.19.4";
|
||||
constexpr auto TESTED_YTMUSICAPI_VERSION = "0.20.0";
|
||||
|
||||
struct UNEXPORT YTMusicPrivate {
|
||||
py::scoped_interpreter guard {};
|
||||
|
||||
Reference in new issue
Block a user