Compare commits

..
76 Commits
Author SHA1 Message Date
Jonah Brüchert 188f4363c4 Improve layout of the search popup 2022-05-22 14:23:30 +02:00
Jonah Brüchert 4c43b60714 Use new database framework 2022-05-22 13:11:07 +02:00
Jonah Brüchert 7824d796b4 lay the foundation for a Qt database framework 2022-05-21 19:39:34 +02:00
Jonah Brüchert 692df68464 Port library over to SQLite / diesel 2022-05-21 14:21:11 +02:00
Jonah Brüchert 45a3e9540f wip 2022-05-20 21:35:24 +02:00
Jonah Brüchert d2561d8f1f Add library feature 2022-05-20 21:35:24 +02:00
Jonah Brüchert aa8c5fefca Import QPersistentData 2022-05-20 21:35:21 +02:00
l10n daemon script c83d7f582b GIT_SILENT Sync po/docbooks with svn 2022-05-16 01:54:43 +00:00
l10n daemon script 62385df312 GIT_SILENT Sync po/docbooks with svn 2022-05-12 01:46:03 +00:00
l10n daemon script dad015195b GIT_SILENT Sync po/docbooks with svn 2022-05-11 01:51:51 +00:00
l10n daemon script b89fed88ee GIT_SILENT Sync po/docbooks with svn 2022-05-09 01:48:16 +00:00
l10n daemon script 613dd1eb51 GIT_SILENT Sync po/docbooks with svn 2022-04-27 01:50:09 +00:00
Nicolas Fella b7754e7d60 Use proper reuse CI job 2022-04-26 00:26:30 +02:00
l10n daemon script 644c343e95 GIT_SILENT Sync po/docbooks with svn 2022-04-25 01:56:33 +00:00
l10n daemon script c9d530be76 SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2022-04-24 01:41:29 +00:00
l10n daemon script aa0268694f GIT_SILENT made messages (after extraction) 2022-04-24 00:45:48 +00:00
Bhushan Shah 8e873e726d GIT_SILENT: add changelog entries for 22.04 2022-04-23 17:22:06 +05:30
Bhushan Shah dca3373056 GIT_SILENT: bump version to 22.04 2022-04-23 17:04:12 +05:30
l10n daemon script 6d57cad22c GIT_SILENT Sync po/docbooks with svn 2022-04-23 01:49:01 +00:00
l10n daemon script b67fdc384f GIT_SILENT Sync po/docbooks with svn 2022-04-22 01:51:41 +00:00
l10n daemon script 32f98a7c9b GIT_SILENT Sync po/docbooks with svn 2022-04-17 01:54:46 +00:00
l10n daemon script 2b8ac69916 SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2022-04-16 01:48:18 +00:00
l10n daemon script ad6fe5e633 GIT_SILENT made messages (after extraction) 2022-04-16 00:48:08 +00:00
Tobias Fella 1ad6586a07 Add ki18n_install 2022-04-07 16:53:01 +02:00
l10n daemon script 0249604199 GIT_SILENT made messages (after extraction) 2022-03-23 00:57:01 +00:00
Jonah Brüchert 1a93e0d944 flatpak: Update urllib 2022-03-16 22:57:24 +01:00
Jonah Brüchert 347613a7f2 Update ytmusicapi to 0.21.0 2022-03-10 14:30:16 +01:00
l10n daemon script 424c67a268 SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2022-02-16 01:50:45 +00:00
l10n daemon script 64d7a911fa GIT_SILENT made messages (after extraction) 2022-02-16 00:47:36 +00:00
Jonah Brüchert 961dca328d MultiIterableView: Add convinience std::vector constructor 2022-02-14 17:59:30 +01:00
Dimitris Kardarakos 433d95969c Power search page list sections
The Search Page list view can present content in sections according to media type (artist, playlist, song, video). For sections to work, the model that provides the search results should sort the results accordingly.
2022-02-14 16:26:47 +00:00
Jonah Brüchert 15f439c38e Simplify std::sort use 2022-02-14 17:21:34 +01:00
Jonah Brüchert ca4d5d8b77 MultiIterableView: Add documentation 2022-02-14 17:17:12 +01:00
Jonah Brüchert 06338900dc Fix some implicit this captures 2022-02-14 17:14:46 +01:00
Jonah Brüchert 8ec674fbef MultiIterableView: Generalize by using std::span
Requires upgrading to C++20
2022-02-14 17:10:06 +01:00
Jonah Brüchert 58eefc29eb ArtistModel: Simplify use of MultiIterableView
Now that it is truly a view type, it is possible to just modify the
underlying data, instead of recreating the view.
This allows to get rid of the optional, in which the view was previously
stored.
2022-02-14 15:52:25 +01:00
Jonah Brüchert 9312331755 MultiIterableView: Refactor into an actual view type 2022-02-14 15:52:25 +01:00
Jonah Brüchert 1d25f46f52 MultiIterableView: Try to make code easier to understand 2022-02-14 15:52:22 +01:00
Jonah Brüchert a775cd58be flatpak: Update pybind11 2022-02-13 20:16:18 +01:00
Bhushan Shah ae5ed3b10b GIT_SILENT: fixup typo 22.02 2022-02-08 20:58:24 +05:30
Bhushan Shah 8aab91568e GIT_SILENT: Update version and appstream data for 22.02 2022-02-08 18:23:50 +05:30
l10n daemon script 6647346314 GIT_SILENT made messages (after extraction) 2022-02-06 00:49:47 +00:00
Jonah Brüchert bfb41310ad update ytmusicapi 2022-01-22 13:50:40 +01:00
l10n daemon script c3371adda4 SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2022-01-22 01:50:01 +00:00
l10n daemon script 9bcf017c8d GIT_SILENT made messages (after extraction) 2022-01-22 00:46:14 +00:00
Ml Ge 2c0b13ff9f minor readme and gitignore updates 2022-01-09 19:47:57 +01:00
Ml Ge abfd901a82 replace .license files with entries in dep5 2022-01-09 19:47:33 +01:00
Jonah Brüchert 135b8859d6 Fix typo in requirements.txt 2021-12-31 18:43:16 +01:00
Jonah Brüchert f8637b686c flatpak: Update yt-dlp 2021-12-31 18:17:32 +01:00
Jonah Brüchert 53f466c976 Update tested ytmusicapi version to 0.19.5 2021-12-21 12:53:38 +01:00
Bhushan Shah 762976159e appdata: add changelog for 21.12 2021-12-05 19:39:47 +05:30
Bhushan Shah de1a872f74 GIT_SILENT Update version number for 21.12 2021-12-05 10:15:46 +05:30
Dimitris Kardarakos 3f39fc9e90 Substitute video-play passive notification with an inline message
On mobile, the action of the video play notification was not properly shown. On top of that, an inline message seems more suitable for this purpose. An explicit cancel action has been added as well; previously it was accomplished by ignoring the notification.
2021-12-03 15:43:00 +02:00
Jonah Brüchert e8e6a5f1e2 Update some more places to ytdlp 2021-11-29 23:21:32 +01:00
Jonah Brüchert 51a9799281 Switch to yt-dlp 2021-11-29 01:43:37 +01:00
Jonah Brüchert 73078eb5e7 Commit forgotten changes 2021-11-11 12:00:51 +01:00
Jonah Brüchert d138b5c67b Further reduce amount of code of the async YTMusic wrapper 2021-11-11 12:00:16 +01:00
Jonah Brüchert ef3846df6b More awesome map_optional function 2021-11-11 11:40:27 +01:00
Jonah Brüchert a31494d83f Simplify multithreading 2021-11-11 11:22:43 +01:00
Jonah Brüchert b131fcc7d9 Make the progress bar smooth
The position update seems to come every second, so an animation duration of a second should produce a smooth progress.
2021-11-11 11:02:23 +01:00
Jonah Brüchert e6379db384 Update tested ytmusicapi version to 0.19.4 2021-11-04 22:36:17 +01:00
Jonah Brüchert f50cf92e9a flatpak: Update python packages 2021-10-27 00:37:12 +02:00
Jonah Brüchert e9e0e4f143 Fix typo 2021-09-30 01:08:06 +02:00
Jonah Brüchert dfc0b8cbcd Fix extracting album tracks with ytmusicapi 0.19.3 2021-09-30 00:02:03 +02:00
Jonah Brüchert 473b4ee83e Update flatpak runtime 2021-09-29 23:46:25 +02:00
Jonah Brüchert f40c9fd497 Update ytmusicapi 2021-09-29 23:32:36 +02:00
Jonah Brüchert 0ca21344f8 Fix some crashes 2021-09-23 21:58:18 +02:00
l10n daemon script d4d85a8b58 GIT_SILENT made messages (after extraction) 2021-09-22 00:20:11 +00:00
l10n daemon script cfd4b3de53 GIT_SILENT made messages (after extraction) 2021-09-20 00:21:06 +00:00
Jonah Brüchert 96c5f344cc Also offer playlist actions for videos 2021-09-06 23:26:54 +02:00
Jonah Brüchert d6e8ddca11 get_artist: Make description key optional 2021-09-04 21:35:35 +02:00
Jonah Brüchert db63766cc7 Make optional_key do the right thing even if the key exists but is None 2021-09-04 21:34:57 +02:00
Jonah Brüchert 048e876710 Add debug output for search results that could not be understood 2021-09-03 20:11:08 +02:00
Jonah Brüchert d7e74e2a5f SearchPage: Add section headers 2021-09-03 20:05:44 +02:00
Jonah Brüchert 722b78e49b Make album description optional 2021-09-03 19:40:27 +02:00
Jonah Brüchert 734caa0280 Fix compatibility with ytmusicapi 0.19.1 2021-09-03 17:39:50 +02:00
79 changed files with 6134 additions and 470 deletions

No files matched your search

+1
View File
@@ -6,3 +6,4 @@
*.kdev4
CMakeLists.txt.user
flatpak/flatpak-builder-tools/
build/
+1 -1
View File
@@ -3,4 +3,4 @@
# SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
include:
- 'https://invent.kde.org/sysadmin/ci-tooling/raw/master/invent/ci-reuse.yml'
- 'https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/reuse-lint.yml'
+21
View File
@@ -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
+6 -4
View File
@@ -8,11 +8,11 @@ project(audiotube)
include(FeatureSummary)
set(PROJECT_VERSION "21.08")
set(PROJECT_VERSION "22.04")
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)
@@ -33,12 +33,14 @@ ecm_setup_version(${PROJECT_VERSION}
kde_enable_exceptions()
find_package(Qt5 ${QT5_MIN_VERSION} REQUIRED COMPONENTS Core Gui Qml QuickControls2 Svg)
find_package(Qt5 ${QT5_MIN_VERSION} REQUIRED COMPONENTS Core Gui Qml QuickControls2 Svg Sql)
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Kirigami2 I18n CoreAddons Crash)
find_package(pybind11 REQUIRED)
find_package(YoutubeDL REQUIRED RUNTIME)
find_package(Ytdlp REQUIRED RUNTIME)
find_package(YTMusicAPI REQUIRED RUNTIME)
ki18n_install(po)
add_subdirectory(src)
install(PROGRAMS org.kde.audiotube.desktop DESTINATION ${KDE_INSTALL_APPDIR})
+1 -1
View File
@@ -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.
-3
View File
@@ -1,3 +0,0 @@
SPDX-FileCopyrightText: 2021 Jonah Brüchert <[email protected]>
SPDX-License-Identifier: CC0-1.0
@@ -4,8 +4,8 @@
find_package(Python3 REQUIRED COMPONENTS Interpreter)
execute_process(COMMAND ${Python3_EXECUTABLE} -c "import youtube_dl" RESULT_VARIABLE YTDL_CHECK_RESULT)
execute_process(COMMAND ${Python3_EXECUTABLE} -c "import yt_dlp" RESULT_VARIABLE YTDLP_CHECK_RESULT)
if (${YTDL_CHECK_RESULT} EQUAL 0)
set(YoutubeDL_FOUND TRUE)
if (${YTDLP_CHECK_RESULT} EQUAL 0)
set(Ytdlp_FOUND TRUE)
endif()
-14
View File
@@ -1,14 +0,0 @@
{
"name": "python3-youtube_dl",
"buildsystem": "simple",
"build-commands": [
"pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"youtube_dl\" --no-build-isolation"
],
"sources": [
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/c6/75/05979677d9abc76851d13d8db3951e39017ac223545adab6e8576fa0cbe7/youtube_dl-2021.6.6.tar.gz",
"sha256": "cb2d3ee002158ede783e97a82c95f3817594df54367ea6a77ce5ceea4772f0ab"
}
]
}
+34
View File
@@ -0,0 +1,34 @@
{
"name": "python3-yt-dlp",
"buildsystem": "simple",
"build-commands": [
"pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"yt-dlp\" --no-build-isolation"
],
"sources": [
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/b4/7b/0960d02701f783bb052ec69ea32789d878d2cce05a03950adbd75f164758/websockets-10.2.tar.gz",
"sha256": "8351c3c86b08156337b0e4ece0e3c5ec3e01fcd14e8950996832a23c99416098"
},
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/2a/18/70c32fe9357f3eea18598b23aa9ed29b1711c3001835f7cf99a9818985d0/Brotli-1.0.9.zip",
"sha256": "4d1b810aa0ed773f81dceda2cc7b403d01057458730e309856356d4ef4188438"
},
{
"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/c0/61/3a08dc08c0c60498a0136a7eec1a098f08be79f75bf35fdd0a15a1bace70/yt_dlp-2022.3.8.2-py3-none-any.whl",
"sha256": "05179f0f2c34f06910003bb9f80af68ff798b072ca0f826c0e6704a3fbd5b306"
},
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/24/40/e249ac3845a2333ce50f1bb02299ffb766babdfe80ca9d31e0158ad06afd/pycryptodomex-3.14.1.tar.gz",
"sha256": "2ce76ed0081fd6ac8c74edc75b9d14eca2064173af79843c24fa62573263c1f2"
}
]
}
File renamed without changes.
+12 -12
View File
@@ -7,33 +7,33 @@
"sources": [
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/4f/5a/597ef5911cb8919efe4d86206aa8b2658616d676a7088f0825ca08bd7cb8/urllib3-1.26.6.tar.gz",
"sha256": "f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"
"url": "https://files.pythonhosted.org/packages/cb/58/834a6445479c72544d9285badce0a58650520c8ba5adfae7b98e61d133bb/ytmusicapi-0.21.0-py3-none-any.whl",
"sha256": "29d8d44e1911458041882a453b05c37021ff850757c648f93a45d1775dddbae2"
},
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/ea/b7/e0e3c1c467636186c39925827be42f16fee389dc404ac29e930e9136be70/idna-2.10.tar.gz",
"sha256": "b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"
"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/ee/2d/9cdc2b527e127b4c9db64b86647d567985940ac3698eeabc7ffaccb4ea61/chardet-4.0.0.tar.gz",
"sha256": "0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"
"url": "https://files.pythonhosted.org/packages/ec/03/062e6444ce4baf1eac17a6a0ebfe36bb1ad05e1df0e20b110de59c278498/urllib3-1.26.9-py2.py3-none-any.whl",
"sha256": "44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"
},
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/6d/78/f8db8d57f520a54f0b8a438319c342c61c22759d8f9a1cd2e2180b5e5ea9/certifi-2021.5.30.tar.gz",
"sha256": "2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"
"url": "https://files.pythonhosted.org/packages/06/b3/24afc8868eba069a7f03650ac750a778862dc34941a4bebeb58706715726/charset_normalizer-2.0.12-py3-none-any.whl",
"sha256": "6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"
},
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/6b/47/c14abc08432ab22dc18b9892252efaf005ab44066de871e72a38d6af464b/requests-2.25.1.tar.gz",
"sha256": "27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"
"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/83/aa/3e2c231fbef90d5bd75614e3739bb27e7e33e28c9891d6ee0561fe52a9df/ytmusicapi-0.18.0.tar.gz",
"sha256": "447d1b6a594f49942f7ac75412cba59c191b6f3540d98ac65a1333445beec16d"
"url": "https://files.pythonhosted.org/packages/04/a2/d918dcd22354d8958fe113e1a3630137e0fc8b44859ade3063982eacd2a4/idna-3.3-py3-none-any.whl",
"sha256": "84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"
}
]
}
+1 -1
View File
@@ -16,5 +16,5 @@ else
git -C flatpak-builder-tools pull
fi
./flatpak-builder-tools/pip/flatpak-pip-generator youtube_dl
./flatpak-builder-tools/pip/flatpak-pip-generator yt-dlp
./flatpak-builder-tools/pip/flatpak-pip-generator ytmusicapi
-3
View File
@@ -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
+42 -3
View File
@@ -7,6 +7,8 @@ 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="az">AudioTube</name>
<name xml:lang="ca">AudioTube</name>
<name xml:lang="ca-valencia">AudioTube</name>
<name xml:lang="cs">AudioTube</name>
@@ -24,14 +26,19 @@ 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="tr">AudioTube</name>
<name xml:lang="uk">AudioTube</name>
<name xml:lang="x-test">xxAudioTubexx</name>
<name xml:lang="zh-CN">AudioTube</name>
<summary>Convergent YouTube Music client</summary>
<summary xml:lang="ca">Client convergent per a la música de YouTube</summary>
<summary xml:lang="ca-valencia">Client convergent per a la música de YouTube</summary>
<summary xml:lang="ar">عميل موسيقى يوتيوب متعدد المنصات</summary>
<summary xml:lang="az">Birləşmiş YouTube Musiqi müştərisi</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>
<summary xml:lang="en-GB">Convergent YouTube Music client</summary>
<summary xml:lang="es">Cliente de música de YouTube convergente</summary>
@@ -45,13 +52,18 @@ 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="tr">Yakınsak YouTube Müzik istemcisi</summary>
<summary xml:lang="uk">Зручний музичний клієнт YouTube</summary>
<summary xml:lang="x-test">xxConvergent YouTube Music clientxx</summary>
<summary xml:lang="zh-CN">桌面和移动设备通用 YouTube Music 客户端</summary>
<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="az">KDE İcması</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,14 +81,37 @@ 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>
<developer_name xml:lang="tr">KDE Topluluğu</developer_name>
<developer_name xml:lang="uk">Спільнота KDE</developer_name>
<developer_name xml:lang="x-test">xxKDE Communityxx</developer_name>
<developer_name xml:lang="zh-CN">KDE 社区</developer_name>
<content_rating type="oars-1.0"/>
<url type="homepage">https://apps.kde.org/en/audiotube</url>
<releases>
<release version="22.04" date="2022-04-26">
<url>https://www.plasma-mobile.org/2022/04/26/plasma-mobile-gear-22-04/</url>
<description>
<ul>
<li>Update the version of ytmusicapi</li>
<li>General code-refactoring and translation updates</li>
</ul>
</description>
</release>
<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,8 +132,10 @@ 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="az">AudioTube YouTube Musiqi, albomların və ifaçıların siyahısı üzrə axtarış etməyə, yaradılmış mahnı siyahısı və albomları avtomatik oxutmağa və öz mahnı siyahınızı yaratmağa imkan verir.</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="ca-valencia">L'AudioTube pot buscar 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>
<p xml:lang="en-GB">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="es">AudioTube puede buscar música de YouTube, listar álbumes y artistas, reproducir las listas de reproducción generadas, álbumes y le permite crear su propia lista de reproducción.</p>
@@ -114,7 +151,9 @@ SPDX-License-Identifier: CC0-1.0
<p xml:lang="pt-BR">O AudioTube pode pesquisar no YouTube Music, listar álbuns e artistas, rodar listas de reprodução geradas automaticamente, álbuns inteiros, e criar a sua própria lista de reprodução.</p>
<p xml:lang="sl">AudioTube lahko išče po YouTube Music, izdela sezname albumov in avtorjev, samodejno predvaja generirane sezname, albume in sestavlja vaše sezname predvajanja.</p>
<p xml:lang="sv">AudioTube kan söka på YouTube Music, lista album och artister, spela upp automatiskt genererade spellistor, albums och låter dig sammanställa egna spellistor.</p>
<p xml:lang="tr">AudioTube, YouTube Müzik'te arama yapabilir; albümleri, sanatçıları listeler; kendiliğinden oluşturulmuş çalma listelerini, albümleri çalar ve kendi çalma listenizi oluşturmanıza izin verir.</p>
<p xml:lang="uk">AudioTube може виконувати пошук на YouTube Music, показувати списки альбомів та виконавців, відтворювати створені автоматично списки відтворення, альбоми та надає вам змогу створювати власні списки відворення.</p>
<p xml:lang="x-test">xxAudioTube can search YouTube Music, list albums and artists, play automatically generated playlists, albums and allows to put your own playlist together.xx</p>
<p xml:lang="zh-CN">AudioTube 可以搜索 YouTube Music 的内容,列出专辑和艺人,播放自动生成的播放列表,还可以自行制作播放列表。</p>
</description>
</component>
+8
View File
@@ -1,5 +1,7 @@
[Desktop Entry]
Name=AudioTube
Name[ar]=تيوب الصوتي
Name[az]=AudioTube
Name[ca]=AudioTube
Name[ca@valencia]=AudioTube
Name[cs]=AudioTube
@@ -21,9 +23,13 @@ Name[ro]=AudioTube
Name[sk]=AudioTube
Name[sl]=AudioTube
Name[sv]=AudioTube
Name[tr]=AudioTube
Name[uk]=AudioTube
Name[x-test]=xxAudioTubexx
Name[zh_CN]=AudioTube
Comment=YouTube Music client
Comment[ar]=عميل يوتيوب ميوزيك
Comment[az]=YouTube Musiqi müştərisi
Comment[ca]=Client de música de YouTube
Comment[ca@valencia]=Client de música de YouTube
Comment[cs]=Hudební klient YouTube
@@ -45,8 +51,10 @@ Comment[ro]=Client de muzică pentru YouTube
Comment[sk]=Hudobný klient YouTube
Comment[sl]=Glasbeni odjemalec Youtube
Comment[sv]=YouTube-musikprogram
Comment[tr]=YouTube Müzik istemcisi
Comment[uk]=Музичний клієнт YouTube
Comment[x-test]=xxYouTube Music clientxx
Comment[zh_CN]=YouTube Music 客户端
Version=1.0
Exec=audiotube
Icon=org.kde.audiotube
-3
View File
@@ -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
+3 -3
View File
@@ -1,7 +1,7 @@
{
"id": "org.kde.audiotube",
"runtime": "org.kde.Platform",
"runtime-version": "5.15",
"runtime-version": "5.15-21.08",
"sdk": "org.kde.Sdk",
"command": "audiotube",
"tags": ["nightly"],
@@ -21,14 +21,14 @@
],
"modules": [
"flatpak/python3-youtube_dl.json",
"flatpak/python3-yt-dlp.json",
"flatpak/python3-ytmusicapi.json",
{
"name": "pybind11",
"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",
-3
View File
@@ -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
-3
View File
@@ -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
+154
View File
@@ -0,0 +1,154 @@
# Copyright (C) YEAR This file is copyright:
# This file is distributed under the same license as the audiotube package.
#
# Zayed Al-Saidi <[email protected]>, 2022.
msgid ""
msgstr ""
"Project-Id-Version: audiotube\n"
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
"POT-Creation-Date: 2021-12-05 00:45+0000\n"
"PO-Revision-Date: 2022-01-21 22:52+0400\n"
"Last-Translator: Zayed Al-Saidi <[email protected]>\n"
"Language-Team: ar\n"
"Language: ar\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 "
"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
"X-Generator: Lokalize 21.07.70\n"
#, kde-format
msgctxt "NAME OF TRANSLATORS"
msgid "Your names"
msgstr "زايد السعيدي"
#, kde-format
msgctxt "EMAIL OF TRANSLATORS"
msgid "Your emails"
msgstr "[email protected]"
#: contents/ui/AlbumPage.qml:19 contents/ui/PlayerFooter.qml:173
#: contents/ui/PlaylistPage.qml:19
#, kde-format
msgid "Play"
msgstr "شغّل"
#: contents/ui/AlbumPage.qml:61 contents/ui/ArtistPage.qml:101
#: contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "شغل التالي"
#: contents/ui/AlbumPage.qml:66 contents/ui/ArtistPage.qml:107
#: contents/ui/PlaylistPage.qml:72 contents/ui/SearchPage.qml:123
#, kde-format
msgid "Add to Playlist"
msgstr "أضف إلى قائمة التّشغيل"
#: contents/ui/ArtistPage.qml:39 contents/ui/SearchPage.qml:67
#, kde-format
msgid ""
"Video playback is not supported yet. Do you want to play only the audio of "
"\"%1\"?"
msgstr "تشغيل الفيديو غير مدعوم حتى الآن. هل تريد تشغيل صوت فقط من \"%1\"؟"
#: contents/ui/ArtistPage.qml:55 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "الألبومات"
#: contents/ui/ArtistPage.qml:57
#, kde-format
msgid "Singles"
msgstr "فردي"
#: contents/ui/ArtistPage.qml:59 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "أغاني"
#: contents/ui/ArtistPage.qml:61 contents/ui/SearchPage.qml:33
#, kde-format
msgid "Videos"
msgstr "الفيديوهات"
#: contents/ui/ConfirmationMessage.qml:16
#, kde-format
msgid "OK"
msgstr "حسنًا"
#: contents/ui/ConfirmationMessage.qml:21
#, kde-format
msgid "Cancel"
msgstr "ألغِ"
#: contents/ui/main.qml:51
#, kde-format
msgid "AudioTube"
msgstr "تيوب الصوتي"
#: contents/ui/PlayerFooter.qml:106
#, kde-format
msgid "Remove Track"
msgstr "أزل المسار"
#: contents/ui/PlayerFooter.qml:123
#, kde-format
msgid "Clear"
msgstr "امسح"
#: contents/ui/PlayerFooter.qml:128 contents/ui/PlaylistPage.qml:26
#, kde-format
msgid "Shuffle"
msgstr "اخلط"
#: contents/ui/PlayerFooter.qml:173
#, kde-format
msgid "Pause"
msgstr "ألبث"
#: contents/ui/PlayerFooter.qml:203
#, kde-format
msgid "No media playing"
msgstr "لا يوجد وسائط تعمل"
#: contents/ui/PlayerFooter.qml:247
#, kde-format
msgid "Expand"
msgstr "وسع"
#: contents/ui/SearchPage.qml:27
#, kde-format
msgid "Artists"
msgstr "الفنّانون"
#: contents/ui/SearchPage.qml:29
#, kde-format
msgid "Playlists"
msgstr "قوائم التّشغيل"
#: contents/ui/SearchPage.qml:36
#, kde-format
msgid "Unknown"
msgstr "مجهول"
#: contents/ui/SearchPage.qml:117
#, kde-format
msgid "Play next"
msgstr "شغل التالي"
#: contents/ui/SearchPage.qml:129
#, kde-format
msgid "Radio"
msgstr "راديو"
#: contents/ui/SearchPage.qml:147
#, kde-format
msgid "Find music on YouTube Music"
msgstr "اعثر على موسيقى على موسيقى يوتيوب"
#: contents/ui/SearchPage.qml:150
#, kde-format
msgid "Search"
msgstr "ابحث"
+155
View File
@@ -0,0 +1,155 @@
# Copyright (C) YEAR This file is copyright:
# This file is distributed under the same license as the audiotube package.
#
# Kheyyam Gojayev <[email protected]>, 2022.
msgid ""
msgstr ""
"Project-Id-Version: audiotube\n"
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
"POT-Creation-Date: 2021-12-05 00:45+0000\n"
"PO-Revision-Date: 2022-04-22 18:44+0400\n"
"Last-Translator: Kheyyam Gojayev <[email protected]>\n"
"Language-Team: Azerbaijani <[email protected]>\n"
"Language: az\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Lokalize 21.12.3\n"
#, kde-format
msgctxt "NAME OF TRANSLATORS"
msgid "Your names"
msgstr "Xəyyam Qocayev"
#, kde-format
msgctxt "EMAIL OF TRANSLATORS"
msgid "Your emails"
msgstr "[email protected]"
#: contents/ui/AlbumPage.qml:19 contents/ui/PlayerFooter.qml:173
#: contents/ui/PlaylistPage.qml:19
#, kde-format
msgid "Play"
msgstr "Başladın"
#: contents/ui/AlbumPage.qml:61 contents/ui/ArtistPage.qml:101
#: contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Sonrakını oxudun"
#: contents/ui/AlbumPage.qml:66 contents/ui/ArtistPage.qml:107
#: contents/ui/PlaylistPage.qml:72 contents/ui/SearchPage.qml:123
#, kde-format
msgid "Add to Playlist"
msgstr "Mahnı siyahısına əlavə edin"
#: contents/ui/ArtistPage.qml:39 contents/ui/SearchPage.qml:67
#, kde-format
msgid ""
"Video playback is not supported yet. Do you want to play only the audio of "
"\"%1\"?"
msgstr ""
"Video göstərilməsi hələlik dəstəklənmir. Siz yalnız \"%1\" videosunun səsini "
"oynatmaq istəyirsiniz?"
#: contents/ui/ArtistPage.qml:55 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Albomlar"
#: contents/ui/ArtistPage.qml:57
#, kde-format
msgid "Singles"
msgstr "Musiqilər"
#: contents/ui/ArtistPage.qml:59 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Mahnılar"
#: contents/ui/ArtistPage.qml:61 contents/ui/SearchPage.qml:33
#, kde-format
msgid "Videos"
msgstr "Videolar"
#: contents/ui/ConfirmationMessage.qml:16
#, kde-format
msgid "OK"
msgstr "OLDU"
#: contents/ui/ConfirmationMessage.qml:21
#, kde-format
msgid "Cancel"
msgstr "İmtina"
#: contents/ui/main.qml:51
#, kde-format
msgid "AudioTube"
msgstr "AudioTube"
#: contents/ui/PlayerFooter.qml:106
#, kde-format
msgid "Remove Track"
msgstr "Mahnını silin"
#: contents/ui/PlayerFooter.qml:123
#, kde-format
msgid "Clear"
msgstr "Təmizləyin"
#: contents/ui/PlayerFooter.qml:128 contents/ui/PlaylistPage.qml:26
#, kde-format
msgid "Shuffle"
msgstr "Qarışıq"
#: contents/ui/PlayerFooter.qml:173
#, kde-format
msgid "Pause"
msgstr "Fasilə"
#: contents/ui/PlayerFooter.qml:203
#, kde-format
msgid "No media playing"
msgstr "Media faylı oynadılmır"
#: contents/ui/PlayerFooter.qml:247
#, kde-format
msgid "Expand"
msgstr "Açmaq"
#: contents/ui/SearchPage.qml:27
#, kde-format
msgid "Artists"
msgstr "İfaçılar"
#: contents/ui/SearchPage.qml:29
#, kde-format
msgid "Playlists"
msgstr "Mahnı siyahıları"
#: contents/ui/SearchPage.qml:36
#, kde-format
msgid "Unknown"
msgstr "Bilinməyən"
#: contents/ui/SearchPage.qml:117
#, kde-format
msgid "Play next"
msgstr "Sonrakını başladın"
#: contents/ui/SearchPage.qml:129
#, kde-format
msgid "Radio"
msgstr "Radio"
#: contents/ui/SearchPage.qml:147
#, kde-format
msgid "Find music on YouTube Music"
msgstr "Musiqini YouTube Musiqi-də tapın"
#: contents/ui/SearchPage.qml:150
#, kde-format
msgid "Search"
msgstr "Axtarın"
+160
View File
@@ -0,0 +1,160 @@
# Translation of audiotube.po to Catalan
# Copyright (C) 2021 This_file_is_part_of_KDE
# This file is distributed under the license LGPL version 2.1 or
# version 3 or later versions approved by the membership of KDE e.V.
#
# Antoni Bella Pérez <[email protected]>, 2021.
msgid ""
msgstr ""
"Project-Id-Version: audiotube\n"
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
"POT-Creation-Date: 2021-12-05 00:45+0000\n"
"PO-Revision-Date: 2021-12-05 16:03+0100\n"
"Last-Translator: Antoni Bella Pérez <[email protected]>\n"
"Language-Team: Catalan <[email protected]>\n"
"Language: ca\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Lokalize 21.11.90\n"
#, kde-format
msgctxt "NAME OF TRANSLATORS"
msgid "Your names"
msgstr "Antoni Bella"
#, kde-format
msgctxt "EMAIL OF TRANSLATORS"
msgid "Your emails"
msgstr "[email protected]"
#: contents/ui/AlbumPage.qml:19 contents/ui/PlayerFooter.qml:173
#: contents/ui/PlaylistPage.qml:19
#, kde-format
msgid "Play"
msgstr "Reprodueix"
#: contents/ui/AlbumPage.qml:61 contents/ui/ArtistPage.qml:101
#: contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Reprodueix el següent"
#: contents/ui/AlbumPage.qml:66 contents/ui/ArtistPage.qml:107
#: contents/ui/PlaylistPage.qml:72 contents/ui/SearchPage.qml:123
#, kde-format
msgid "Add to Playlist"
msgstr "Afegeix a la llista de reproducció"
#: contents/ui/ArtistPage.qml:39 contents/ui/SearchPage.qml:67
#, kde-format
msgid ""
"Video playback is not supported yet. Do you want to play only the audio of "
"\"%1\"?"
msgstr ""
"Encara no s'admet la reproducció de vídeo. Voleu reproduir només l'àudio de "
"«%1»?"
#: contents/ui/ArtistPage.qml:55 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Àlbums"
#: contents/ui/ArtistPage.qml:57
#, kde-format
msgid "Singles"
msgstr "Individuals"
#: contents/ui/ArtistPage.qml:59 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Cançons"
#: contents/ui/ArtistPage.qml:61 contents/ui/SearchPage.qml:33
#, kde-format
msgid "Videos"
msgstr "Vídeos"
#: contents/ui/ConfirmationMessage.qml:16
#, kde-format
msgid "OK"
msgstr "D'acord"
#: contents/ui/ConfirmationMessage.qml:21
#, kde-format
msgid "Cancel"
msgstr "Cancel·la"
#: contents/ui/main.qml:51
#, kde-format
msgid "AudioTube"
msgstr "AudioTube"
#: contents/ui/PlayerFooter.qml:106
#, kde-format
msgid "Remove Track"
msgstr "Elimina la pista"
#: contents/ui/PlayerFooter.qml:123
#, kde-format
msgid "Clear"
msgstr "Neteja"
#: contents/ui/PlayerFooter.qml:128 contents/ui/PlaylistPage.qml:26
#, kde-format
msgid "Shuffle"
msgstr "Barreja"
#: contents/ui/PlayerFooter.qml:173
#, kde-format
msgid "Pause"
msgstr "Pausa"
#: contents/ui/PlayerFooter.qml:203
#, kde-format
msgid "No media playing"
msgstr "No s'està reproduint cap suport"
#: contents/ui/PlayerFooter.qml:247
#, kde-format
msgid "Expand"
msgstr "Expandeix"
#: contents/ui/SearchPage.qml:27
#, kde-format
msgid "Artists"
msgstr "Artistes"
#: contents/ui/SearchPage.qml:29
#, kde-format
msgid "Playlists"
msgstr "Llistes de reproducció"
#: contents/ui/SearchPage.qml:36
#, kde-format
msgid "Unknown"
msgstr "Desconeguda"
#: contents/ui/SearchPage.qml:117
#, kde-format
msgid "Play next"
msgstr "Reprodueix el següent"
#: contents/ui/SearchPage.qml:129
#, kde-format
msgid "Radio"
msgstr "Ràdio"
#: contents/ui/SearchPage.qml:147
#, kde-format
msgid "Find music on YouTube Music"
msgstr "Cerca a la Música de Youtube"
#: contents/ui/SearchPage.qml:150
#, kde-format
msgid "Search"
msgstr "Cerca"
#~ msgid "Play only Audio"
#~ msgstr "Reprodueix només l'àudio"
+157
View File
@@ -0,0 +1,157 @@
# Translation of audiotube.po to Catalan (Valencian)
# Copyright (C) 2021 This_file_is_part_of_KDE
# This file is distributed under the license LGPL version 2.1 or
# version 3 or later versions approved by the membership of KDE e.V.
#
# Antoni Bella Pérez <[email protected]>, 2021.
msgid ""
msgstr ""
"Project-Id-Version: audiotube\n"
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
"POT-Creation-Date: 2021-12-05 00:45+0000\n"
"PO-Revision-Date: 2021-12-05 16:03+0100\n"
"Last-Translator: Antoni Bella Pérez <[email protected]>\n"
"Language-Team: Catalan <[email protected]>\n"
"Language: ca@valencia\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Lokalize 21.11.90\n"
#, kde-format
msgctxt "NAME OF TRANSLATORS"
msgid "Your names"
msgstr "Antoni Bella"
#, kde-format
msgctxt "EMAIL OF TRANSLATORS"
msgid "Your emails"
msgstr "[email protected]"
#: contents/ui/AlbumPage.qml:19 contents/ui/PlayerFooter.qml:173
#: contents/ui/PlaylistPage.qml:19
#, kde-format
msgid "Play"
msgstr "Reprodueix"
#: contents/ui/AlbumPage.qml:61 contents/ui/ArtistPage.qml:101
#: contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Reprodueix el següent"
#: contents/ui/AlbumPage.qml:66 contents/ui/ArtistPage.qml:107
#: contents/ui/PlaylistPage.qml:72 contents/ui/SearchPage.qml:123
#, kde-format
msgid "Add to Playlist"
msgstr "Afig a la llista de reproducció"
#: contents/ui/ArtistPage.qml:39 contents/ui/SearchPage.qml:67
#, kde-format
msgid ""
"Video playback is not supported yet. Do you want to play only the audio of "
"\"%1\"?"
msgstr ""
"Encara no s'admet la reproducció de vídeo. Voleu reproduir només l'àudio de "
"«%1»?"
#: contents/ui/ArtistPage.qml:55 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Àlbums"
#: contents/ui/ArtistPage.qml:57
#, kde-format
msgid "Singles"
msgstr "Individuals"
#: contents/ui/ArtistPage.qml:59 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Cançons"
#: contents/ui/ArtistPage.qml:61 contents/ui/SearchPage.qml:33
#, kde-format
msgid "Videos"
msgstr "Vídeos"
#: contents/ui/ConfirmationMessage.qml:16
#, kde-format
msgid "OK"
msgstr "D'acord"
#: contents/ui/ConfirmationMessage.qml:21
#, kde-format
msgid "Cancel"
msgstr "Cancel·la"
#: contents/ui/main.qml:51
#, kde-format
msgid "AudioTube"
msgstr "AudioTube"
#: contents/ui/PlayerFooter.qml:106
#, kde-format
msgid "Remove Track"
msgstr "Elimina la pista"
#: contents/ui/PlayerFooter.qml:123
#, kde-format
msgid "Clear"
msgstr "Neteja"
#: contents/ui/PlayerFooter.qml:128 contents/ui/PlaylistPage.qml:26
#, kde-format
msgid "Shuffle"
msgstr "Barreja"
#: contents/ui/PlayerFooter.qml:173
#, kde-format
msgid "Pause"
msgstr "Pausa"
#: contents/ui/PlayerFooter.qml:203
#, kde-format
msgid "No media playing"
msgstr "No s'està reproduint cap suport"
#: contents/ui/PlayerFooter.qml:247
#, kde-format
msgid "Expand"
msgstr "Expandeix"
#: contents/ui/SearchPage.qml:27
#, kde-format
msgid "Artists"
msgstr "Artistes"
#: contents/ui/SearchPage.qml:29
#, kde-format
msgid "Playlists"
msgstr "Llistes de reproducció"
#: contents/ui/SearchPage.qml:36
#, kde-format
msgid "Unknown"
msgstr "Desconeguda"
#: contents/ui/SearchPage.qml:117
#, kde-format
msgid "Play next"
msgstr "Reprodueix el següent"
#: contents/ui/SearchPage.qml:129
#, kde-format
msgid "Radio"
msgstr "Ràdio"
#: contents/ui/SearchPage.qml:147
#, kde-format
msgid "Find music on YouTube Music"
msgstr "Busca a la Música de Youtube"
#: contents/ui/SearchPage.qml:150
#, kde-format
msgid "Search"
msgstr "Busca"
+155
View File
@@ -0,0 +1,155 @@
# Copyright (C) YEAR This file is copyright:
# This file is distributed under the same license as the audiotube package.
# Vit Pelcak <[email protected]>, 2021, 2022.
#
msgid ""
msgstr ""
"Project-Id-Version: audiotube\n"
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
"POT-Creation-Date: 2021-12-05 00:45+0000\n"
"PO-Revision-Date: 2022-01-11 17:36+0100\n"
"Last-Translator: Vit Pelcak <[email protected]>\n"
"Language-Team: Czech <[email protected]>\n"
"Language: cs\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
"X-Generator: Lokalize 21.12.0\n"
#, kde-format
msgctxt "NAME OF TRANSLATORS"
msgid "Your names"
msgstr "Vít Pelčák"
#, kde-format
msgctxt "EMAIL OF TRANSLATORS"
msgid "Your emails"
msgstr "[email protected]"
#: contents/ui/AlbumPage.qml:19 contents/ui/PlayerFooter.qml:173
#: contents/ui/PlaylistPage.qml:19
#, kde-format
msgid "Play"
msgstr "Přehrát"
#: contents/ui/AlbumPage.qml:61 contents/ui/ArtistPage.qml:101
#: contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Přehrát následující"
#: contents/ui/AlbumPage.qml:66 contents/ui/ArtistPage.qml:107
#: contents/ui/PlaylistPage.qml:72 contents/ui/SearchPage.qml:123
#, kde-format
msgid "Add to Playlist"
msgstr "Přidat do seznamu skladeb"
#: contents/ui/ArtistPage.qml:39 contents/ui/SearchPage.qml:67
#, kde-format
msgid ""
"Video playback is not supported yet. Do you want to play only the audio of "
"\"%1\"?"
msgstr ""
"Přehrávání videa ještě není podporováno. Chcete z \"%1\" přehrávat pouze "
"zvuk?"
#: contents/ui/ArtistPage.qml:55 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Alba"
#: contents/ui/ArtistPage.qml:57
#, kde-format
msgid "Singles"
msgstr "Samotné"
#: contents/ui/ArtistPage.qml:59 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Písně"
#: contents/ui/ArtistPage.qml:61 contents/ui/SearchPage.qml:33
#, kde-format
msgid "Videos"
msgstr "Videa"
#: contents/ui/ConfirmationMessage.qml:16
#, kde-format
msgid "OK"
msgstr "OK"
#: contents/ui/ConfirmationMessage.qml:21
#, kde-format
msgid "Cancel"
msgstr "Zrušit"
#: contents/ui/main.qml:51
#, kde-format
msgid "AudioTube"
msgstr "AudioTube"
#: contents/ui/PlayerFooter.qml:106
#, kde-format
msgid "Remove Track"
msgstr "Odstranit skladbu"
#: contents/ui/PlayerFooter.qml:123
#, kde-format
msgid "Clear"
msgstr "Vyprázdnit"
#: contents/ui/PlayerFooter.qml:128 contents/ui/PlaylistPage.qml:26
#, kde-format
msgid "Shuffle"
msgstr "Zamíchat"
#: contents/ui/PlayerFooter.qml:173
#, kde-format
msgid "Pause"
msgstr "Pozastavit"
#: contents/ui/PlayerFooter.qml:203
#, kde-format
msgid "No media playing"
msgstr "Nepřehrává se žádné médium"
#: contents/ui/PlayerFooter.qml:247
#, kde-format
msgid "Expand"
msgstr "Rozvinout"
#: contents/ui/SearchPage.qml:27
#, kde-format
msgid "Artists"
msgstr "Umělci"
#: contents/ui/SearchPage.qml:29
#, kde-format
msgid "Playlists"
msgstr "Seznamy skladeb"
#: contents/ui/SearchPage.qml:36
#, kde-format
msgid "Unknown"
msgstr "Neznámý"
#: contents/ui/SearchPage.qml:117
#, kde-format
msgid "Play next"
msgstr "Přehrát následující"
#: contents/ui/SearchPage.qml:129
#, kde-format
msgid "Radio"
msgstr "Rádio"
#: contents/ui/SearchPage.qml:147
#, kde-format
msgid "Find music on YouTube Music"
msgstr "Najít hudbu na Hudba YouTube"
#: contents/ui/SearchPage.qml:150
#, kde-format
msgid "Search"
msgstr "Hledat"
+159
View File
@@ -0,0 +1,159 @@
# Copyright (C) YEAR This file is copyright:
# This file is distributed under the same license as the audiotube package.
# Burkhard Lück <[email protected]>, 2021.
# Frederik Schwarzer <[email protected]>, 2021, 2022.
#
msgid ""
msgstr ""
"Project-Id-Version: audiotube\n"
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
"POT-Creation-Date: 2021-12-05 00:45+0000\n"
"PO-Revision-Date: 2022-02-20 10:37+0100\n"
"Last-Translator: Frederik Schwarzer <[email protected]>\n"
"Language-Team: German <[email protected]>\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Lokalize 21.08.0\n"
#, kde-format
msgctxt "NAME OF TRANSLATORS"
msgid "Your names"
msgstr "Deutsches KDE-Übersetzerteam"
#, kde-format
msgctxt "EMAIL OF TRANSLATORS"
msgid "Your emails"
msgstr "[email protected]"
#: contents/ui/AlbumPage.qml:19 contents/ui/PlayerFooter.qml:173
#: contents/ui/PlaylistPage.qml:19
#, kde-format
msgid "Play"
msgstr "Wiedergabe"
#: contents/ui/AlbumPage.qml:61 contents/ui/ArtistPage.qml:101
#: contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Nächste Wiedergabe"
#: contents/ui/AlbumPage.qml:66 contents/ui/ArtistPage.qml:107
#: contents/ui/PlaylistPage.qml:72 contents/ui/SearchPage.qml:123
#, kde-format
msgid "Add to Playlist"
msgstr "Zur Wiedergabeliste hinzufügen"
#: contents/ui/ArtistPage.qml:39 contents/ui/SearchPage.qml:67
#, kde-format
msgid ""
"Video playback is not supported yet. Do you want to play only the audio of "
"\"%1\"?"
msgstr ""
"Die Video-Wiedergabe wird noch nicht unterstützt. Möchten Sie nur die "
"Audiospur von „%1“ abspielen?"
#: contents/ui/ArtistPage.qml:55 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Alben"
#: contents/ui/ArtistPage.qml:57
#, kde-format
msgid "Singles"
msgstr "Singles"
#: contents/ui/ArtistPage.qml:59 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Songs"
#: contents/ui/ArtistPage.qml:61 contents/ui/SearchPage.qml:33
#, kde-format
msgid "Videos"
msgstr "Videos"
#: contents/ui/ConfirmationMessage.qml:16
#, kde-format
msgid "OK"
msgstr "OK"
#: contents/ui/ConfirmationMessage.qml:21
#, kde-format
msgid "Cancel"
msgstr "Abbrechen"
#: contents/ui/main.qml:51
#, kde-format
msgid "AudioTube"
msgstr "AudioTube"
#: contents/ui/PlayerFooter.qml:106
#, kde-format
msgid "Remove Track"
msgstr "Stück entfernen"
#: contents/ui/PlayerFooter.qml:123
#, kde-format
msgid "Clear"
msgstr "Löschen"
#: contents/ui/PlayerFooter.qml:128 contents/ui/PlaylistPage.qml:26
#, kde-format
msgid "Shuffle"
msgstr "Zufällig"
#: contents/ui/PlayerFooter.qml:173
#, kde-format
msgid "Pause"
msgstr "Pause"
#: contents/ui/PlayerFooter.qml:203
#, kde-format
msgid "No media playing"
msgstr "Es werden keine Medien wiedergegeben"
#: contents/ui/PlayerFooter.qml:247
#, kde-format
msgid "Expand"
msgstr "Erweitern"
#: contents/ui/SearchPage.qml:27
#, kde-format
msgid "Artists"
msgstr "Interpreten"
#: contents/ui/SearchPage.qml:29
#, kde-format
msgid "Playlists"
msgstr "Wiedergabelisten"
#: contents/ui/SearchPage.qml:36
#, kde-format
msgid "Unknown"
msgstr "Unbekannt"
#: contents/ui/SearchPage.qml:117
#, kde-format
msgid "Play next"
msgstr "Nächste Wiedergabe"
#: contents/ui/SearchPage.qml:129
#, kde-format
msgid "Radio"
msgstr "Radio"
#: contents/ui/SearchPage.qml:147
#, kde-format
msgid "Find music on YouTube Music"
msgstr "Musik auf YouTube suchen"
#: contents/ui/SearchPage.qml:150
#, kde-format
msgid "Search"
msgstr "Suchen"
#~ msgid "Play only Audio"
#~ msgstr "Nur Audio abspielen"
+158
View File
@@ -0,0 +1,158 @@
# Copyright (C) YEAR This file is copyright:
# This file is distributed under the same license as the audiotube package.
#
# Steve Allewell <[email protected]>, 2021, 2022.
msgid ""
msgstr ""
"Project-Id-Version: audiotube\n"
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
"POT-Creation-Date: 2021-12-05 00:45+0000\n"
"PO-Revision-Date: 2022-01-01 15:21+0000\n"
"Last-Translator: Steve Allewell <[email protected]>\n"
"Language-Team: British English <[email protected]>\n"
"Language: en_GB\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Lokalize 21.12.0\n"
#, kde-format
msgctxt "NAME OF TRANSLATORS"
msgid "Your names"
msgstr "Steve Allewell"
#, kde-format
msgctxt "EMAIL OF TRANSLATORS"
msgid "Your emails"
msgstr "[email protected]"
#: contents/ui/AlbumPage.qml:19 contents/ui/PlayerFooter.qml:173
#: contents/ui/PlaylistPage.qml:19
#, kde-format
msgid "Play"
msgstr "Play"
#: contents/ui/AlbumPage.qml:61 contents/ui/ArtistPage.qml:101
#: contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Play Next"
#: contents/ui/AlbumPage.qml:66 contents/ui/ArtistPage.qml:107
#: contents/ui/PlaylistPage.qml:72 contents/ui/SearchPage.qml:123
#, kde-format
msgid "Add to Playlist"
msgstr "Add to Playlist"
#: contents/ui/ArtistPage.qml:39 contents/ui/SearchPage.qml:67
#, kde-format
msgid ""
"Video playback is not supported yet. Do you want to play only the audio of "
"\"%1\"?"
msgstr ""
"Video playback is not supported yet. Do you want to play only the audio of "
"\"%1\"?"
#: contents/ui/ArtistPage.qml:55 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Albums"
#: contents/ui/ArtistPage.qml:57
#, kde-format
msgid "Singles"
msgstr "Singles"
#: contents/ui/ArtistPage.qml:59 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Songs"
#: contents/ui/ArtistPage.qml:61 contents/ui/SearchPage.qml:33
#, kde-format
msgid "Videos"
msgstr "Videos"
#: contents/ui/ConfirmationMessage.qml:16
#, kde-format
msgid "OK"
msgstr "OK"
#: contents/ui/ConfirmationMessage.qml:21
#, kde-format
msgid "Cancel"
msgstr "Cancel"
#: contents/ui/main.qml:51
#, kde-format
msgid "AudioTube"
msgstr "AudioTube"
#: contents/ui/PlayerFooter.qml:106
#, kde-format
msgid "Remove Track"
msgstr "Remove Track"
#: contents/ui/PlayerFooter.qml:123
#, kde-format
msgid "Clear"
msgstr "Clear"
#: contents/ui/PlayerFooter.qml:128 contents/ui/PlaylistPage.qml:26
#, kde-format
msgid "Shuffle"
msgstr "Shuffle"
#: contents/ui/PlayerFooter.qml:173
#, kde-format
msgid "Pause"
msgstr "Pause"
#: contents/ui/PlayerFooter.qml:203
#, kde-format
msgid "No media playing"
msgstr "No media playing"
#: contents/ui/PlayerFooter.qml:247
#, kde-format
msgid "Expand"
msgstr "Expand"
#: contents/ui/SearchPage.qml:27
#, kde-format
msgid "Artists"
msgstr "Artists"
#: contents/ui/SearchPage.qml:29
#, kde-format
msgid "Playlists"
msgstr "Playlists"
#: contents/ui/SearchPage.qml:36
#, kde-format
msgid "Unknown"
msgstr "Unknown"
#: contents/ui/SearchPage.qml:117
#, kde-format
msgid "Play next"
msgstr "Play next"
#: contents/ui/SearchPage.qml:129
#, kde-format
msgid "Radio"
msgstr "Radio"
#: contents/ui/SearchPage.qml:147
#, kde-format
msgid "Find music on YouTube Music"
msgstr "Find music on YouTube Music"
#: contents/ui/SearchPage.qml:150
#, kde-format
msgid "Search"
msgstr "Search"
#~ msgid "Play only Audio"
#~ msgstr "Play only Audio"
+160
View File
@@ -0,0 +1,160 @@
# Spanish translations for audiotube.po package.
# Copyright (C) 2021 This file is copyright:
# This file is distributed under the same license as the audiotube package.
#
# Automatically generated, 2021.
# Eloy Cuadra <[email protected]>, 2021.
msgid ""
msgstr ""
"Project-Id-Version: audiotube\n"
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
"POT-Creation-Date: 2021-12-05 00:45+0000\n"
"PO-Revision-Date: 2021-12-06 00:36+0100\n"
"Last-Translator: Eloy Cuadra <[email protected]>\n"
"Language-Team: Spanish <[email protected]>\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Lokalize 21.11.90\n"
#, kde-format
msgctxt "NAME OF TRANSLATORS"
msgid "Your names"
msgstr "Eloy Cuadra"
#, kde-format
msgctxt "EMAIL OF TRANSLATORS"
msgid "Your emails"
msgstr "[email protected]"
#: contents/ui/AlbumPage.qml:19 contents/ui/PlayerFooter.qml:173
#: contents/ui/PlaylistPage.qml:19
#, kde-format
msgid "Play"
msgstr "Reproducir"
#: contents/ui/AlbumPage.qml:61 contents/ui/ArtistPage.qml:101
#: contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Reproducir siguiente"
#: contents/ui/AlbumPage.qml:66 contents/ui/ArtistPage.qml:107
#: contents/ui/PlaylistPage.qml:72 contents/ui/SearchPage.qml:123
#, kde-format
msgid "Add to Playlist"
msgstr "Añadir a la lista de reproducción"
#: contents/ui/ArtistPage.qml:39 contents/ui/SearchPage.qml:67
#, kde-format
msgid ""
"Video playback is not supported yet. Do you want to play only the audio of "
"\"%1\"?"
msgstr ""
"La reproducción de vídeo aún no está permitida. ¿Desea reproducir solo el "
"sonido de «%1»?"
#: contents/ui/ArtistPage.qml:55 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Álbumes"
#: contents/ui/ArtistPage.qml:57
#, kde-format
msgid "Singles"
msgstr "Sencillos"
#: contents/ui/ArtistPage.qml:59 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Canciones"
#: contents/ui/ArtistPage.qml:61 contents/ui/SearchPage.qml:33
#, kde-format
msgid "Videos"
msgstr "Vídeos"
#: contents/ui/ConfirmationMessage.qml:16
#, kde-format
msgid "OK"
msgstr "Aceptar"
#: contents/ui/ConfirmationMessage.qml:21
#, kde-format
msgid "Cancel"
msgstr "Cancelar"
#: contents/ui/main.qml:51
#, kde-format
msgid "AudioTube"
msgstr "AudioTube"
#: contents/ui/PlayerFooter.qml:106
#, kde-format
msgid "Remove Track"
msgstr "Eliminar pista"
#: contents/ui/PlayerFooter.qml:123
#, kde-format
msgid "Clear"
msgstr "Borrar todo"
#: contents/ui/PlayerFooter.qml:128 contents/ui/PlaylistPage.qml:26
#, kde-format
msgid "Shuffle"
msgstr "Desordenar"
#: contents/ui/PlayerFooter.qml:173
#, kde-format
msgid "Pause"
msgstr "Pausar"
#: contents/ui/PlayerFooter.qml:203
#, kde-format
msgid "No media playing"
msgstr "Ningún medio en reproducción"
#: contents/ui/PlayerFooter.qml:247
#, kde-format
msgid "Expand"
msgstr "Expandir"
#: contents/ui/SearchPage.qml:27
#, kde-format
msgid "Artists"
msgstr "Artistas"
#: contents/ui/SearchPage.qml:29
#, kde-format
msgid "Playlists"
msgstr "Listas de reproducción"
#: contents/ui/SearchPage.qml:36
#, kde-format
msgid "Unknown"
msgstr "Desconocido"
#: contents/ui/SearchPage.qml:117
#, kde-format
msgid "Play next"
msgstr "Reproducir siguiente"
#: contents/ui/SearchPage.qml:129
#, kde-format
msgid "Radio"
msgstr "Radio"
#: contents/ui/SearchPage.qml:147
#, kde-format
msgid "Find music on YouTube Music"
msgstr "Encuentre música en YouTube Music"
#: contents/ui/SearchPage.qml:150
#, kde-format
msgid "Search"
msgstr "Buscar"
#~ msgid "Play only Audio"
#~ msgstr "Reproducir solo el sonido"
+161
View File
@@ -0,0 +1,161 @@
# Translation for audiotube.po to Euskara/Basque (eu).
# Copyright (C) 2021-2022, This file is copyright:
# This file is distributed under the same license as the audiotube package.
# KDE euskaratzeko proiektuko arduraduna <[email protected]>.
#
# Translators:
# Iñigo Salvador Azurmendi <[email protected]>, 2021, 2022.
msgid ""
msgstr ""
"Project-Id-Version: audiotube\n"
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
"POT-Creation-Date: 2021-12-05 00:45+0000\n"
"PO-Revision-Date: 2022-02-21 18:58+0100\n"
"Last-Translator: Iñigo Salvador Azurmendi <[email protected]>\n"
"Language-Team: Basque <[email protected]>\n"
"Language: eu\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Lokalize 21.12.2\n"
#, kde-format
msgctxt "NAME OF TRANSLATORS"
msgid "Your names"
msgstr "Iñigo Salvador Azurmendi"
#, kde-format
msgctxt "EMAIL OF TRANSLATORS"
msgid "Your emails"
msgstr "[email protected]"
#: contents/ui/AlbumPage.qml:19 contents/ui/PlayerFooter.qml:173
#: contents/ui/PlaylistPage.qml:19
#, kde-format
msgid "Play"
msgstr "Jo"
#: contents/ui/AlbumPage.qml:61 contents/ui/ArtistPage.qml:101
#: contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Jo hurrengoa"
#: contents/ui/AlbumPage.qml:66 contents/ui/ArtistPage.qml:107
#: contents/ui/PlaylistPage.qml:72 contents/ui/SearchPage.qml:123
#, kde-format
msgid "Add to Playlist"
msgstr "Gehitu jotze-zerrendara"
#: contents/ui/ArtistPage.qml:39 contents/ui/SearchPage.qml:67
#, kde-format
msgid ""
"Video playback is not supported yet. Do you want to play only the audio of "
"\"%1\"?"
msgstr ""
"Oraindik ez dago bideoa atzera jotzeko euskarririk. «%1»(r)en audioa "
"bakarrik jo nahi duzu?"
#: contents/ui/ArtistPage.qml:55 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Albumak"
#: contents/ui/ArtistPage.qml:57
#, kde-format
msgid "Singles"
msgstr "Banakoa"
#: contents/ui/ArtistPage.qml:59 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Abestiak"
#: contents/ui/ArtistPage.qml:61 contents/ui/SearchPage.qml:33
#, kde-format
msgid "Videos"
msgstr "Bideoak"
#: contents/ui/ConfirmationMessage.qml:16
#, kde-format
msgid "OK"
msgstr "Ados"
#: contents/ui/ConfirmationMessage.qml:21
#, kde-format
msgid "Cancel"
msgstr "Utzi"
#: contents/ui/main.qml:51
#, kde-format
msgid "AudioTube"
msgstr "AudioTube"
#: contents/ui/PlayerFooter.qml:106
#, kde-format
msgid "Remove Track"
msgstr "Kendu pista"
#: contents/ui/PlayerFooter.qml:123
#, kde-format
msgid "Clear"
msgstr "Garbitu"
#: contents/ui/PlayerFooter.qml:128 contents/ui/PlaylistPage.qml:26
#, kde-format
msgid "Shuffle"
msgstr "Nahastu"
#: contents/ui/PlayerFooter.qml:173
#, kde-format
msgid "Pause"
msgstr "Eten"
#: contents/ui/PlayerFooter.qml:203
#, kde-format
msgid "No media playing"
msgstr "Ez da euskarririk jotzen ari"
#: contents/ui/PlayerFooter.qml:247
#, kde-format
msgid "Expand"
msgstr "Zabaldu"
#: contents/ui/SearchPage.qml:27
#, kde-format
msgid "Artists"
msgstr "Artistak"
#: contents/ui/SearchPage.qml:29
#, kde-format
msgid "Playlists"
msgstr "Jotze-zerrendak"
#: contents/ui/SearchPage.qml:36
#, kde-format
msgid "Unknown"
msgstr "Ezezaguna"
#: contents/ui/SearchPage.qml:117
#, kde-format
msgid "Play next"
msgstr "Jo hurrengoa"
#: contents/ui/SearchPage.qml:129
#, kde-format
msgid "Radio"
msgstr "Irratia"
#: contents/ui/SearchPage.qml:147
#, kde-format
msgid "Find music on YouTube Music"
msgstr "Aurkitu musika «Youtube Music»en"
#: contents/ui/SearchPage.qml:150
#, kde-format
msgid "Search"
msgstr "Bilatu"
#~ msgid "Play only Audio"
#~ msgstr "Audio bakarrik jo"
+157
View File
@@ -0,0 +1,157 @@
# Copyright (C) YEAR This file is copyright:
# This file is distributed under the same license as the audiotube package.
# Tommi Nieminen <[email protected]>, 2021, 2022.
#
msgid ""
msgstr ""
"Project-Id-Version: audiotube\n"
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
"POT-Creation-Date: 2021-12-05 00:45+0000\n"
"PO-Revision-Date: 2022-01-11 22:47+0200\n"
"Last-Translator: Tommi Nieminen <[email protected]>\n"
"Language-Team: Finnish <[email protected]>\n"
"Language: fi\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Lokalize 20.04.2\n"
#, kde-format
msgctxt "NAME OF TRANSLATORS"
msgid "Your names"
msgstr "Tommi Nieminen"
#, kde-format
msgctxt "EMAIL OF TRANSLATORS"
msgid "Your emails"
msgstr "[email protected]"
#: contents/ui/AlbumPage.qml:19 contents/ui/PlayerFooter.qml:173
#: contents/ui/PlaylistPage.qml:19
#, kde-format
msgid "Play"
msgstr "Toista"
#: contents/ui/AlbumPage.qml:61 contents/ui/ArtistPage.qml:101
#: contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Toista seuraava"
#: contents/ui/AlbumPage.qml:66 contents/ui/ArtistPage.qml:107
#: contents/ui/PlaylistPage.qml:72 contents/ui/SearchPage.qml:123
#, kde-format
msgid "Add to Playlist"
msgstr "Lisää soittolistaan"
#: contents/ui/ArtistPage.qml:39 contents/ui/SearchPage.qml:67
#, kde-format
msgid ""
"Video playback is not supported yet. Do you want to play only the audio of "
"\"%1\"?"
msgstr ""
"Videotoistoa ei vielä tueta. Haluatko toistaa videosta ”%1” vain äänen?"
#: contents/ui/ArtistPage.qml:55 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Albumit"
#: contents/ui/ArtistPage.qml:57
#, kde-format
msgid "Singles"
msgstr "Singlet"
#: contents/ui/ArtistPage.qml:59 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Kappaleet"
#: contents/ui/ArtistPage.qml:61 contents/ui/SearchPage.qml:33
#, kde-format
msgid "Videos"
msgstr "Videot"
#: contents/ui/ConfirmationMessage.qml:16
#, kde-format
msgid "OK"
msgstr "OK"
#: contents/ui/ConfirmationMessage.qml:21
#, kde-format
msgid "Cancel"
msgstr "Peru"
#: contents/ui/main.qml:51
#, kde-format
msgid "AudioTube"
msgstr "AudioTube"
#: contents/ui/PlayerFooter.qml:106
#, kde-format
msgid "Remove Track"
msgstr "Poista raita"
#: contents/ui/PlayerFooter.qml:123
#, kde-format
msgid "Clear"
msgstr "Tyhjennä"
#: contents/ui/PlayerFooter.qml:128 contents/ui/PlaylistPage.qml:26
#, kde-format
msgid "Shuffle"
msgstr "Sekoita"
#: contents/ui/PlayerFooter.qml:173
#, kde-format
msgid "Pause"
msgstr "Tauko"
#: contents/ui/PlayerFooter.qml:203
#, kde-format
msgid "No media playing"
msgstr "Mitään ei toisteta"
#: contents/ui/PlayerFooter.qml:247
#, kde-format
msgid "Expand"
msgstr "Laajenna"
#: contents/ui/SearchPage.qml:27
#, kde-format
msgid "Artists"
msgstr "Artistit"
#: contents/ui/SearchPage.qml:29
#, kde-format
msgid "Playlists"
msgstr "Soittolistat"
#: contents/ui/SearchPage.qml:36
#, kde-format
msgid "Unknown"
msgstr "Tuntematon"
#: contents/ui/SearchPage.qml:117
#, kde-format
msgid "Play next"
msgstr "Toista seuraava"
#: contents/ui/SearchPage.qml:129
#, kde-format
msgid "Radio"
msgstr "Radio"
#: contents/ui/SearchPage.qml:147
#, kde-format
msgid "Find music on YouTube Music"
msgstr "Etsi musiikkia Youtube Musicista"
#: contents/ui/SearchPage.qml:150
#, kde-format
msgid "Search"
msgstr "Etsi"
#~ msgid "Play only Audio"
#~ msgstr "Toista vain ääni"
+155
View File
@@ -0,0 +1,155 @@
# Xavier Besnard <[email protected]>, 2021.
msgid ""
msgstr ""
"Project-Id-Version: audiotube\n"
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
"POT-Creation-Date: 2021-12-05 00:45+0000\n"
"PO-Revision-Date: 2021-12-06 10:18+0100\n"
"Last-Translator: Xavier Besnard <[email protected]>\n"
"Language-Team: French <[email protected]>\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Generator: Lokalize 21.11.90\n"
#, kde-format
msgctxt "NAME OF TRANSLATORS"
msgid "Your names"
msgstr "Xavier Besnard"
#, kde-format
msgctxt "EMAIL OF TRANSLATORS"
msgid "Your emails"
msgstr "[email protected]"
#: contents/ui/AlbumPage.qml:19 contents/ui/PlayerFooter.qml:173
#: contents/ui/PlaylistPage.qml:19
#, kde-format
msgid "Play"
msgstr "Lire"
#: contents/ui/AlbumPage.qml:61 contents/ui/ArtistPage.qml:101
#: contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Lire le suivant"
#: contents/ui/AlbumPage.qml:66 contents/ui/ArtistPage.qml:107
#: contents/ui/PlaylistPage.qml:72 contents/ui/SearchPage.qml:123
#, kde-format
msgid "Add to Playlist"
msgstr "Ajouter à la liste de lecture"
#: contents/ui/ArtistPage.qml:39 contents/ui/SearchPage.qml:67
#, kde-format
msgid ""
"Video playback is not supported yet. Do you want to play only the audio of "
"\"%1\"?"
msgstr ""
"La lecture de vidéos n'est pas encore prise en charge. Voulez-vous vraiment "
"lire uniquement l'audio de « %1 » ?"
#: contents/ui/ArtistPage.qml:55 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Albums"
#: contents/ui/ArtistPage.qml:57
#, kde-format
msgid "Singles"
msgstr "Titres uniques"
#: contents/ui/ArtistPage.qml:59 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Chansons"
#: contents/ui/ArtistPage.qml:61 contents/ui/SearchPage.qml:33
#, kde-format
msgid "Videos"
msgstr "Vidéos"
#: contents/ui/ConfirmationMessage.qml:16
#, kde-format
msgid "OK"
msgstr "Ok"
#: contents/ui/ConfirmationMessage.qml:21
#, kde-format
msgid "Cancel"
msgstr "Annuler"
#: contents/ui/main.qml:51
#, kde-format
msgid "AudioTube"
msgstr "AudioTube"
#: contents/ui/PlayerFooter.qml:106
#, kde-format
msgid "Remove Track"
msgstr "Supprimer une piste"
#: contents/ui/PlayerFooter.qml:123
#, kde-format
msgid "Clear"
msgstr "Effacer"
#: contents/ui/PlayerFooter.qml:128 contents/ui/PlaylistPage.qml:26
#, kde-format
msgid "Shuffle"
msgstr "Mélanger"
#: contents/ui/PlayerFooter.qml:173
#, kde-format
msgid "Pause"
msgstr "Pause"
#: contents/ui/PlayerFooter.qml:203
#, kde-format
msgid "No media playing"
msgstr "Aucun média en lecture"
#: contents/ui/PlayerFooter.qml:247
#, kde-format
msgid "Expand"
msgstr "Développer"
#: contents/ui/SearchPage.qml:27
#, kde-format
msgid "Artists"
msgstr "Artistes"
#: contents/ui/SearchPage.qml:29
#, kde-format
msgid "Playlists"
msgstr "Listes de lecture"
#: contents/ui/SearchPage.qml:36
#, kde-format
msgid "Unknown"
msgstr "Inconnu"
#: contents/ui/SearchPage.qml:117
#, kde-format
msgid "Play next"
msgstr "Lire le suivant"
#: contents/ui/SearchPage.qml:129
#, kde-format
msgid "Radio"
msgstr "Radio"
#: contents/ui/SearchPage.qml:147
#, kde-format
msgid "Find music on YouTube Music"
msgstr "Rechercher de la musique sur « YouTube Music »"
#: contents/ui/SearchPage.qml:150
#, kde-format
msgid "Search"
msgstr "Rechercher"
#~ msgid "Play only Audio"
#~ msgstr "Ne lire que l'audio"
+157
View File
@@ -0,0 +1,157 @@
# Copyright (C) YEAR This file is copyright:
# This file is distributed under the same license as the audiotube package.
#
# Raghavendra Kamath <[email protected]>, 2021.
msgid ""
msgstr ""
"Project-Id-Version: audiotube\n"
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
"POT-Creation-Date: 2021-12-05 00:45+0000\n"
"PO-Revision-Date: 2021-09-17 20:09+0530\n"
"Last-Translator: Raghavendra Kamath <[email protected]>\n"
"Language-Team: kde-hindi\n"
"Language: hi\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n!=1);\n"
"X-Generator: Lokalize 21.08.1\n"
#, kde-format
msgctxt "NAME OF TRANSLATORS"
msgid "Your names"
msgstr "राघवेंद्र कामत"
#, kde-format
msgctxt "EMAIL OF TRANSLATORS"
msgid "Your emails"
msgstr "[email protected]"
#: contents/ui/AlbumPage.qml:19 contents/ui/PlayerFooter.qml:173
#: contents/ui/PlaylistPage.qml:19
#, kde-format
msgid "Play"
msgstr "बजाएँ"
#: contents/ui/AlbumPage.qml:61 contents/ui/ArtistPage.qml:101
#: contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "अगला बजाएँ"
#: contents/ui/AlbumPage.qml:66 contents/ui/ArtistPage.qml:107
#: contents/ui/PlaylistPage.qml:72 contents/ui/SearchPage.qml:123
#, kde-format
msgid "Add to Playlist"
msgstr "गीत-सूची में जोड़ें"
#: contents/ui/ArtistPage.qml:39 contents/ui/SearchPage.qml:67
#, kde-format
msgid ""
"Video playback is not supported yet. Do you want to play only the audio of "
"\"%1\"?"
msgstr ""
"वीडियो चलाना अभी तक समर्थित नहीं है। क्या आप केवल \"%1\" का ध्वनी चलाना चाहते हैं?"
#: contents/ui/ArtistPage.qml:55 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "एल्बम्स"
#: contents/ui/ArtistPage.qml:57
#, kde-format
msgid "Singles"
msgstr "एक गीत"
#: contents/ui/ArtistPage.qml:59 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "गीत"
#: contents/ui/ArtistPage.qml:61 contents/ui/SearchPage.qml:33
#, kde-format
msgid "Videos"
msgstr "वीडियो"
#: contents/ui/ConfirmationMessage.qml:16
#, kde-format
msgid "OK"
msgstr ""
#: contents/ui/ConfirmationMessage.qml:21
#, kde-format
msgid "Cancel"
msgstr ""
#: contents/ui/main.qml:51
#, kde-format
msgid "AudioTube"
msgstr "ओडियो-ट्यूब"
#: contents/ui/PlayerFooter.qml:106
#, kde-format
msgid "Remove Track"
msgstr "गाना हटाएँ"
#: contents/ui/PlayerFooter.qml:123
#, kde-format
msgid "Clear"
msgstr "साफ करें"
#: contents/ui/PlayerFooter.qml:128 contents/ui/PlaylistPage.qml:26
#, kde-format
msgid "Shuffle"
msgstr "फेंटें"
#: contents/ui/PlayerFooter.qml:173
#, kde-format
msgid "Pause"
msgstr "रोकें"
#: contents/ui/PlayerFooter.qml:203
#, kde-format
msgid "No media playing"
msgstr "कोई मीडिया नहीं बजा रहा है"
#: contents/ui/PlayerFooter.qml:247
#, kde-format
msgid "Expand"
msgstr "विस्तृत करें"
#: contents/ui/SearchPage.qml:27
#, kde-format
msgid "Artists"
msgstr "कलाकार"
#: contents/ui/SearchPage.qml:29
#, kde-format
msgid "Playlists"
msgstr "गीत-सूचीयाँ"
#: contents/ui/SearchPage.qml:36
#, kde-format
msgid "Unknown"
msgstr "अज्ञात"
#: contents/ui/SearchPage.qml:117
#, kde-format
msgid "Play next"
msgstr "अगला बजाएँ"
#: contents/ui/SearchPage.qml:129
#, kde-format
msgid "Radio"
msgstr "रेडियो"
#: contents/ui/SearchPage.qml:147
#, kde-format
msgid "Find music on YouTube Music"
msgstr "यूट्यूब संगीत पर संगीत ढूंढें"
#: contents/ui/SearchPage.qml:150
#, kde-format
msgid "Search"
msgstr "खोजें"
#~ msgid "Play only Audio"
#~ msgstr "केवल ध्वनी चलाएँ"
+158
View File
@@ -0,0 +1,158 @@
# Copyright (C) YEAR This file is copyright:
# This file is distributed under the same license as the audiotube package.
# Vincenzo Reale <[email protected]>, 2021.
#
msgid ""
msgstr ""
"Project-Id-Version: audiotube\n"
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
"POT-Creation-Date: 2021-12-05 00:45+0000\n"
"PO-Revision-Date: 2021-12-06 02:46+0100\n"
"Last-Translator: Vincenzo Reale <[email protected]>\n"
"Language-Team: Italian <[email protected]>\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Lokalize 21.08.3\n"
#, kde-format
msgctxt "NAME OF TRANSLATORS"
msgid "Your names"
msgstr "Vincenzo Reale"
#, kde-format
msgctxt "EMAIL OF TRANSLATORS"
msgid "Your emails"
msgstr "[email protected]"
#: contents/ui/AlbumPage.qml:19 contents/ui/PlayerFooter.qml:173
#: contents/ui/PlaylistPage.qml:19
#, kde-format
msgid "Play"
msgstr "Riproduci"
#: contents/ui/AlbumPage.qml:61 contents/ui/ArtistPage.qml:101
#: contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Riproduci successivo"
#: contents/ui/AlbumPage.qml:66 contents/ui/ArtistPage.qml:107
#: contents/ui/PlaylistPage.qml:72 contents/ui/SearchPage.qml:123
#, kde-format
msgid "Add to Playlist"
msgstr "Aggiungi alla scaletta"
#: contents/ui/ArtistPage.qml:39 contents/ui/SearchPage.qml:67
#, kde-format
msgid ""
"Video playback is not supported yet. Do you want to play only the audio of "
"\"%1\"?"
msgstr ""
"La riproduzione dei video non è ancora supportata. Vuoi riprodurre solo "
"l'audio di «%1»?"
#: contents/ui/ArtistPage.qml:55 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Album"
#: contents/ui/ArtistPage.qml:57
#, kde-format
msgid "Singles"
msgstr "Singoli"
#: contents/ui/ArtistPage.qml:59 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Brani"
#: contents/ui/ArtistPage.qml:61 contents/ui/SearchPage.qml:33
#, kde-format
msgid "Videos"
msgstr "Video"
#: contents/ui/ConfirmationMessage.qml:16
#, kde-format
msgid "OK"
msgstr "OK"
#: contents/ui/ConfirmationMessage.qml:21
#, kde-format
msgid "Cancel"
msgstr "Annulla"
#: contents/ui/main.qml:51
#, kde-format
msgid "AudioTube"
msgstr "AudioTube"
#: contents/ui/PlayerFooter.qml:106
#, kde-format
msgid "Remove Track"
msgstr "Rimuovi traccia"
#: contents/ui/PlayerFooter.qml:123
#, kde-format
msgid "Clear"
msgstr "Pulisci"
#: contents/ui/PlayerFooter.qml:128 contents/ui/PlaylistPage.qml:26
#, kde-format
msgid "Shuffle"
msgstr "Mescola"
#: contents/ui/PlayerFooter.qml:173
#, kde-format
msgid "Pause"
msgstr "Pausa"
#: contents/ui/PlayerFooter.qml:203
#, kde-format
msgid "No media playing"
msgstr "Nessun supporto in riproduzione"
#: contents/ui/PlayerFooter.qml:247
#, kde-format
msgid "Expand"
msgstr "Espandi"
#: contents/ui/SearchPage.qml:27
#, kde-format
msgid "Artists"
msgstr "Artisti"
#: contents/ui/SearchPage.qml:29
#, kde-format
msgid "Playlists"
msgstr "Scalette"
#: contents/ui/SearchPage.qml:36
#, kde-format
msgid "Unknown"
msgstr "Sconosciuto"
#: contents/ui/SearchPage.qml:117
#, kde-format
msgid "Play next"
msgstr "Riproduci successivo"
#: contents/ui/SearchPage.qml:129
#, kde-format
msgid "Radio"
msgstr "Radio"
#: contents/ui/SearchPage.qml:147
#, kde-format
msgid "Find music on YouTube Music"
msgstr "Trova musica su Youtube Music"
#: contents/ui/SearchPage.qml:150
#, kde-format
msgid "Search"
msgstr "Cerca"
#~ msgid "Play only Audio"
#~ msgstr "Riproduci solo l'audio"
+156
View File
@@ -0,0 +1,156 @@
# Copyright (C) YEAR This file is copyright:
# This file is distributed under the same license as the audiotube package.
# Shinjo Park <[email protected]>, 2021.
#
msgid ""
msgstr ""
"Project-Id-Version: audiotube\n"
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
"POT-Creation-Date: 2021-12-05 00:45+0000\n"
"PO-Revision-Date: 2021-12-08 00:45+0100\n"
"Last-Translator: Shinjo Park <[email protected]>\n"
"Language-Team: Korean <[email protected]>\n"
"Language: ko\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Lokalize 21.08.1\n"
#, kde-format
msgctxt "NAME OF TRANSLATORS"
msgid "Your names"
msgstr "박신조"
#, kde-format
msgctxt "EMAIL OF TRANSLATORS"
msgid "Your emails"
msgstr "[email protected]"
#: contents/ui/AlbumPage.qml:19 contents/ui/PlayerFooter.qml:173
#: contents/ui/PlaylistPage.qml:19
#, kde-format
msgid "Play"
msgstr "재생"
#: contents/ui/AlbumPage.qml:61 contents/ui/ArtistPage.qml:101
#: contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "다음 재생"
#: contents/ui/AlbumPage.qml:66 contents/ui/ArtistPage.qml:107
#: contents/ui/PlaylistPage.qml:72 contents/ui/SearchPage.qml:123
#, kde-format
msgid "Add to Playlist"
msgstr "재생 목록에 추가"
#: contents/ui/ArtistPage.qml:39 contents/ui/SearchPage.qml:67
#, kde-format
msgid ""
"Video playback is not supported yet. Do you want to play only the audio of "
"\"%1\"?"
msgstr "비디오 재생은 지원하지 않습니다. \"%1\"의 오디오만 재생하시겠습니까?"
#: contents/ui/ArtistPage.qml:55 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "앨범"
#: contents/ui/ArtistPage.qml:57
#, kde-format
msgid "Singles"
msgstr "싱글"
#: contents/ui/ArtistPage.qml:59 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "곡"
#: contents/ui/ArtistPage.qml:61 contents/ui/SearchPage.qml:33
#, kde-format
msgid "Videos"
msgstr "비디오"
#: contents/ui/ConfirmationMessage.qml:16
#, kde-format
msgid "OK"
msgstr "확인"
#: contents/ui/ConfirmationMessage.qml:21
#, kde-format
msgid "Cancel"
msgstr "취소"
#: contents/ui/main.qml:51
#, kde-format
msgid "AudioTube"
msgstr "AudioTube"
#: contents/ui/PlayerFooter.qml:106
#, kde-format
msgid "Remove Track"
msgstr "트랙 삭제"
#: contents/ui/PlayerFooter.qml:123
#, kde-format
msgid "Clear"
msgstr "지우기"
#: contents/ui/PlayerFooter.qml:128 contents/ui/PlaylistPage.qml:26
#, kde-format
msgid "Shuffle"
msgstr "무순서 재생"
#: contents/ui/PlayerFooter.qml:173
#, kde-format
msgid "Pause"
msgstr "일시 정지"
#: contents/ui/PlayerFooter.qml:203
#, kde-format
msgid "No media playing"
msgstr "재생 중인 미디어 없음"
#: contents/ui/PlayerFooter.qml:247
#, kde-format
msgid "Expand"
msgstr "확장"
#: contents/ui/SearchPage.qml:27
#, kde-format
msgid "Artists"
msgstr "아티스트"
#: contents/ui/SearchPage.qml:29
#, kde-format
msgid "Playlists"
msgstr "재생 목록"
#: contents/ui/SearchPage.qml:36
#, kde-format
msgid "Unknown"
msgstr "알 수 없음"
#: contents/ui/SearchPage.qml:117
#, kde-format
msgid "Play next"
msgstr "다음 재생"
#: contents/ui/SearchPage.qml:129
#, kde-format
msgid "Radio"
msgstr "라디오"
#: contents/ui/SearchPage.qml:147
#, kde-format
msgid "Find music on YouTube Music"
msgstr "YouTube Music에서 음악 찾기"
#: contents/ui/SearchPage.qml:150
#, kde-format
msgid "Search"
msgstr "검색"
#~ msgid "Play only Audio"
#~ msgstr "오디오만 재생"
+158
View File
@@ -0,0 +1,158 @@
# Copyright (C) YEAR This file is copyright:
# This file is distributed under the same license as the audiotube package.
#
# Freek de Kruijf <[email protected]>, 2021.
msgid ""
msgstr ""
"Project-Id-Version: audiotube\n"
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
"POT-Creation-Date: 2021-12-05 00:45+0000\n"
"PO-Revision-Date: 2021-12-05 16:48+0100\n"
"Last-Translator: Freek de Kruijf <[email protected]>\n"
"Language-Team: \n"
"Language: nl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Lokalize 21.08.3\n"
#, kde-format
msgctxt "NAME OF TRANSLATORS"
msgid "Your names"
msgstr "Freek de Kruijf - 2021"
#, kde-format
msgctxt "EMAIL OF TRANSLATORS"
msgid "Your emails"
msgstr "[email protected]"
#: contents/ui/AlbumPage.qml:19 contents/ui/PlayerFooter.qml:173
#: contents/ui/PlaylistPage.qml:19
#, kde-format
msgid "Play"
msgstr "Afspelen"
#: contents/ui/AlbumPage.qml:61 contents/ui/ArtistPage.qml:101
#: contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Volgende afspelen"
#: contents/ui/AlbumPage.qml:66 contents/ui/ArtistPage.qml:107
#: contents/ui/PlaylistPage.qml:72 contents/ui/SearchPage.qml:123
#, kde-format
msgid "Add to Playlist"
msgstr "Aan afspeellijst toevoegen"
#: contents/ui/ArtistPage.qml:39 contents/ui/SearchPage.qml:67
#, kde-format
msgid ""
"Video playback is not supported yet. Do you want to play only the audio of "
"\"%1\"?"
msgstr ""
"Afspelen van video wordt nog niet ondersteund. Wilt u alleen het geluid van "
"\"%1\" afspelen?"
#: contents/ui/ArtistPage.qml:55 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Albums"
#: contents/ui/ArtistPage.qml:57
#, kde-format
msgid "Singles"
msgstr "Enkelvoudige"
#: contents/ui/ArtistPage.qml:59 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Songs"
#: contents/ui/ArtistPage.qml:61 contents/ui/SearchPage.qml:33
#, kde-format
msgid "Videos"
msgstr "Video's"
#: contents/ui/ConfirmationMessage.qml:16
#, kde-format
msgid "OK"
msgstr "OK"
#: contents/ui/ConfirmationMessage.qml:21
#, kde-format
msgid "Cancel"
msgstr "Annuleren"
#: contents/ui/main.qml:51
#, kde-format
msgid "AudioTube"
msgstr "AudioTube"
#: contents/ui/PlayerFooter.qml:106
#, kde-format
msgid "Remove Track"
msgstr "Track verwijderen"
#: contents/ui/PlayerFooter.qml:123
#, kde-format
msgid "Clear"
msgstr "Wissen"
#: contents/ui/PlayerFooter.qml:128 contents/ui/PlaylistPage.qml:26
#, kde-format
msgid "Shuffle"
msgstr "Willekeurig afspelen"
#: contents/ui/PlayerFooter.qml:173
#, kde-format
msgid "Pause"
msgstr "Pauzeren"
#: contents/ui/PlayerFooter.qml:203
#, kde-format
msgid "No media playing"
msgstr "Er speelt geen medium"
#: contents/ui/PlayerFooter.qml:247
#, kde-format
msgid "Expand"
msgstr "Uitbreiden"
#: contents/ui/SearchPage.qml:27
#, kde-format
msgid "Artists"
msgstr "Artiesten"
#: contents/ui/SearchPage.qml:29
#, kde-format
msgid "Playlists"
msgstr "Afspeellijsten"
#: contents/ui/SearchPage.qml:36
#, kde-format
msgid "Unknown"
msgstr "Onbekend"
#: contents/ui/SearchPage.qml:117
#, kde-format
msgid "Play next"
msgstr "Volgende afspelen"
#: contents/ui/SearchPage.qml:129
#, kde-format
msgid "Radio"
msgstr "Radio"
#: contents/ui/SearchPage.qml:147
#, kde-format
msgid "Find music on YouTube Music"
msgstr "Muziek zoeken op YouTube Music"
#: contents/ui/SearchPage.qml:150
#, kde-format
msgid "Search"
msgstr "Zoeken"
#~ msgid "Play only Audio"
#~ msgstr "Alleen geluid afspelen"
+156
View File
@@ -0,0 +1,156 @@
# Copyright (C) YEAR This file is copyright:
# This file is distributed under the same license as the audiotube package.
#
# A S Alam <[email protected]>, 2021, 2022.
msgid ""
msgstr ""
"Project-Id-Version: audiotube\n"
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
"POT-Creation-Date: 2021-12-05 00:45+0000\n"
"PO-Revision-Date: 2022-01-01 09:11-0800\n"
"Last-Translator: A S Alam <[email protected]>\n"
"Language-Team: Punjabi <[email protected]>\n"
"Language: pa\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Lokalize 21.04.3\n"
#, kde-format
msgctxt "NAME OF TRANSLATORS"
msgid "Your names"
msgstr "ਅਮਨਪਰੀਤ ਸਿੰਘ ਆਲਮ ੨੦੨੧"
#, kde-format
msgctxt "EMAIL OF TRANSLATORS"
msgid "Your emails"
msgstr "alam yellow @ gmail.com"
#: contents/ui/AlbumPage.qml:19 contents/ui/PlayerFooter.qml:173
#: contents/ui/PlaylistPage.qml:19
#, kde-format
msgid "Play"
msgstr "ਚਲਾਓ"
#: contents/ui/AlbumPage.qml:61 contents/ui/ArtistPage.qml:101
#: contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "ਅਗਲਾ ਚਲਾਓ"
#: contents/ui/AlbumPage.qml:66 contents/ui/ArtistPage.qml:107
#: contents/ui/PlaylistPage.qml:72 contents/ui/SearchPage.qml:123
#, kde-format
msgid "Add to Playlist"
msgstr "ਪਲੇਅਲਿਸਟ ਵਿੱਚ ਜੋੜੋ"
#: contents/ui/ArtistPage.qml:39 contents/ui/SearchPage.qml:67
#, kde-format
msgid ""
"Video playback is not supported yet. Do you want to play only the audio of "
"\"%1\"?"
msgstr "ਵਿਡੀਓ ਪਲੇਅਬੈਕ ਹਾਲੇ ਸਹਾਇਕ ਨਹੀਂ ਹੈ। ਕੀ ਤੁਸੀਂ \"%1\" ਦਾ ਆਡੀਓ ਹੀ ਚਲਾਉਣਾ ਚਾਹੋਗੇ?"
#: contents/ui/ArtistPage.qml:55 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "ਐਲਬਮਾਂ"
#: contents/ui/ArtistPage.qml:57
#, kde-format
msgid "Singles"
msgstr "ਇਕੱਲੇ"
#: contents/ui/ArtistPage.qml:59 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "ਗੀਤ"
#: contents/ui/ArtistPage.qml:61 contents/ui/SearchPage.qml:33
#, kde-format
msgid "Videos"
msgstr "ਵਿਡੀਓ"
#: contents/ui/ConfirmationMessage.qml:16
#, kde-format
msgid "OK"
msgstr "ਠੀਕ ਹੈ"
#: contents/ui/ConfirmationMessage.qml:21
#, kde-format
msgid "Cancel"
msgstr "ਰੱਦ ਕਰੋ"
#: contents/ui/main.qml:51
#, kde-format
msgid "AudioTube"
msgstr "ਆਡੀਓ-ਟਿਊਬ"
#: contents/ui/PlayerFooter.qml:106
#, kde-format
msgid "Remove Track"
msgstr "ਟਰੈਕ ਨੂੰ ਹਟਾਓ"
#: contents/ui/PlayerFooter.qml:123
#, kde-format
msgid "Clear"
msgstr "ਮਿਟਾਓ"
#: contents/ui/PlayerFooter.qml:128 contents/ui/PlaylistPage.qml:26
#, kde-format
msgid "Shuffle"
msgstr "ਰਲਵੇਂ"
#: contents/ui/PlayerFooter.qml:173
#, kde-format
msgid "Pause"
msgstr "ਵਿਰਾਮ"
#: contents/ui/PlayerFooter.qml:203
#, kde-format
msgid "No media playing"
msgstr "ਕੋਈ ਮੀਡੀਆ ਨਹੀਂ ਚੱਲਦਾ"
#: contents/ui/PlayerFooter.qml:247
#, kde-format
msgid "Expand"
msgstr "ਫੈਲਾਓ"
#: contents/ui/SearchPage.qml:27
#, kde-format
msgid "Artists"
msgstr "ਕਲਾਕਾਰ"
#: contents/ui/SearchPage.qml:29
#, kde-format
msgid "Playlists"
msgstr "ਪਲੇਅਲਿਸਟਾਂ"
#: contents/ui/SearchPage.qml:36
#, kde-format
msgid "Unknown"
msgstr "ਅਣਪਛਾਤਾ"
#: contents/ui/SearchPage.qml:117
#, kde-format
msgid "Play next"
msgstr "ਅਗਲਾ ਚਲਾਓ"
#: contents/ui/SearchPage.qml:129
#, kde-format
msgid "Radio"
msgstr "ਰੇਡੀਓ"
#: contents/ui/SearchPage.qml:147
#, kde-format
msgid "Find music on YouTube Music"
msgstr "ਯੂਟਿਊਬ ਸੰਗੀਤ ਉੱਤੇ ਸੰਗੀਤ ਲੱਭੋ"
#: contents/ui/SearchPage.qml:150
#, kde-format
msgid "Search"
msgstr "ਖੋਜੋ"
#~ msgid "Play only Audio"
#~ msgstr "ਸਿਰਫ਼ ਆਡੀਓ ਹੀ ਚਲਾਓ"
+160
View File
@@ -0,0 +1,160 @@
# Copyright (C) YEAR This file is copyright:
# This file is distributed under the same license as the audiotube package.
#
# Łukasz Wojniłowicz <[email protected]>, 2021.
# Ignacy Kajdan <[email protected]>, 2021.
msgid ""
msgstr ""
"Project-Id-Version: audiotube\n"
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
"POT-Creation-Date: 2021-12-05 00:45+0000\n"
"PO-Revision-Date: 2021-12-30 12:38+0100\n"
"Last-Translator: Łukasz Wojniłowicz <[email protected]>\n"
"Language-Team: Polish <[email protected]>\n"
"Language: pl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
"|| n%100>=20) ? 1 : 2);\n"
"X-Generator: Lokalize 22.03.70\n"
#, kde-format
msgctxt "NAME OF TRANSLATORS"
msgid "Your names"
msgstr "Łukasz Wojniłowicz, Ignacy Kajdan"
#, kde-format
msgctxt "EMAIL OF TRANSLATORS"
msgid "Your emails"
msgstr "[email protected], [email protected]"
#: contents/ui/AlbumPage.qml:19 contents/ui/PlayerFooter.qml:173
#: contents/ui/PlaylistPage.qml:19
#, kde-format
msgid "Play"
msgstr "Odtwórz"
#: contents/ui/AlbumPage.qml:61 contents/ui/ArtistPage.qml:101
#: contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Odtwórz następny"
#: contents/ui/AlbumPage.qml:66 contents/ui/ArtistPage.qml:107
#: contents/ui/PlaylistPage.qml:72 contents/ui/SearchPage.qml:123
#, kde-format
msgid "Add to Playlist"
msgstr "Dodaj do listy odtwarzania"
#: contents/ui/ArtistPage.qml:39 contents/ui/SearchPage.qml:67
#, kde-format
msgid ""
"Video playback is not supported yet. Do you want to play only the audio of "
"\"%1\"?"
msgstr ""
"Odtwarzanie wideo nie jest jeszcze obsługiwane. Czy odtworzyć tylko dźwięk z "
"„%1”?"
#: contents/ui/ArtistPage.qml:55 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Albumy"
#: contents/ui/ArtistPage.qml:57
#, kde-format
msgid "Singles"
msgstr "Single"
#: contents/ui/ArtistPage.qml:59 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Piosenki"
#: contents/ui/ArtistPage.qml:61 contents/ui/SearchPage.qml:33
#, kde-format
msgid "Videos"
msgstr "Filmy"
#: contents/ui/ConfirmationMessage.qml:16
#, kde-format
msgid "OK"
msgstr "OK"
#: contents/ui/ConfirmationMessage.qml:21
#, kde-format
msgid "Cancel"
msgstr "Anuluj"
#: contents/ui/main.qml:51
#, kde-format
msgid "AudioTube"
msgstr "AudioTube"
#: contents/ui/PlayerFooter.qml:106
#, kde-format
msgid "Remove Track"
msgstr "Usuń utwór"
#: contents/ui/PlayerFooter.qml:123
#, kde-format
msgid "Clear"
msgstr "Wyczyść"
#: contents/ui/PlayerFooter.qml:128 contents/ui/PlaylistPage.qml:26
#, kde-format
msgid "Shuffle"
msgstr "Wymieszaj"
#: contents/ui/PlayerFooter.qml:173
#, kde-format
msgid "Pause"
msgstr "Wstrzymaj"
#: contents/ui/PlayerFooter.qml:203
#, kde-format
msgid "No media playing"
msgstr "Nic nie jest odtwarzane"
#: contents/ui/PlayerFooter.qml:247
#, kde-format
msgid "Expand"
msgstr "Rozwiń"
#: contents/ui/SearchPage.qml:27
#, kde-format
msgid "Artists"
msgstr "Wykonawcy"
#: contents/ui/SearchPage.qml:29
#, kde-format
msgid "Playlists"
msgstr "Listy odtwarzania"
#: contents/ui/SearchPage.qml:36
#, kde-format
msgid "Unknown"
msgstr "Nieznany"
#: contents/ui/SearchPage.qml:117
#, kde-format
msgid "Play next"
msgstr "Odtwórz następny"
#: contents/ui/SearchPage.qml:129
#, kde-format
msgid "Radio"
msgstr "Radio"
#: contents/ui/SearchPage.qml:147
#, kde-format
msgid "Find music on YouTube Music"
msgstr "Znajdź muzykę w serwisie YouTube Music"
#: contents/ui/SearchPage.qml:150
#, kde-format
msgid "Search"
msgstr "Szukaj"
#~ msgid "Play only Audio"
#~ msgstr "Odtwórz tylko dźwięk"
+160
View File
@@ -0,0 +1,160 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR This file is copyright:
# This file is distributed under the same license as the audiotube package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: audiotube\n"
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
"POT-Creation-Date: 2021-12-05 00:45+0000\n"
"PO-Revision-Date: 2021-09-04 18:29+0100\n"
"Last-Translator: José Nuno Coelho Pires <[email protected]>\n"
"Language-Team: Portuguese <[email protected]>\n"
"Language: pt\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-POFile-SpellExtra: Singles AudioTube YouTube Music\n"
"X-POFile-IgnoreConsistency: Singles\n"
#, kde-format
msgctxt "NAME OF TRANSLATORS"
msgid "Your names"
msgstr "José Nuno Pires"
#, kde-format
msgctxt "EMAIL OF TRANSLATORS"
msgid "Your emails"
msgstr "[email protected]"
#: contents/ui/AlbumPage.qml:19 contents/ui/PlayerFooter.qml:173
#: contents/ui/PlaylistPage.qml:19
#, kde-format
msgid "Play"
msgstr "Reproduzir"
#: contents/ui/AlbumPage.qml:61 contents/ui/ArtistPage.qml:101
#: contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Reproduzir o Seguinte"
#: contents/ui/AlbumPage.qml:66 contents/ui/ArtistPage.qml:107
#: contents/ui/PlaylistPage.qml:72 contents/ui/SearchPage.qml:123
#, kde-format
msgid "Add to Playlist"
msgstr "Adicionar à Lista de Reprodução"
#: contents/ui/ArtistPage.qml:39 contents/ui/SearchPage.qml:67
#, kde-format
msgid ""
"Video playback is not supported yet. Do you want to play only the audio of "
"\"%1\"?"
msgstr ""
"A reprodução de vídeos ainda não é suportada. Deseja reproduzir apenas o "
"áudio de \"%1\"?"
#: contents/ui/ArtistPage.qml:55 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Álbuns"
#: contents/ui/ArtistPage.qml:57
#, kde-format
msgid "Singles"
msgstr "'Singles'"
#: contents/ui/ArtistPage.qml:59 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Músicas"
#: contents/ui/ArtistPage.qml:61 contents/ui/SearchPage.qml:33
#, kde-format
msgid "Videos"
msgstr "Vídeos"
#: contents/ui/ConfirmationMessage.qml:16
#, kde-format
msgid "OK"
msgstr "OK"
#: contents/ui/ConfirmationMessage.qml:21
#, kde-format
msgid "Cancel"
msgstr "Cancelar"
#: contents/ui/main.qml:51
#, kde-format
msgid "AudioTube"
msgstr "AudioTube"
#: contents/ui/PlayerFooter.qml:106
#, kde-format
msgid "Remove Track"
msgstr "Remover a Faixa"
#: contents/ui/PlayerFooter.qml:123
#, kde-format
msgid "Clear"
msgstr "Limpar"
#: contents/ui/PlayerFooter.qml:128 contents/ui/PlaylistPage.qml:26
#, kde-format
msgid "Shuffle"
msgstr "Baralhar"
#: contents/ui/PlayerFooter.qml:173
#, kde-format
msgid "Pause"
msgstr "Pausa"
#: contents/ui/PlayerFooter.qml:203
#, kde-format
msgid "No media playing"
msgstr "Nada em reprodução"
#: contents/ui/PlayerFooter.qml:247
#, kde-format
msgid "Expand"
msgstr "Expandir"
#: contents/ui/SearchPage.qml:27
#, kde-format
msgid "Artists"
msgstr "Artistas"
#: contents/ui/SearchPage.qml:29
#, kde-format
msgid "Playlists"
msgstr "Listas de Reprodução"
#: contents/ui/SearchPage.qml:36
#, kde-format
msgid "Unknown"
msgstr "Desconhecido"
#: contents/ui/SearchPage.qml:117
#, kde-format
msgid "Play next"
msgstr "Reproduzir o seguinte"
#: contents/ui/SearchPage.qml:129
#, kde-format
msgid "Radio"
msgstr "Rádio"
#: contents/ui/SearchPage.qml:147
#, kde-format
msgid "Find music on YouTube Music"
msgstr "Procurar música no YouTube Music"
#: contents/ui/SearchPage.qml:150
#, kde-format
msgid "Search"
msgstr "Procurar"
#~ msgid "Play only Audio"
#~ msgstr "Reproduzir só o Áudio"
+159
View File
@@ -0,0 +1,159 @@
# Copyright (C) YEAR This file is copyright:
# This file is distributed under the same license as the audiotube package.
#
# Luiz Fernando Ranghetti <[email protected]>, 2021.
# Thiago Masato Costa Sueto <[email protected]>, 2021.
msgid ""
msgstr ""
"Project-Id-Version: audiotube\n"
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
"POT-Creation-Date: 2021-12-05 00:45+0000\n"
"PO-Revision-Date: 2021-12-09 10:13-0300\n"
"Last-Translator: Luiz Fernando Ranghetti <[email protected]>\n"
"Language-Team: Portuguese <[email protected]>\n"
"Language: pt_BR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Generator: Lokalize 20.04.2\n"
#, kde-format
msgctxt "NAME OF TRANSLATORS"
msgid "Your names"
msgstr "Luiz Fernando Ranghetti"
#, kde-format
msgctxt "EMAIL OF TRANSLATORS"
msgid "Your emails"
msgstr "[email protected]"
#: contents/ui/AlbumPage.qml:19 contents/ui/PlayerFooter.qml:173
#: contents/ui/PlaylistPage.qml:19
#, kde-format
msgid "Play"
msgstr "Reproduzir"
#: contents/ui/AlbumPage.qml:61 contents/ui/ArtistPage.qml:101
#: contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Reproduzir próxima"
#: contents/ui/AlbumPage.qml:66 contents/ui/ArtistPage.qml:107
#: contents/ui/PlaylistPage.qml:72 contents/ui/SearchPage.qml:123
#, kde-format
msgid "Add to Playlist"
msgstr "Adicionar à lista de reprodução"
#: contents/ui/ArtistPage.qml:39 contents/ui/SearchPage.qml:67
#, kde-format
msgid ""
"Video playback is not supported yet. Do you want to play only the audio of "
"\"%1\"?"
msgstr ""
"A reprodução de vídeos ainda não é suportada. Deseja reproduzir apenas o "
"áudio de \"%1\"?"
#: contents/ui/ArtistPage.qml:55 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Álbuns"
#: contents/ui/ArtistPage.qml:57
#, kde-format
msgid "Singles"
msgstr "Singles"
#: contents/ui/ArtistPage.qml:59 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Músicas"
#: contents/ui/ArtistPage.qml:61 contents/ui/SearchPage.qml:33
#, kde-format
msgid "Videos"
msgstr "Vídeos"
#: contents/ui/ConfirmationMessage.qml:16
#, kde-format
msgid "OK"
msgstr "OK"
#: contents/ui/ConfirmationMessage.qml:21
#, kde-format
msgid "Cancel"
msgstr "Cancelar"
#: contents/ui/main.qml:51
#, kde-format
msgid "AudioTube"
msgstr "AudioTube"
#: contents/ui/PlayerFooter.qml:106
#, kde-format
msgid "Remove Track"
msgstr "Remover faixa"
#: contents/ui/PlayerFooter.qml:123
#, kde-format
msgid "Clear"
msgstr "Limpar"
#: contents/ui/PlayerFooter.qml:128 contents/ui/PlaylistPage.qml:26
#, kde-format
msgid "Shuffle"
msgstr "Embaralhar"
#: contents/ui/PlayerFooter.qml:173
#, kde-format
msgid "Pause"
msgstr "Pausar"
#: contents/ui/PlayerFooter.qml:203
#, kde-format
msgid "No media playing"
msgstr "Nenhuma mídia em reprodução"
#: contents/ui/PlayerFooter.qml:247
#, kde-format
msgid "Expand"
msgstr "Expandir"
#: contents/ui/SearchPage.qml:27
#, kde-format
msgid "Artists"
msgstr "Artistas"
#: contents/ui/SearchPage.qml:29
#, kde-format
msgid "Playlists"
msgstr "Listas de músicas"
#: contents/ui/SearchPage.qml:36
#, kde-format
msgid "Unknown"
msgstr "Desconhecido"
#: contents/ui/SearchPage.qml:117
#, kde-format
msgid "Play next"
msgstr "Reproduzir próxima"
#: contents/ui/SearchPage.qml:129
#, kde-format
msgid "Radio"
msgstr "Rádio"
#: contents/ui/SearchPage.qml:147
#, kde-format
msgid "Find music on YouTube Music"
msgstr "Encontre músicas no YouTube Music"
#: contents/ui/SearchPage.qml:150
#, kde-format
msgid "Search"
msgstr "Pesquisar"
#~ msgid "Play only Audio"
#~ msgstr "Reproduzir apenas áudio"
+154
View File
@@ -0,0 +1,154 @@
# Copyright (C) YEAR This file is copyright:
# This file is distributed under the same license as the audiotube package.
#
# Alexander Yavorsky <[email protected]>, 2022.
msgid ""
msgstr ""
"Project-Id-Version: audiotube\n"
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
"POT-Creation-Date: 2021-12-05 00:45+0000\n"
"PO-Revision-Date: 2022-05-10 21:39+0300\n"
"Last-Translator: Alexander Yavorsky <[email protected]>\n"
"Language-Team: Russian <[email protected]>\n"
"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=4; plural=n==1 ? 3 : n%10==1 && n%100!=11 ? 0 : n"
"%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
"X-Generator: Lokalize 21.08.3\n"
#, kde-format
msgctxt "NAME OF TRANSLATORS"
msgid "Your names"
msgstr "Александр Яворский"
#, kde-format
msgctxt "EMAIL OF TRANSLATORS"
msgid "Your emails"
msgstr "[email protected]"
#: contents/ui/AlbumPage.qml:19 contents/ui/PlayerFooter.qml:173
#: contents/ui/PlaylistPage.qml:19
#, kde-format
msgid "Play"
msgstr "Воспроизвести"
#: contents/ui/AlbumPage.qml:61 contents/ui/ArtistPage.qml:101
#: contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Воспроизвести следующий"
#: contents/ui/AlbumPage.qml:66 contents/ui/ArtistPage.qml:107
#: contents/ui/PlaylistPage.qml:72 contents/ui/SearchPage.qml:123
#, kde-format
msgid "Add to Playlist"
msgstr ""
#: contents/ui/ArtistPage.qml:39 contents/ui/SearchPage.qml:67
#, kde-format
msgid ""
"Video playback is not supported yet. Do you want to play only the audio of "
"\"%1\"?"
msgstr ""
#: contents/ui/ArtistPage.qml:55 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Альбомы"
#: contents/ui/ArtistPage.qml:57
#, kde-format
msgid "Singles"
msgstr ""
#: contents/ui/ArtistPage.qml:59 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr ""
#: contents/ui/ArtistPage.qml:61 contents/ui/SearchPage.qml:33
#, kde-format
msgid "Videos"
msgstr "Видеозаписи"
#: contents/ui/ConfirmationMessage.qml:16
#, kde-format
msgid "OK"
msgstr "ОК"
#: contents/ui/ConfirmationMessage.qml:21
#, kde-format
msgid "Cancel"
msgstr "Отмена"
#: contents/ui/main.qml:51
#, kde-format
msgid "AudioTube"
msgstr "AudioTube"
#: contents/ui/PlayerFooter.qml:106
#, kde-format
msgid "Remove Track"
msgstr "Удалить дорожку"
#: contents/ui/PlayerFooter.qml:123
#, kde-format
msgid "Clear"
msgstr "Очистить"
#: contents/ui/PlayerFooter.qml:128 contents/ui/PlaylistPage.qml:26
#, kde-format
msgid "Shuffle"
msgstr "Перемешать"
#: contents/ui/PlayerFooter.qml:173
#, kde-format
msgid "Pause"
msgstr "Приостановить"
#: contents/ui/PlayerFooter.qml:203
#, kde-format
msgid "No media playing"
msgstr ""
#: contents/ui/PlayerFooter.qml:247
#, kde-format
msgid "Expand"
msgstr ""
#: contents/ui/SearchPage.qml:27
#, kde-format
msgid "Artists"
msgstr ""
#: contents/ui/SearchPage.qml:29
#, kde-format
msgid "Playlists"
msgstr ""
#: contents/ui/SearchPage.qml:36
#, kde-format
msgid "Unknown"
msgstr ""
#: contents/ui/SearchPage.qml:117
#, kde-format
msgid "Play next"
msgstr ""
#: contents/ui/SearchPage.qml:129
#, kde-format
msgid "Radio"
msgstr "Радио"
#: contents/ui/SearchPage.qml:147
#, kde-format
msgid "Find music on YouTube Music"
msgstr ""
#: contents/ui/SearchPage.qml:150
#, kde-format
msgid "Search"
msgstr "Поиск"
+152
View File
@@ -0,0 +1,152 @@
# translation of audiotube.po Slovak
# Roman Paholik <[email protected]>, 2021, 2022.
msgid ""
msgstr ""
"Project-Id-Version: audiotube\n"
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
"POT-Creation-Date: 2021-12-05 00:45+0000\n"
"PO-Revision-Date: 2022-04-07 18:01+0200\n"
"Last-Translator: Roman Paholik <[email protected]>\n"
"Language-Team: Slovak <[email protected]>\n"
"Language: sk\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Lokalize 21.12.3\n"
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
#, kde-format
msgctxt "NAME OF TRANSLATORS"
msgid "Your names"
msgstr "Roman Paholík"
#, kde-format
msgctxt "EMAIL OF TRANSLATORS"
msgid "Your emails"
msgstr "[email protected]"
#: contents/ui/AlbumPage.qml:19 contents/ui/PlayerFooter.qml:173
#: contents/ui/PlaylistPage.qml:19
#, kde-format
msgid "Play"
msgstr "Prehrať"
#: contents/ui/AlbumPage.qml:61 contents/ui/ArtistPage.qml:101
#: contents/ui/PlaylistPage.qml:67
#, fuzzy, kde-format
#| msgid "Play"
msgid "Play Next"
msgstr "Prehrať"
#: contents/ui/AlbumPage.qml:66 contents/ui/ArtistPage.qml:107
#: contents/ui/PlaylistPage.qml:72 contents/ui/SearchPage.qml:123
#, kde-format
msgid "Add to Playlist"
msgstr "Pridať do zoznamu skladieb"
#: contents/ui/ArtistPage.qml:39 contents/ui/SearchPage.qml:67
#, kde-format
msgid ""
"Video playback is not supported yet. Do you want to play only the audio of "
"\"%1\"?"
msgstr ""
#: contents/ui/ArtistPage.qml:55 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Albumy"
#: contents/ui/ArtistPage.qml:57
#, kde-format
msgid "Singles"
msgstr "Samotné"
#: contents/ui/ArtistPage.qml:59 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr ""
#: contents/ui/ArtistPage.qml:61 contents/ui/SearchPage.qml:33
#, kde-format
msgid "Videos"
msgstr "Videá"
#: contents/ui/ConfirmationMessage.qml:16
#, kde-format
msgid "OK"
msgstr "OK"
#: contents/ui/ConfirmationMessage.qml:21
#, kde-format
msgid "Cancel"
msgstr "Zrušiť"
#: contents/ui/main.qml:51
#, kde-format
msgid "AudioTube"
msgstr "AudioTube"
#: contents/ui/PlayerFooter.qml:106
#, kde-format
msgid "Remove Track"
msgstr ""
#: contents/ui/PlayerFooter.qml:123
#, kde-format
msgid "Clear"
msgstr "Vyčistiť"
#: contents/ui/PlayerFooter.qml:128 contents/ui/PlaylistPage.qml:26
#, kde-format
msgid "Shuffle"
msgstr "Zamiešať"
#: contents/ui/PlayerFooter.qml:173
#, kde-format
msgid "Pause"
msgstr "Pozastaviť"
#: contents/ui/PlayerFooter.qml:203
#, kde-format
msgid "No media playing"
msgstr "Neprehráva sa žiadne médium"
#: contents/ui/PlayerFooter.qml:247
#, kde-format
msgid "Expand"
msgstr "Rozvinúť"
#: contents/ui/SearchPage.qml:27
#, kde-format
msgid "Artists"
msgstr "Interpreti"
#: contents/ui/SearchPage.qml:29
#, kde-format
msgid "Playlists"
msgstr "Zoznamy skladieb"
#: contents/ui/SearchPage.qml:36
#, kde-format
msgid "Unknown"
msgstr "Neznáme"
#: contents/ui/SearchPage.qml:117
#, kde-format
msgid "Play next"
msgstr ""
#: contents/ui/SearchPage.qml:129
#, kde-format
msgid "Radio"
msgstr "Rádio"
#: contents/ui/SearchPage.qml:147
#, kde-format
msgid "Find music on YouTube Music"
msgstr ""
#: contents/ui/SearchPage.qml:150
#, kde-format
msgid "Search"
msgstr "Hľadať"
+157
View File
@@ -0,0 +1,157 @@
# Copyright (C) YEAR This file is copyright:
# This file is distributed under the same license as the audiotube package.
#
# Matjaž Jeran <[email protected]>, 2021.
msgid ""
msgstr ""
"Project-Id-Version: audiotube\n"
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
"POT-Creation-Date: 2021-12-05 00:45+0000\n"
"PO-Revision-Date: 2021-12-09 13:26+0100\n"
"Last-Translator: Matjaž Jeran <[email protected]>\n"
"Language-Team: Slovenian <[email protected]>\n"
"Language: sl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Lokalize 21.04.3\n"
"Plural-Forms: nplurals=4; plural=(n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || n"
"%100==4 ? 3 : 0);\n"
#, kde-format
msgctxt "NAME OF TRANSLATORS"
msgid "Your names"
msgstr "Matjaž Jeran"
#, kde-format
msgctxt "EMAIL OF TRANSLATORS"
msgid "Your emails"
msgstr "[email protected]"
#: contents/ui/AlbumPage.qml:19 contents/ui/PlayerFooter.qml:173
#: contents/ui/PlaylistPage.qml:19
#, kde-format
msgid "Play"
msgstr "Predvajaj"
#: contents/ui/AlbumPage.qml:61 contents/ui/ArtistPage.qml:101
#: contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Predvajaj naslednjo"
#: contents/ui/AlbumPage.qml:66 contents/ui/ArtistPage.qml:107
#: contents/ui/PlaylistPage.qml:72 contents/ui/SearchPage.qml:123
#, kde-format
msgid "Add to Playlist"
msgstr "Dodaj v seznam predvajanja"
#: contents/ui/ArtistPage.qml:39 contents/ui/SearchPage.qml:67
#, kde-format
msgid ""
"Video playback is not supported yet. Do you want to play only the audio of "
"\"%1\"?"
msgstr "Video playback še ni podprt. Ali želite predvajati samo zvok \"%1\"?"
#: contents/ui/ArtistPage.qml:55 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Albumi"
#: contents/ui/ArtistPage.qml:57
#, kde-format
msgid "Singles"
msgstr "Singli"
#: contents/ui/ArtistPage.qml:59 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Glasba"
#: contents/ui/ArtistPage.qml:61 contents/ui/SearchPage.qml:33
#, kde-format
msgid "Videos"
msgstr "Videposnetki"
#: contents/ui/ConfirmationMessage.qml:16
#, kde-format
msgid "OK"
msgstr "V redu"
#: contents/ui/ConfirmationMessage.qml:21
#, kde-format
msgid "Cancel"
msgstr "Prekliči"
#: contents/ui/main.qml:51
#, kde-format
msgid "AudioTube"
msgstr "AudioTube"
#: contents/ui/PlayerFooter.qml:106
#, kde-format
msgid "Remove Track"
msgstr "Odstrani sled"
#: contents/ui/PlayerFooter.qml:123
#, kde-format
msgid "Clear"
msgstr "Očisti"
#: contents/ui/PlayerFooter.qml:128 contents/ui/PlaylistPage.qml:26
#, kde-format
msgid "Shuffle"
msgstr "Premešaj"
#: contents/ui/PlayerFooter.qml:173
#, kde-format
msgid "Pause"
msgstr "Premor"
#: contents/ui/PlayerFooter.qml:203
#, kde-format
msgid "No media playing"
msgstr "Niče se ne predvaja"
#: contents/ui/PlayerFooter.qml:247
#, kde-format
msgid "Expand"
msgstr "Razširi"
#: contents/ui/SearchPage.qml:27
#, kde-format
msgid "Artists"
msgstr "Avtorji"
#: contents/ui/SearchPage.qml:29
#, kde-format
msgid "Playlists"
msgstr "Seznami predvajanja"
#: contents/ui/SearchPage.qml:36
#, kde-format
msgid "Unknown"
msgstr "Neznano"
#: contents/ui/SearchPage.qml:117
#, kde-format
msgid "Play next"
msgstr "Predvajaj naslednjo"
#: contents/ui/SearchPage.qml:129
#, kde-format
msgid "Radio"
msgstr "Radio"
#: contents/ui/SearchPage.qml:147
#, kde-format
msgid "Find music on YouTube Music"
msgstr "Najdi glasbo na Youtube Music"
#: contents/ui/SearchPage.qml:150
#, kde-format
msgid "Search"
msgstr "Išči"
#~ msgid "Play only Audio"
#~ msgstr "Predvajaj samo zvok"
+156
View File
@@ -0,0 +1,156 @@
# Copyright (C) YEAR This file is copyright:
# This file is distributed under the same license as the audiotube package.
#
# Stefan Asserhäll <[email protected]>, 2021.
msgid ""
msgstr ""
"Project-Id-Version: audiotube\n"
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
"POT-Creation-Date: 2021-12-05 00:45+0000\n"
"PO-Revision-Date: 2021-12-05 11:41+0100\n"
"Last-Translator: Stefan Asserhäll <[email protected]>\n"
"Language-Team: Swedish <[email protected]>\n"
"Language: sv\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Lokalize 20.08.1\n"
#, kde-format
msgctxt "NAME OF TRANSLATORS"
msgid "Your names"
msgstr "Stefan Asserhäll"
#, kde-format
msgctxt "EMAIL OF TRANSLATORS"
msgid "Your emails"
msgstr "[email protected]"
#: contents/ui/AlbumPage.qml:19 contents/ui/PlayerFooter.qml:173
#: contents/ui/PlaylistPage.qml:19
#, kde-format
msgid "Play"
msgstr "Spela"
#: contents/ui/AlbumPage.qml:61 contents/ui/ArtistPage.qml:101
#: contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Spela nästa"
#: contents/ui/AlbumPage.qml:66 contents/ui/ArtistPage.qml:107
#: contents/ui/PlaylistPage.qml:72 contents/ui/SearchPage.qml:123
#, kde-format
msgid "Add to Playlist"
msgstr "Lägg till i spellista"
#: contents/ui/ArtistPage.qml:39 contents/ui/SearchPage.qml:67
#, kde-format
msgid ""
"Video playback is not supported yet. Do you want to play only the audio of "
"\"%1\"?"
msgstr "Videouppspelning stöds inte ännu. Vill du bara spela ljudet på \"%1\"?"
#: contents/ui/ArtistPage.qml:55 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Album"
#: contents/ui/ArtistPage.qml:57
#, kde-format
msgid "Singles"
msgstr "Singlar"
#: contents/ui/ArtistPage.qml:59 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Låtar"
#: contents/ui/ArtistPage.qml:61 contents/ui/SearchPage.qml:33
#, kde-format
msgid "Videos"
msgstr "Videor"
#: contents/ui/ConfirmationMessage.qml:16
#, kde-format
msgid "OK"
msgstr "Ok"
#: contents/ui/ConfirmationMessage.qml:21
#, kde-format
msgid "Cancel"
msgstr "Avbryt"
#: contents/ui/main.qml:51
#, kde-format
msgid "AudioTube"
msgstr "AudioTube"
#: contents/ui/PlayerFooter.qml:106
#, kde-format
msgid "Remove Track"
msgstr "Ta bort spår"
#: contents/ui/PlayerFooter.qml:123
#, kde-format
msgid "Clear"
msgstr "Rensa"
#: contents/ui/PlayerFooter.qml:128 contents/ui/PlaylistPage.qml:26
#, kde-format
msgid "Shuffle"
msgstr "Blanda"
#: contents/ui/PlayerFooter.qml:173
#, kde-format
msgid "Pause"
msgstr "Paus"
#: contents/ui/PlayerFooter.qml:203
#, kde-format
msgid "No media playing"
msgstr "Inga media spelar"
#: contents/ui/PlayerFooter.qml:247
#, kde-format
msgid "Expand"
msgstr "Expandera"
#: contents/ui/SearchPage.qml:27
#, kde-format
msgid "Artists"
msgstr "Artister"
#: contents/ui/SearchPage.qml:29
#, kde-format
msgid "Playlists"
msgstr "Spellistor"
#: contents/ui/SearchPage.qml:36
#, kde-format
msgid "Unknown"
msgstr "Okänd"
#: contents/ui/SearchPage.qml:117
#, kde-format
msgid "Play next"
msgstr "Spela nästa"
#: contents/ui/SearchPage.qml:129
#, kde-format
msgid "Radio"
msgstr "Radio"
#: contents/ui/SearchPage.qml:147
#, kde-format
msgid "Find music on YouTube Music"
msgstr "Sök efter musik på YouTube musik"
#: contents/ui/SearchPage.qml:150
#, kde-format
msgid "Search"
msgstr "Sök"
#~ msgid "Play only Audio"
#~ msgstr "Spela bara ljud"
+155
View File
@@ -0,0 +1,155 @@
# Copyright (C) YEAR This file is copyright:
# This file is distributed under the same license as the audiotube package.
#
# Emir SARI <[email protected]>, 2022.
msgid ""
msgstr ""
"Project-Id-Version: audiotube\n"
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
"POT-Creation-Date: 2021-12-05 00:45+0000\n"
"PO-Revision-Date: 2022-04-15 14:41+0300\n"
"Last-Translator: Emir SARI <[email protected]>\n"
"Language-Team: Turkish <[email protected]>\n"
"Language: tr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Generator: Lokalize 21.12.3\n"
#, kde-format
msgctxt "NAME OF TRANSLATORS"
msgid "Your names"
msgstr "Emir SARI"
#, kde-format
msgctxt "EMAIL OF TRANSLATORS"
msgid "Your emails"
msgstr "[email protected]"
#: contents/ui/AlbumPage.qml:19 contents/ui/PlayerFooter.qml:173
#: contents/ui/PlaylistPage.qml:19
#, kde-format
msgid "Play"
msgstr "Çal"
#: contents/ui/AlbumPage.qml:61 contents/ui/ArtistPage.qml:101
#: contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Sonrakini Çal"
#: contents/ui/AlbumPage.qml:66 contents/ui/ArtistPage.qml:107
#: contents/ui/PlaylistPage.qml:72 contents/ui/SearchPage.qml:123
#, kde-format
msgid "Add to Playlist"
msgstr "Çalma Listesine Ekle"
#: contents/ui/ArtistPage.qml:39 contents/ui/SearchPage.qml:67
#, kde-format
msgid ""
"Video playback is not supported yet. Do you want to play only the audio of "
"\"%1\"?"
msgstr ""
"Video oynatımı henüz desteklenmiyor. \"%1\" videosunun yalnızca sesinin "
"çalınmasını ister misiniz?"
#: contents/ui/ArtistPage.qml:55 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Albümler"
#: contents/ui/ArtistPage.qml:57
#, kde-format
msgid "Singles"
msgstr "Tekliler"
#: contents/ui/ArtistPage.qml:59 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Şarkılar"
#: contents/ui/ArtistPage.qml:61 contents/ui/SearchPage.qml:33
#, kde-format
msgid "Videos"
msgstr "Videolar"
#: contents/ui/ConfirmationMessage.qml:16
#, kde-format
msgid "OK"
msgstr "Tamam"
#: contents/ui/ConfirmationMessage.qml:21
#, kde-format
msgid "Cancel"
msgstr "İptal"
#: contents/ui/main.qml:51
#, kde-format
msgid "AudioTube"
msgstr "AudioTube"
#: contents/ui/PlayerFooter.qml:106
#, kde-format
msgid "Remove Track"
msgstr "Parçayı Kaldır"
#: contents/ui/PlayerFooter.qml:123
#, kde-format
msgid "Clear"
msgstr "Temizle"
#: contents/ui/PlayerFooter.qml:128 contents/ui/PlaylistPage.qml:26
#, kde-format
msgid "Shuffle"
msgstr "Karıştır"
#: contents/ui/PlayerFooter.qml:173
#, kde-format
msgid "Pause"
msgstr "Duraklat"
#: contents/ui/PlayerFooter.qml:203
#, kde-format
msgid "No media playing"
msgstr "Ortam çalmıyor"
#: contents/ui/PlayerFooter.qml:247
#, kde-format
msgid "Expand"
msgstr "Genişlet"
#: contents/ui/SearchPage.qml:27
#, kde-format
msgid "Artists"
msgstr "Sanatçılar"
#: contents/ui/SearchPage.qml:29
#, kde-format
msgid "Playlists"
msgstr "Çalma Listeleri"
#: contents/ui/SearchPage.qml:36
#, kde-format
msgid "Unknown"
msgstr "Bilinmiyor"
#: contents/ui/SearchPage.qml:117
#, kde-format
msgid "Play next"
msgstr "Sonrakini çal"
#: contents/ui/SearchPage.qml:129
#, kde-format
msgid "Radio"
msgstr "Radyo"
#: contents/ui/SearchPage.qml:147
#, kde-format
msgid "Find music on YouTube Music"
msgstr "YouTube Müzik'te müzik bul"
#: contents/ui/SearchPage.qml:150
#, kde-format
msgid "Search"
msgstr "Ara"
+159
View File
@@ -0,0 +1,159 @@
# Copyright (C) YEAR This file is copyright:
# This file is distributed under the same license as the PACKAGE package.
#
# Yuri Chornoivan <[email protected]>, 2021.
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE\n"
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
"POT-Creation-Date: 2021-12-05 00:45+0000\n"
"PO-Revision-Date: 2021-12-05 08:42+0200\n"
"Last-Translator: Yuri Chornoivan <[email protected]>\n"
"Language-Team: Ukrainian <[email protected]>\n"
"Language: uk\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=4; plural=n==1 ? 3 : n%10==1 && n%100!=11 ? 0 : n"
"%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
"X-Generator: Lokalize 20.12.0\n"
#, kde-format
msgctxt "NAME OF TRANSLATORS"
msgid "Your names"
msgstr "Юрій Чорноіван"
#, kde-format
msgctxt "EMAIL OF TRANSLATORS"
msgid "Your emails"
msgstr "[email protected]"
#: contents/ui/AlbumPage.qml:19 contents/ui/PlayerFooter.qml:173
#: contents/ui/PlaylistPage.qml:19
#, kde-format
msgid "Play"
msgstr "Відтворити"
#: contents/ui/AlbumPage.qml:61 contents/ui/ArtistPage.qml:101
#: contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Відтворити наступну"
#: contents/ui/AlbumPage.qml:66 contents/ui/ArtistPage.qml:107
#: contents/ui/PlaylistPage.qml:72 contents/ui/SearchPage.qml:123
#, kde-format
msgid "Add to Playlist"
msgstr "Додати до списку відтворення"
#: contents/ui/ArtistPage.qml:39 contents/ui/SearchPage.qml:67
#, kde-format
msgid ""
"Video playback is not supported yet. Do you want to play only the audio of "
"\"%1\"?"
msgstr ""
"Підтримки відтворення відео ще не передбачено. Хочете, щоб було відтворено "
"лише звукову частину «%1»?"
#: contents/ui/ArtistPage.qml:55 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Альбоми"
#: contents/ui/ArtistPage.qml:57
#, kde-format
msgid "Singles"
msgstr "Одинарні"
#: contents/ui/ArtistPage.qml:59 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Пісні"
#: contents/ui/ArtistPage.qml:61 contents/ui/SearchPage.qml:33
#, kde-format
msgid "Videos"
msgstr "Відео"
#: contents/ui/ConfirmationMessage.qml:16
#, kde-format
msgid "OK"
msgstr "Гаразд"
#: contents/ui/ConfirmationMessage.qml:21
#, kde-format
msgid "Cancel"
msgstr "Скасувати"
#: contents/ui/main.qml:51
#, kde-format
msgid "AudioTube"
msgstr "AudioTube"
#: contents/ui/PlayerFooter.qml:106
#, kde-format
msgid "Remove Track"
msgstr "Вилучити композицію"
#: contents/ui/PlayerFooter.qml:123
#, kde-format
msgid "Clear"
msgstr "Спорожнити"
#: contents/ui/PlayerFooter.qml:128 contents/ui/PlaylistPage.qml:26
#, kde-format
msgid "Shuffle"
msgstr "Перемішати"
#: contents/ui/PlayerFooter.qml:173
#, kde-format
msgid "Pause"
msgstr "Призупинити"
#: contents/ui/PlayerFooter.qml:203
#, kde-format
msgid "No media playing"
msgstr "Відтворення не відбувається"
#: contents/ui/PlayerFooter.qml:247
#, kde-format
msgid "Expand"
msgstr "Розгорнути"
#: contents/ui/SearchPage.qml:27
#, kde-format
msgid "Artists"
msgstr "Виконавці"
#: contents/ui/SearchPage.qml:29
#, kde-format
msgid "Playlists"
msgstr "Списки відтворення"
#: contents/ui/SearchPage.qml:36
#, kde-format
msgid "Unknown"
msgstr "Невідомий"
#: contents/ui/SearchPage.qml:117
#, kde-format
msgid "Play next"
msgstr "Відтворити наступну"
#: contents/ui/SearchPage.qml:129
#, kde-format
msgid "Radio"
msgstr "Радіо"
#: contents/ui/SearchPage.qml:147
#, kde-format
msgid "Find music on YouTube Music"
msgstr "Шукати музику на сайті «Музика Youtube»"
#: contents/ui/SearchPage.qml:150
#, kde-format
msgid "Search"
msgstr "Пошук"
#~ msgid "Play only Audio"
#~ msgstr "Відтворити лише звук"
+155
View File
@@ -0,0 +1,155 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR This file is copyright:
# This file is distributed under the same license as the audiotube package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: audiotube\n"
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
"POT-Creation-Date: 2021-12-05 00:45+0000\n"
"PO-Revision-Date: 2021-12-05 01:46+0000\n"
"Last-Translator: transxx.py program <[email protected]>\n"
"Language-Team: KDE Test Language <[email protected]>\n"
"Language: x-test\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#, kde-format
msgctxt "NAME OF TRANSLATORS"
msgid "Your names"
msgstr "xxYour namesxx"
#, kde-format
msgctxt "EMAIL OF TRANSLATORS"
msgid "Your emails"
msgstr "xxYour emailsxx"
#: contents/ui/AlbumPage.qml:19 contents/ui/PlayerFooter.qml:173
#: contents/ui/PlaylistPage.qml:19
#, kde-format
msgid "Play"
msgstr "xxPlayxx"
#: contents/ui/AlbumPage.qml:61 contents/ui/ArtistPage.qml:101
#: contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "xxPlay Nextxx"
#: contents/ui/AlbumPage.qml:66 contents/ui/ArtistPage.qml:107
#: contents/ui/PlaylistPage.qml:72 contents/ui/SearchPage.qml:123
#, kde-format
msgid "Add to Playlist"
msgstr "xxAdd to Playlistxx"
#: contents/ui/ArtistPage.qml:39 contents/ui/SearchPage.qml:67
#, kde-format
msgid ""
"Video playback is not supported yet. Do you want to play only the audio of "
"\"%1\"?"
msgstr ""
"xxVideo playback is not supported yet. Do you want to play only the audio of "
"\"%1\"?xx"
#: contents/ui/ArtistPage.qml:55 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "xxAlbumsxx"
#: contents/ui/ArtistPage.qml:57
#, kde-format
msgid "Singles"
msgstr "xxSinglesxx"
#: contents/ui/ArtistPage.qml:59 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "xxSongsxx"
#: contents/ui/ArtistPage.qml:61 contents/ui/SearchPage.qml:33
#, kde-format
msgid "Videos"
msgstr "xxVideosxx"
#: contents/ui/ConfirmationMessage.qml:16
#, kde-format
msgid "OK"
msgstr "xxOKxx"
#: contents/ui/ConfirmationMessage.qml:21
#, kde-format
msgid "Cancel"
msgstr "xxCancelxx"
#: contents/ui/main.qml:51
#, kde-format
msgid "AudioTube"
msgstr "xxAudioTubexx"
#: contents/ui/PlayerFooter.qml:106
#, kde-format
msgid "Remove Track"
msgstr "xxRemove Trackxx"
#: contents/ui/PlayerFooter.qml:123
#, kde-format
msgid "Clear"
msgstr "xxClearxx"
#: contents/ui/PlayerFooter.qml:128 contents/ui/PlaylistPage.qml:26
#, kde-format
msgid "Shuffle"
msgstr "xxShufflexx"
#: contents/ui/PlayerFooter.qml:173
#, kde-format
msgid "Pause"
msgstr "xxPausexx"
#: contents/ui/PlayerFooter.qml:203
#, kde-format
msgid "No media playing"
msgstr "xxNo media playingxx"
#: contents/ui/PlayerFooter.qml:247
#, kde-format
msgid "Expand"
msgstr "xxExpandxx"
#: contents/ui/SearchPage.qml:27
#, kde-format
msgid "Artists"
msgstr "xxArtistsxx"
#: contents/ui/SearchPage.qml:29
#, kde-format
msgid "Playlists"
msgstr "xxPlaylistsxx"
#: contents/ui/SearchPage.qml:36
#, kde-format
msgid "Unknown"
msgstr "xxUnknownxx"
#: contents/ui/SearchPage.qml:117
#, kde-format
msgid "Play next"
msgstr "xxPlay nextxx"
#: contents/ui/SearchPage.qml:129
#, kde-format
msgid "Radio"
msgstr "xxRadioxx"
#: contents/ui/SearchPage.qml:147
#, kde-format
msgid "Find music on YouTube Music"
msgstr "xxFind music on YouTube Musicxx"
#: contents/ui/SearchPage.qml:150
#, kde-format
msgid "Search"
msgstr "xxSearchxx"
+153
View File
@@ -0,0 +1,153 @@
msgid ""
msgstr ""
"Project-Id-Version: kdeorg\n"
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
"POT-Creation-Date: 2021-12-05 00:45+0000\n"
"PO-Revision-Date: 2022-05-15 15:46\n"
"Last-Translator: \n"
"Language-Team: Chinese Simplified\n"
"Language: zh_CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Crowdin-Project: kdeorg\n"
"X-Crowdin-Project-ID: 269464\n"
"X-Crowdin-Language: zh-CN\n"
"X-Crowdin-File: /kf5-trunk/messages/audiotube/audiotube.pot\n"
"X-Crowdin-File-ID: 25474\n"
#, kde-format
msgctxt "NAME OF TRANSLATORS"
msgid "Your names"
msgstr "Qi Binye, Tyson Tan"
#, kde-format
msgctxt "EMAIL OF TRANSLATORS"
msgid "Your emails"
msgstr "[email protected], [email protected]"
#: contents/ui/AlbumPage.qml:19 contents/ui/PlayerFooter.qml:173
#: contents/ui/PlaylistPage.qml:19
#, kde-format
msgid "Play"
msgstr "播放"
#: contents/ui/AlbumPage.qml:61 contents/ui/ArtistPage.qml:101
#: contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "播放下一首"
#: contents/ui/AlbumPage.qml:66 contents/ui/ArtistPage.qml:107
#: contents/ui/PlaylistPage.qml:72 contents/ui/SearchPage.qml:123
#, kde-format
msgid "Add to Playlist"
msgstr "添加到播放列表"
#: contents/ui/ArtistPage.qml:39 contents/ui/SearchPage.qml:67
#, kde-format
msgid ""
"Video playback is not supported yet. Do you want to play only the audio of "
"\"%1\"?"
msgstr "视频播放尚未被支持。您想要只播放“%1”中的音频吗?"
#: contents/ui/ArtistPage.qml:55 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "专辑"
#: contents/ui/ArtistPage.qml:57
#, kde-format
msgid "Singles"
msgstr "单曲"
#: contents/ui/ArtistPage.qml:59 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "曲目"
#: contents/ui/ArtistPage.qml:61 contents/ui/SearchPage.qml:33
#, kde-format
msgid "Videos"
msgstr "视频"
#: contents/ui/ConfirmationMessage.qml:16
#, kde-format
msgid "OK"
msgstr "确定"
#: contents/ui/ConfirmationMessage.qml:21
#, kde-format
msgid "Cancel"
msgstr "取消"
#: contents/ui/main.qml:51
#, kde-format
msgid "AudioTube"
msgstr "AudioTube"
#: contents/ui/PlayerFooter.qml:106
#, kde-format
msgid "Remove Track"
msgstr "移除曲目"
#: contents/ui/PlayerFooter.qml:123
#, kde-format
msgid "Clear"
msgstr "清除"
#: contents/ui/PlayerFooter.qml:128 contents/ui/PlaylistPage.qml:26
#, kde-format
msgid "Shuffle"
msgstr "随机播放"
#: contents/ui/PlayerFooter.qml:173
#, kde-format
msgid "Pause"
msgstr "暂停"
#: contents/ui/PlayerFooter.qml:203
#, kde-format
msgid "No media playing"
msgstr "没有正在播放的媒体"
#: contents/ui/PlayerFooter.qml:247
#, kde-format
msgid "Expand"
msgstr "展开"
#: contents/ui/SearchPage.qml:27
#, kde-format
msgid "Artists"
msgstr "艺人"
#: contents/ui/SearchPage.qml:29
#, kde-format
msgid "Playlists"
msgstr "播放列表"
#: contents/ui/SearchPage.qml:36
#, kde-format
msgid "Unknown"
msgstr "未知"
#: contents/ui/SearchPage.qml:117
#, kde-format
msgid "Play next"
msgstr "播放下一首"
#: contents/ui/SearchPage.qml:129
#, kde-format
msgid "Radio"
msgstr "电台"
#: contents/ui/SearchPage.qml:147
#, kde-format
msgid "Find music on YouTube Music"
msgstr "查找 YouTube Music 中的音乐"
#: contents/ui/SearchPage.qml:150
#, kde-format
msgid "Search"
msgstr "搜索"
+2 -2
View File
@@ -3,5 +3,5 @@
#
# SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
ytmusicapi==0.18.0
youtube_dl
ytmusicapi==0.21.0
yt-dlp
+12 -1
View File
@@ -10,6 +10,14 @@ target_link_libraries(ytm PUBLIC pybind11::embed)
target_include_directories(ytm PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_definitions(ytm PRIVATE -DRANDALL_WAS_HERE)
add_library(asyncdatabase STATIC
asyncdatabase.cpp
asyncdatabase_p.h
)
target_include_directories(asyncdatabase PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(asyncdatabase PUBLIC Qt5::Sql)
add_subdirectory(example)
add_executable(audiotube
@@ -26,6 +34,7 @@ add_executable(audiotube
playerutils.cpp
abstractytmusicmodel.cpp
multiiterableview.h
library.cpp
resources.qrc
)
@@ -40,7 +49,9 @@ target_link_libraries(audiotube
KF5::CoreAddons
KF5::Crash
ytm
asyncdatabase
)
target_compile_definitions(audiotube PRIVATE -DQT_NO_KEYWORDS -DQT_NO_URL_CAST_FROM_STRING)
target_compile_definitions(audiotube PRIVATE -DQT_NO_KEYWORDS -DQT_NO_URL_CAST_FROM_STRING -DQT_QML_DEBUG)
install(TARGETS audiotube ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
+16 -20
View File
@@ -9,23 +9,24 @@
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) {
setLoading(false);
auto future = YTMusicThread::instance()->fetchAlbum(m_browseId);
connectFuture(future, this, [=, this](const album::Album &album) {
setLoading(false);
beginResetModel();
m_album = album;
endResetModel();
std::sort(m_album.thumbnails.begin(), m_album.thumbnails.end());
beginResetModel();
m_album = album;
endResetModel();
std::sort(m_album.thumbnails.begin(), m_album.thumbnails.end());
Q_EMIT titleChanged();
Q_EMIT thumbnailUrlChanged();
Q_EMIT playlistIdChanged();
Q_EMIT titleChanged();
Q_EMIT thumbnailUrlChanged();
Q_EMIT playlistIdChanged();
});
});
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::errorOccurred, this, [=] {
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::errorOccurred, this, [=, this] {
setLoading(false);
});
}
@@ -43,12 +44,7 @@ QVariant AlbumModel::data(const QModelIndex &index, int role) const
case VideoId:
return QString::fromStdString(m_album.tracks[index.row()].video_id.value_or(std::string()));
case Artists:
return QVariant::fromValue(std::vector<meta::Artist> {
{
m_album.tracks[index.row()].artists,
std::nullopt
}
});
return QVariant::fromValue(m_album.tracks[index.row()].artists);
}
Q_UNREACHABLE();
@@ -92,5 +88,5 @@ QUrl AlbumModel::thumbnailUrl() const
QString AlbumModel::playlistId() const
{
return QString::fromStdString(m_album.playlist_id);
return QString::fromStdString(m_album.audio_playlist_id);
}
+41 -49
View File
@@ -8,54 +8,48 @@
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;
}
setLoading(true);
YTMusicThread::instance()->fetchArtist(m_channelId);
auto future = YTMusicThread::instance()->fetchArtist(m_channelId);
connectFuture(future, this, [=, this](const artist::Artist &artist) {
setLoading(false);
beginResetModel();
m_artist = artist;
std::sort(m_artist.thumbnails.begin(), m_artist.thumbnails.end());
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>();
// 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::fetchArtistFinished, 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>();
m_view = std::optional(MultiIterableView(std::move(albums),
std::move(singles),
std::move(songs),
std::move(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
View File
@@ -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;
};
+272
View File
@@ -0,0 +1,272 @@
// SPDX-FileCopyrightText: 2022 Jonah Brüchert <[email protected]>
//
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "asyncdatabase.h"
#include <QDir>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QUrl>
#include <QStringBuilder>
#include <QVariant>
#include <QSqlResult>
#include <QSqlError>
#include <QLoggingCategory>
#define SCHAMA_MIGRATIONS_TABLE "__qt_schema_migrations"
Q_DECLARE_LOGGING_CATEGORY(asyncdatabase)
Q_LOGGING_CATEGORY(asyncdatabase, "asyncdatabase")
namespace asyncdatabase_private {
// migrations
void createInternalTable(QSqlDatabase &database) {
QSqlQuery query(QStringLiteral("create table if not exists " SCHAMA_MIGRATIONS_TABLE " ("
"version Text primary key not null, "
"run_on timestamp not null default current_timestamp)"), database);
if (!query.exec()) {
printSqlError(query);
}
}
void markMigrationRun(QSqlDatabase &database, const QString &name) {
qCDebug(asyncdatabase) << "Marking migration" << name << "as done.";
QSqlQuery query(database);
if (!query.prepare(QStringLiteral("insert into " SCHAMA_MIGRATIONS_TABLE " (version) values (:name)"))) {
printSqlError(query);
}
query.bindValue(QStringLiteral(":name"), QVariant(name));
if (!query.exec()) {
printSqlError(query);
}
}
bool checkMigrationAlreadyRun(QSqlDatabase &database, const QString &name) {
qCDebug(asyncdatabase) << "Checking whether migration" << name << "is already applied";
QSqlQuery query(database);
query.prepare(QStringLiteral("select count(*) from " SCHAMA_MIGRATIONS_TABLE " where version = :name"));
query.bindValue(QStringLiteral(":name"), QVariant(name));
query.exec();
query.next();
int count = query.value(0).toInt();
bool isApplied = count > 0;
if (isApplied) {
qCDebug(asyncdatabase) << "… yes";
} else {
qDebug(asyncdatabase) << "… no";
}
return isApplied;
}
void runDatabaseMigrations(QSqlDatabase &database, const QString &migrationDirectory)
{
createInternalTable(database);
QDir dir(migrationDirectory);
auto entries = dir.entryList(QDir::Filter::Dirs | QDir::Filter::NoDotAndDotDot, QDir::SortFlag::Name);
for (const auto &entry : entries) {
QDir subdir(entry);
if (!checkMigrationAlreadyRun(database, subdir.dirName())) {
QFile file(migrationDirectory % QDir::separator() % entry % QDir::separator() % "up.sql");
if (!file.open(QFile::ReadOnly)) {
qCDebug(asyncdatabase) << "Failed to open migration file" << file.fileName();
}
qCDebug(asyncdatabase) << "Running migration" << subdir.dirName();
// Hackish
const auto statements = file.readAll().split(';');
bool migrationSuccessful = true;
for (const QByteArray &statement : statements) {
const auto trimmedStatement = statement.trimmed();
QSqlQuery query(database);
if (!trimmedStatement.isEmpty()) {
qCDebug(asyncdatabase) << "Running" << trimmedStatement;
if (!query.prepare(trimmedStatement)) {
printSqlError(query);
} else {
bool success = query.exec();
migrationSuccessful &= success;
if (!success) {
printSqlError(query);
}
}
}
}
if (migrationSuccessful) {
markMigrationRun(database, subdir.dirName());
}
}
}
qCDebug(asyncdatabase) << "Migrations finished";
}
// Internal asynchronous database class
QFuture<void> AsyncSqlDatabase::establishConnection(const DatabaseConfiguration &configuration)
{
return runAsync<void>([=, this] {
m_database = std::make_unique<QSqlDatabase>(QSqlDatabase::addDatabase(configuration.type()));
if (configuration.databaseName()) {
m_database->setDatabaseName(*configuration.databaseName());
}
if (configuration.hostName()) {
m_database->setHostName(*configuration.hostName());
}
if (configuration.userName()) {
m_database->setUserName(*configuration.userName());
}
if (configuration.password()) {
m_database->setPassword(*configuration.password());
}
m_database->open();
});
}
AsyncSqlDatabase::AsyncSqlDatabase()
: QObject()
, m_database(nullptr)
{
}
Row AsyncSqlDatabase::retrieveRow(const QSqlQuery &query) {
Row row;
int i = 0;
while (true) {
if (query.isValid()) {
QVariant value = query.value(i);
if (value.isValid()) {
row.push_back(std::move(value));
i++;
} else {
break;
}
} else {
break;
}
}
return row;
}
Rows AsyncSqlDatabase::retrieveRows(QSqlQuery &query)
{
Rows rows;
while (query.next()) {
rows.push_back(retrieveRow(query));
}
return rows;
}
std::optional<Row> AsyncSqlDatabase::retrieveOptionalRow(QSqlQuery &query)
{
query.next();
if (query.isValid()) {
return retrieveRow(query);
} else {
return std::nullopt;
}
}
AsyncSqlDatabase::~AsyncSqlDatabase() = default;
void printSqlError(const QSqlQuery &query)
{
qCDebug(asyncdatabase) << "SQL error:" << query.lastError().text();
}
QSqlQuery prepareQuery(const QSqlDatabase &database, const QString &sqlQuery)
{
qCDebug(asyncdatabase) << "Running" << sqlQuery;
QSqlQuery query(database);
if (!query.prepare(sqlQuery)) {
printSqlError(query);
}
return query;
}
}
struct DatabaseConfigurationPrivate : public QSharedData {
QString type;
std::optional<QString> hostName;
std::optional<QString> databaseName;
std::optional<QString> userName;
std::optional<QString> password;
};
DatabaseConfiguration::DatabaseConfiguration() : d(new DatabaseConfigurationPrivate)
{}
DatabaseConfiguration::~DatabaseConfiguration() = default;
DatabaseConfiguration::DatabaseConfiguration(const DatabaseConfiguration &) = default;
void DatabaseConfiguration::setType(const QString &type) {
d->type = type;
}
const QString &DatabaseConfiguration::type() const {
return d->type;
}
void DatabaseConfiguration::setHostName(const QString &hostName) {
d->hostName = hostName;
}
const std::optional<QString> &DatabaseConfiguration::hostName() const {
return d->hostName;
}
void DatabaseConfiguration::setDatabaseName(const QString &databaseName) {
d->databaseName = databaseName;
}
const std::optional<QString> &DatabaseConfiguration::databaseName() const {
return d->databaseName;
}
void DatabaseConfiguration::setUserName(const QString &userName) {
d->userName = userName;
}
const std::optional<QString> &DatabaseConfiguration::userName() const {
return d->userName;
}
void DatabaseConfiguration::setPassword(const QString &password) {
d->password = password;
}
const std::optional<QString> &DatabaseConfiguration::password() const {
return d->password;
}
std::unique_ptr<ThreadedDatabase> ThreadedDatabase::establishConnection(DatabaseConfiguration config) {
auto threadedDb = std::make_unique<ThreadedDatabase>();
threadedDb->setObjectName(QStringLiteral("database thread"));
threadedDb->m_db->moveToThread(&*threadedDb);
threadedDb->start();
threadedDb->m_db->establishConnection(config);
return threadedDb;
}
ThreadedDatabase::ThreadedDatabase()
: QThread()
, m_db(std::make_unique<asyncdatabase_private::AsyncSqlDatabase>())
{
}
ThreadedDatabase::~ThreadedDatabase()
{
quit();
wait();
}
+157
View File
@@ -0,0 +1,157 @@
// SPDX-FileCopyrightText: 2022 Jonah Brüchert <[email protected]>
//
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
class QUrl;
class QSqlDatabase;
#include <QString>
#include <QObject>
#include <QFuture>
#include <QFutureWatcher>
#include <QSqlQuery>
#include <QDebug>
#include <QThread>
#include <QSharedDataPointer>
#include <memory>
#include <optional>
#include <tuple>
#include <vector>
#include "asyncdatabase_p.h"
using Row = std::vector<QVariant>;
using Rows = std::vector<Row>;
struct DatabaseConfigurationPrivate;
///
/// Options for connecting to a database
///
class DatabaseConfiguration {
public:
DatabaseConfiguration();
DatabaseConfiguration(const DatabaseConfiguration &);
~DatabaseConfiguration();
/// Set the name of the database driver, for example DATABASE_TYPE_SQLITE or a custom string.
void setType(const QString &type);
const QString &type() const;
/// Set the hostname
void setHostName(const QString &hostName);
const std::optional<QString> &hostName() const;
/// Set the name of the database (path of the file for SQLite)
void setDatabaseName(const QString &databaseName);
const std::optional<QString> &databaseName() const;
/// Set user name
void setUserName(const QString &userName);
const std::optional<QString> &userName() const;
/// Set password
void setPassword(const QString &password);
const std::optional<QString> &password() const;
private:
QSharedDataPointer<DatabaseConfigurationPrivate> d;
};
///
/// The SQLite database driver
///
const QString DATABASE_TYPE_SQLITE = QStringLiteral("QSQLITE");
///
/// Parse the row into a tuple of the given types.
/// The types need to be deserializable from QVariant.
///
template <typename ...RowTypes>
auto parseRow(const Row &row) -> std::tuple<RowTypes...>
{
auto tuple = std::tuple<RowTypes...>();
int i = 0;
asyncdatabase_private::iterate_tuple(tuple, [&](auto &elem) {
elem = row.at(i).value<std::decay_t<decltype(elem)>>();
i++;
});
return tuple;
}
///
/// Parse the rows into a list of tuples of the given types.
/// The types need to be deserializable from QVariant.
///
template <typename ...RowTypes>
auto parseRows(const Rows &rows) -> std::vector<std::tuple<RowTypes...>> {
std::vector<std::tuple<RowTypes...>> parsedRows;
parsedRows.reserve(rows.size());
for (const auto &row : rows) {
parsedRows.push_back(parseRow<RowTypes...>(row));
}
return parsedRows;
}
class ThreadedDatabase : public QThread {
public:
///
/// \brief Connect to a database
/// \param configuration of the database connection
/// \return
///
static std::unique_ptr<ThreadedDatabase> establishConnection(DatabaseConfiguration config);
///
/// \brief Execute an SQL query on the database, ignoring the result.
/// \param SQL query string to execute
/// \param Parameters to bind to the placeholders in the SQL Query
/// \return
///
template <typename ...Args>
auto execute(const QString &sqlQuery, Args... args) -> QFuture<void> {
return m_db->execute(sqlQuery, args...);
}
///
/// Run database migrations in the given directory.
/// The directory needs to contain a subdirectory for each migration.
/// The subdirectories need to be named so that when sorted alphabetically the migrations will be run in the correct order.
/// Each subdirectory needs to contain a file named up.sql.
///
/// \param Directory which contains the migrations.
/// \return
///
auto runMigrations(const QString &migrationDirectory) -> QFuture<void> {
return m_db->runMigrations(migrationDirectory);
}
///
/// \brief Execute an SQL query on the database, retrieving the result.
/// \param SQL Query to execute
/// \param parameters to bind to the placeholders in the SQL query.
/// \return Future of a list of lists of variants.
///
template <typename ...Args>
auto getResults(const QString &sqlQuery, Args... args) -> QFuture<Rows> {
return m_db->getResults(sqlQuery, args...);
}
///
/// \brief Like getResults, but for retrieving just one row.
///
template <typename ...Args>
auto getResult(const QString &sqlQuery, Args... args) -> QFuture<std::optional<Row>> {
return m_db->getResult(sqlQuery, args...);
}
ThreadedDatabase();
~ThreadedDatabase();
private:
std::unique_ptr<asyncdatabase_private::AsyncSqlDatabase> m_db;
};
+149
View File
@@ -0,0 +1,149 @@
// SPDX-FileCopyrightText: 2022 Jonah Brüchert <[email protected]>
//
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <memory>
#include <tuple>
#include <optional>
#include <QFuture>
#include <QFutureWatcher>
#include <QSqlQuery>
#include <QSqlDatabase>
#include <QThread>
class DatabaseConfiguration;
namespace asyncdatabase_private {
// Helpers
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 asyncdatabase_private::iterate_impl<Tuple, Func, i + 1>(tup, fun);
}
}
template <typename Tuple, typename Func>
inline constexpr void iterate_tuple(Tuple &tup, Func fun)
{
asyncdatabase_private::iterate_impl<Tuple, Func, 0>(tup, fun);
}
template <typename T, typename QObjectDerivedType, typename Function>
void connectFuture(const QFuture<T> &future, QObjectDerivedType *self, const Function &fun) {
auto watcher = std::make_shared<QFutureWatcher<T>>();
watcher->setFuture(future);
QObject::connect(watcher.get(), &QFutureWatcherBase::finished, self, [self, watcher, fun, future] {
if constexpr (std::is_same_v<void, T>) {
if constexpr (std::is_member_function_pointer_v<Function>) {
fun->*(self);
} else {
fun();
}
} else if (future.resultCount() > 0) {
if constexpr (std::is_member_function_pointer_v<Function>) {
(self->*fun)(watcher->result());
} else {
fun(watcher->result());
}
}
});
}
using Row = std::vector<QVariant>;
using Rows = std::vector<Row>;
void runDatabaseMigrations(QSqlDatabase &database, const QString &migrationDirectory);
void printSqlError(const QSqlQuery &query);
QSqlQuery prepareQuery(const QSqlDatabase &database, const QString &sqlQuery);
class AsyncSqlDatabase : public QObject {
Q_OBJECT
public:
QFuture<void> establishConnection(const DatabaseConfiguration &configuration);
template <typename ...Args>
auto getResults(const QString &sqlQuery, Args... args) -> QFuture<Rows> {
return runAsync<Rows>([=, this] {
auto query = executeQuery(sqlQuery, args...);
return retrieveRows(query);
});
}
template <typename ...Args>
auto getResult(const QString &sqlQuery, Args... args) -> QFuture<std::optional<Row>> {
return runAsync<std::optional<Row>>([=, this] {
auto query = executeQuery(sqlQuery, args...);
return retrieveOptionalRow(query);
});
}
template <typename ...Args>
auto execute(const QString &sqlQuery, Args... args) -> QFuture<void> {
return runAsync<void>([=, this] {
executeQuery(sqlQuery, args...);
});
}
auto runMigrations(const QString &migrationDirectory) -> QFuture<void> {
return runAsync<void>([=, this] {
runDatabaseMigrations(*m_database, migrationDirectory);
});
}
AsyncSqlDatabase();
~AsyncSqlDatabase();
private:
template <typename ...Args>
QSqlQuery executeQuery(const QString &sqlQuery, Args... args) {
auto query = prepareQuery(*m_database, sqlQuery);
auto argsTuple = std::make_tuple<Args...>(std::move(args)...);
int i = 0;
asyncdatabase_private::iterate_tuple(argsTuple, [&](auto &arg) {
query.bindValue(i, arg);
i++;
});
if (!query.exec()) {
printSqlError(query);
}
return query;
}
template <typename T, typename Functor>
QFuture<T> runAsync(Functor func) {
auto interface = std::make_shared<QFutureInterface<T>>();
QMetaObject::invokeMethod(this, [interface, func] {
if constexpr (!std::is_same_v<T, void>) {
auto result = func();
interface->reportResult(result);
} else {
func();
}
interface->reportFinished();
});
return interface->future();
}
Row retrieveRow(const QSqlQuery &query);
Rows retrieveRows(QSqlQuery &query);
std::optional<Row> retrieveOptionalRow(QSqlQuery &query);
std::unique_ptr<QSqlDatabase> m_database;
};
}
+50 -101
View File
@@ -6,6 +6,9 @@
#include <QThread>
#include <QDebug>
#include <QJsonObject>
#include <QJsonArray>
#include <QFutureInterface>
#include <pybind11/embed.h>
@@ -13,10 +16,21 @@
namespace py = pybind11;
#include <iostream>
#include <algorithm>
#include <unordered_map>
#include <ranges>
#include <type_traits>
#include <memory>
template <typename R, typename T, typename OP>
std::optional<R> map_optional(const std::optional<T> &optional, OP op) {
if (optional.has_value()) {
return op(optional.value());
if constexpr (std::is_member_function_pointer<OP>::value) {
return (&optional.value()->*op)();
} else {
return op(optional.value());
}
}
return std::nullopt;
@@ -37,15 +51,6 @@ AsyncYTMusic::AsyncYTMusic(QObject *parent)
qRegisterMetaType<std::vector<meta::Artist>>();
qRegisterMetaType<meta::Artist>();
connect(this, &AsyncYTMusic::startSearch, this, &AsyncYTMusic::internalSearch);
connect(this, &AsyncYTMusic::startFetchArtist, this, &AsyncYTMusic::internalFetchArtist);
connect(this, &AsyncYTMusic::startFetchAlbum, this, &AsyncYTMusic::internalFetchAlbum);
connect(this, &AsyncYTMusic::startFetchSong, this, &AsyncYTMusic::internalFetchSong);
connect(this, &AsyncYTMusic::startFetchPlaylist, this, &AsyncYTMusic::internalFetchPlaylist);
connect(this, &AsyncYTMusic::startFetchArtistAlbums, this, &AsyncYTMusic::internalFetchArtistAlbums);
connect(this, &AsyncYTMusic::startExtractVideoInfo, this, &AsyncYTMusic::internalExtractVideoInfo);
connect(this, &AsyncYTMusic::startFetchWatchPlaylist, this, &AsyncYTMusic::internalFetchWatchPlaylist);
connect(this, &AsyncYTMusic::errorOccurred, this, [](const QString &err) {
std::cerr << qPrintable(err);
});
@@ -54,139 +59,83 @@ AsyncYTMusic::AsyncYTMusic(QObject *parent)
//
// search
//
void AsyncYTMusic::search(const QString &query)
QFuture<std::vector<search::SearchResultItem>> AsyncYTMusic::search(const QString &query)
{
Q_EMIT startSearch(query);
}
void AsyncYTMusic::internalSearch(const QString &query)
{
try {
Q_EMIT searchFinished(m_ytm->search(query.toStdString()));
} catch (const py::error_already_set &err) {
Q_EMIT errorOccurred(QString::fromLocal8Bit(err.what()));
}
return invokeAndCatchOnThread<std::vector<search::SearchResultItem>>([=, this]() {
return m_ytm->search(query.toStdString());
});
}
//
// fetchArtist
//
void AsyncYTMusic::fetchArtist(const QString &channelId)
QFuture<artist::Artist> AsyncYTMusic::fetchArtist(const QString &channelId)
{
Q_EMIT startFetchArtist(channelId);
}
void AsyncYTMusic::internalFetchArtist(const QString &channelId)
{
try {
Q_EMIT fetchArtistFinished(m_ytm->get_artist(channelId.toStdString()));
} catch (const py::error_already_set &err) {
Q_EMIT errorOccurred(QString::fromLocal8Bit(err.what()));
}
return invokeAndCatchOnThread<artist::Artist>([=, this]() {
return m_ytm->get_artist(channelId.toStdString());
});
}
//
// fetchAlbum
//
void AsyncYTMusic::fetchAlbum(const QString &browseId)
QFuture<album::Album> AsyncYTMusic::fetchAlbum(const QString &browseId)
{
Q_EMIT startFetchAlbum(browseId);
}
void AsyncYTMusic::internalFetchAlbum(const QString &browseId)
{
try {
Q_EMIT fetchAlbumFinished(m_ytm->get_album(browseId.toStdString()));
} catch (const py::error_already_set &err) {
Q_EMIT errorOccurred(QString::fromLocal8Bit(err.what()));
}
return invokeAndCatchOnThread<album::Album>([=, this]() {
return m_ytm->get_album(browseId.toStdString());
});
}
//
// fetchSong
//
void AsyncYTMusic::fetchSong(const QString &videoId)
QFuture<std::optional<song::Song>> AsyncYTMusic::fetchSong(const QString &videoId)
{
Q_EMIT startFetchSong(videoId);
}
void AsyncYTMusic::internalFetchSong(const QString &videoId)
{
try {
Q_EMIT fetchSongFinished(m_ytm->get_song(videoId.toStdString()));
} catch (const py::error_already_set &err) {
Q_EMIT errorOccurred(QString::fromLocal8Bit(err.what()));
}
return invokeAndCatchOnThread<std::optional<song::Song>>([=, this]() {
return m_ytm->get_song(videoId.toStdString());
});
}
//
// fetchPlaylist
//
void AsyncYTMusic::fetchPlaylist(const QString &playlistId) {
Q_EMIT startFetchPlaylist(playlistId);
}
void AsyncYTMusic::internalFetchPlaylist(const QString &playlistId)
{
try {
Q_EMIT fetchPlaylistFinished(m_ytm->get_playlist(playlistId.toStdString()));
} catch (const py::error_already_set &err) {
Q_EMIT errorOccurred(QString::fromLocal8Bit(err.what()));
}
QFuture<playlist::Playlist> AsyncYTMusic::fetchPlaylist(const QString &playlistId) {
return invokeAndCatchOnThread<playlist::Playlist>([=, this]() {
return m_ytm->get_playlist(playlistId.toStdString());
});
}
//
// fetchArtistAlbum
//
void AsyncYTMusic::fetchArtistAlbums(const QString &channelId, const QString &params)
QFuture<std::vector<artist::Artist::Album>> AsyncYTMusic::fetchArtistAlbums(const QString &channelId, const QString &params)
{
Q_EMIT startFetchArtistAlbums(channelId, params);
}
void AsyncYTMusic::internalFetchArtistAlbums(const QString &channelid, const QString &params)
{
try {
Q_EMIT fetchArtistAlbumsFinished(m_ytm->get_artist_albums(channelid.toStdString(), params.toStdString()));
} catch (const py::error_already_set &err) {
Q_EMIT errorOccurred(QString::fromLocal8Bit(err.what()));
}
return invokeAndCatchOnThread<std::vector<artist::Artist::Album>>([=, this]() {
return m_ytm->get_artist_albums(channelId.toStdString(), params.toStdString());
});
}
//
// extractVideoInfo
//
void AsyncYTMusic::extractVideoInfo(const QString &videoId)
QFuture<video_info::VideoInfo> AsyncYTMusic::extractVideoInfo(const QString &videoId)
{
Q_EMIT startExtractVideoInfo(videoId);
}
void AsyncYTMusic::internalExtractVideoInfo(const QString &videoId)
{
try {
Q_EMIT extractVideoInfoFinished(m_ytm->extract_video_info(videoId.toStdString()));
} catch (const py::error_already_set &err) {
Q_EMIT errorOccurred(QString::fromLocal8Bit(err.what()));
}
return invokeAndCatchOnThread<video_info::VideoInfo>([=, this]() {
return m_ytm->extract_video_info(videoId.toStdString());
});
}
//
// fetchWatchPlaylist
//
void AsyncYTMusic::fetchWatchPlaylist(const std::optional<QString> &videoId, const std::optional<QString> &playlistId)
QFuture<watch::Playlist> AsyncYTMusic::fetchWatchPlaylist(const std::optional<QString> &videoId, const std::optional<QString> &playlistId)
{
Q_EMIT startFetchWatchPlaylist(videoId, playlistId);
}
void AsyncYTMusic::internalFetchWatchPlaylist(const std::optional<QString> &videoId, const std::optional<QString> &playlistId)
{
try {
Q_EMIT fetchWatchPlaylistFinished(m_ytm->get_watch_playlist(
map_optional<std::string>(videoId, [](const QString &value) { return value.toStdString(); }),
map_optional<std::string>(playlistId, [](const QString &value) { return value.toStdString(); })
));
} catch (const py::error_already_set &err) {
Q_EMIT errorOccurred(QString::fromLocal8Bit(err.what()));
}
return invokeAndCatchOnThread<watch::Playlist>([=, this]() {
return m_ytm->get_watch_playlist(
map_optional<std::string>(videoId, &QString::toStdString),
map_optional<std::string>(playlistId, &QString::toStdString)
);
});
}
YTMusicThread &YTMusicThread::instance()
+70 -39
View File
@@ -6,6 +6,8 @@
#include <QObject>
#include <QThread>
#include <QFuture>
#include <QFutureWatcher>
#include <vector>
@@ -45,6 +47,50 @@ private:
std::unique_ptr<T> m_item = nullptr;
};
///
/// Similar to QObject::connect, except that the first two arguments are replaced with a QFuture.
///
template <typename T, typename QObjectDerivedType, typename Function>
void connectFuture(const QFuture<T> &future, QObjectDerivedType *self, const Function &fun) {
auto watcher = std::make_shared<QFutureWatcher<T>>();
watcher->setFuture(future);
QObject::connect(watcher.get(), &QFutureWatcherBase::finished, self, [self, watcher, fun, future] {
if constexpr (std::is_same_v<void, T>) {
if constexpr (std::is_member_function_pointer_v<Function>) {
(self->*fun)();
} else {
fun();
}
} else if (future.resultCount() > 0) {
if constexpr (std::is_member_function_pointer_v<Function>) {
(self->*fun)(watcher->result());
} else {
fun(watcher->result());
}
}
});
}
///
/// Applies a function to a the contained value of a QFuture once it finishes.
/// @returns a QFuture that finishes once the future given as first argument finished
///
template <typename T, typename U, typename Func>
QFuture<U> mapFuture(const QFuture<T> &future, Func mapFunction) {
auto watcher = std::make_shared<QFutureWatcher<T>>();
watcher->setFuture(future);
auto interface = std::make_shared<QFutureInterface<U>>();
QObject::connect(watcher.get(), &QFutureWatcherBase::finished, watcher.get(), [interface, watcher, mapFunction] {
auto result = watcher->result();
auto mapped = mapFunction(std::move(result));
interface->reportResult(mapped);
interface->reportFinished();
});
return interface->future();
}
class AsyncYTMusic : public QObject
{
friend class YTMusicThread;
@@ -53,30 +99,22 @@ class AsyncYTMusic : public QObject
public:
// public functions need to be thread safe
void search(const QString &query);
Q_SIGNAL void searchFinished(std::vector<search::SearchResultItem>);
QFuture<std::vector<search::SearchResultItem>> search(const QString &query);
void fetchArtist(const QString &channelId);
Q_SIGNAL void fetchArtistFinished(artist::Artist);
QFuture<artist::Artist> fetchArtist(const QString &channelId);
void fetchAlbum(const QString &browseId);
Q_SIGNAL void fetchAlbumFinished(album::Album);
QFuture<album::Album> fetchAlbum(const QString &browseId);
void fetchSong(const QString &videoId);
Q_SIGNAL void fetchSongFinished(song::Song);
QFuture<std::optional<song::Song> > fetchSong(const QString &videoId);
void fetchPlaylist(const QString &playlistId);
Q_SIGNAL void fetchPlaylistFinished(playlist::Playlist);
QFuture<playlist::Playlist> fetchPlaylist(const QString &playlistId);
void fetchArtistAlbums(const QString &channelId, const QString &params);
Q_SIGNAL void fetchArtistAlbumsFinished(std::vector<artist::Artist::Album>);
QFuture<std::vector<artist::Artist::Album>> fetchArtistAlbums(const QString &channelId, const QString &params);
void extractVideoInfo(const QString &videoId);
Q_SIGNAL void extractVideoInfoFinished(video_info::VideoInfo);
QFuture<video_info::VideoInfo> extractVideoInfo(const QString &videoId);
void fetchWatchPlaylist(const std::optional<QString> &videoId = std::nullopt ,
QFuture<watch::Playlist> fetchWatchPlaylist(const std::optional<QString> &videoId = std::nullopt ,
const std::optional<QString> &playlistId = std::nullopt);
Q_SIGNAL void fetchWatchPlaylistFinished(watch::Playlist);
Q_SIGNAL void errorOccurred(const QString &error);
@@ -84,29 +122,22 @@ protected:
explicit AsyncYTMusic(QObject *parent = nullptr);
private:
Q_SIGNAL void startSearch(const QString &query);
Q_SLOT void internalSearch(const QString &query);
Q_SIGNAL void startFetchArtist(const QString &channelId);
Q_SLOT void internalFetchArtist(const QString &channelId);
Q_SIGNAL void startFetchAlbum(const QString &browseId);
Q_SLOT void internalFetchAlbum(const QString &browseId);
Q_SIGNAL void startFetchSong(const QString &videoId);
Q_SLOT void internalFetchSong(const QString &videoId);
Q_SIGNAL void startFetchPlaylist(const QString &playlistId);
Q_SLOT void internalFetchPlaylist(const QString &playlistId);
Q_SIGNAL void startFetchArtistAlbums(const QString &channelid, const QString &params);
Q_SLOT void internalFetchArtistAlbums(const QString &channelid, const QString &params);
Q_SIGNAL void startExtractVideoInfo(const QString &videoId);
Q_SLOT void internalExtractVideoInfo(const QString &videoId);
Q_SIGNAL void startFetchWatchPlaylist(const std::optional<QString> &videoId, const std::optional<QString> &playlistId);
Q_SLOT void internalFetchWatchPlaylist(const std::optional<QString> &videoId, const std::optional<QString> &playlistId);
/// Invokes the given function on the thread of the YTMusic object, and handles exceptions that occur while invoking it.
template <typename ReturnType>
QFuture<ReturnType> invokeAndCatchOnThread(const std::function<ReturnType()> &fun) {
auto interface = std::make_shared<QFutureInterface<ReturnType>>();
QMetaObject::invokeMethod(this, [=, this]() {
try {
ReturnType val = fun();
interface->reportResult(val);
interface->reportFinished();
} catch (const std::exception &err) {
interface->reportFinished();
Q_EMIT errorOccurred(QString::fromLocal8Bit(err.what()));
}
});
return interface->future();
}
// Python interpreter will be initialized from the thread calling the methods
Lazy<YTMusic> m_ytm;
+13 -1
View File
@@ -36,7 +36,15 @@ Kirigami.ScrollablePage {
}
onOpenVideo: (videoId, title) => {
askPlayVideoAudio(videoId, title)
videoPlayMessage.text = i18n("Video playback is not supported yet. Do you want to play only the audio of \"%1\"?", title)
videoPlayMessage.visible = true
videoPlayMessage.okAction = function() {
play(videoId)
videoPlayMessage.visible = false
}
videoPlayMessage.cancelAction = function() {
videoPlayMessage.visible = false
}
}
}
section.property: "type"
@@ -110,4 +118,8 @@ Kirigami.ScrollablePage {
visible: artistModel.loading
}
}
footer: ConfirmationMessage {
id: videoPlayMessage
}
}
+26
View File
@@ -0,0 +1,26 @@
// SPDX-FileCopyrightText: 2021 Dimitris Kardarakos <[email protected]>
//
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
import QtQuick 2.15
import org.kde.kirigami 2.14 as Kirigami
Kirigami.InlineMessage {
property var okAction
property var cancelAction
visible: false
actions: [
Kirigami.Action {
text: i18n("OK")
onTriggered: okAction()
},
Kirigami.Action {
text: i18n("Cancel")
onTriggered: cancelAction()
}
]
}
+119
View File
@@ -0,0 +1,119 @@
// SPDX-FileCopyrightText: 2022 Jonah Brüchert <[email protected]>
//
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
import QtQuick 2.1
import QtQuick.Controls 2.12 as Controls
import QtQuick.Layouts 1.3
import org.kde.kirigami 2.14 as Kirigami
import org.kde.ytmusic 1.0
Kirigami.ScrollablePage {
ColumnLayout {
Kirigami.Heading {
text: i18n("Favourites")
}
Controls.ScrollView {
Layout.fillWidth: true
RowLayout {
Repeater {
Layout.fillWidth: true
model: Library.favourites
delegate: ColumnLayout {
id: delegateItem
required property string title
required property string artist
required property string videoId
Layout.fillWidth: false
Layout.maximumWidth: 200
Kirigami.Card {
id: card
Layout.preferredHeight: 200
onClicked: play(delegateItem.videoId)
ThumbnailSource {
id: thumbnailSource
videoId: delegateItem.videoId
}
header: Image {
sourceSize: "200x200"
source: thumbnailSource.cachedPath
fillMode: Image.PreserveAspectCrop
asynchronous: true
}
actions: [
Kirigami.Action {
icon.name: "delete"
onTriggered: Library.removeFavourite(delegateItem.videoId)
}
]
}
Controls.Label {
Layout.maximumWidth: 200
text: delegateItem.title
elide: Qt.ElideRight
}
}
}
}
}
Kirigami.Heading {
text: i18n("Most played")
}
Controls.ScrollView {
Layout.fillWidth: true
RowLayout {
Repeater {
Layout.fillWidth: true
model: Library.mostPlayed
delegate: ColumnLayout {
id: mpdelegateItem
required property string title
required property string artist
required property string videoId
Layout.fillWidth: false
Layout.maximumWidth: 200
Kirigami.Card {
id: mpcard
Layout.preferredHeight: 200
onClicked: play(mpdelegateItem.videoId)
ThumbnailSource {
id: mpthumbnailSource
videoId: mpdelegateItem.videoId
}
header: Image {
sourceSize: "200x200"
source: mpthumbnailSource.cachedPath
fillMode: Image.PreserveAspectCrop
asynchronous: true
}
actions: [
Kirigami.Action {
icon.name: "delete"
onTriggered: Library.removeFavourite(mpdelegateItem.videoId)
}
]
}
Controls.Label {
Layout.maximumWidth: 200
text: mpdelegateItem.title
elide: Qt.ElideRight
}
}
}
}
}
}
}
+27 -7
View File
@@ -23,6 +23,7 @@ Item {
anchors.fill: footerLayout
}
ColumnLayout {
id: footerLayout
height: parent.height
@@ -38,20 +39,37 @@ Item {
flow: width > height ? GridLayout.LeftToRight : GridLayout.TopToBottom
visible: footerItem.maximized
Item {
Layout.fillWidth: info.thumbnail
Layout.fillHeight: info.thumbnail
Image {
anchors.fill: parent
ColumnLayout {
Layout.preferredWidth: footerLayout.width * (playerLayout.mobile ? 1 : 0.5)
Layout.preferredHeight: footerLayout.height * (playerLayout.mobile ? 0.5 : 1)
source: info.thumbnail
fillMode: Image.PreserveAspectFit
Item {
Layout.fillWidth: true
Layout.fillHeight: true
Image {
anchors.fill: parent
source: info.thumbnail
fillMode: Image.PreserveAspectFit
}
}
Controls.RoundButton {
readonly property QtObject favouriteWatcher: Library.favouriteWatcher(info.videoId)
Layout.alignment: Qt.AlignHCenter
icon.name: favouriteWatcher ? (favouriteWatcher.isFavourite ? "starred-symbolic" : "non-starred-symbolic") : ""
onClicked: if (favouriteWatcher)
favouriteWatcher.isFavourite
? Library.removeFavourite(info.videoId)
: Library.addFavourite(info.videoId, info.title)
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.preferredWidth: footerLayout.width * (playerLayout.mobile ? 1 : 0.5)
Layout.preferredHeight: footerLayout.height * (playerLayout.mobile ? 0.5 : 1)
Controls.ScrollView {
anchors.fill: parent
@@ -151,6 +169,7 @@ Item {
onSongChanged: audio.play()
videoId: UserPlaylistModel.currentVideoId
onTitleChanged: Library.addPlaybackHistoryItem(info.videoId, info.title)
}
Audio {
@@ -223,6 +242,7 @@ Item {
Behavior on value {
NumberAnimation {
duration: 1000
}
}
+36 -3
View File
@@ -17,6 +17,27 @@ Kirigami.ScrollablePage {
ListView {
reuseItems: true
section.delegate: Kirigami.ListSectionHeader {
required property string section
text: {
switch (parseInt(section)) {
case SearchModel.Album:
return i18n("Albums")
case SearchModel.Artist:
return i18n("Artists")
case SearchModel.Playlist:
return i18n("Playlists")
case SearchModel.Song:
return i18n("Songs")
case SearchModel.Video:
return i18n("Videos")
}
return i18n("Unknown")
}
}
section.property: "type"
model: SearchModel {
id: searchModel
@@ -43,7 +64,15 @@ Kirigami.ScrollablePage {
}
onOpenVideo: (videoId, title) => {
askPlayVideoAudio(videoId, title)
videoPlayMessage.text = i18n("Video playback is not supported yet. Do you want to play only the audio of \"%1\"?", title)
videoPlayMessage.visible = true
videoPlayMessage.okAction = function() {
play(videoId)
videoPlayMessage.visible = false
}
videoPlayMessage.cancelAction = function() {
videoPlayMessage.visible = false
}
}
}
delegate: Kirigami.SwipeListItem {
@@ -86,13 +115,13 @@ Kirigami.ScrollablePage {
Kirigami.Action {
icon.name: "go-next"
text: i18n("Play next")
visible: delegateItem.type === SearchModel.Song
visible: delegateItem.type === SearchModel.Song || delegateItem.type == SearchModel.Video
onTriggered: UserPlaylistModel.playNext(delegateItem.videoId, delegateItem.title, delegateItem.artists)
},
Kirigami.Action {
icon.name: "media-playlist-append"
text: i18n("Add to Playlist")
visible: delegateItem.type === SearchModel.Song
visible: delegateItem.type === SearchModel.Song || delegateItem.type == SearchModel.Video
onTriggered: UserPlaylistModel.append(delegateItem.videoId, delegateItem.title, delegateItem.artists)
},
Kirigami.Action {
@@ -123,4 +152,8 @@ Kirigami.ScrollablePage {
}
}
}
footer: ConfirmationMessage {
id: videoPlayMessage
}
}
+44 -11
View File
@@ -36,10 +36,51 @@ Kirigami.ApplicationWindow {
selectByMouse: true
Layout.fillWidth: true
Layout.maximumWidth: 400
onPressed: {
popup.open()
}
Controls.Popup {
id: popup
x: searchField.y
y: searchField.y + searchField.height
visible: true
width: searchField.width
height: completionList
? Math.min(completionList.count * Kirigami.Units.gridUnit * 2 + Kirigami.Units.gridUnit * 2, Kirigami.Units.gridUnit * 20)
: Kirigami.Units.gridUnit * 20
contentItem: Controls.ScrollView {
Controls.ScrollBar.horizontal.policy: Controls.ScrollBar.AlwaysOff
ListView {
id: completionList
model: Library.searches
delegate: Controls.ItemDelegate {
id: completionDelegate
width: parent.width
height: Kirigami.Units.gridUnit * 2
text: model.display
onClicked: {
searchField.text = model.display
searchField.accepted()
}
}
}
}
}
onAccepted: {
pageStack.clear()
pageStack.push("qrc:/SearchPage.qml", {
"searchQuery": text})
if (text) {
Library.addSearch(text)
pageStack.push("qrc:/SearchPage.qml", {
"searchQuery": text})
} else {
pageStack.replace("qrc:/LibraryPage.qml")
}
popup.close()
}
}
Item {
@@ -56,7 +97,7 @@ Kirigami.ApplicationWindow {
id: contextDrawer
}
pageStack.initialPage: "qrc:/SearchPage.qml"
pageStack.initialPage: "qrc:/LibraryPage.qml"
function play(videoId) {
UserPlaylistModel.initialVideoId = videoId
@@ -71,14 +112,6 @@ Kirigami.ApplicationWindow {
UserPlaylistModel.playlistId = playlistId
}
function askPlayVideoAudio(videoId, title) {
showPassiveNotification(i18n("Video playback is not supported yet. Do you want to play only the audio of \"%1\"?", title),
"long",
i18n("Play only Audio"),
function() {
play(videoId)
})
}
Connections {
target: ErrorHandler
+1 -1
View File
@@ -8,7 +8,7 @@
int main() {
const auto ytm = YTMusic();
const auto results = ytm.search("Thomas Bangalter");
const auto results = ytm.search("Another one bites the dust");
std::cout << "Found " << results.size() << " results." << std::endl;
+283
View File
@@ -0,0 +1,283 @@
// SPDX-FileCopyrightText: 2022 Jonah Brüchert <[email protected]>
//
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "library.h"
#include <QStandardPaths>
#include <QDir>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QStringBuilder>
#include "asyncdatabase.h"
namespace ranges = std::ranges;
Library::Library(QObject *parent)
: QObject{parent}
, m_database(ThreadedDatabase::establishConnection([]() -> DatabaseConfiguration {
DatabaseConfiguration config;
config.setDatabaseName(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) % QDir::separator() % "library.sqlite");
config.setType(DATABASE_TYPE_SQLITE);
return config;
}()))
{
m_database->runMigrations(":/migrations/");
}
Library::~Library() = default;
Library &Library::instance()
{
static Library inst;
return inst;
}
FavouritesModel *Library::favourites()
{
auto future = mapFuture<Rows, std::vector<Song>>(
m_database->getResults("select * from favourites natural join songs"),
[](auto rows)
{
std::vector<Song> songs;
ranges::transform(parseRows<QString, QString, QString, QString>(rows),
std::back_inserter(songs),
Song::fromSql);
return songs;
});
return new FavouritesModel(std::move(future), this);
}
void Library::addFavourite(const QString &videoId, const QString &title)
{
connectFuture(addSong(videoId, title), this, [=, this] {
connectFuture(m_database->execute("insert or ignore into favourites (video_id) values (?)", videoId), this, &Library::favouritesChanged);
});
}
void Library::removeFavourite(const QString &videoId)
{
connectFuture(m_database->execute("delete from favourites where video_id = ?", videoId), this, &Library::favouritesChanged);
}
FavouriteWatcher *Library::favouriteWatcher(const QString &videoId)
{
if (videoId.isEmpty()) {
return nullptr;
}
return new FavouriteWatcher(this, videoId);
}
SearchHistoryModel *Library::searches()
{
return new SearchHistoryModel(mapFuture<Rows, std::vector<QString>>(
m_database->getResults("select (search_query) from searches order by search_id desc"),
[](const auto &rows)
{
std::vector<QString> out;
ranges::transform(rows, std::back_inserter(out), [](auto row) {
return row[0].toString();
});
return out;
}), this);
}
void Library::addSearch(const QString &text)
{
connectFuture(m_database->execute("insert into searches (search_query) values (?)", text), this, &Library::searchesChanged);
}
PlaybackHistoryModel *Library::playbackHistory()
{
auto future = mapFuture<Rows, std::vector<PlayedSong>>(
m_database->getResults("select * from played_songs natural join songs"),
[](auto rows)
{
std::vector<PlayedSong> playedSongs;
ranges::transform(parseRows<QString, int, QString, QString, QString>(rows),
std::back_inserter(playedSongs),
PlayedSong::fromSql);
return playedSongs;
});
return new PlaybackHistoryModel(std::move(future), this);
}
void Library::addPlaybackHistoryItem(const QString &videoId, const QString &title)
{
connectFuture(addSong(videoId, title), this, [=, this] {
connectFuture(m_database->execute("insert or ignore into played_songs (video_id, plays) values (?, ?)", videoId, 0), this, [=, this] {
connectFuture(m_database->execute("update played_songs set plays = plays + 1 where video_id = ? ", videoId), this, &Library::playbackHistoryChanged);
});
});
}
PlaybackHistoryModel *Library::mostPlayed()
{
auto future = mapFuture<Rows, std::vector<PlayedSong>>(
m_database->getResults("select * from played_songs natural join songs order by plays desc limit 10"),
[](auto rows)
{
std::vector<PlayedSong> playedSongs;
ranges::transform(parseRows<QString, int, QString, QString, QString>(rows),
std::back_inserter(playedSongs),
PlayedSong::fromSql);
return playedSongs;
});
return new PlaybackHistoryModel(std::move(future), this);
}
QNetworkAccessManager &Library::nam()
{
return m_networkImageCacher;
}
QFuture<void> Library::addSong(const QString &videoId, const QString &title)
{
return m_database->execute("insert or ignore into songs (video_id, title, artist, album) values (?, ?, null, null)", videoId, title);
}
void ThumbnailSource::setVideoId(const QString &id) {
if (m_videoId == id) {
return;
}
m_videoId = id;
Q_EMIT videoIdChanged();
const QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) % QDir::separator() % "thumbnails";
QDir(cacheDir).mkpath(QStringLiteral("."));
const QString cacheLocation = cacheDir % QDir::separator() % id % ".webp";
if (QFile::exists(cacheLocation)) {
setCachedPath(QUrl::fromLocalFile(cacheLocation));
return;
}
auto future = YTMusicThread::instance()->extractVideoInfo(id);
connectFuture(future, this, [this, id, cacheLocation](video_info::VideoInfo &&info) {
auto *reply = Library::instance().nam().get(QNetworkRequest(QUrl(QString::fromStdString(info.thumbnail))));
connect(reply, &QNetworkReply::finished, this, [id, reply, this, cacheLocation]() {
QFile file(cacheLocation);
file.open(QFile::WriteOnly);
file.write(reply->readAll());
setCachedPath(QUrl::fromLocalFile(cacheLocation));
});
});
}
PlaybackHistoryModel::PlaybackHistoryModel(QFuture<std::vector<PlayedSong> > &&songs, QObject *parent)
: QAbstractListModel(parent)
{
connectFuture(songs, this, [this](const auto songs) {
beginResetModel();
m_playedSongs = songs;
endResetModel();
});
}
QHash<int, QByteArray> PlaybackHistoryModel::roleNames() const {
return {
{Roles::VideoId, "videoId"},
{Roles::Title, "title"},
{Roles::Artist, "artist"},
{Roles::Plays, "plays"}
};
}
int PlaybackHistoryModel::rowCount(const QModelIndex &parent) const {
return parent.isValid() ? 0 : m_playedSongs.size();
}
QVariant PlaybackHistoryModel::data(const QModelIndex &index, int role) const {
switch (role) {
case Roles::VideoId:
return m_playedSongs.at(index.row()).videoId;
case Roles::Title:
return m_playedSongs.at(index.row()).title;
case Roles::Artist:
return m_playedSongs.at(index.row()).artist;
case Roles::Plays:
return m_playedSongs.at(index.row()).plays;
}
Q_UNREACHABLE();
}
FavouritesModel::FavouritesModel(QFuture<std::vector<Song>> &&songs, QObject *parent)
: QAbstractListModel(parent)
{
connectFuture(songs, this, [this](const auto songs) {
beginResetModel();
m_favouriteSongs = songs;
endResetModel();
});
}
QHash<int, QByteArray> FavouritesModel::roleNames() const {
return {
{Roles::VideoId, "videoId"},
{Roles::Title, "title"},
{Roles::Artist, "artist"}
};
}
int FavouritesModel::rowCount(const QModelIndex &parent) const {
return parent.isValid() ? 0 : m_favouriteSongs.size();
}
QVariant FavouritesModel::data(const QModelIndex &index, int role) const {
switch (role) {
case Roles::VideoId:
return m_favouriteSongs.at(index.row()).videoId;
case Roles::Title:
return m_favouriteSongs.at(index.row()).title;
case Roles::Artist:
return m_favouriteSongs.at(index.row()).artist;
}
Q_UNREACHABLE();
}
FavouriteWatcher::FavouriteWatcher(Library *library, const QString &videoId)
: QObject(library), m_videoId(videoId), m_library(library)
{
auto update = [this] {
connectFuture(m_library->database().getResult("select count(*) from favourites where video_id = ?", m_videoId), this, [this](auto row) {
if (row) {
auto [favCount] = parseRow<int>(*row);
m_isFavourite = favCount > 0;
Q_EMIT isFavouriteChanged();
}
});
};
update();
connect(library, &Library::favouritesChanged, this, update);
}
bool FavouriteWatcher::isFavourite() const {
return m_isFavourite;
}
SearchHistoryModel::SearchHistoryModel(QFuture<std::vector<QString>> &&historyFuture, QObject *parent)
: QAbstractListModel(parent)
{
connectFuture(historyFuture, this, [this](const auto history) {
beginResetModel();
m_history = history;
endResetModel();
});
}
int SearchHistoryModel::rowCount(const QModelIndex &parent) const {
return parent.isValid() ? 0 : m_history.size();
}
QVariant SearchHistoryModel::data(const QModelIndex &index, int role) const {
switch (role) {
case Qt::DisplayRole:
return m_history[index.row()];
}
Q_UNREACHABLE();
}
+183
View File
@@ -0,0 +1,183 @@
// SPDX-FileCopyrightText: 2022 Jonah Brüchert <[email protected]>
//
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <QObject>
#include <QQuickAsyncImageProvider>
#include <QNetworkAccessManager>
#include <QAbstractListModel>
#include <memory>
#include "asyncytmusic.h"
#include "asyncdatabase.h"
class FavouriteWatcher;
struct Song {
static Song fromSql(std::tuple<QString, QString, QString, QString> tuple) {
auto [videoId, title, artist, album] = tuple;
return Song {videoId, title, artist, album};
}
QString videoId;
QString title;
QString artist;
QString album;
};
class FavouritesModel : public QAbstractListModel {
Q_OBJECT
enum Roles {
VideoId = Qt::UserRole + 1,
Title,
Artist,
};
public:
FavouritesModel(QFuture<std::vector<Song>> &&songs, QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent) const override;
QVariant data(const QModelIndex &index, int role) const override;
private:
std::vector<Song> m_favouriteSongs;
};
struct PlayedSong {
static PlayedSong fromSql(std::tuple<QString, int, QString, QString, QString> tuple) {
auto [videoId, plays, title, artist, album] = tuple;
return PlayedSong {videoId, title, artist, album, plays};
}
QString videoId;
QString title;
QString artist;
QString album;
int plays;
};
class PlaybackHistoryModel : public QAbstractListModel {
Q_OBJECT
enum Roles {
VideoId = Qt::UserRole + 1,
Title,
Artist,
Plays
};
public:
PlaybackHistoryModel(QFuture<std::vector<PlayedSong>> &&songs, QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent) const override;
QVariant data(const QModelIndex &index, int role) const override;
private:
std::vector<PlayedSong> m_playedSongs;
};
class SearchHistoryModel : public QAbstractListModel {
Q_OBJECT
public:
SearchHistoryModel(QFuture<std::vector<QString> > &&historyFuture, QObject *parent = nullptr);
int rowCount(const QModelIndex &parent) const override;
QVariant data(const QModelIndex &index, int role) const override;
private:
std::vector<QString> m_history;
};
class Library : public QObject
{
Q_OBJECT
Q_PROPERTY(QAbstractListModel *favourites READ favourites NOTIFY favouritesChanged)
Q_PROPERTY(QAbstractListModel *searches READ searches NOTIFY searchesChanged)
Q_PROPERTY(QAbstractListModel *playbackHistory READ playbackHistory NOTIFY playbackHistoryChanged)
Q_PROPERTY(QAbstractListModel *mostPlayed READ mostPlayed NOTIFY playbackHistoryChanged)
public:
explicit Library(QObject *parent = nullptr);
~Library();
static Library &instance();
FavouritesModel *favourites();
Q_SIGNAL void favouritesChanged();
Q_INVOKABLE void addFavourite(const QString &videoId, const QString &title);
Q_INVOKABLE void removeFavourite(const QString &videoId);
Q_INVOKABLE FavouriteWatcher *favouriteWatcher(const QString &videoId);
SearchHistoryModel *searches();
Q_SIGNAL void searchesChanged();
Q_INVOKABLE void addSearch(const QString &text);
PlaybackHistoryModel *playbackHistory();
Q_SIGNAL void playbackHistoryChanged();
Q_INVOKABLE void addPlaybackHistoryItem(const QString &videoId, const QString &title);
PlaybackHistoryModel *mostPlayed();
QNetworkAccessManager &nam();
ThreadedDatabase &database() {
return *m_database;
}
private:
QFuture<void> addSong(const QString &videoId, const QString &title);
QNetworkAccessManager m_networkImageCacher;
std::unique_ptr<ThreadedDatabase> m_database;
};
class ThumbnailSource : public QObject {
Q_OBJECT
Q_PROPERTY(QString videoId READ videoId WRITE setVideoId NOTIFY videoIdChanged)
Q_PROPERTY(QUrl cachedPath READ cachedPath NOTIFY cachedPathChanged)
QString videoId() const {
return m_videoId;
}
void setVideoId(const QString &id);
Q_SIGNAL void videoIdChanged();
QUrl cachedPath() const {
return m_cachedPath;
}
void setCachedPath(const QUrl &path) {
m_cachedPath = path;
cachedPathChanged();
}
Q_SIGNAL void cachedPathChanged();
private:
QString m_videoId;
QUrl m_cachedPath;
};
class FavouriteWatcher : public QObject {
Q_OBJECT
Q_PROPERTY(bool isFavourite READ isFavourite NOTIFY isFavouriteChanged)
public:
FavouriteWatcher(Library *library, const QString &videoId);
bool isFavourite() const;
Q_SIGNAL void isFavouriteChanged();
Q_SIGNAL void videoIdChanged();
private:
QString m_videoId;
Library *m_library;
bool m_isFavourite = false;
};
+6
View File
@@ -19,6 +19,9 @@
#include "errorhandler.h"
#include "playlistmodel.h"
#include "playerutils.h"
#include "library.h"
#include "asyncdatabase.h"
constexpr auto URI = "org.kde.ytmusic";
@@ -51,6 +54,9 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
qmlRegisterSingletonType<PlayerUtils>(URI, 1, 0, "PlayerUtils", [](QQmlEngine *, QJSEngine *) {
return new PlayerUtils();
});
qmlRegisterSingletonInstance<Library>(URI, 1, 0, "Library", &Library::instance());
qmlRegisterType<ThumbnailSource>(URI, 1, 0, "ThumbnailSource");
qmlRegisterAnonymousType<FavouriteWatcher>(URI, 1);
engine.rootContext()->setContextObject(new KLocalizedContext(&engine));
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
View File
Whitespace-only changes.
@@ -0,0 +1,8 @@
-- SPDX-FileCopyrightText: 2022 Jonah Brüchert <[email protected]>
--
-- SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
drop table songs;
drop table played_songs;
drop table favourites;
drop table searches;
@@ -0,0 +1,26 @@
-- SPDX-FileCopyrightText: 2022 Jonah Brüchert <[email protected]>
--
-- SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
create table songs (
video_id Text primary key not null,
title Text not null,
artist Text,
album Text
);
create table played_songs (
video_id Text primary key not null,
plays Int not null,
foreign key (video_id) references songs(video_id)
);
create table favourites (
video_id Text primary key not null,
foreign key (video_id) references songs(video_id)
);
create table searches (
search_id integer primary key autoincrement not null,
search_query Text not null
);
+49 -52
View File
@@ -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;
};
+11 -11
View File
@@ -13,19 +13,19 @@
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) {
setLoading(false);
beginResetModel();
m_playlist = playlist;
std::sort(m_playlist.thumbnails.begin(), m_playlist.thumbnails.end());
endResetModel();
auto future = YTMusicThread::instance()->fetchPlaylist(m_playlistId);
connectFuture(future, this, [=, this](const playlist::Playlist &playlist) {
setLoading(false);
beginResetModel();
m_playlist = playlist;
std::sort(m_playlist.thumbnails.begin(), m_playlist.thumbnails.end());
endResetModel();
Q_EMIT titleChanged();
Q_EMIT thumbnailUrlChanged();
Q_EMIT titleChanged();
Q_EMIT thumbnailUrlChanged();
});
});
}
+5 -4
View File
@@ -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 {};
+4
View File
@@ -12,5 +12,9 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
<file alias="PlayerFooter.qml">contents/ui/PlayerFooter.qml</file>
<file alias="SearchPage.qml">contents/ui/SearchPage.qml</file>
<file alias="PlaylistPage.qml">contents/ui/PlaylistPage.qml</file>
<file alias="ConfirmationMessage.qml">contents/ui/ConfirmationMessage.qml</file>
<file alias="LibraryPage.qml">contents/ui/LibraryPage.qml</file>
<file>migrations/2022-05-20-194850_init/down.sql</file>
<file>migrations/2022-05-20-194850_init/up.sql</file>
</qresource>
</RCC>
+30 -9
View File
@@ -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();
@@ -16,15 +16,15 @@ SearchModel::SearchModel(QObject *parent)
}
setLoading(true);
YTMusicThread::instance()->search(m_searchQuery);
auto future = YTMusicThread::instance()->search(m_searchQuery);
connectFuture(future, this, [=, this](const std::vector<search::SearchResultItem> &results) {
beginResetModel();
setLoading(false);
m_searchResults = results;
endResetModel();
});
});
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::searchFinished, this, [=](const std::vector<search::SearchResultItem> &results) {
beginResetModel();
setLoading(false);
m_searchResults = results;
endResetModel();
});
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::errorOccurred, this, [=] {
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::errorOccurred, this, [this] {
setLoading(false);
});
}
@@ -165,3 +165,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);
}
+1
View File
@@ -55,4 +55,5 @@ public:
private:
QString m_searchQuery;
std::vector<search::SearchResultItem> m_searchResults;
static int itemType(search::SearchResultItem const &item);
};
+26 -23
View File
@@ -9,29 +9,14 @@
#include <random>
#include <QRandomGenerator>
#include <QFutureWatcher>
#include "playlistutils.h"
UserPlaylistModel::UserPlaylistModel(QObject *parent)
: AbstractYTMusicModel(parent)
{
connect(this, &UserPlaylistModel::initialVideoIdChanged, this, [=] {
if (m_initialVideoId.isEmpty()) {
return;
}
setLoading(true);
YTMusicThread::instance()->fetchWatchPlaylist(m_initialVideoId);
});
connect(this, &UserPlaylistModel::playlistIdChanged, this, [=] {
if (m_playlistId.isEmpty()) {
return;
}
setLoading(true);
YTMusicThread::instance()->fetchWatchPlaylist(std::nullopt, m_playlistId);
});
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::fetchWatchPlaylistFinished, this, [=](const watch::Playlist &playlist) {
auto handleResult = [=, this](const watch::Playlist &playlist) {
setLoading(false);
beginResetModel();
@@ -48,8 +33,26 @@ UserPlaylistModel::UserPlaylistModel(QObject *parent)
} else {
setCurrentVideoId({});
}
};
connect(this, &UserPlaylistModel::initialVideoIdChanged, this, [=, this] {
if (m_initialVideoId.isEmpty()) {
return;
}
setLoading(true);
auto future = YTMusicThread::instance()->fetchWatchPlaylist(m_initialVideoId);
connectFuture(future, this, handleResult);
});
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::errorOccurred, this, [=] {
connect(this, &UserPlaylistModel::playlistIdChanged, this, [=, this] {
if (m_playlistId.isEmpty()) {
return;
}
setLoading(true);
auto future = YTMusicThread::instance()->fetchWatchPlaylist(std::nullopt, m_playlistId);
connectFuture(future, this, handleResult);;
});
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::errorOccurred, this, [this] {
setLoading(false);
});
}
@@ -101,7 +104,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 +132,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 +160,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 +242,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 +260,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();
});
+24 -22
View File
@@ -4,12 +4,15 @@
#include "videoinfoextractor.h"
#include <asyncytmusic.h>
#include <QFutureWatcher>
#include "asyncytmusic.h"
#include "library.h"
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();
@@ -20,23 +23,23 @@ VideoInfoExtractor::VideoInfoExtractor(QObject *parent)
}
setLoading(true);
YTMusicThread::instance()->extractVideoInfo(QString::fromStdString(m_videoId.toStdString()));
});
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::extractVideoInfoFinished, this, [=](const video_info::VideoInfo &info) {
m_videoInfo = info;
setLoading(false);
Q_EMIT audioUrlChanged();
Q_EMIT titleChanged();
Q_EMIT songChanged();
Q_EMIT thumbnailChanged();
auto future = YTMusicThread::instance()->extractVideoInfo(QString::fromStdString(m_videoId.toStdString()));
connectFuture(future, this, [this](const video_info::VideoInfo &videoInfo) {
m_videoInfo = videoInfo;
setLoading(false);
Q_EMIT audioUrlChanged();
Q_EMIT titleChanged();
Q_EMIT songChanged();
Q_EMIT thumbnailChanged();
});
});
}
QUrl VideoInfoExtractor::audioUrl() const
{
if (m_videoInfo.formats.empty()) {
return QUrl();
return {};
}
std::vector<video_info::Format> audioFormats;
@@ -48,17 +51,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));
}
@@ -70,6 +69,9 @@ QString VideoInfoExtractor::videoId() const
void VideoInfoExtractor::setVideoId(const QString &videoId)
{
if (m_videoId == videoId || videoId.isEmpty()) {
return;
}
m_videoId = videoId;
Q_EMIT videoIdChanged();
}
@@ -81,7 +83,7 @@ QString VideoInfoExtractor::title() const
QString VideoInfoExtractor::thumbnail() const
{
return QString::fromStdString(m_videoInfo.thumnnail);
return QString::fromStdString(m_videoInfo.thumbnail);
}
bool VideoInfoExtractor::loading() const
+51 -43
View File
@@ -13,9 +13,15 @@ namespace py = pybind11;
using namespace py::literals;
/// Useful for debugging
void pyPrintPretty(py::handle obj) {
auto json = py::module::import("json");
py::print(json.attr("dumps")(obj, "indent"_a=py::int_(4)));
}
#define UNEXPORT __attribute__ ((visibility("hidden")))
constexpr auto TESTED_YTMUSICAPI_VERSION = "0.18.0";
constexpr auto TESTED_YTMUSICAPI_VERSION = "0.21.0";
struct UNEXPORT YTMusicPrivate {
py::scoped_interpreter guard {};
@@ -47,7 +53,7 @@ struct UNEXPORT YTMusicPrivate {
py::object get_ytdl() {
// lazy initialization
if (ytdl.is_none()) {
ytdl = py::module::import("youtube_dl").attr("YoutubeDL")(py::dict());
ytdl = py::module::import("yt_dlp").attr("YoutubeDL")(py::dict());
}
return ytdl;
@@ -64,7 +70,7 @@ std::optional<T> optional_key(py::handle obj, const char *name) {
return std::nullopt;
}
return obj[name].cast<T>();
return obj[name].cast<std::optional<T>>();
}
meta::Thumbnail extract_thumbnail(py::handle thumbnail) {
@@ -82,28 +88,10 @@ meta::Artist extract_meta_artist(py::handle artist) {
};
};
album::Track extract_album_track(py::handle track) {
return {
track["index"].cast<std::string>(),
track["title"].cast<std::string>(),
track["artists"].cast<std::string>(),
track["videoId"].cast<std::optional<std::string>>(), // E rated songs don't have a videoId
track["lengthMs"].cast<std::optional<std::string>>(), //
track["likeStatus"].cast<std::optional<std::string>>()
};
}
video_info::Format extract_format(py::handle format) {
return {
optional_key<int>(format, "quality"),
format["url"].cast<std::string>(),
format["vcodec"].cast<std::string>(),
format["acodec"].cast<std::string>()
};
}
playlist::Track extract_playlist_track(py::handle track);
watch::Playlist::Track extract_watch_track(py::handle track);
album::Track extract_album_track(py::handle track);
video_info::Format extract_format(py::handle format);
template <typename T>
inline auto extract_py_list(py::handle obj) {
@@ -135,6 +123,27 @@ inline auto extract_py_list(py::handle obj) {
return output;
}
album::Track extract_album_track(py::handle track) {
return {
track["isExplicit"].cast<bool>(),
track["title"].cast<std::string>(),
extract_py_list<meta::Artist>(track["artists"]),
track["album"].cast<std::optional<std::string>>(),
track["videoId"].cast<std::optional<std::string>>(), // E rated songs don't have a videoId
track["duration"].cast<std::optional<std::string>>(), //
track["likeStatus"].cast<std::optional<std::string>>()
};
}
video_info::Format extract_format(py::handle format) {
return {
optional_key<int>(format, "quality"),
format["url"].cast<std::string>(),
format["vcodec"].cast<std::string>(),
format["acodec"].cast<std::string>()
};
}
meta::Album extract_meta_album(py::handle album) {
return meta::Album {
album["name"].cast<std::string>(),
@@ -280,7 +289,7 @@ std::optional<search::SearchResultItem> extract_search_result(py::handle result)
result["title"].cast<std::string>(),
result["type"].cast<std::string>(),
extract_py_list<meta::Artist>(result["artists"]),
result["year"].cast<std::string>(),
result["year"].cast<std::optional<std::string>>(),
result["isExplicit"].cast<bool>()
};
} else if (resultType == "playlist") {
@@ -299,7 +308,8 @@ std::optional<search::SearchResultItem> extract_search_result(py::handle result)
};
} else {
std::cerr << "Warning: Unsupported search result type found" << std::endl;
std::cerr << "It's called: " << resultType;
std::cerr << "It's called: " << resultType << std::endl;
pyPrintPretty(result);
return std::nullopt;
}
}
@@ -324,10 +334,11 @@ YTMusic::~YTMusic() = default;
std::vector<search::SearchResultItem> YTMusic::search(
const std::string &query,
const std::optional<std::string> &filter,
const std::optional<std::string> &scope,
const int limit,
const bool ignore_spelling) const
{
const auto results = d->get_ytmusic().attr("search")(query, filter, limit, ignore_spelling).cast<py::list>();
const auto results = d->get_ytmusic().attr("search")("query"_a=query, "filter"_a=filter, "scope"_a=scope, "limit"_a = limit, "ignore_spelling"_a = ignore_spelling).cast<py::list>();
std::vector<search::SearchResultItem> output;
for (const auto &result : results) {
@@ -343,7 +354,7 @@ artist::Artist YTMusic::get_artist(const std::string &channel_id) const
{
const auto artist = d->get_ytmusic().attr("get_artist")(channel_id);
return artist::Artist {
artist["description"].cast<std::optional<std::string>>(),
optional_key<std::string>(artist, "description"),
artist["views"].cast<std::optional<std::string>>(),
artist["name"].cast<std::string>(),
artist["channelId"].cast<std::string>(),
@@ -360,31 +371,28 @@ artist::Artist YTMusic::get_artist(const std::string &channel_id) const
album::Album YTMusic::get_album(const std::string &browseId) const
{
const auto album = d->get_ytmusic().attr("get_album")(browseId);
return {
album["title"].cast<std::string>(),
album["trackCount"].cast<std::string>(),
album["durationMs"].cast<std::string>(),
album["playlistId"].cast<std::string>(),
[&]() -> album::Album::ReleaseDate {
const auto date = album["releaseDate"];
return {
date["year"].cast<int>(),
date["month"].cast<int>(),
date["day"].cast<int>()
};
}(),
album["description"].cast<std::string>(),
album["trackCount"].cast<int>(),
album["duration"].cast<std::string>(),
album["audioPlaylistId"].cast<std::string>(),
album["year"].cast<std::string>(),
optional_key<std::string>(album, "description"),
extract_py_list<meta::Thumbnail>(album["thumbnails"]),
extract_py_list<album::Track>(album["tracks"])
extract_py_list<album::Track>(album["tracks"]),
extract_py_list<meta::Artist>(album["artists"])
};
}
song::Song YTMusic::get_song(const std::string &video_id) const
std::optional<song::Song> YTMusic::get_song(const std::string &video_id) const
{
const auto song = d->get_ytmusic().attr("get_song")(video_id);
return {
if (!song.cast<py::dict>().contains("videoId")) {
return std::nullopt;
}
return song::Song {
song["videoId"].cast<std::string>(),
song["title"].cast<std::string>(),
song["lengthSeconds"].cast<std::string>(),
+15 -12
View File
@@ -67,7 +67,7 @@ struct Album {
std::string title;
std::string type;
std::vector<meta::Artist> artists;
std::string year;
std::optional<std::string> year;
bool is_explicit;
};
@@ -122,7 +122,7 @@ struct Artist {
struct Single {
std::string title;
std::vector<meta::Thumbnail> thumnnails;
std::vector<meta::Thumbnail> thumbnails;
std::string year;
std::string browse_id;
};
@@ -143,11 +143,12 @@ struct Artist {
namespace album {
struct Track {
std::string index;
bool is_explicit;
std::string title;
std::string artists;
std::vector<meta::Artist> artists;
std::optional<std::string> album;
std::optional<std::string> video_id;
std::optional<std::string> length_ms;
std::optional<std::string> duration;
std::optional<std::string> like_status;
};
@@ -159,13 +160,14 @@ namespace album {
};
std::string title;
std::string track_count;
std::string duration_ms;
std::string playlist_id;
ReleaseDate release_date;
std::string description;
int track_count;
std::string duration;
std::string audio_playlist_id;
std::string year;
std::optional<std::string> description;
std::vector<meta::Thumbnail> thumbnails;
std::vector<Track> tracks;
std::vector<meta::Artist> artists;
};
}
@@ -240,7 +242,7 @@ struct VideoInfo {
std::string id;
std::string title;
std::vector<Format> formats;
std::string thumnnail;
std::string thumbnail;
// More, but not interesting for us right now
};
@@ -277,6 +279,7 @@ public:
std::vector<search::SearchResultItem> search(const std::string &query,
const std::optional<std::string> &filter = std::nullopt,
const std::optional<std::string> &scope = std::nullopt,
const int limit = 20,
const bool ignore_spelling = false) const;
@@ -287,7 +290,7 @@ public:
album::Album get_album(const std::string &browse_id) const;
/// https://ytmusicapi.readthedocs.io/en/latest/reference.html#ytmusicapi.YTMusic.get_song
song::Song get_song(const std::string &video_id) const;
std::optional<song::Song> get_song(const std::string &video_id) const;
/// https://ytmusicapi.readthedocs.io/en/latest/reference.html#ytmusicapi.YTMusic.get_playlist
playlist::Playlist get_playlist(const std::string &playlist_id, int limit = 100) const;