Compare commits
129
Commits
search_header
...
v22.06
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
303ce7c816 | ||
|
|
e7cbd07b80 | ||
|
|
923ae5fc96 | ||
|
|
51a3b3c68e | ||
|
|
ef1582c67c | ||
|
|
2091e39b90 | ||
|
|
ab02e72f1f | ||
|
|
d498d6a407 | ||
|
|
6dc6e9538c | ||
|
|
3b0cbefe85 | ||
|
|
38e152fc52 | ||
|
|
0f111b43be | ||
|
|
ffbeab0b14 | ||
|
|
8860b62eec | ||
|
|
93433d8714 | ||
|
|
211a0c6f93 | ||
|
|
aadfc71200 | ||
|
|
1532adedb1 | ||
|
|
16ce656201 | ||
|
|
a398723bdb | ||
|
|
f7a4788e73 | ||
|
|
15425c7b01 | ||
|
|
96c2a505a7 | ||
|
|
b58e3fc2d4 | ||
|
|
4b1cbf11ad | ||
|
|
1037ac8c02 | ||
|
|
572f3c4c94 | ||
|
|
e48d6c8bb6 | ||
|
|
d717e7dff9 | ||
|
|
cb16ebb812 | ||
|
|
2c1df5e9e4 | ||
|
|
c3873d05ac | ||
|
|
4fca0ca0c5 | ||
|
|
c02f369a0f | ||
|
|
2e68ec0880 | ||
|
|
c7b22b348e | ||
|
|
f4c8172201 | ||
|
|
9fa7757efd | ||
|
|
fc2c359bd7 | ||
|
|
f17987b3e7 | ||
|
|
7c2bbf3a98 | ||
|
|
4320921a96 | ||
|
|
306a4c866c | ||
|
|
2bfd142300 | ||
|
|
bec70c37db | ||
|
|
a7e9588a90 | ||
|
|
463bdde0a0 | ||
|
|
c83d7f582b | ||
|
|
62385df312 | ||
|
|
dad015195b | ||
|
|
b89fed88ee | ||
|
|
613dd1eb51 | ||
|
|
b7754e7d60 | ||
|
|
644c343e95 | ||
|
|
c9d530be76 | ||
|
|
aa0268694f | ||
|
|
8e873e726d | ||
|
|
dca3373056 | ||
|
|
6d57cad22c | ||
|
|
b67fdc384f | ||
|
|
32f98a7c9b | ||
|
|
2b8ac69916 | ||
|
|
ad6fe5e633 | ||
|
|
1ad6586a07 | ||
|
|
0249604199 | ||
|
|
1a93e0d944 | ||
|
|
347613a7f2 | ||
|
|
424c67a268 | ||
|
|
64d7a911fa | ||
|
|
961dca328d | ||
|
|
433d95969c | ||
|
|
15f439c38e | ||
|
|
ca4d5d8b77 | ||
|
|
06338900dc | ||
|
|
8ec674fbef | ||
|
|
58eefc29eb | ||
|
|
9312331755 | ||
|
|
1d25f46f52 | ||
|
|
a775cd58be | ||
|
|
ae5ed3b10b | ||
|
|
8aab91568e | ||
|
|
6647346314 | ||
|
|
bfb41310ad | ||
|
|
c3371adda4 | ||
|
|
9bcf017c8d | ||
|
|
2c0b13ff9f | ||
|
|
abfd901a82 | ||
|
|
135b8859d6 | ||
|
|
f8637b686c | ||
|
|
53f466c976 | ||
|
|
762976159e | ||
|
|
de1a872f74 | ||
|
|
3f39fc9e90 | ||
|
|
e8e6a5f1e2 | ||
|
|
51a9799281 | ||
|
|
73078eb5e7 | ||
|
|
d138b5c67b | ||
|
|
ef3846df6b | ||
|
|
a31494d83f | ||
|
|
b131fcc7d9 | ||
|
|
e6379db384 | ||
|
|
f50cf92e9a | ||
|
|
e9e0e4f143 | ||
|
|
dfc0b8cbcd | ||
|
|
473b4ee83e | ||
|
|
f40c9fd497 | ||
|
|
0ca21344f8 | ||
|
|
d4d85a8b58 | ||
|
|
cfd4b3de53 | ||
|
|
96c5f344cc | ||
|
|
d6e8ddca11 | ||
|
|
db63766cc7 | ||
|
|
048e876710 | ||
|
|
d7e74e2a5f | ||
|
|
722b78e49b | ||
|
|
734caa0280 | ||
|
|
69901cb69c | ||
|
|
e230adf223 | ||
|
|
07584b73f4 | ||
|
|
1b29a028a6 | ||
|
|
736d1789c9 | ||
|
|
01f22c517a | ||
|
|
edd171c97b | ||
|
|
873c791262 | ||
|
|
068a5b756a | ||
|
|
db35f2a5e6 | ||
|
|
c06bb7ad03 | ||
|
|
1c63395497 | ||
|
|
ba79cd6b5a |
No files matched your search
@@ -1,6 +1,9 @@
|
||||
# SPDX-FileCopyrightText: 2021 Jonah Brüchert <[email protected]>
|
||||
# SPDX-FileCopyrightText: 2021 Bhushan Shah <[email protected]>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
*.kdev4
|
||||
CMakeLists.txt.user
|
||||
flatpak/flatpak-builder-tools/
|
||||
build/
|
||||
+1
-1
@@ -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
@@ -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
|
||||
+11
-3
@@ -8,10 +8,11 @@ project(audiotube)
|
||||
|
||||
include(FeatureSummary)
|
||||
|
||||
set(PROJECT_VERSION "22.06")
|
||||
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)
|
||||
@@ -25,14 +26,21 @@ include(ECMSetupVersion)
|
||||
include(ECMGenerateHeaders)
|
||||
include(ECMPoQmTools)
|
||||
|
||||
ecm_setup_version(${PROJECT_VERSION}
|
||||
VARIABLE_PREFIX AUDIOTUBE
|
||||
VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/version.h
|
||||
)
|
||||
|
||||
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})
|
||||
|
||||
@@ -4,9 +4,9 @@ AudioTube is a client for YouTube Music.
|
||||
|
||||
# Dependencies
|
||||
|
||||
It depends on Qt and Kirigami in addition to a few python packages:
|
||||
It depends on Qt, Kirigami, KI18n, KCoreAddons and KCrash in addition to a few python packages:
|
||||
* ytmusicapi
|
||||
* youtube_dl
|
||||
* yt_dlp
|
||||
|
||||
YouTube uses webp images for the cover art, so you will need to install qtimageformats.
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
SPDX-FileCopyrightText: 2021 Jonah Brüchert <[email protected]>
|
||||
|
||||
SPDX-License-Identifier: CC0-1.0
|
||||
@@ -1,4 +0,0 @@
|
||||
[Project]
|
||||
CreatedFrom=CMakeLists.txt
|
||||
Manager=KDevCMakeManager
|
||||
Name=audiotube
|
||||
@@ -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()
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"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/9a/bf/1bc7e3096ddc2c398b3d04176530a9c02efe08546d9730c9eef0b07a0a43/yt_dlp-2022.5.18-py2.py3-none-any.whl",
|
||||
"sha256": "deec1009442312c1e2ee5298966842194d0e950b433f0d4fc844ef464b9c32a7"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/11/dd/e015f3780f42dd9af62cf0107b44ea1298926627ecd70c17b0e484e95bcd/certifi-2022.5.18.1-py3-none-any.whl",
|
||||
"sha256": "f1d53542ee8cbedbe2118b5686372fb33c297fcd6379b050cca0ef13a597382a"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/f8/a3/622d9acbfb9a71144b5d7609906bc648c62e3ca5fdbb1c8cca222949d82c/websockets-10.3.tar.gz",
|
||||
"sha256": "fc06cc8073c8e87072138ba1e431300e2d408f054b27047d047b549455066ff4"
|
||||
},
|
||||
{
|
||||
"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/2a/18/70c32fe9357f3eea18598b23aa9ed29b1711c3001835f7cf99a9818985d0/Brotli-1.0.9.zip",
|
||||
"sha256": "4d1b810aa0ed773f81dceda2cc7b403d01057458730e309856356d4ef4188438"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/24/40/e249ac3845a2333ce50f1bb02299ffb766babdfe80ca9d31e0158ad06afd/pycryptodomex-3.14.1.tar.gz",
|
||||
"sha256": "2ce76ed0081fd6ac8c74edc75b9d14eca2064173af79843c24fa62573263c1f2"
|
||||
}
|
||||
]
|
||||
}
|
||||
File renamed without changes.
@@ -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/ec/03/062e6444ce4baf1eac17a6a0ebfe36bb1ad05e1df0e20b110de59c278498/urllib3-1.26.9-py2.py3-none-any.whl",
|
||||
"sha256": "44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/ea/b7/e0e3c1c467636186c39925827be42f16fee389dc404ac29e930e9136be70/idna-2.10.tar.gz",
|
||||
"sha256": "b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"
|
||||
"url": "https://files.pythonhosted.org/packages/11/dd/e015f3780f42dd9af62cf0107b44ea1298926627ecd70c17b0e484e95bcd/certifi-2022.5.18.1-py3-none-any.whl",
|
||||
"sha256": "f1d53542ee8cbedbe2118b5686372fb33c297fcd6379b050cca0ef13a597382a"
|
||||
},
|
||||
{
|
||||
"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/06/b3/24afc8868eba069a7f03650ac750a778862dc34941a4bebeb58706715726/charset_normalizer-2.0.12-py3-none-any.whl",
|
||||
"sha256": "6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"
|
||||
},
|
||||
{
|
||||
"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/2d/61/08076519c80041bc0ffa1a8af0cbd3bf3e2b62af10435d269a9d0f40564d/requests-2.27.1-py2.py3-none-any.whl",
|
||||
"sha256": "f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"
|
||||
},
|
||||
{
|
||||
"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/0e/5a/4a5316bddbb697d4b1787159291edfeffd10f73c08b59c7248b15291b601/ytmusicapi-0.22.0-py3-none-any.whl",
|
||||
"sha256": "d042520f3f76eac2c1498130dbc4e8a4af21ca73496590a5ff054fc4da0af1d8"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/1b/8e/2bc4c8160083bbfaac215637f1e9f4a43263833c2e81212cba3073aeaa25/ytmusicapi-0.17.3.tar.gz",
|
||||
"sha256": "9a249cc53efd6408034f401a9a4375272a9b339e8eb64f3a2e713a1ccd06d55b"
|
||||
"url": "https://files.pythonhosted.org/packages/04/a2/d918dcd22354d8958fe113e1a3630137e0fc8b44859ade3063982eacd2a4/idna-3.3-py3-none-any.whl",
|
||||
"sha256": "84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
@@ -1,3 +0,0 @@
|
||||
SPDX-FileCopyrightText: 2021 Mathis Brüchert <[email protected]>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
@@ -7,6 +7,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>
|
||||
@@ -42,16 +49,21 @@ SPDX-License-Identifier: CC0-1.0
|
||||
<summary xml:lang="it">Client YouTube Music convergente</summary>
|
||||
<summary xml:lang="ko">올인원 YouTube Music 클라이언트</summary>
|
||||
<summary xml:lang="nl">Convergente muziekclient van YouTube</summary>
|
||||
<summary xml:lang="pl">Całościowy program do muzyki z YouTube</summary>
|
||||
<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,13 +81,51 @@ 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.06" date="2022-06-24">
|
||||
<url>https://www.plasma-mobile.org/2022/06/28/plasma-mobile-gear-22-06/</url>
|
||||
<description>
|
||||
<ul>
|
||||
<li>Add library page which shows favourites and most played songs</li>
|
||||
<li>Improve layout of the search popup</li>
|
||||
<li>General performance improvements including startup</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<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>
|
||||
</releases>
|
||||
<launchable type="desktop-id">org.kde.audiotube.desktop</launchable>
|
||||
<screenshots>
|
||||
<screenshot>
|
||||
@@ -92,8 +142,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>
|
||||
@@ -104,12 +156,14 @@ SPDX-License-Identifier: CC0-1.0
|
||||
<p xml:lang="it">AudioTube può eseguire ricerche su YouTube Music, elencare album e artisti, riprodurre scalette e album generati automaticamente e ti consente di creare la tua scaletta.</p>
|
||||
<p xml:lang="ko">AudioTube는 YouTube Music을 검색하고, 앨범과 아티스트를 표시하고, 자동 생성된 재생 목록, 앨범을 재생하고, 사용자 정의 재생 목록을 만들 수 있습니다.</p>
|
||||
<p xml:lang="nl">AudioTube kan in YouTube Music zoeken, een lijst maken van albums en artiesten, automatically gegenereerde afspeellijsten, albums afspelen en biedt u het samenstellen van uw eigen afspeellijst.</p>
|
||||
<p xml:lang="pl">AudioTube może wyszukać muzyki na YouTube, wypisać albumy i wykonawców, odtwarzać samoczynnie utworzone listy odtwarzania, albumy oraz umożliwia tworzenie własnych list odtwarzania.</p>
|
||||
<p xml:lang="pl">AudioTube może przeszukiwać serwis YouTube Music, wypisać albumy i wykonawców, odtwarzać samoczynnie utworzone listy odtwarzania, albumy oraz umożliwia tworzenie własnych list odtwarzania.</p>
|
||||
<p xml:lang="pt">O AudioTube consegue pesquisar no YouTube Music, enumerar os álbuns e artistas, reproduzir listas geradas automaticamente, álbuns e permite-lhe reproduzir as suas próprias listas de reprodução.</p>
|
||||
<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>
|
||||
@@ -1,5 +1,7 @@
|
||||
[Desktop Entry]
|
||||
Name=AudioTube
|
||||
Name[ar]=تيوب الصوتي
|
||||
Name[az]=AudioTube
|
||||
Name[ca]=AudioTube
|
||||
Name[ca@valencia]=AudioTube
|
||||
Name[cs]=AudioTube
|
||||
@@ -17,12 +19,17 @@ Name[pa]=ਆਡੀਓ-ਟਿਊਬ
|
||||
Name[pl]=AudioTube
|
||||
Name[pt]=AudioTube
|
||||
Name[pt_BR]=AudioTube
|
||||
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
|
||||
@@ -37,14 +44,17 @@ Comment[it]=Client YouTube Music
|
||||
Comment[ko]=YouTube Music 클라이언트
|
||||
Comment[nl]=Muziekclient van YouTube
|
||||
Comment[pa]=ਯੂਟਿਊਬ ਸੰਗੀਤ ਕਲਾਈਂਟ
|
||||
Comment[pl]=Program do muzyki z YouTube
|
||||
Comment[pl]=Program do YouTube Music
|
||||
Comment[pt]=Cliente do YouTube Music
|
||||
Comment[pt_BR]=Cliente YouTube Music
|
||||
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
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
SPDX-FileCopyrightText: 2021 Jonah Brüchert <[email protected]>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
@@ -1,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",
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
SPDX-FileCopyrightText: 2021 Jonah Brüchert <[email protected]>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
@@ -1,3 +0,0 @@
|
||||
SPDX-FileCopyrightText: 2021 Mathis Brüchert <[email protected]>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
@@ -0,0 +1,199 @@
|
||||
# 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: 2022-05-30 00:47+0000\n"
|
||||
"PO-Revision-Date: 2022-05-28 23:09+0400\n"
|
||||
"Last-Translator: Zayed Al-Saidi <[email protected]>\n"
|
||||
"Language-Team: Arabic <[email protected]>\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/PlaylistPage.qml:19
|
||||
#, kde-format
|
||||
msgid "Play"
|
||||
msgstr "شغّل"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:26 contents/ui/PlaylistPage.qml:26
|
||||
#, kde-format
|
||||
msgid "Shuffle"
|
||||
msgstr "اخلط"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:68 contents/ui/ArtistPage.qml:101
|
||||
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
|
||||
#, kde-format
|
||||
msgid "Play Next"
|
||||
msgstr "شغل التالي"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:73 contents/ui/ArtistPage.qml:107
|
||||
#: contents/ui/PlaybackHistory.qml:79 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/LibraryPage.qml:27
|
||||
#, kde-format
|
||||
msgid "Favourites"
|
||||
msgstr "المفضلة"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:37 contents/ui/LibraryPage.qml:108
|
||||
#, kde-format
|
||||
msgid "Show All"
|
||||
msgstr "أظهر الكلّ"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:42
|
||||
#, kde-format
|
||||
msgid "Favourite Songs"
|
||||
msgstr "الأغاني المفضّلة"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:98
|
||||
#, kde-format
|
||||
msgid "Most played"
|
||||
msgstr "الأكثر تشغيلا"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:113
|
||||
#, kde-format
|
||||
msgid "Played Songs"
|
||||
msgstr "الأغاني المشغلة"
|
||||
|
||||
#: contents/ui/main.qml:39
|
||||
#, kde-format
|
||||
msgid "Back"
|
||||
msgstr "عُد"
|
||||
|
||||
#: contents/ui/main.qml:73 contents/ui/SearchPage.qml:150
|
||||
#, kde-format
|
||||
msgid "Search"
|
||||
msgstr "ابحث"
|
||||
|
||||
#: contents/ui/main.qml:85
|
||||
#, kde-format
|
||||
msgid "AudioTube"
|
||||
msgstr "تيوب الصوتي"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:86
|
||||
#, kde-format
|
||||
msgid "Now Playing"
|
||||
msgstr "المشغل الآن"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:112
|
||||
#, kde-format
|
||||
msgid "Queue"
|
||||
msgstr "صُفّ"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:224
|
||||
#, kde-format
|
||||
msgid "Remove Track"
|
||||
msgstr "أزل المسار"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:244
|
||||
#: contents/ui/MinimizedPlayerControls.qml:96
|
||||
#, kde-format
|
||||
msgid "No media playing"
|
||||
msgstr "لا يوجد وسائط تعمل"
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:15
|
||||
#, kde-format
|
||||
msgid "Unknown list of songs"
|
||||
msgstr "قائمة غير معروفة من الأغاني"
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:25
|
||||
#, kde-format
|
||||
msgid "No songs here yet"
|
||||
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 "اعثر على موسيقى على موسيقى يوتيوب"
|
||||
|
||||
#~ msgid "Clear"
|
||||
#~ msgstr "امسح"
|
||||
|
||||
#~ msgid "Pause"
|
||||
#~ msgstr "ألبث"
|
||||
|
||||
#~ msgid "Expand"
|
||||
#~ msgstr "وسع"
|
||||
@@ -0,0 +1,200 @@
|
||||
# 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: 2022-05-30 00:47+0000\n"
|
||||
"PO-Revision-Date: 2022-06-19 12:16+0400\n"
|
||||
"Last-Translator: Kheyyam <[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 22.04.2\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/PlaylistPage.qml:19
|
||||
#, kde-format
|
||||
msgid "Play"
|
||||
msgstr "Başladın"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:26 contents/ui/PlaylistPage.qml:26
|
||||
#, kde-format
|
||||
msgid "Shuffle"
|
||||
msgstr "Qarışıq"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:68 contents/ui/ArtistPage.qml:101
|
||||
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
|
||||
#, kde-format
|
||||
msgid "Play Next"
|
||||
msgstr "Sonrakını oxudun"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:73 contents/ui/ArtistPage.qml:107
|
||||
#: contents/ui/PlaybackHistory.qml:79 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/LibraryPage.qml:27
|
||||
#, kde-format
|
||||
msgid "Favourites"
|
||||
msgstr "Süçilmişlər"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:37 contents/ui/LibraryPage.qml:108
|
||||
#, kde-format
|
||||
msgid "Show All"
|
||||
msgstr "Hamısı göstərilsin"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:42
|
||||
#, kde-format
|
||||
msgid "Favourite Songs"
|
||||
msgstr "Seçilmiş mahnılar"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:98
|
||||
#, kde-format
|
||||
msgid "Most played"
|
||||
msgstr "Daha çox dinlənilən"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:113
|
||||
#, kde-format
|
||||
msgid "Played Songs"
|
||||
msgstr "Oxudulan mahnılar"
|
||||
|
||||
#: contents/ui/main.qml:39
|
||||
#, kde-format
|
||||
msgid "Back"
|
||||
msgstr "Geriyə"
|
||||
|
||||
#: contents/ui/main.qml:73 contents/ui/SearchPage.qml:150
|
||||
#, kde-format
|
||||
msgid "Search"
|
||||
msgstr "Axtarın"
|
||||
|
||||
#: contents/ui/main.qml:85
|
||||
#, kde-format
|
||||
msgid "AudioTube"
|
||||
msgstr "AudioTube"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:86
|
||||
#, kde-format
|
||||
msgid "Now Playing"
|
||||
msgstr "İndi səslənir"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:112
|
||||
#, kde-format
|
||||
msgid "Queue"
|
||||
msgstr "Növbə"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:224
|
||||
#, kde-format
|
||||
msgid "Remove Track"
|
||||
msgstr "Mahnını silin"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:244
|
||||
#: contents/ui/MinimizedPlayerControls.qml:96
|
||||
#, kde-format
|
||||
msgid "No media playing"
|
||||
msgstr "Media faylı oynadılmır"
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:15
|
||||
#, kde-format
|
||||
msgid "Unknown list of songs"
|
||||
msgstr "Naməlum mahnı siyahısı"
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:25
|
||||
#, kde-format
|
||||
msgid "No songs here yet"
|
||||
msgstr "Hələlik burada mahnılar yoxdur"
|
||||
|
||||
#: 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"
|
||||
|
||||
#~ msgid "Clear"
|
||||
#~ msgstr "Təmizləyin"
|
||||
|
||||
#~ msgid "Pause"
|
||||
#~ msgstr "Fasilə"
|
||||
|
||||
#~ msgid "Expand"
|
||||
#~ msgstr "Açmaq"
|
||||
@@ -0,0 +1,194 @@
|
||||
# Translation of audiotube.po to Catalan
|
||||
# Copyright (C) 2021-2022 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.
|
||||
# Josep M. Ferrer <[email protected]>, 2022.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: audiotube\n"
|
||||
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
|
||||
"POT-Creation-Date: 2022-05-30 00:47+0000\n"
|
||||
"PO-Revision-Date: 2022-05-26 09:09+0200\n"
|
||||
"Last-Translator: Josep M. Ferrer <[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 20.12.0\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/PlaylistPage.qml:19
|
||||
#, kde-format
|
||||
msgid "Play"
|
||||
msgstr "Reprodueix"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:26 contents/ui/PlaylistPage.qml:26
|
||||
#, kde-format
|
||||
msgid "Shuffle"
|
||||
msgstr "Barreja"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:68 contents/ui/ArtistPage.qml:101
|
||||
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
|
||||
#, kde-format
|
||||
msgid "Play Next"
|
||||
msgstr "Reprodueix el següent"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:73 contents/ui/ArtistPage.qml:107
|
||||
#: contents/ui/PlaybackHistory.qml:79 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/LibraryPage.qml:27
|
||||
#, kde-format
|
||||
msgid "Favourites"
|
||||
msgstr "Preferides"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:37 contents/ui/LibraryPage.qml:108
|
||||
#, kde-format
|
||||
msgid "Show All"
|
||||
msgstr "Mostra-ho tot"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:42
|
||||
#, kde-format
|
||||
msgid "Favourite Songs"
|
||||
msgstr "Cançons preferides"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:98
|
||||
#, kde-format
|
||||
msgid "Most played"
|
||||
msgstr "Les més reproduïdes"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:113
|
||||
#, kde-format
|
||||
msgid "Played Songs"
|
||||
msgstr "Cançons reproduïdes"
|
||||
|
||||
#: contents/ui/main.qml:39
|
||||
#, kde-format
|
||||
msgid "Back"
|
||||
msgstr "Enrere"
|
||||
|
||||
#: contents/ui/main.qml:73 contents/ui/SearchPage.qml:150
|
||||
#, kde-format
|
||||
msgid "Search"
|
||||
msgstr "Cerca"
|
||||
|
||||
#: contents/ui/main.qml:85
|
||||
#, kde-format
|
||||
msgid "AudioTube"
|
||||
msgstr "AudioTube"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:86
|
||||
#, kde-format
|
||||
msgid "Now Playing"
|
||||
msgstr "Ara s'està reproduint"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:112
|
||||
#, kde-format
|
||||
msgid "Queue"
|
||||
msgstr "Cua"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:224
|
||||
#, kde-format
|
||||
msgid "Remove Track"
|
||||
msgstr "Elimina la pista"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:244
|
||||
#: contents/ui/MinimizedPlayerControls.qml:96
|
||||
#, kde-format
|
||||
msgid "No media playing"
|
||||
msgstr "No s'està reproduint cap suport"
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:15
|
||||
#, kde-format
|
||||
msgid "Unknown list of songs"
|
||||
msgstr "Llista desconeguda de cançons"
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:25
|
||||
#, kde-format
|
||||
msgid "No songs here yet"
|
||||
msgstr "Encara no hi ha cap cançó"
|
||||
|
||||
#: 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"
|
||||
@@ -0,0 +1,194 @@
|
||||
# Translation of audiotube.po to Catalan (Valencian)
|
||||
# Copyright (C) 2021-2022 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.
|
||||
# Josep M. Ferrer <[email protected]>, 2022.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: audiotube\n"
|
||||
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
|
||||
"POT-Creation-Date: 2022-05-30 00:47+0000\n"
|
||||
"PO-Revision-Date: 2022-05-26 09:09+0200\n"
|
||||
"Last-Translator: Josep M. Ferrer <[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 20.12.0\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/PlaylistPage.qml:19
|
||||
#, kde-format
|
||||
msgid "Play"
|
||||
msgstr "Reproduïx"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:26 contents/ui/PlaylistPage.qml:26
|
||||
#, kde-format
|
||||
msgid "Shuffle"
|
||||
msgstr "Barreja"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:68 contents/ui/ArtistPage.qml:101
|
||||
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
|
||||
#, kde-format
|
||||
msgid "Play Next"
|
||||
msgstr "Reproduïx el següent"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:73 contents/ui/ArtistPage.qml:107
|
||||
#: contents/ui/PlaybackHistory.qml:79 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/LibraryPage.qml:27
|
||||
#, kde-format
|
||||
msgid "Favourites"
|
||||
msgstr "Preferides"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:37 contents/ui/LibraryPage.qml:108
|
||||
#, kde-format
|
||||
msgid "Show All"
|
||||
msgstr "Mostra-ho tot"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:42
|
||||
#, kde-format
|
||||
msgid "Favourite Songs"
|
||||
msgstr "Cançons preferides"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:98
|
||||
#, kde-format
|
||||
msgid "Most played"
|
||||
msgstr "Les més reproduïdes"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:113
|
||||
#, kde-format
|
||||
msgid "Played Songs"
|
||||
msgstr "Cançons reproduïdes"
|
||||
|
||||
#: contents/ui/main.qml:39
|
||||
#, kde-format
|
||||
msgid "Back"
|
||||
msgstr "Enrere"
|
||||
|
||||
#: contents/ui/main.qml:73 contents/ui/SearchPage.qml:150
|
||||
#, kde-format
|
||||
msgid "Search"
|
||||
msgstr "Busca"
|
||||
|
||||
#: contents/ui/main.qml:85
|
||||
#, kde-format
|
||||
msgid "AudioTube"
|
||||
msgstr "AudioTube"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:86
|
||||
#, kde-format
|
||||
msgid "Now Playing"
|
||||
msgstr "Ara s'està reproduint"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:112
|
||||
#, kde-format
|
||||
msgid "Queue"
|
||||
msgstr "Cua"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:224
|
||||
#, kde-format
|
||||
msgid "Remove Track"
|
||||
msgstr "Elimina la pista"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:244
|
||||
#: contents/ui/MinimizedPlayerControls.qml:96
|
||||
#, kde-format
|
||||
msgid "No media playing"
|
||||
msgstr "No s'està reproduint cap suport"
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:15
|
||||
#, kde-format
|
||||
msgid "Unknown list of songs"
|
||||
msgstr "Llista desconeguda de cançons"
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:25
|
||||
#, kde-format
|
||||
msgid "No songs here yet"
|
||||
msgstr "Encara no hi ha cap cançó"
|
||||
|
||||
#: 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 "Reproduïx 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"
|
||||
@@ -0,0 +1,201 @@
|
||||
# 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: 2022-05-30 00:47+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/PlaylistPage.qml:19
|
||||
#, kde-format
|
||||
msgid "Play"
|
||||
msgstr "Přehrát"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:26 contents/ui/PlaylistPage.qml:26
|
||||
#, kde-format
|
||||
msgid "Shuffle"
|
||||
msgstr "Zamíchat"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:68 contents/ui/ArtistPage.qml:101
|
||||
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
|
||||
#, kde-format
|
||||
msgid "Play Next"
|
||||
msgstr "Přehrát následující"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:73 contents/ui/ArtistPage.qml:107
|
||||
#: contents/ui/PlaybackHistory.qml:79 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/LibraryPage.qml:27
|
||||
#, kde-format
|
||||
msgid "Favourites"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:37 contents/ui/LibraryPage.qml:108
|
||||
#, kde-format
|
||||
msgid "Show All"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:42
|
||||
#, kde-format
|
||||
msgid "Favourite Songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:98
|
||||
#, kde-format
|
||||
msgid "Most played"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:113
|
||||
#, kde-format
|
||||
msgid "Played Songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/main.qml:39
|
||||
#, kde-format
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/main.qml:73 contents/ui/SearchPage.qml:150
|
||||
#, kde-format
|
||||
msgid "Search"
|
||||
msgstr "Hledat"
|
||||
|
||||
#: contents/ui/main.qml:85
|
||||
#, kde-format
|
||||
msgid "AudioTube"
|
||||
msgstr "AudioTube"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:86
|
||||
#, fuzzy, kde-format
|
||||
#| msgid "No media playing"
|
||||
msgid "Now Playing"
|
||||
msgstr "Nepřehrává se žádné médium"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:112
|
||||
#, kde-format
|
||||
msgid "Queue"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:224
|
||||
#, kde-format
|
||||
msgid "Remove Track"
|
||||
msgstr "Odstranit skladbu"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:244
|
||||
#: contents/ui/MinimizedPlayerControls.qml:96
|
||||
#, kde-format
|
||||
msgid "No media playing"
|
||||
msgstr "Nepřehrává se žádné médium"
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:15
|
||||
#, kde-format
|
||||
msgid "Unknown list of songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:25
|
||||
#, kde-format
|
||||
msgid "No songs here yet"
|
||||
msgstr ""
|
||||
|
||||
#: 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"
|
||||
|
||||
#~ msgid "Clear"
|
||||
#~ msgstr "Vyprázdnit"
|
||||
|
||||
#~ msgid "Pause"
|
||||
#~ msgstr "Pozastavit"
|
||||
|
||||
#~ msgid "Expand"
|
||||
#~ msgstr "Rozvinout"
|
||||
@@ -0,0 +1,204 @@
|
||||
# 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: 2022-05-30 00:47+0000\n"
|
||||
"PO-Revision-Date: 2022-06-15 11:16+0200\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 22.04.1\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/PlaylistPage.qml:19
|
||||
#, kde-format
|
||||
msgid "Play"
|
||||
msgstr "Wiedergabe"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:26 contents/ui/PlaylistPage.qml:26
|
||||
#, kde-format
|
||||
msgid "Shuffle"
|
||||
msgstr "Zufällig"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:68 contents/ui/ArtistPage.qml:101
|
||||
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
|
||||
#, kde-format
|
||||
msgid "Play Next"
|
||||
msgstr "Nächste Wiedergabe"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:73 contents/ui/ArtistPage.qml:107
|
||||
#: contents/ui/PlaybackHistory.qml:79 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/LibraryPage.qml:27
|
||||
#, kde-format
|
||||
msgid "Favourites"
|
||||
msgstr "Favoriten"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:37 contents/ui/LibraryPage.qml:108
|
||||
#, kde-format
|
||||
msgid "Show All"
|
||||
msgstr "Alle anzeigen"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:42
|
||||
#, kde-format
|
||||
msgid "Favourite Songs"
|
||||
msgstr "Lieblingsstücke"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:98
|
||||
#, kde-format
|
||||
msgid "Most played"
|
||||
msgstr "Am meisten abgespielt"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:113
|
||||
#, kde-format
|
||||
msgid "Played Songs"
|
||||
msgstr "Abgespielte Stücke"
|
||||
|
||||
#: contents/ui/main.qml:39
|
||||
#, kde-format
|
||||
msgid "Back"
|
||||
msgstr "Zurück"
|
||||
|
||||
#: contents/ui/main.qml:73 contents/ui/SearchPage.qml:150
|
||||
#, kde-format
|
||||
msgid "Search"
|
||||
msgstr "Suchen"
|
||||
|
||||
#: contents/ui/main.qml:85
|
||||
#, kde-format
|
||||
msgid "AudioTube"
|
||||
msgstr "AudioTube"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:86
|
||||
#, kde-format
|
||||
msgid "Now Playing"
|
||||
msgstr "Aktueller Titel"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:112
|
||||
#, kde-format
|
||||
msgid "Queue"
|
||||
msgstr "Warteschlange"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:224
|
||||
#, kde-format
|
||||
msgid "Remove Track"
|
||||
msgstr "Stück entfernen"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:244
|
||||
#: contents/ui/MinimizedPlayerControls.qml:96
|
||||
#, kde-format
|
||||
msgid "No media playing"
|
||||
msgstr "Es werden keine Medien wiedergegeben"
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:15
|
||||
#, kde-format
|
||||
msgid "Unknown list of songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:25
|
||||
#, kde-format
|
||||
msgid "No songs here yet"
|
||||
msgstr ""
|
||||
|
||||
#: 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"
|
||||
|
||||
#~ msgid "Clear"
|
||||
#~ msgstr "Löschen"
|
||||
|
||||
#~ msgid "Pause"
|
||||
#~ msgstr "Pause"
|
||||
|
||||
#~ msgid "Expand"
|
||||
#~ msgstr "Erweitern"
|
||||
|
||||
#~ msgid "Play only Audio"
|
||||
#~ msgstr "Nur Audio abspielen"
|
||||
@@ -0,0 +1,204 @@
|
||||
# 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: 2022-05-30 00:47+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/PlaylistPage.qml:19
|
||||
#, kde-format
|
||||
msgid "Play"
|
||||
msgstr "Play"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:26 contents/ui/PlaylistPage.qml:26
|
||||
#, kde-format
|
||||
msgid "Shuffle"
|
||||
msgstr "Shuffle"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:68 contents/ui/ArtistPage.qml:101
|
||||
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
|
||||
#, kde-format
|
||||
msgid "Play Next"
|
||||
msgstr "Play Next"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:73 contents/ui/ArtistPage.qml:107
|
||||
#: contents/ui/PlaybackHistory.qml:79 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/LibraryPage.qml:27
|
||||
#, kde-format
|
||||
msgid "Favourites"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:37 contents/ui/LibraryPage.qml:108
|
||||
#, kde-format
|
||||
msgid "Show All"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:42
|
||||
#, kde-format
|
||||
msgid "Favourite Songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:98
|
||||
#, kde-format
|
||||
msgid "Most played"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:113
|
||||
#, kde-format
|
||||
msgid "Played Songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/main.qml:39
|
||||
#, kde-format
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/main.qml:73 contents/ui/SearchPage.qml:150
|
||||
#, kde-format
|
||||
msgid "Search"
|
||||
msgstr "Search"
|
||||
|
||||
#: contents/ui/main.qml:85
|
||||
#, kde-format
|
||||
msgid "AudioTube"
|
||||
msgstr "AudioTube"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:86
|
||||
#, fuzzy, kde-format
|
||||
#| msgid "No media playing"
|
||||
msgid "Now Playing"
|
||||
msgstr "No media playing"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:112
|
||||
#, kde-format
|
||||
msgid "Queue"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:224
|
||||
#, kde-format
|
||||
msgid "Remove Track"
|
||||
msgstr "Remove Track"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:244
|
||||
#: contents/ui/MinimizedPlayerControls.qml:96
|
||||
#, kde-format
|
||||
msgid "No media playing"
|
||||
msgstr "No media playing"
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:15
|
||||
#, kde-format
|
||||
msgid "Unknown list of songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:25
|
||||
#, kde-format
|
||||
msgid "No songs here yet"
|
||||
msgstr ""
|
||||
|
||||
#: 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"
|
||||
|
||||
#~ msgid "Clear"
|
||||
#~ msgstr "Clear"
|
||||
|
||||
#~ msgid "Pause"
|
||||
#~ msgstr "Pause"
|
||||
|
||||
#~ msgid "Expand"
|
||||
#~ msgstr "Expand"
|
||||
|
||||
#~ msgid "Play only Audio"
|
||||
#~ msgstr "Play only Audio"
|
||||
@@ -0,0 +1,205 @@
|
||||
# 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, 2022.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: audiotube\n"
|
||||
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
|
||||
"POT-Creation-Date: 2022-05-30 00:47+0000\n"
|
||||
"PO-Revision-Date: 2022-05-27 12:34+0200\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 22.04.1\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/PlaylistPage.qml:19
|
||||
#, kde-format
|
||||
msgid "Play"
|
||||
msgstr "Reproducir"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:26 contents/ui/PlaylistPage.qml:26
|
||||
#, kde-format
|
||||
msgid "Shuffle"
|
||||
msgstr "Desordenar"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:68 contents/ui/ArtistPage.qml:101
|
||||
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
|
||||
#, kde-format
|
||||
msgid "Play Next"
|
||||
msgstr "Reproducir siguiente"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:73 contents/ui/ArtistPage.qml:107
|
||||
#: contents/ui/PlaybackHistory.qml:79 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/LibraryPage.qml:27
|
||||
#, kde-format
|
||||
msgid "Favourites"
|
||||
msgstr "Favoritas"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:37 contents/ui/LibraryPage.qml:108
|
||||
#, kde-format
|
||||
msgid "Show All"
|
||||
msgstr "Mostrar todas"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:42
|
||||
#, kde-format
|
||||
msgid "Favourite Songs"
|
||||
msgstr "Canciones favoritas"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:98
|
||||
#, kde-format
|
||||
msgid "Most played"
|
||||
msgstr "Más reproducidas"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:113
|
||||
#, kde-format
|
||||
msgid "Played Songs"
|
||||
msgstr "Canciones reproducidas"
|
||||
|
||||
#: contents/ui/main.qml:39
|
||||
#, kde-format
|
||||
msgid "Back"
|
||||
msgstr "Volver"
|
||||
|
||||
#: contents/ui/main.qml:73 contents/ui/SearchPage.qml:150
|
||||
#, kde-format
|
||||
msgid "Search"
|
||||
msgstr "Buscar"
|
||||
|
||||
#: contents/ui/main.qml:85
|
||||
#, kde-format
|
||||
msgid "AudioTube"
|
||||
msgstr "AudioTube"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:86
|
||||
#, kde-format
|
||||
msgid "Now Playing"
|
||||
msgstr "Reproduciendo"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:112
|
||||
#, kde-format
|
||||
msgid "Queue"
|
||||
msgstr "Cola"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:224
|
||||
#, kde-format
|
||||
msgid "Remove Track"
|
||||
msgstr "Eliminar pista"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:244
|
||||
#: contents/ui/MinimizedPlayerControls.qml:96
|
||||
#, kde-format
|
||||
msgid "No media playing"
|
||||
msgstr "Ningún medio en reproducción"
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:15
|
||||
#, kde-format
|
||||
msgid "Unknown list of songs"
|
||||
msgstr "Lista de canciones desconocida"
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:25
|
||||
#, kde-format
|
||||
msgid "No songs here yet"
|
||||
msgstr "Todavía no contiene canciones"
|
||||
|
||||
#: 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"
|
||||
|
||||
#~ msgid "Clear"
|
||||
#~ msgstr "Borrar todo"
|
||||
|
||||
#~ msgid "Pause"
|
||||
#~ msgstr "Pausar"
|
||||
|
||||
#~ msgid "Expand"
|
||||
#~ msgstr "Expandir"
|
||||
|
||||
#~ msgid "Play only Audio"
|
||||
#~ msgstr "Reproducir solo el sonido"
|
||||
@@ -0,0 +1,207 @@
|
||||
# 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: 2022-05-30 00:47+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/PlaylistPage.qml:19
|
||||
#, kde-format
|
||||
msgid "Play"
|
||||
msgstr "Jo"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:26 contents/ui/PlaylistPage.qml:26
|
||||
#, kde-format
|
||||
msgid "Shuffle"
|
||||
msgstr "Nahastu"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:68 contents/ui/ArtistPage.qml:101
|
||||
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
|
||||
#, kde-format
|
||||
msgid "Play Next"
|
||||
msgstr "Jo hurrengoa"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:73 contents/ui/ArtistPage.qml:107
|
||||
#: contents/ui/PlaybackHistory.qml:79 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/LibraryPage.qml:27
|
||||
#, kde-format
|
||||
msgid "Favourites"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:37 contents/ui/LibraryPage.qml:108
|
||||
#, kde-format
|
||||
msgid "Show All"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:42
|
||||
#, kde-format
|
||||
msgid "Favourite Songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:98
|
||||
#, kde-format
|
||||
msgid "Most played"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:113
|
||||
#, kde-format
|
||||
msgid "Played Songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/main.qml:39
|
||||
#, kde-format
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/main.qml:73 contents/ui/SearchPage.qml:150
|
||||
#, kde-format
|
||||
msgid "Search"
|
||||
msgstr "Bilatu"
|
||||
|
||||
#: contents/ui/main.qml:85
|
||||
#, kde-format
|
||||
msgid "AudioTube"
|
||||
msgstr "AudioTube"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:86
|
||||
#, fuzzy, kde-format
|
||||
#| msgid "No media playing"
|
||||
msgid "Now Playing"
|
||||
msgstr "Ez da euskarririk jotzen ari"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:112
|
||||
#, kde-format
|
||||
msgid "Queue"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:224
|
||||
#, kde-format
|
||||
msgid "Remove Track"
|
||||
msgstr "Kendu pista"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:244
|
||||
#: contents/ui/MinimizedPlayerControls.qml:96
|
||||
#, kde-format
|
||||
msgid "No media playing"
|
||||
msgstr "Ez da euskarririk jotzen ari"
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:15
|
||||
#, kde-format
|
||||
msgid "Unknown list of songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:25
|
||||
#, kde-format
|
||||
msgid "No songs here yet"
|
||||
msgstr ""
|
||||
|
||||
#: 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"
|
||||
|
||||
#~ msgid "Clear"
|
||||
#~ msgstr "Garbitu"
|
||||
|
||||
#~ msgid "Pause"
|
||||
#~ msgstr "Eten"
|
||||
|
||||
#~ msgid "Expand"
|
||||
#~ msgstr "Zabaldu"
|
||||
|
||||
#~ msgid "Play only Audio"
|
||||
#~ msgstr "Audio bakarrik jo"
|
||||
@@ -0,0 +1,203 @@
|
||||
# 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: 2022-05-30 00:47+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/PlaylistPage.qml:19
|
||||
#, kde-format
|
||||
msgid "Play"
|
||||
msgstr "Toista"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:26 contents/ui/PlaylistPage.qml:26
|
||||
#, kde-format
|
||||
msgid "Shuffle"
|
||||
msgstr "Sekoita"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:68 contents/ui/ArtistPage.qml:101
|
||||
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
|
||||
#, kde-format
|
||||
msgid "Play Next"
|
||||
msgstr "Toista seuraava"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:73 contents/ui/ArtistPage.qml:107
|
||||
#: contents/ui/PlaybackHistory.qml:79 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/LibraryPage.qml:27
|
||||
#, kde-format
|
||||
msgid "Favourites"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:37 contents/ui/LibraryPage.qml:108
|
||||
#, kde-format
|
||||
msgid "Show All"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:42
|
||||
#, kde-format
|
||||
msgid "Favourite Songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:98
|
||||
#, kde-format
|
||||
msgid "Most played"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:113
|
||||
#, kde-format
|
||||
msgid "Played Songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/main.qml:39
|
||||
#, kde-format
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/main.qml:73 contents/ui/SearchPage.qml:150
|
||||
#, kde-format
|
||||
msgid "Search"
|
||||
msgstr "Etsi"
|
||||
|
||||
#: contents/ui/main.qml:85
|
||||
#, kde-format
|
||||
msgid "AudioTube"
|
||||
msgstr "AudioTube"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:86
|
||||
#, fuzzy, kde-format
|
||||
#| msgid "No media playing"
|
||||
msgid "Now Playing"
|
||||
msgstr "Mitään ei toisteta"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:112
|
||||
#, kde-format
|
||||
msgid "Queue"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:224
|
||||
#, kde-format
|
||||
msgid "Remove Track"
|
||||
msgstr "Poista raita"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:244
|
||||
#: contents/ui/MinimizedPlayerControls.qml:96
|
||||
#, kde-format
|
||||
msgid "No media playing"
|
||||
msgstr "Mitään ei toisteta"
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:15
|
||||
#, kde-format
|
||||
msgid "Unknown list of songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:25
|
||||
#, kde-format
|
||||
msgid "No songs here yet"
|
||||
msgstr ""
|
||||
|
||||
#: 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"
|
||||
|
||||
#~ msgid "Clear"
|
||||
#~ msgstr "Tyhjennä"
|
||||
|
||||
#~ msgid "Pause"
|
||||
#~ msgstr "Tauko"
|
||||
|
||||
#~ msgid "Expand"
|
||||
#~ msgstr "Laajenna"
|
||||
|
||||
#~ msgid "Play only Audio"
|
||||
#~ msgstr "Toista vain ääni"
|
||||
@@ -0,0 +1,200 @@
|
||||
# Xavier Besnard <[email protected]>, 2021, 2022.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: audiotube\n"
|
||||
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
|
||||
"POT-Creation-Date: 2022-05-30 00:47+0000\n"
|
||||
"PO-Revision-Date: 2022-06-05 18:56+0200\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 22.04.1\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/PlaylistPage.qml:19
|
||||
#, kde-format
|
||||
msgid "Play"
|
||||
msgstr "Lire"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:26 contents/ui/PlaylistPage.qml:26
|
||||
#, kde-format
|
||||
msgid "Shuffle"
|
||||
msgstr "Mélanger"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:68 contents/ui/ArtistPage.qml:101
|
||||
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
|
||||
#, kde-format
|
||||
msgid "Play Next"
|
||||
msgstr "Lire le suivant"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:73 contents/ui/ArtistPage.qml:107
|
||||
#: contents/ui/PlaybackHistory.qml:79 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/LibraryPage.qml:27
|
||||
#, kde-format
|
||||
msgid "Favourites"
|
||||
msgstr "Favoris"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:37 contents/ui/LibraryPage.qml:108
|
||||
#, kde-format
|
||||
msgid "Show All"
|
||||
msgstr "Tout afficher"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:42
|
||||
#, kde-format
|
||||
msgid "Favourite Songs"
|
||||
msgstr "Titres favoris"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:98
|
||||
#, kde-format
|
||||
msgid "Most played"
|
||||
msgstr "Les plus lus"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:113
|
||||
#, kde-format
|
||||
msgid "Played Songs"
|
||||
msgstr "Titres lus"
|
||||
|
||||
#: contents/ui/main.qml:39
|
||||
#, kde-format
|
||||
msgid "Back"
|
||||
msgstr "En arrière"
|
||||
|
||||
#: contents/ui/main.qml:73 contents/ui/SearchPage.qml:150
|
||||
#, kde-format
|
||||
msgid "Search"
|
||||
msgstr "Rechercher"
|
||||
|
||||
#: contents/ui/main.qml:85
|
||||
#, kde-format
|
||||
msgid "AudioTube"
|
||||
msgstr "AudioTube"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:86
|
||||
#, kde-format
|
||||
msgid "Now Playing"
|
||||
msgstr "En cours de lecture"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:112
|
||||
#, kde-format
|
||||
msgid "Queue"
|
||||
msgstr "Mettre en file d'attente"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:224
|
||||
#, kde-format
|
||||
msgid "Remove Track"
|
||||
msgstr "Supprimer une piste"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:244
|
||||
#: contents/ui/MinimizedPlayerControls.qml:96
|
||||
#, kde-format
|
||||
msgid "No media playing"
|
||||
msgstr "Aucun média en lecture"
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:15
|
||||
#, kde-format
|
||||
msgid "Unknown list of songs"
|
||||
msgstr "Liste inconnue de titres"
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:25
|
||||
#, kde-format
|
||||
msgid "No songs here yet"
|
||||
msgstr "Aucun titre ici encore"
|
||||
|
||||
#: 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 »"
|
||||
|
||||
#~ msgid "Clear"
|
||||
#~ msgstr "Effacer"
|
||||
|
||||
#~ msgid "Pause"
|
||||
#~ msgstr "Pause"
|
||||
|
||||
#~ msgid "Expand"
|
||||
#~ msgstr "Développer"
|
||||
|
||||
#~ msgid "Play only Audio"
|
||||
#~ msgstr "Ne lire que l'audio"
|
||||
@@ -0,0 +1,203 @@
|
||||
# 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: 2022-05-30 00:47+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/PlaylistPage.qml:19
|
||||
#, kde-format
|
||||
msgid "Play"
|
||||
msgstr "बजाएँ"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:26 contents/ui/PlaylistPage.qml:26
|
||||
#, kde-format
|
||||
msgid "Shuffle"
|
||||
msgstr "फेंटें"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:68 contents/ui/ArtistPage.qml:101
|
||||
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
|
||||
#, kde-format
|
||||
msgid "Play Next"
|
||||
msgstr "अगला बजाएँ"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:73 contents/ui/ArtistPage.qml:107
|
||||
#: contents/ui/PlaybackHistory.qml:79 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/LibraryPage.qml:27
|
||||
#, kde-format
|
||||
msgid "Favourites"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:37 contents/ui/LibraryPage.qml:108
|
||||
#, kde-format
|
||||
msgid "Show All"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:42
|
||||
#, kde-format
|
||||
msgid "Favourite Songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:98
|
||||
#, kde-format
|
||||
msgid "Most played"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:113
|
||||
#, kde-format
|
||||
msgid "Played Songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/main.qml:39
|
||||
#, kde-format
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/main.qml:73 contents/ui/SearchPage.qml:150
|
||||
#, kde-format
|
||||
msgid "Search"
|
||||
msgstr "खोजें"
|
||||
|
||||
#: contents/ui/main.qml:85
|
||||
#, kde-format
|
||||
msgid "AudioTube"
|
||||
msgstr "ओडियो-ट्यूब"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:86
|
||||
#, fuzzy, kde-format
|
||||
#| msgid "No media playing"
|
||||
msgid "Now Playing"
|
||||
msgstr "कोई मीडिया नहीं बजा रहा है"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:112
|
||||
#, kde-format
|
||||
msgid "Queue"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:224
|
||||
#, kde-format
|
||||
msgid "Remove Track"
|
||||
msgstr "गाना हटाएँ"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:244
|
||||
#: contents/ui/MinimizedPlayerControls.qml:96
|
||||
#, kde-format
|
||||
msgid "No media playing"
|
||||
msgstr "कोई मीडिया नहीं बजा रहा है"
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:15
|
||||
#, kde-format
|
||||
msgid "Unknown list of songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:25
|
||||
#, kde-format
|
||||
msgid "No songs here yet"
|
||||
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 "यूट्यूब संगीत पर संगीत ढूंढें"
|
||||
|
||||
#~ msgid "Clear"
|
||||
#~ msgstr "साफ करें"
|
||||
|
||||
#~ msgid "Pause"
|
||||
#~ msgstr "रोकें"
|
||||
|
||||
#~ msgid "Expand"
|
||||
#~ msgstr "विस्तृत करें"
|
||||
|
||||
#~ msgid "Play only Audio"
|
||||
#~ msgstr "केवल ध्वनी चलाएँ"
|
||||
@@ -0,0 +1,203 @@
|
||||
# Copyright (C) YEAR This file is copyright:
|
||||
# This file is distributed under the same license as the audiotube package.
|
||||
# Vincenzo Reale <[email protected]>, 2021, 2022.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: audiotube\n"
|
||||
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
|
||||
"POT-Creation-Date: 2022-05-30 00:47+0000\n"
|
||||
"PO-Revision-Date: 2022-05-26 14:13+0200\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 22.04.1\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/PlaylistPage.qml:19
|
||||
#, kde-format
|
||||
msgid "Play"
|
||||
msgstr "Riproduci"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:26 contents/ui/PlaylistPage.qml:26
|
||||
#, kde-format
|
||||
msgid "Shuffle"
|
||||
msgstr "Mescola"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:68 contents/ui/ArtistPage.qml:101
|
||||
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
|
||||
#, kde-format
|
||||
msgid "Play Next"
|
||||
msgstr "Riproduci successivo"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:73 contents/ui/ArtistPage.qml:107
|
||||
#: contents/ui/PlaybackHistory.qml:79 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/LibraryPage.qml:27
|
||||
#, kde-format
|
||||
msgid "Favourites"
|
||||
msgstr "Preferiti"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:37 contents/ui/LibraryPage.qml:108
|
||||
#, kde-format
|
||||
msgid "Show All"
|
||||
msgstr "Mostra tutto"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:42
|
||||
#, kde-format
|
||||
msgid "Favourite Songs"
|
||||
msgstr "Brani preferiti"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:98
|
||||
#, kde-format
|
||||
msgid "Most played"
|
||||
msgstr "Più riprodotti"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:113
|
||||
#, kde-format
|
||||
msgid "Played Songs"
|
||||
msgstr "Brani riprodotti"
|
||||
|
||||
#: contents/ui/main.qml:39
|
||||
#, kde-format
|
||||
msgid "Back"
|
||||
msgstr "Indietro"
|
||||
|
||||
#: contents/ui/main.qml:73 contents/ui/SearchPage.qml:150
|
||||
#, kde-format
|
||||
msgid "Search"
|
||||
msgstr "Cerca"
|
||||
|
||||
#: contents/ui/main.qml:85
|
||||
#, kde-format
|
||||
msgid "AudioTube"
|
||||
msgstr "AudioTube"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:86
|
||||
#, kde-format
|
||||
msgid "Now Playing"
|
||||
msgstr "In riproduzione"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:112
|
||||
#, kde-format
|
||||
msgid "Queue"
|
||||
msgstr "Coda"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:224
|
||||
#, kde-format
|
||||
msgid "Remove Track"
|
||||
msgstr "Rimuovi traccia"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:244
|
||||
#: contents/ui/MinimizedPlayerControls.qml:96
|
||||
#, kde-format
|
||||
msgid "No media playing"
|
||||
msgstr "Nessun supporto in riproduzione"
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:15
|
||||
#, kde-format
|
||||
msgid "Unknown list of songs"
|
||||
msgstr "Elenco di brani sconosciuto"
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:25
|
||||
#, kde-format
|
||||
msgid "No songs here yet"
|
||||
msgstr "Non c'è ancora alcun brano"
|
||||
|
||||
#: 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"
|
||||
|
||||
#~ msgid "Clear"
|
||||
#~ msgstr "Pulisci"
|
||||
|
||||
#~ msgid "Pause"
|
||||
#~ msgstr "Pausa"
|
||||
|
||||
#~ msgid "Expand"
|
||||
#~ msgstr "Espandi"
|
||||
|
||||
#~ msgid "Play only Audio"
|
||||
#~ msgstr "Riproduci solo l'audio"
|
||||
@@ -0,0 +1,202 @@
|
||||
# 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: 2022-05-30 00:47+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/PlaylistPage.qml:19
|
||||
#, kde-format
|
||||
msgid "Play"
|
||||
msgstr "재생"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:26 contents/ui/PlaylistPage.qml:26
|
||||
#, kde-format
|
||||
msgid "Shuffle"
|
||||
msgstr "무순서 재생"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:68 contents/ui/ArtistPage.qml:101
|
||||
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
|
||||
#, kde-format
|
||||
msgid "Play Next"
|
||||
msgstr "다음 재생"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:73 contents/ui/ArtistPage.qml:107
|
||||
#: contents/ui/PlaybackHistory.qml:79 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/LibraryPage.qml:27
|
||||
#, kde-format
|
||||
msgid "Favourites"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:37 contents/ui/LibraryPage.qml:108
|
||||
#, kde-format
|
||||
msgid "Show All"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:42
|
||||
#, kde-format
|
||||
msgid "Favourite Songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:98
|
||||
#, kde-format
|
||||
msgid "Most played"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:113
|
||||
#, kde-format
|
||||
msgid "Played Songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/main.qml:39
|
||||
#, kde-format
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/main.qml:73 contents/ui/SearchPage.qml:150
|
||||
#, kde-format
|
||||
msgid "Search"
|
||||
msgstr "검색"
|
||||
|
||||
#: contents/ui/main.qml:85
|
||||
#, kde-format
|
||||
msgid "AudioTube"
|
||||
msgstr "AudioTube"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:86
|
||||
#, fuzzy, kde-format
|
||||
#| msgid "No media playing"
|
||||
msgid "Now Playing"
|
||||
msgstr "재생 중인 미디어 없음"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:112
|
||||
#, kde-format
|
||||
msgid "Queue"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:224
|
||||
#, kde-format
|
||||
msgid "Remove Track"
|
||||
msgstr "트랙 삭제"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:244
|
||||
#: contents/ui/MinimizedPlayerControls.qml:96
|
||||
#, kde-format
|
||||
msgid "No media playing"
|
||||
msgstr "재생 중인 미디어 없음"
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:15
|
||||
#, kde-format
|
||||
msgid "Unknown list of songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:25
|
||||
#, kde-format
|
||||
msgid "No songs here yet"
|
||||
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에서 음악 찾기"
|
||||
|
||||
#~ msgid "Clear"
|
||||
#~ msgstr "지우기"
|
||||
|
||||
#~ msgid "Pause"
|
||||
#~ msgstr "일시 정지"
|
||||
|
||||
#~ msgid "Expand"
|
||||
#~ msgstr "확장"
|
||||
|
||||
#~ msgid "Play only Audio"
|
||||
#~ msgstr "오디오만 재생"
|
||||
@@ -0,0 +1,203 @@
|
||||
# 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, 2022.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: audiotube\n"
|
||||
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
|
||||
"POT-Creation-Date: 2022-05-30 00:47+0000\n"
|
||||
"PO-Revision-Date: 2022-05-26 13:17+0200\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 22.04.1\n"
|
||||
|
||||
#, kde-format
|
||||
msgctxt "NAME OF TRANSLATORS"
|
||||
msgid "Your names"
|
||||
msgstr "Freek de Kruijf - 2021 t/m 2022"
|
||||
|
||||
#, kde-format
|
||||
msgctxt "EMAIL OF TRANSLATORS"
|
||||
msgid "Your emails"
|
||||
msgstr "[email protected]"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:19 contents/ui/PlaylistPage.qml:19
|
||||
#, kde-format
|
||||
msgid "Play"
|
||||
msgstr "Afspelen"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:26 contents/ui/PlaylistPage.qml:26
|
||||
#, kde-format
|
||||
msgid "Shuffle"
|
||||
msgstr "Willekeurig afspelen"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:68 contents/ui/ArtistPage.qml:101
|
||||
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
|
||||
#, kde-format
|
||||
msgid "Play Next"
|
||||
msgstr "Volgende afspelen"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:73 contents/ui/ArtistPage.qml:107
|
||||
#: contents/ui/PlaybackHistory.qml:79 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/LibraryPage.qml:27
|
||||
#, kde-format
|
||||
msgid "Favourites"
|
||||
msgstr "Favorieten"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:37 contents/ui/LibraryPage.qml:108
|
||||
#, kde-format
|
||||
msgid "Show All"
|
||||
msgstr "Alles tonen"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:42
|
||||
#, kde-format
|
||||
msgid "Favourite Songs"
|
||||
msgstr "Favoriete songs"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:98
|
||||
#, kde-format
|
||||
msgid "Most played"
|
||||
msgstr "Meest afgespeeld"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:113
|
||||
#, kde-format
|
||||
msgid "Played Songs"
|
||||
msgstr "Afgespeelde songs"
|
||||
|
||||
#: contents/ui/main.qml:39
|
||||
#, kde-format
|
||||
msgid "Back"
|
||||
msgstr "Terug"
|
||||
|
||||
#: contents/ui/main.qml:73 contents/ui/SearchPage.qml:150
|
||||
#, kde-format
|
||||
msgid "Search"
|
||||
msgstr "Zoeken"
|
||||
|
||||
#: contents/ui/main.qml:85
|
||||
#, kde-format
|
||||
msgid "AudioTube"
|
||||
msgstr "AudioTube"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:86
|
||||
#, kde-format
|
||||
msgid "Now Playing"
|
||||
msgstr "Speelt nu"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:112
|
||||
#, kde-format
|
||||
msgid "Queue"
|
||||
msgstr "Wachtrij"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:224
|
||||
#, kde-format
|
||||
msgid "Remove Track"
|
||||
msgstr "Track verwijderen"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:244
|
||||
#: contents/ui/MinimizedPlayerControls.qml:96
|
||||
#, kde-format
|
||||
msgid "No media playing"
|
||||
msgstr "Er speelt geen medium"
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:15
|
||||
#, kde-format
|
||||
msgid "Unknown list of songs"
|
||||
msgstr "Onbekende lijst met songs"
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:25
|
||||
#, kde-format
|
||||
msgid "No songs here yet"
|
||||
msgstr "Er zijn hier nog geen songs"
|
||||
|
||||
#: 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"
|
||||
|
||||
#~ msgid "Clear"
|
||||
#~ msgstr "Wissen"
|
||||
|
||||
#~ msgid "Pause"
|
||||
#~ msgstr "Pauzeren"
|
||||
|
||||
#~ msgid "Expand"
|
||||
#~ msgstr "Uitbreiden"
|
||||
|
||||
#~ msgid "Play only Audio"
|
||||
#~ msgstr "Alleen geluid afspelen"
|
||||
@@ -0,0 +1,202 @@
|
||||
# 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: 2022-05-30 00:47+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/PlaylistPage.qml:19
|
||||
#, kde-format
|
||||
msgid "Play"
|
||||
msgstr "ਚਲਾਓ"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:26 contents/ui/PlaylistPage.qml:26
|
||||
#, kde-format
|
||||
msgid "Shuffle"
|
||||
msgstr "ਰਲਵੇਂ"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:68 contents/ui/ArtistPage.qml:101
|
||||
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
|
||||
#, kde-format
|
||||
msgid "Play Next"
|
||||
msgstr "ਅਗਲਾ ਚਲਾਓ"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:73 contents/ui/ArtistPage.qml:107
|
||||
#: contents/ui/PlaybackHistory.qml:79 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/LibraryPage.qml:27
|
||||
#, kde-format
|
||||
msgid "Favourites"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:37 contents/ui/LibraryPage.qml:108
|
||||
#, kde-format
|
||||
msgid "Show All"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:42
|
||||
#, kde-format
|
||||
msgid "Favourite Songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:98
|
||||
#, kde-format
|
||||
msgid "Most played"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:113
|
||||
#, kde-format
|
||||
msgid "Played Songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/main.qml:39
|
||||
#, kde-format
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/main.qml:73 contents/ui/SearchPage.qml:150
|
||||
#, kde-format
|
||||
msgid "Search"
|
||||
msgstr "ਖੋਜੋ"
|
||||
|
||||
#: contents/ui/main.qml:85
|
||||
#, kde-format
|
||||
msgid "AudioTube"
|
||||
msgstr "ਆਡੀਓ-ਟਿਊਬ"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:86
|
||||
#, fuzzy, kde-format
|
||||
#| msgid "No media playing"
|
||||
msgid "Now Playing"
|
||||
msgstr "ਕੋਈ ਮੀਡੀਆ ਨਹੀਂ ਚੱਲਦਾ"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:112
|
||||
#, kde-format
|
||||
msgid "Queue"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:224
|
||||
#, kde-format
|
||||
msgid "Remove Track"
|
||||
msgstr "ਟਰੈਕ ਨੂੰ ਹਟਾਓ"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:244
|
||||
#: contents/ui/MinimizedPlayerControls.qml:96
|
||||
#, kde-format
|
||||
msgid "No media playing"
|
||||
msgstr "ਕੋਈ ਮੀਡੀਆ ਨਹੀਂ ਚੱਲਦਾ"
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:15
|
||||
#, kde-format
|
||||
msgid "Unknown list of songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:25
|
||||
#, kde-format
|
||||
msgid "No songs here yet"
|
||||
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 "ਯੂਟਿਊਬ ਸੰਗੀਤ ਉੱਤੇ ਸੰਗੀਤ ਲੱਭੋ"
|
||||
|
||||
#~ msgid "Clear"
|
||||
#~ msgstr "ਮਿਟਾਓ"
|
||||
|
||||
#~ msgid "Pause"
|
||||
#~ msgstr "ਵਿਰਾਮ"
|
||||
|
||||
#~ msgid "Expand"
|
||||
#~ msgstr "ਫੈਲਾਓ"
|
||||
|
||||
#~ msgid "Play only Audio"
|
||||
#~ msgstr "ਸਿਰਫ਼ ਆਡੀਓ ਹੀ ਚਲਾਓ"
|
||||
@@ -0,0 +1,206 @@
|
||||
# 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: 2022-05-30 00:47+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/PlaylistPage.qml:19
|
||||
#, kde-format
|
||||
msgid "Play"
|
||||
msgstr "Odtwórz"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:26 contents/ui/PlaylistPage.qml:26
|
||||
#, kde-format
|
||||
msgid "Shuffle"
|
||||
msgstr "Wymieszaj"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:68 contents/ui/ArtistPage.qml:101
|
||||
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
|
||||
#, kde-format
|
||||
msgid "Play Next"
|
||||
msgstr "Odtwórz następny"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:73 contents/ui/ArtistPage.qml:107
|
||||
#: contents/ui/PlaybackHistory.qml:79 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/LibraryPage.qml:27
|
||||
#, kde-format
|
||||
msgid "Favourites"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:37 contents/ui/LibraryPage.qml:108
|
||||
#, kde-format
|
||||
msgid "Show All"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:42
|
||||
#, kde-format
|
||||
msgid "Favourite Songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:98
|
||||
#, kde-format
|
||||
msgid "Most played"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:113
|
||||
#, kde-format
|
||||
msgid "Played Songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/main.qml:39
|
||||
#, kde-format
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/main.qml:73 contents/ui/SearchPage.qml:150
|
||||
#, kde-format
|
||||
msgid "Search"
|
||||
msgstr "Szukaj"
|
||||
|
||||
#: contents/ui/main.qml:85
|
||||
#, kde-format
|
||||
msgid "AudioTube"
|
||||
msgstr "AudioTube"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:86
|
||||
#, fuzzy, kde-format
|
||||
#| msgid "No media playing"
|
||||
msgid "Now Playing"
|
||||
msgstr "Nic nie jest odtwarzane"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:112
|
||||
#, kde-format
|
||||
msgid "Queue"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:224
|
||||
#, kde-format
|
||||
msgid "Remove Track"
|
||||
msgstr "Usuń utwór"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:244
|
||||
#: contents/ui/MinimizedPlayerControls.qml:96
|
||||
#, kde-format
|
||||
msgid "No media playing"
|
||||
msgstr "Nic nie jest odtwarzane"
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:15
|
||||
#, kde-format
|
||||
msgid "Unknown list of songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:25
|
||||
#, kde-format
|
||||
msgid "No songs here yet"
|
||||
msgstr ""
|
||||
|
||||
#: 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"
|
||||
|
||||
#~ msgid "Clear"
|
||||
#~ msgstr "Wyczyść"
|
||||
|
||||
#~ msgid "Pause"
|
||||
#~ msgstr "Wstrzymaj"
|
||||
|
||||
#~ msgid "Expand"
|
||||
#~ msgstr "Rozwiń"
|
||||
|
||||
#~ msgid "Play only Audio"
|
||||
#~ msgstr "Odtwórz tylko dźwięk"
|
||||
@@ -0,0 +1,193 @@
|
||||
# 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: 2022-05-30 00:47+0000\n"
|
||||
"PO-Revision-Date: 2022-06-03 17:36+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/PlaylistPage.qml:19
|
||||
#, kde-format
|
||||
msgid "Play"
|
||||
msgstr "Reproduzir"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:26 contents/ui/PlaylistPage.qml:26
|
||||
#, kde-format
|
||||
msgid "Shuffle"
|
||||
msgstr "Baralhar"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:68 contents/ui/ArtistPage.qml:101
|
||||
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
|
||||
#, kde-format
|
||||
msgid "Play Next"
|
||||
msgstr "Reproduzir o Seguinte"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:73 contents/ui/ArtistPage.qml:107
|
||||
#: contents/ui/PlaybackHistory.qml:79 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/LibraryPage.qml:27
|
||||
#, kde-format
|
||||
msgid "Favourites"
|
||||
msgstr "Favoritos"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:37 contents/ui/LibraryPage.qml:108
|
||||
#, kde-format
|
||||
msgid "Show All"
|
||||
msgstr "Mostrar Tudo"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:42
|
||||
#, kde-format
|
||||
msgid "Favourite Songs"
|
||||
msgstr "Músicas Favoritas"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:98
|
||||
#, kde-format
|
||||
msgid "Most played"
|
||||
msgstr "Mais Tocadas"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:113
|
||||
#, kde-format
|
||||
msgid "Played Songs"
|
||||
msgstr "Músicas Tocadas"
|
||||
|
||||
#: contents/ui/main.qml:39
|
||||
#, kde-format
|
||||
msgid "Back"
|
||||
msgstr "Recuar"
|
||||
|
||||
#: contents/ui/main.qml:73 contents/ui/SearchPage.qml:150
|
||||
#, kde-format
|
||||
msgid "Search"
|
||||
msgstr "Procurar"
|
||||
|
||||
#: contents/ui/main.qml:85
|
||||
#, kde-format
|
||||
msgid "AudioTube"
|
||||
msgstr "AudioTube"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:86
|
||||
#, kde-format
|
||||
msgid "Now Playing"
|
||||
msgstr "Agora a Ouvir"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:112
|
||||
#, kde-format
|
||||
msgid "Queue"
|
||||
msgstr "Fila"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:224
|
||||
#, kde-format
|
||||
msgid "Remove Track"
|
||||
msgstr "Remover a Faixa"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:244
|
||||
#: contents/ui/MinimizedPlayerControls.qml:96
|
||||
#, kde-format
|
||||
msgid "No media playing"
|
||||
msgstr "Nada em reprodução"
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:15
|
||||
#, kde-format
|
||||
msgid "Unknown list of songs"
|
||||
msgstr "Lista de músicas desconhecida"
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:25
|
||||
#, kde-format
|
||||
msgid "No songs here yet"
|
||||
msgstr "Ainda sem músicas aqui"
|
||||
|
||||
#: 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"
|
||||
@@ -0,0 +1,205 @@
|
||||
# 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: 2022-05-30 00:47+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/PlaylistPage.qml:19
|
||||
#, kde-format
|
||||
msgid "Play"
|
||||
msgstr "Reproduzir"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:26 contents/ui/PlaylistPage.qml:26
|
||||
#, kde-format
|
||||
msgid "Shuffle"
|
||||
msgstr "Embaralhar"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:68 contents/ui/ArtistPage.qml:101
|
||||
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
|
||||
#, kde-format
|
||||
msgid "Play Next"
|
||||
msgstr "Reproduzir próxima"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:73 contents/ui/ArtistPage.qml:107
|
||||
#: contents/ui/PlaybackHistory.qml:79 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/LibraryPage.qml:27
|
||||
#, kde-format
|
||||
msgid "Favourites"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:37 contents/ui/LibraryPage.qml:108
|
||||
#, kde-format
|
||||
msgid "Show All"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:42
|
||||
#, kde-format
|
||||
msgid "Favourite Songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:98
|
||||
#, kde-format
|
||||
msgid "Most played"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:113
|
||||
#, kde-format
|
||||
msgid "Played Songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/main.qml:39
|
||||
#, kde-format
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/main.qml:73 contents/ui/SearchPage.qml:150
|
||||
#, kde-format
|
||||
msgid "Search"
|
||||
msgstr "Pesquisar"
|
||||
|
||||
#: contents/ui/main.qml:85
|
||||
#, kde-format
|
||||
msgid "AudioTube"
|
||||
msgstr "AudioTube"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:86
|
||||
#, fuzzy, kde-format
|
||||
#| msgid "No media playing"
|
||||
msgid "Now Playing"
|
||||
msgstr "Nenhuma mídia em reprodução"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:112
|
||||
#, kde-format
|
||||
msgid "Queue"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:224
|
||||
#, kde-format
|
||||
msgid "Remove Track"
|
||||
msgstr "Remover faixa"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:244
|
||||
#: contents/ui/MinimizedPlayerControls.qml:96
|
||||
#, kde-format
|
||||
msgid "No media playing"
|
||||
msgstr "Nenhuma mídia em reprodução"
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:15
|
||||
#, kde-format
|
||||
msgid "Unknown list of songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:25
|
||||
#, kde-format
|
||||
msgid "No songs here yet"
|
||||
msgstr ""
|
||||
|
||||
#: 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"
|
||||
|
||||
#~ msgid "Clear"
|
||||
#~ msgstr "Limpar"
|
||||
|
||||
#~ msgid "Pause"
|
||||
#~ msgstr "Pausar"
|
||||
|
||||
#~ msgid "Expand"
|
||||
#~ msgstr "Expandir"
|
||||
|
||||
#~ msgid "Play only Audio"
|
||||
#~ msgstr "Reproduzir apenas áudio"
|
||||
@@ -0,0 +1,196 @@
|
||||
# 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: 2022-05-30 00:47+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/PlaylistPage.qml:19
|
||||
#, kde-format
|
||||
msgid "Play"
|
||||
msgstr "Воспроизвести"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:26 contents/ui/PlaylistPage.qml:26
|
||||
#, kde-format
|
||||
msgid "Shuffle"
|
||||
msgstr "Перемешать"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:68 contents/ui/ArtistPage.qml:101
|
||||
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
|
||||
#, kde-format
|
||||
msgid "Play Next"
|
||||
msgstr "Воспроизвести следующий"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:73 contents/ui/ArtistPage.qml:107
|
||||
#: contents/ui/PlaybackHistory.qml:79 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/LibraryPage.qml:27
|
||||
#, kde-format
|
||||
msgid "Favourites"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:37 contents/ui/LibraryPage.qml:108
|
||||
#, kde-format
|
||||
msgid "Show All"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:42
|
||||
#, kde-format
|
||||
msgid "Favourite Songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:98
|
||||
#, kde-format
|
||||
msgid "Most played"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:113
|
||||
#, kde-format
|
||||
msgid "Played Songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/main.qml:39
|
||||
#, kde-format
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/main.qml:73 contents/ui/SearchPage.qml:150
|
||||
#, kde-format
|
||||
msgid "Search"
|
||||
msgstr "Поиск"
|
||||
|
||||
#: contents/ui/main.qml:85
|
||||
#, kde-format
|
||||
msgid "AudioTube"
|
||||
msgstr "AudioTube"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:86
|
||||
#, kde-format
|
||||
msgid "Now Playing"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:112
|
||||
#, kde-format
|
||||
msgid "Queue"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:224
|
||||
#, kde-format
|
||||
msgid "Remove Track"
|
||||
msgstr "Удалить дорожку"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:244
|
||||
#: contents/ui/MinimizedPlayerControls.qml:96
|
||||
#, kde-format
|
||||
msgid "No media playing"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:15
|
||||
#, kde-format
|
||||
msgid "Unknown list of songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:25
|
||||
#, kde-format
|
||||
msgid "No songs here yet"
|
||||
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 ""
|
||||
|
||||
#~ msgid "Clear"
|
||||
#~ msgstr "Очистить"
|
||||
|
||||
#~ msgid "Pause"
|
||||
#~ msgstr "Приостановить"
|
||||
@@ -0,0 +1,198 @@
|
||||
# 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: 2022-05-30 00:47+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/PlaylistPage.qml:19
|
||||
#, kde-format
|
||||
msgid "Play"
|
||||
msgstr "Prehrať"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:26 contents/ui/PlaylistPage.qml:26
|
||||
#, kde-format
|
||||
msgid "Shuffle"
|
||||
msgstr "Zamiešať"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:68 contents/ui/ArtistPage.qml:101
|
||||
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
|
||||
#, fuzzy, kde-format
|
||||
#| msgid "Play"
|
||||
msgid "Play Next"
|
||||
msgstr "Prehrať"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:73 contents/ui/ArtistPage.qml:107
|
||||
#: contents/ui/PlaybackHistory.qml:79 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/LibraryPage.qml:27
|
||||
#, kde-format
|
||||
msgid "Favourites"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:37 contents/ui/LibraryPage.qml:108
|
||||
#, kde-format
|
||||
msgid "Show All"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:42
|
||||
#, kde-format
|
||||
msgid "Favourite Songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:98
|
||||
#, kde-format
|
||||
msgid "Most played"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:113
|
||||
#, kde-format
|
||||
msgid "Played Songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/main.qml:39
|
||||
#, kde-format
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/main.qml:73 contents/ui/SearchPage.qml:150
|
||||
#, kde-format
|
||||
msgid "Search"
|
||||
msgstr "Hľadať"
|
||||
|
||||
#: contents/ui/main.qml:85
|
||||
#, kde-format
|
||||
msgid "AudioTube"
|
||||
msgstr "AudioTube"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:86
|
||||
#, fuzzy, kde-format
|
||||
#| msgid "No media playing"
|
||||
msgid "Now Playing"
|
||||
msgstr "Neprehráva sa žiadne médium"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:112
|
||||
#, kde-format
|
||||
msgid "Queue"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:224
|
||||
#, kde-format
|
||||
msgid "Remove Track"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:244
|
||||
#: contents/ui/MinimizedPlayerControls.qml:96
|
||||
#, kde-format
|
||||
msgid "No media playing"
|
||||
msgstr "Neprehráva sa žiadne médium"
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:15
|
||||
#, kde-format
|
||||
msgid "Unknown list of songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:25
|
||||
#, kde-format
|
||||
msgid "No songs here yet"
|
||||
msgstr ""
|
||||
|
||||
#: 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 ""
|
||||
|
||||
#~ msgid "Clear"
|
||||
#~ msgstr "Vyčistiť"
|
||||
|
||||
#~ msgid "Pause"
|
||||
#~ msgstr "Pozastaviť"
|
||||
|
||||
#~ msgid "Expand"
|
||||
#~ msgstr "Rozvinúť"
|
||||
@@ -0,0 +1,202 @@
|
||||
# Copyright (C) YEAR This file is copyright:
|
||||
# This file is distributed under the same license as the audiotube package.
|
||||
#
|
||||
# Matjaž Jeran <[email protected]>, 2021, 2022.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: audiotube\n"
|
||||
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
|
||||
"POT-Creation-Date: 2022-05-30 00:47+0000\n"
|
||||
"PO-Revision-Date: 2022-05-27 07:50+0200\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.12.2\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/PlaylistPage.qml:19
|
||||
#, kde-format
|
||||
msgid "Play"
|
||||
msgstr "Predvajaj"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:26 contents/ui/PlaylistPage.qml:26
|
||||
#, kde-format
|
||||
msgid "Shuffle"
|
||||
msgstr "Premešaj"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:68 contents/ui/ArtistPage.qml:101
|
||||
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
|
||||
#, kde-format
|
||||
msgid "Play Next"
|
||||
msgstr "Predvajaj naslednjo"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:73 contents/ui/ArtistPage.qml:107
|
||||
#: contents/ui/PlaybackHistory.qml:79 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/LibraryPage.qml:27
|
||||
#, kde-format
|
||||
msgid "Favourites"
|
||||
msgstr "Priljubljene"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:37 contents/ui/LibraryPage.qml:108
|
||||
#, kde-format
|
||||
msgid "Show All"
|
||||
msgstr "Prikaži vse"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:42
|
||||
#, kde-format
|
||||
msgid "Favourite Songs"
|
||||
msgstr "Priljubljene pesmi"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:98
|
||||
#, kde-format
|
||||
msgid "Most played"
|
||||
msgstr "Največkrat predvajane"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:113
|
||||
#, kde-format
|
||||
msgid "Played Songs"
|
||||
msgstr "Predvajane pesmi"
|
||||
|
||||
#: contents/ui/main.qml:39
|
||||
#, kde-format
|
||||
msgid "Back"
|
||||
msgstr "Nazaj"
|
||||
|
||||
#: contents/ui/main.qml:73 contents/ui/SearchPage.qml:150
|
||||
#, kde-format
|
||||
msgid "Search"
|
||||
msgstr "Išči"
|
||||
|
||||
#: contents/ui/main.qml:85
|
||||
#, kde-format
|
||||
msgid "AudioTube"
|
||||
msgstr "AudioTube"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:86
|
||||
#, kde-format
|
||||
msgid "Now Playing"
|
||||
msgstr "Pravkar predvajano"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:112
|
||||
#, kde-format
|
||||
msgid "Queue"
|
||||
msgstr "Čakalna vrsta"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:224
|
||||
#, kde-format
|
||||
msgid "Remove Track"
|
||||
msgstr "Odstrani sled"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:244
|
||||
#: contents/ui/MinimizedPlayerControls.qml:96
|
||||
#, kde-format
|
||||
msgid "No media playing"
|
||||
msgstr "Niče se ne predvaja"
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:15
|
||||
#, kde-format
|
||||
msgid "Unknown list of songs"
|
||||
msgstr "Neznan seznam pesmi"
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:25
|
||||
#, kde-format
|
||||
msgid "No songs here yet"
|
||||
msgstr "Tu še ni nobene pesmi"
|
||||
|
||||
#: 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"
|
||||
|
||||
#~ msgid "Clear"
|
||||
#~ msgstr "Očisti"
|
||||
|
||||
#~ msgid "Pause"
|
||||
#~ msgstr "Premor"
|
||||
|
||||
#~ msgid "Expand"
|
||||
#~ msgstr "Razširi"
|
||||
|
||||
#~ msgid "Play only Audio"
|
||||
#~ msgstr "Predvajaj samo zvok"
|
||||
@@ -0,0 +1,201 @@
|
||||
# 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, 2022.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: audiotube\n"
|
||||
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
|
||||
"POT-Creation-Date: 2022-05-30 00:47+0000\n"
|
||||
"PO-Revision-Date: 2022-05-26 10:40+0200\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/PlaylistPage.qml:19
|
||||
#, kde-format
|
||||
msgid "Play"
|
||||
msgstr "Spela"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:26 contents/ui/PlaylistPage.qml:26
|
||||
#, kde-format
|
||||
msgid "Shuffle"
|
||||
msgstr "Blanda"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:68 contents/ui/ArtistPage.qml:101
|
||||
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
|
||||
#, kde-format
|
||||
msgid "Play Next"
|
||||
msgstr "Spela nästa"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:73 contents/ui/ArtistPage.qml:107
|
||||
#: contents/ui/PlaybackHistory.qml:79 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/LibraryPage.qml:27
|
||||
#, kde-format
|
||||
msgid "Favourites"
|
||||
msgstr "Favoriter"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:37 contents/ui/LibraryPage.qml:108
|
||||
#, kde-format
|
||||
msgid "Show All"
|
||||
msgstr "Visa alla"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:42
|
||||
#, kde-format
|
||||
msgid "Favourite Songs"
|
||||
msgstr "Favoritlåtar"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:98
|
||||
#, kde-format
|
||||
msgid "Most played"
|
||||
msgstr "Mest spelade"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:113
|
||||
#, kde-format
|
||||
msgid "Played Songs"
|
||||
msgstr "Spelade låtar"
|
||||
|
||||
#: contents/ui/main.qml:39
|
||||
#, kde-format
|
||||
msgid "Back"
|
||||
msgstr "Bakåt"
|
||||
|
||||
#: contents/ui/main.qml:73 contents/ui/SearchPage.qml:150
|
||||
#, kde-format
|
||||
msgid "Search"
|
||||
msgstr "Sök"
|
||||
|
||||
#: contents/ui/main.qml:85
|
||||
#, kde-format
|
||||
msgid "AudioTube"
|
||||
msgstr "AudioTube"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:86
|
||||
#, kde-format
|
||||
msgid "Now Playing"
|
||||
msgstr "Spelar nu"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:112
|
||||
#, kde-format
|
||||
msgid "Queue"
|
||||
msgstr "Köa"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:224
|
||||
#, kde-format
|
||||
msgid "Remove Track"
|
||||
msgstr "Ta bort spår"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:244
|
||||
#: contents/ui/MinimizedPlayerControls.qml:96
|
||||
#, kde-format
|
||||
msgid "No media playing"
|
||||
msgstr "Inga media spelar"
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:15
|
||||
#, kde-format
|
||||
msgid "Unknown list of songs"
|
||||
msgstr "Okänd låtlista"
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:25
|
||||
#, kde-format
|
||||
msgid "No songs here yet"
|
||||
msgstr "Inga låtar här ännu"
|
||||
|
||||
#: 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"
|
||||
|
||||
#~ msgid "Clear"
|
||||
#~ msgstr "Rensa"
|
||||
|
||||
#~ msgid "Pause"
|
||||
#~ msgstr "Paus"
|
||||
|
||||
#~ msgid "Expand"
|
||||
#~ msgstr "Expandera"
|
||||
|
||||
#~ msgid "Play only Audio"
|
||||
#~ msgstr "Spela bara ljud"
|
||||
@@ -0,0 +1,200 @@
|
||||
# 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: 2022-05-30 00:47+0000\n"
|
||||
"PO-Revision-Date: 2022-05-26 10:29+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 22.04.0\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/PlaylistPage.qml:19
|
||||
#, kde-format
|
||||
msgid "Play"
|
||||
msgstr "Çal"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:26 contents/ui/PlaylistPage.qml:26
|
||||
#, kde-format
|
||||
msgid "Shuffle"
|
||||
msgstr "Karıştır"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:68 contents/ui/ArtistPage.qml:101
|
||||
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
|
||||
#, kde-format
|
||||
msgid "Play Next"
|
||||
msgstr "Sonrakini Çal"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:73 contents/ui/ArtistPage.qml:107
|
||||
#: contents/ui/PlaybackHistory.qml:79 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/LibraryPage.qml:27
|
||||
#, kde-format
|
||||
msgid "Favourites"
|
||||
msgstr "Beğeniler"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:37 contents/ui/LibraryPage.qml:108
|
||||
#, kde-format
|
||||
msgid "Show All"
|
||||
msgstr "Tümünü Göster"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:42
|
||||
#, kde-format
|
||||
msgid "Favourite Songs"
|
||||
msgstr "Beğenilen Şarkılar"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:98
|
||||
#, kde-format
|
||||
msgid "Most played"
|
||||
msgstr "En Çok Çalınanlar"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:113
|
||||
#, kde-format
|
||||
msgid "Played Songs"
|
||||
msgstr "Çalınan Şarkılar"
|
||||
|
||||
#: contents/ui/main.qml:39
|
||||
#, kde-format
|
||||
msgid "Back"
|
||||
msgstr "Geri"
|
||||
|
||||
#: contents/ui/main.qml:73 contents/ui/SearchPage.qml:150
|
||||
#, kde-format
|
||||
msgid "Search"
|
||||
msgstr "Ara"
|
||||
|
||||
#: contents/ui/main.qml:85
|
||||
#, kde-format
|
||||
msgid "AudioTube"
|
||||
msgstr "AudioTube"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:86
|
||||
#, kde-format
|
||||
msgid "Now Playing"
|
||||
msgstr "Şimdi Çalınıyor"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:112
|
||||
#, kde-format
|
||||
msgid "Queue"
|
||||
msgstr "Kuyruk"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:224
|
||||
#, kde-format
|
||||
msgid "Remove Track"
|
||||
msgstr "Parçayı Kaldır"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:244
|
||||
#: contents/ui/MinimizedPlayerControls.qml:96
|
||||
#, kde-format
|
||||
msgid "No media playing"
|
||||
msgstr "Ortam çalmıyor"
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:15
|
||||
#, kde-format
|
||||
msgid "Unknown list of songs"
|
||||
msgstr "Bilinmeyen şarkılar listesi"
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:25
|
||||
#, kde-format
|
||||
msgid "No songs here yet"
|
||||
msgstr "Burada henüz şarkı yok"
|
||||
|
||||
#: 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"
|
||||
|
||||
#~ msgid "Clear"
|
||||
#~ msgstr "Temizle"
|
||||
|
||||
#~ msgid "Pause"
|
||||
#~ msgstr "Duraklat"
|
||||
|
||||
#~ msgid "Expand"
|
||||
#~ msgstr "Genişlet"
|
||||
@@ -0,0 +1,204 @@
|
||||
# Copyright (C) YEAR This file is copyright:
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
#
|
||||
# Yuri Chornoivan <[email protected]>, 2021, 2022.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE\n"
|
||||
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
|
||||
"POT-Creation-Date: 2022-05-30 00:47+0000\n"
|
||||
"PO-Revision-Date: 2022-05-26 08:15+0300\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/PlaylistPage.qml:19
|
||||
#, kde-format
|
||||
msgid "Play"
|
||||
msgstr "Відтворити"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:26 contents/ui/PlaylistPage.qml:26
|
||||
#, kde-format
|
||||
msgid "Shuffle"
|
||||
msgstr "Перемішати"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:68 contents/ui/ArtistPage.qml:101
|
||||
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
|
||||
#, kde-format
|
||||
msgid "Play Next"
|
||||
msgstr "Відтворити наступну"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:73 contents/ui/ArtistPage.qml:107
|
||||
#: contents/ui/PlaybackHistory.qml:79 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/LibraryPage.qml:27
|
||||
#, kde-format
|
||||
msgid "Favourites"
|
||||
msgstr "Улюблені"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:37 contents/ui/LibraryPage.qml:108
|
||||
#, kde-format
|
||||
msgid "Show All"
|
||||
msgstr "Показати всі"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:42
|
||||
#, kde-format
|
||||
msgid "Favourite Songs"
|
||||
msgstr "Улюблені композиції"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:98
|
||||
#, kde-format
|
||||
msgid "Most played"
|
||||
msgstr "Часто слухали"
|
||||
|
||||
#: contents/ui/LibraryPage.qml:113
|
||||
#, kde-format
|
||||
msgid "Played Songs"
|
||||
msgstr "Відтворені композиції"
|
||||
|
||||
#: contents/ui/main.qml:39
|
||||
#, kde-format
|
||||
msgid "Back"
|
||||
msgstr "Назад"
|
||||
|
||||
#: contents/ui/main.qml:73 contents/ui/SearchPage.qml:150
|
||||
#, kde-format
|
||||
msgid "Search"
|
||||
msgstr "Пошук"
|
||||
|
||||
#: contents/ui/main.qml:85
|
||||
#, kde-format
|
||||
msgid "AudioTube"
|
||||
msgstr "AudioTube"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:86
|
||||
#, kde-format
|
||||
msgid "Now Playing"
|
||||
msgstr "Зараз відтворюємо"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:112
|
||||
#, kde-format
|
||||
msgid "Queue"
|
||||
msgstr "Додати до черги"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:224
|
||||
#, kde-format
|
||||
msgid "Remove Track"
|
||||
msgstr "Вилучити композицію"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:244
|
||||
#: contents/ui/MinimizedPlayerControls.qml:96
|
||||
#, kde-format
|
||||
msgid "No media playing"
|
||||
msgstr "Відтворення не відбувається"
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:15
|
||||
#, kde-format
|
||||
msgid "Unknown list of songs"
|
||||
msgstr "Невідомий список композицій"
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:25
|
||||
#, kde-format
|
||||
msgid "No songs here yet"
|
||||
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»"
|
||||
|
||||
#~ msgid "Clear"
|
||||
#~ msgstr "Спорожнити"
|
||||
|
||||
#~ msgid "Pause"
|
||||
#~ msgstr "Призупинити"
|
||||
|
||||
#~ msgid "Expand"
|
||||
#~ msgstr "Розгорнути"
|
||||
|
||||
#~ msgid "Play only Audio"
|
||||
#~ msgstr "Відтворити лише звук"
|
||||
@@ -0,0 +1,189 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: kdeorg\n"
|
||||
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
|
||||
"POT-Creation-Date: 2022-05-30 00:47+0000\n"
|
||||
"PO-Revision-Date: 2022-06-19 07:00\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/PlaylistPage.qml:19
|
||||
#, kde-format
|
||||
msgid "Play"
|
||||
msgstr "播放"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:26 contents/ui/PlaylistPage.qml:26
|
||||
#, kde-format
|
||||
msgid "Shuffle"
|
||||
msgstr "随机播放"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:68 contents/ui/ArtistPage.qml:101
|
||||
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
|
||||
#, kde-format
|
||||
msgid "Play Next"
|
||||
msgstr "播放下一首"
|
||||
|
||||
#: contents/ui/AlbumPage.qml:73 contents/ui/ArtistPage.qml:107
|
||||
#: contents/ui/PlaybackHistory.qml:79 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/LibraryPage.qml:27
|
||||
#, kde-format
|
||||
msgid "Favourites"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:37 contents/ui/LibraryPage.qml:108
|
||||
#, kde-format
|
||||
msgid "Show All"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:42
|
||||
#, kde-format
|
||||
msgid "Favourite Songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:98
|
||||
#, kde-format
|
||||
msgid "Most played"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:113
|
||||
#, kde-format
|
||||
msgid "Played Songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/main.qml:39
|
||||
#, kde-format
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/main.qml:73 contents/ui/SearchPage.qml:150
|
||||
#, kde-format
|
||||
msgid "Search"
|
||||
msgstr "搜索"
|
||||
|
||||
#: contents/ui/main.qml:85
|
||||
#, kde-format
|
||||
msgid "AudioTube"
|
||||
msgstr "AudioTube"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:86
|
||||
#, kde-format
|
||||
msgid "Now Playing"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:112
|
||||
#, kde-format
|
||||
msgid "Queue"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:224
|
||||
#, kde-format
|
||||
msgid "Remove Track"
|
||||
msgstr "移除曲目"
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:244
|
||||
#: contents/ui/MinimizedPlayerControls.qml:96
|
||||
#, kde-format
|
||||
msgid "No media playing"
|
||||
msgstr "没有正在播放的媒体"
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:15
|
||||
#, kde-format
|
||||
msgid "Unknown list of songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:25
|
||||
#, kde-format
|
||||
msgid "No songs here yet"
|
||||
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 中的音乐"
|
||||
@@ -0,0 +1,190 @@
|
||||
# Chinese translations for audiotube package
|
||||
# audiotube 套件的正體中文翻譯.
|
||||
# Copyright (C) 2022 This file is copyright:
|
||||
# This file is distributed under the same license as the audiotube package.
|
||||
# Automatically generated, 2022.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: audiotube\n"
|
||||
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
|
||||
"POT-Creation-Date: 2022-05-30 00:47+0000\n"
|
||||
"PO-Revision-Date: 2022-05-30 00:47+0000\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: zh_TW\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 ""
|
||||
|
||||
#, kde-format
|
||||
msgctxt "EMAIL OF TRANSLATORS"
|
||||
msgid "Your emails"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/AlbumPage.qml:19 contents/ui/PlaylistPage.qml:19
|
||||
#, kde-format
|
||||
msgid "Play"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/AlbumPage.qml:26 contents/ui/PlaylistPage.qml:26
|
||||
#, kde-format
|
||||
msgid "Shuffle"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/AlbumPage.qml:68 contents/ui/ArtistPage.qml:101
|
||||
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
|
||||
#, kde-format
|
||||
msgid "Play Next"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/AlbumPage.qml:73 contents/ui/ArtistPage.qml:107
|
||||
#: contents/ui/PlaybackHistory.qml:79 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/LibraryPage.qml:27
|
||||
#, kde-format
|
||||
msgid "Favourites"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:37 contents/ui/LibraryPage.qml:108
|
||||
#, kde-format
|
||||
msgid "Show All"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:42
|
||||
#, kde-format
|
||||
msgid "Favourite Songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:98
|
||||
#, kde-format
|
||||
msgid "Most played"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/LibraryPage.qml:113
|
||||
#, kde-format
|
||||
msgid "Played Songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/main.qml:39
|
||||
#, kde-format
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/main.qml:73 contents/ui/SearchPage.qml:150
|
||||
#, kde-format
|
||||
msgid "Search"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/main.qml:85
|
||||
#, kde-format
|
||||
msgid "AudioTube"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:86
|
||||
#, kde-format
|
||||
msgid "Now Playing"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:112
|
||||
#, kde-format
|
||||
msgid "Queue"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:224
|
||||
#, kde-format
|
||||
msgid "Remove Track"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/MaximizedPlayerPage.qml:244
|
||||
#: contents/ui/MinimizedPlayerControls.qml:96
|
||||
#, kde-format
|
||||
msgid "No media playing"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:15
|
||||
#, kde-format
|
||||
msgid "Unknown list of songs"
|
||||
msgstr ""
|
||||
|
||||
#: contents/ui/PlaybackHistory.qml:25
|
||||
#, kde-format
|
||||
msgid "No songs here yet"
|
||||
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 ""
|
||||
+2
-2
@@ -3,5 +3,5 @@
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
ytmusicapi==0.17.3
|
||||
youtube_dl
|
||||
ytmusicapi==0.22.0
|
||||
yt-dlp
|
||||
@@ -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
|
||||
@@ -24,7 +32,9 @@ add_executable(audiotube
|
||||
playlistutils.cpp
|
||||
errorhandler.cpp
|
||||
playerutils.cpp
|
||||
abstractytmusicmodel.cpp
|
||||
multiiterableview.h
|
||||
library.cpp
|
||||
resources.qrc
|
||||
)
|
||||
|
||||
@@ -39,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)
|
||||
|
||||
install(TARGETS audiotube ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
|
||||
@@ -0,0 +1,27 @@
|
||||
// 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
|
||||
|
||||
#include "abstractytmusicmodel.h"
|
||||
|
||||
#include "asyncytmusic.h"
|
||||
|
||||
|
||||
AbstractYTMusicModel::AbstractYTMusicModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
bool AbstractYTMusicModel::loading() const
|
||||
{
|
||||
return m_loading;
|
||||
}
|
||||
|
||||
void AbstractYTMusicModel::setLoading(bool loading)
|
||||
{
|
||||
if (m_loading == loading)
|
||||
return;
|
||||
|
||||
m_loading = loading;
|
||||
Q_EMIT loadingChanged();
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// 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
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractListModel>
|
||||
|
||||
///
|
||||
/// Base class for all models that work with data from the AsyncYTMusic class.
|
||||
/// It provides a loading property, that is automatically set to false if a query fails.
|
||||
///
|
||||
class AbstractYTMusicModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool loading READ loading WRITE setLoading NOTIFY loadingChanged)
|
||||
|
||||
public:
|
||||
explicit AbstractYTMusicModel(QObject *parent = nullptr);
|
||||
|
||||
bool loading() const;
|
||||
void setLoading(bool loading);
|
||||
Q_SIGNAL void loadingChanged();
|
||||
|
||||
private:
|
||||
bool m_loading = false;
|
||||
};
|
||||
|
||||
+17
-33
@@ -7,25 +7,26 @@
|
||||
#include <QDebug>
|
||||
|
||||
AlbumModel::AlbumModel(QObject *parent)
|
||||
: QAbstractListModel(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();
|
||||
@@ -81,18 +77,6 @@ QString AlbumModel::title() const
|
||||
return QString::fromStdString(m_album.title);
|
||||
}
|
||||
|
||||
bool AlbumModel::loading() const
|
||||
{
|
||||
return m_loading;
|
||||
}
|
||||
|
||||
void AlbumModel::setLoading(bool loading)
|
||||
{
|
||||
m_loading = loading;
|
||||
Q_EMIT loadingChanged();
|
||||
}
|
||||
|
||||
|
||||
QUrl AlbumModel::thumbnailUrl() const
|
||||
{
|
||||
if (m_album.thumbnails.empty()) {
|
||||
@@ -104,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);
|
||||
}
|
||||
+2
-7
@@ -8,15 +8,15 @@
|
||||
#include <QUrl>
|
||||
|
||||
#include "asyncytmusic.h"
|
||||
#include "abstractytmusicmodel.h"
|
||||
|
||||
class AlbumModel : public QAbstractListModel
|
||||
class AlbumModel : public AbstractYTMusicModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString browseId READ browseId WRITE setBrowseId NOTIFY browseIdChanged REQUIRED)
|
||||
|
||||
Q_PROPERTY(QString title READ title NOTIFY titleChanged)
|
||||
Q_PROPERTY(QUrl thumbnailUrl READ thumbnailUrl NOTIFY thumbnailUrlChanged)
|
||||
Q_PROPERTY(bool loading READ loading WRITE setLoading NOTIFY loadingChanged)
|
||||
Q_PROPERTY(QString playlistId READ playlistId NOTIFY playlistIdChanged)
|
||||
|
||||
public:
|
||||
@@ -39,10 +39,6 @@ public:
|
||||
QString title() const;
|
||||
Q_SIGNAL void titleChanged();
|
||||
|
||||
bool loading() const;
|
||||
void setLoading(bool loading);
|
||||
Q_SIGNAL void loadingChanged();
|
||||
|
||||
QUrl thumbnailUrl() const;
|
||||
Q_SIGNAL void thumbnailUrlChanged();
|
||||
|
||||
@@ -51,7 +47,6 @@ public:
|
||||
|
||||
private:
|
||||
QString m_browseId;
|
||||
bool m_loading = false;
|
||||
|
||||
album::Album m_album;
|
||||
};
|
||||
+42
-61
@@ -7,55 +7,49 @@
|
||||
#include <asyncytmusic.h>
|
||||
|
||||
ArtistModel::ArtistModel(QObject *parent)
|
||||
: QAbstractListModel(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();
|
||||
@@ -151,33 +145,20 @@ QUrl ArtistModel::thumbnailUrl() const
|
||||
return QUrl(QString::fromStdString(m_artist.thumbnails.back().url));
|
||||
}
|
||||
|
||||
bool ArtistModel::loading() const
|
||||
{
|
||||
return m_loading;
|
||||
}
|
||||
|
||||
void ArtistModel::setLoading(bool loading)
|
||||
{
|
||||
m_loading = loading;
|
||||
Q_EMIT loadingChanged();
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
+13
-10
@@ -10,9 +10,12 @@
|
||||
#include <optional>
|
||||
|
||||
#include <ytmusic.h>
|
||||
#include "multiiterableview.h"
|
||||
|
||||
class ArtistModel : public QAbstractListModel
|
||||
#include "multiiterableview.h"
|
||||
#include "abstractytmusicmodel.h"
|
||||
|
||||
|
||||
class ArtistModel : public AbstractYTMusicModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
@@ -20,7 +23,6 @@ class ArtistModel : public QAbstractListModel
|
||||
|
||||
Q_PROPERTY(QString title READ title NOTIFY titleChanged)
|
||||
Q_PROPERTY(QUrl thumbnailUrl READ thumbnailUrl NOTIFY thumbnailUrlChanged)
|
||||
Q_PROPERTY(bool loading READ loading WRITE setLoading NOTIFY loadingChanged)
|
||||
|
||||
public:
|
||||
enum Type {
|
||||
@@ -54,10 +56,6 @@ public:
|
||||
QUrl thumbnailUrl() const;
|
||||
Q_SIGNAL void thumbnailUrlChanged();
|
||||
|
||||
bool loading() const;
|
||||
void setLoading(bool loading);
|
||||
Q_SIGNAL void loadingChanged();
|
||||
|
||||
Q_INVOKABLE void triggerItem(int row);
|
||||
|
||||
Q_SIGNAL void openAlbum(const QString &browseId);
|
||||
@@ -66,10 +64,15 @@ public:
|
||||
|
||||
private:
|
||||
QString m_channelId;
|
||||
bool m_loading = false;
|
||||
|
||||
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;
|
||||
};
|
||||
@@ -0,0 +1,309 @@
|
||||
// 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();
|
||||
|
||||
database.transaction();
|
||||
|
||||
// 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) {
|
||||
database.commit();
|
||||
markMigrationRun(database, subdir.dirName());
|
||||
} else {
|
||||
database.rollback();
|
||||
}
|
||||
}
|
||||
}
|
||||
qCDebug(asyncdatabase) << "Migrations finished";
|
||||
}
|
||||
|
||||
struct AsyncSqlDatabasePrivate {
|
||||
QSqlDatabase database;
|
||||
};
|
||||
|
||||
// Internal asynchronous database class
|
||||
QFuture<void> AsyncSqlDatabase::establishConnection(const DatabaseConfiguration &configuration)
|
||||
{
|
||||
return runAsync([=, this] {
|
||||
d->database = QSqlDatabase::addDatabase(configuration.type());
|
||||
if (configuration.databaseName()) {
|
||||
d->database.setDatabaseName(*configuration.databaseName());
|
||||
}
|
||||
if (configuration.hostName()) {
|
||||
d->database.setHostName(*configuration.hostName());
|
||||
}
|
||||
if (configuration.userName()) {
|
||||
d->database.setUserName(*configuration.userName());
|
||||
}
|
||||
if (configuration.password()) {
|
||||
d->database.setPassword(*configuration.password());
|
||||
}
|
||||
|
||||
if (!d->database.open()) {
|
||||
qCDebug(asyncdatabase) << "Failed to open database" << d->database.lastError().text();
|
||||
if (configuration.databaseName()) {
|
||||
qCDebug(asyncdatabase) << "Tried to use database" << *configuration.databaseName();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
AsyncSqlDatabase::AsyncSqlDatabase()
|
||||
: QObject()
|
||||
, d(std::make_unique<AsyncSqlDatabasePrivate>())
|
||||
{
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
QSqlDatabase &AsyncSqlDatabase::db()
|
||||
{
|
||||
return d->database;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
QSqlQuery runQuery(QSqlQuery &query)
|
||||
{
|
||||
if (!query.exec()) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
struct ThreadedDatabasePrivate {
|
||||
asyncdatabase_private::AsyncSqlDatabase db;
|
||||
};
|
||||
|
||||
std::unique_ptr<ThreadedDatabase> ThreadedDatabase::establishConnection(DatabaseConfiguration config) {
|
||||
auto threadedDb = std::make_unique<ThreadedDatabase>();
|
||||
threadedDb->setObjectName(QStringLiteral("database thread"));
|
||||
threadedDb->d->db.moveToThread(&*threadedDb);
|
||||
threadedDb->start();
|
||||
threadedDb->d->db.establishConnection(config);
|
||||
return threadedDb;
|
||||
}
|
||||
|
||||
ThreadedDatabase::ThreadedDatabase()
|
||||
: QThread()
|
||||
, d(std::make_unique<ThreadedDatabasePrivate>())
|
||||
{
|
||||
}
|
||||
|
||||
ThreadedDatabase::~ThreadedDatabase()
|
||||
{
|
||||
quit();
|
||||
wait();
|
||||
}
|
||||
|
||||
asyncdatabase_private::AsyncSqlDatabase &ThreadedDatabase::db()
|
||||
{
|
||||
return d->db;
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
// 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 <QSharedDataPointer>
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include "asyncdatabase_p.h"
|
||||
|
||||
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");
|
||||
|
||||
template <typename T>
|
||||
concept FromSql = requires(T v, typename T::ColumnTypes row)
|
||||
{
|
||||
typename T::ColumnTypes;
|
||||
{ std::tuple(row) } -> std::same_as<typename T::ColumnTypes>;
|
||||
{ T::fromSql(row) } -> std::same_as<T>;
|
||||
};
|
||||
|
||||
struct ThreadedDatabasePrivate;
|
||||
|
||||
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>
|
||||
requires std::conjunction_v<std::is_convertible<Args, QVariant>...>
|
||||
auto execute(const QString &sqlQuery, Args... args) -> QFuture<void> {
|
||||
return 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 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.
|
||||
///
|
||||
/// T must provide a tuple of the column types as `using ColumnTypes = std::tuple<...>`
|
||||
/// and a `static T fromSql(ColumnTypes tuple)` deserialization method.
|
||||
///
|
||||
template <typename T, typename ...Args>
|
||||
requires FromSql<T> && std::conjunction_v<std::is_convertible<Args, QVariant>...>
|
||||
auto getResults(const QString &sqlQuery, Args... args) -> QFuture<std::vector<T>> {
|
||||
return db().getResults<T, Args...>(sqlQuery, args...);
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief Like getResults, but for retrieving just one row.
|
||||
///
|
||||
template <typename T, typename ...Args>
|
||||
requires FromSql<T> && std::conjunction_v<std::is_convertible<Args, QVariant>...>
|
||||
auto getResult(const QString &sqlQuery, Args... args) -> QFuture<std::optional<T>> {
|
||||
return db().getResult<T, Args...>(sqlQuery, args...);
|
||||
}
|
||||
|
||||
ThreadedDatabase();
|
||||
~ThreadedDatabase();
|
||||
|
||||
private:
|
||||
asyncdatabase_private::AsyncSqlDatabase &db();
|
||||
|
||||
std::unique_ptr<ThreadedDatabasePrivate> d;
|
||||
};
|
||||
|
||||
///
|
||||
/// \brief Deserialize just a single value from a query result.
|
||||
///
|
||||
template <typename T>
|
||||
struct SingleValue {
|
||||
using ColumnTypes = std::tuple<T>;
|
||||
|
||||
static SingleValue fromSql(ColumnTypes tuple) {
|
||||
auto [value] = tuple;
|
||||
return SingleValue { value };
|
||||
}
|
||||
|
||||
T value;
|
||||
};
|
||||
@@ -0,0 +1,179 @@
|
||||
// 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>;
|
||||
|
||||
template <typename RowTypesTuple>
|
||||
auto parseRow(const Row &row) -> RowTypesTuple
|
||||
{
|
||||
auto tuple = RowTypesTuple();
|
||||
int i = 0;
|
||||
asyncdatabase_private::iterate_tuple(tuple, [&](auto &elem) {
|
||||
elem = row.at(i).value<std::decay_t<decltype(elem)>>();
|
||||
i++;
|
||||
});
|
||||
return tuple;
|
||||
}
|
||||
|
||||
template <typename RowTypesTuple>
|
||||
auto parseRows(const Rows &rows) -> std::vector<RowTypesTuple> {
|
||||
std::vector<RowTypesTuple> parsedRows;
|
||||
parsedRows.reserve(rows.size());
|
||||
std::ranges::transform(rows, std::back_inserter(parsedRows), parseRow<RowTypesTuple>);
|
||||
return parsedRows;
|
||||
}
|
||||
|
||||
void runDatabaseMigrations(QSqlDatabase &database, const QString &migrationDirectory);
|
||||
|
||||
void printSqlError(const QSqlQuery &query);
|
||||
|
||||
// non-template helper functions to allow patching a much as possible in the shared library
|
||||
QSqlQuery prepareQuery(const QSqlDatabase &database, const QString &sqlQuery);
|
||||
QSqlQuery runQuery(QSqlQuery &query);
|
||||
|
||||
struct AsyncSqlDatabasePrivate;
|
||||
|
||||
class AsyncSqlDatabase : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QFuture<void> establishConnection(const DatabaseConfiguration &configuration);
|
||||
|
||||
template <typename T, typename ...Args>
|
||||
auto getResults(const QString &sqlQuery, Args... args) -> QFuture<std::vector<T>> {
|
||||
return runAsync([=, this] {
|
||||
auto query = executeQuery(sqlQuery, args...);
|
||||
const auto rows = parseRows<typename T::ColumnTypes>(retrieveRows(query));
|
||||
|
||||
std::vector<T> deserializedRows;
|
||||
std::ranges::transform(rows, std::back_inserter(deserializedRows), T::fromSql);
|
||||
return deserializedRows;
|
||||
});
|
||||
}
|
||||
|
||||
template <typename T, typename ...Args>
|
||||
auto getResult(const QString &sqlQuery, Args... args) -> QFuture<std::optional<T>> {
|
||||
return runAsync([=, this]() -> std::optional<T> {
|
||||
auto query = executeQuery(sqlQuery, args...);
|
||||
if (const auto row = retrieveOptionalRow(query)) {
|
||||
return T::fromSql(parseRow<typename T::ColumnTypes>(*row));
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
});
|
||||
}
|
||||
|
||||
template <typename ...Args>
|
||||
auto execute(const QString &sqlQuery, Args... args) -> QFuture<void> {
|
||||
return runAsync([=, this] {
|
||||
executeQuery(sqlQuery, args...);
|
||||
});
|
||||
}
|
||||
|
||||
auto runMigrations(const QString &migrationDirectory) -> QFuture<void> {
|
||||
return runAsync([=, this] {
|
||||
runDatabaseMigrations(db(), migrationDirectory);
|
||||
});
|
||||
}
|
||||
|
||||
AsyncSqlDatabase();
|
||||
~AsyncSqlDatabase();
|
||||
|
||||
private:
|
||||
template <typename ...Args>
|
||||
QSqlQuery executeQuery(const QString &sqlQuery, Args... args) {
|
||||
auto query = prepareQuery(db(), 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++;
|
||||
});
|
||||
return runQuery(query);
|
||||
}
|
||||
|
||||
template <typename Functor>
|
||||
QFuture<std::invoke_result_t<Functor>> runAsync(Functor func) {
|
||||
using ReturnType = std::invoke_result_t<Functor>;
|
||||
auto interface = std::make_shared<QFutureInterface<ReturnType>>();
|
||||
QMetaObject::invokeMethod(this, [interface, func] {
|
||||
if constexpr (!std::is_same_v<ReturnType, 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);
|
||||
|
||||
QSqlDatabase &db();
|
||||
|
||||
std::unique_ptr<AsyncSqlDatabasePrivate> d;
|
||||
};
|
||||
|
||||
}
|
||||
+58
-106
@@ -6,6 +6,10 @@
|
||||
|
||||
#include <QThread>
|
||||
#include <QDebug>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QFutureInterface>
|
||||
#include <QCoreApplication>
|
||||
|
||||
#include <pybind11/embed.h>
|
||||
|
||||
@@ -13,10 +17,21 @@
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
template <typename R, typename T, typename OP>
|
||||
std::optional<R> map_optional(const std::optional<T> &optional, OP op) {
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
#include <unordered_map>
|
||||
#include <ranges>
|
||||
#include <type_traits>
|
||||
#include <memory>
|
||||
|
||||
template <typename T, typename OP>
|
||||
std::optional<std::invoke_result_t<OP, T>> mapOptional(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 +52,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 +60,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([=, 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([=, 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([=, 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([=, 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([=, this]() {
|
||||
return m_ytm->get_playlist(playlistId.toStdString());
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// fetchArtistAlbum
|
||||
//
|
||||
void AsyncYTMusic::fetchArtistAlbums(const QString &channelId, const QString ¶ms)
|
||||
QFuture<std::vector<artist::Artist::Album>> AsyncYTMusic::fetchArtistAlbums(const QString &channelId, const QString ¶ms)
|
||||
{
|
||||
Q_EMIT startFetchArtistAlbums(channelId, params);
|
||||
}
|
||||
|
||||
void AsyncYTMusic::internalFetchArtistAlbums(const QString &channelid, const QString ¶ms)
|
||||
{
|
||||
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([=, 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([=, 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([=, this]() {
|
||||
return m_ytm->get_watch_playlist(
|
||||
mapOptional(videoId, &QString::toStdString),
|
||||
mapOptional(playlistId, &QString::toStdString)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
YTMusicThread &YTMusicThread::instance()
|
||||
@@ -203,17 +153,19 @@ YTMusicThread::~YTMusicThread()
|
||||
|
||||
AsyncYTMusic *YTMusicThread::operator->()
|
||||
{
|
||||
return &m_ytm;
|
||||
return m_ytm;
|
||||
}
|
||||
|
||||
AsyncYTMusic &YTMusicThread::get()
|
||||
{
|
||||
return m_ytm;
|
||||
return *m_ytm;
|
||||
}
|
||||
|
||||
YTMusicThread::YTMusicThread()
|
||||
: m_ytm(new AsyncYTMusic())
|
||||
{
|
||||
connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, m_ytm, &QObject::deleteLater);
|
||||
setObjectName(QStringLiteral("YTMusicAPI"));
|
||||
m_ytm.moveToThread(this);
|
||||
m_ytm->moveToThread(this);
|
||||
start();
|
||||
}
|
||||
+73
-40
@@ -6,6 +6,8 @@
|
||||
|
||||
#include <QObject>
|
||||
#include <QThread>
|
||||
#include <QFuture>
|
||||
#include <QFutureWatcher>
|
||||
|
||||
#include <vector>
|
||||
|
||||
@@ -45,6 +47,51 @@ 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 Func>
|
||||
QFuture<std::invoke_result_t<Func, T>> mapFuture(const QFuture<T> &future, Func mapFunction) {
|
||||
using ReturnType = std::invoke_result_t<Func, T>;
|
||||
auto watcher = std::make_shared<QFutureWatcher<T>>();
|
||||
watcher->setFuture(future);
|
||||
|
||||
auto interface = std::make_shared<QFutureInterface<ReturnType>>();
|
||||
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 +100,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 ¶ms);
|
||||
Q_SIGNAL void fetchArtistAlbumsFinished(std::vector<artist::Artist::Album>);
|
||||
QFuture<std::vector<artist::Artist::Album>> fetchArtistAlbums(const QString &channelId, const QString ¶ms);
|
||||
|
||||
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 +123,23 @@ 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 ¶ms);
|
||||
Q_SLOT void internalFetchArtistAlbums(const QString &channelid, const QString ¶ms);
|
||||
|
||||
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 Func>
|
||||
QFuture<std::invoke_result_t<Func>> invokeAndCatchOnThread(Func fun) {
|
||||
using ReturnType = std::invoke_result_t<Func>;
|
||||
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;
|
||||
@@ -123,5 +156,5 @@ public:
|
||||
private:
|
||||
YTMusicThread();
|
||||
|
||||
AsyncYTMusic m_ytm;
|
||||
AsyncYTMusic *m_ytm;
|
||||
};
|
||||
@@ -21,6 +21,13 @@ Kirigami.ScrollablePage {
|
||||
applicationWindow().playPlaylist(albumModel.playlistId)
|
||||
}
|
||||
}
|
||||
right: Kirigami.Action {
|
||||
icon.name: "media-playlist-shuffle"
|
||||
text: i18n("Shuffle")
|
||||
onTriggered: {
|
||||
applicationWindow().playShufflePlaylist(albumModel.playlistId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
// 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 {
|
||||
|
||||
globalToolBarStyle: Kirigami.ApplicationHeaderStyle.None
|
||||
|
||||
// so that there is still a separator, since the header style is none
|
||||
Kirigami.Separator {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Kirigami.Heading {
|
||||
text: i18n("Favourites")
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
}
|
||||
|
||||
// Spacer
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Controls.ToolButton {
|
||||
text: i18n("Show All")
|
||||
Layout.alignment: Qt.AlignRight
|
||||
icon.name: "arrow-right"
|
||||
onClicked: pageStack.layers.push("qrc:/PlaybackHistory.qml", {
|
||||
"dataModel": Library.favourites,
|
||||
"title": i18n("Favourite Songs")
|
||||
})
|
||||
}
|
||||
}
|
||||
Controls.ScrollView {
|
||||
Layout.fillWidth: true
|
||||
RowLayout {
|
||||
Repeater {
|
||||
Layout.fillWidth: true
|
||||
model: Library.favourites
|
||||
delegate: ColumnLayout {
|
||||
id: delegateItem
|
||||
required property string title
|
||||
required property string artists
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Kirigami.Heading {
|
||||
text: i18n("Most played")
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
}
|
||||
|
||||
// Spacer
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Controls.ToolButton {
|
||||
text: i18n("Show All")
|
||||
Layout.alignment: Qt.AlignRight
|
||||
icon.name: "arrow-right"
|
||||
onClicked: pageStack.layers.push("qrc:/PlaybackHistory.qml", {
|
||||
"dataModel": Library.playbackHistory,
|
||||
"title": i18n("Played Songs")
|
||||
})
|
||||
}
|
||||
}
|
||||
Controls.ScrollView {
|
||||
Layout.fillWidth: true
|
||||
RowLayout {
|
||||
Repeater {
|
||||
Layout.fillWidth: true
|
||||
model: Library.mostPlayed
|
||||
delegate: ColumnLayout {
|
||||
id: mpdelegateItem
|
||||
required property string title
|
||||
required property string artists
|
||||
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
|
||||
}
|
||||
}
|
||||
Controls.Label {
|
||||
Layout.maximumWidth: 200
|
||||
text: mpdelegateItem.title
|
||||
elide: Qt.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,427 @@
|
||||
// SPDX-FileCopyrightText: 2020-2022 Devin Lin <[email protected]>
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtMultimedia 5.12
|
||||
|
||||
import org.kde.kirigami 2.19 as Kirigami
|
||||
import org.kde.ytmusic 1.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property var info // VideoInfoExtractor object
|
||||
required property var audio // Audio object
|
||||
|
||||
signal requestClose()
|
||||
|
||||
// background image
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Qt.rgba(25, 25, 30, 1)
|
||||
}
|
||||
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
asynchronous: true
|
||||
|
||||
source: info.thumbnail
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
|
||||
sourceSize.width: 512
|
||||
sourceSize.height: 512
|
||||
}
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: HueSaturation {
|
||||
cached: true
|
||||
|
||||
lightness: -0.5
|
||||
saturation: 1.9
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: FastBlur {
|
||||
cached: true
|
||||
radius: 100
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// content
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
// hide arrow button
|
||||
ToolButton {
|
||||
id: closeButton
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
|
||||
Layout.maximumHeight: parent.height
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 3
|
||||
Layout.maximumWidth: parent.height
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 3
|
||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||
icon.name: "arrow-down"
|
||||
icon.color: "white"
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
|
||||
Kirigami.Theme.inherit: false
|
||||
onClicked: root.requestClose()
|
||||
}
|
||||
|
||||
// top tab bar
|
||||
RowLayout {
|
||||
id: tabBar
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
ToolButton {
|
||||
id: nowPlayingTab
|
||||
text: i18n("Now Playing")
|
||||
padding: Kirigami.Units.largeSpacing
|
||||
onClicked: swipeView.setCurrentIndex(0)
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 9
|
||||
|
||||
contentItem: Label {
|
||||
text: nowPlayingTab.text
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
color: "white" // we need to ensure the label is white
|
||||
}
|
||||
|
||||
// selection indicator
|
||||
Rectangle {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
color: Kirigami.Theme.highlightColor
|
||||
height: 1
|
||||
visible: swipeView.currentIndex == 0
|
||||
}
|
||||
}
|
||||
ToolButton {
|
||||
id: queueTab
|
||||
text: i18n("Queue")
|
||||
padding: Kirigami.Units.largeSpacing
|
||||
onClicked: swipeView.setCurrentIndex(1)
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 9
|
||||
|
||||
contentItem: Label {
|
||||
text: queueTab.text
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
color: "white"
|
||||
}
|
||||
|
||||
// selection indicator
|
||||
Rectangle {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
color: Kirigami.Theme.highlightColor
|
||||
height: 1
|
||||
visible: swipeView.currentIndex == 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// tabs
|
||||
SwipeView {
|
||||
id: swipeView
|
||||
property double specWidth: {
|
||||
let allowedWidth = root.width - Kirigami.Units.largeSpacing * 4;
|
||||
let allowedHeight = root.height - Kirigami.Units.largeSpacing * 8 - (closeButton.height + tabBar.height + bottomPlayerControls.height);
|
||||
if (allowedWidth > allowedHeight) {
|
||||
return allowedHeight;
|
||||
} else {
|
||||
return allowedWidth;
|
||||
}
|
||||
}
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: specWidth
|
||||
|
||||
// music art
|
||||
Item {
|
||||
height: swipeView.width
|
||||
width: swipeView.height
|
||||
|
||||
Image {
|
||||
anchors.centerIn: parent
|
||||
width: swipeView.specWidth
|
||||
height: swipeView.specWidth
|
||||
|
||||
asynchronous: true
|
||||
mipmap: true
|
||||
|
||||
source: info.thumbnail
|
||||
|
||||
sourceSize {
|
||||
width: 512
|
||||
height: 512
|
||||
}
|
||||
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
}
|
||||
}
|
||||
|
||||
// playlist
|
||||
ListView {
|
||||
id: playlistView
|
||||
width: swipeView.width
|
||||
height: swipeView.height
|
||||
clip: true
|
||||
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
|
||||
Kirigami.Theme.inherit: false
|
||||
|
||||
BusyIndicator {
|
||||
anchors.centerIn: parent
|
||||
visible: UserPlaylistModel.loading || UserPlaylistModel.loading
|
||||
}
|
||||
|
||||
model: UserPlaylistModel
|
||||
|
||||
delegate: Kirigami.SwipeListItem {
|
||||
id: delegateItem
|
||||
required property string title
|
||||
required property string videoId
|
||||
required property string artists
|
||||
required property bool isCurrent
|
||||
|
||||
backgroundColor: 'transparent'
|
||||
highlighted: isCurrent
|
||||
onClicked: UserPlaylistModel.skipTo(videoId)
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Kirigami.Heading {
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
level: 2
|
||||
text: title
|
||||
}
|
||||
|
||||
Label {
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
text: artists
|
||||
}
|
||||
}
|
||||
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
text: i18n("Remove Track")
|
||||
icon.name: "list-remove"
|
||||
icon.color: "white"
|
||||
onTriggered: UserPlaylistModel.remove(delegateItem.videoId)
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: bottomPlayerControls
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
Layout.leftMargin: Kirigami.Units.gridUnit * 2
|
||||
Layout.rightMargin: Kirigami.Units.gridUnit * 2
|
||||
Layout.bottomMargin: Kirigami.Units.gridUnit * 2
|
||||
|
||||
// song name
|
||||
Label {
|
||||
id: mainLabel
|
||||
text: info.title ? info.title : i18n("No media playing")
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
// Hardcoded because the footerbar blur always makes a dark-ish
|
||||
// background, so we don't want to use a color scheme color that
|
||||
// might also be dark
|
||||
color: "white"
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 1.5
|
||||
font.weight: Font.Bold
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
// song artist
|
||||
Kirigami.Heading {
|
||||
id: authorLabel
|
||||
text: info.artist ? info.artist : info.channel
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
// Hardcoded because the footerbar blur always makes a dark-ish
|
||||
// background, so we don't want to use a color scheme color that
|
||||
// might also be dark
|
||||
color: "white"
|
||||
opacity: 0.9
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 1.3
|
||||
font.weight: Font.Bold
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
// slider row
|
||||
RowLayout {
|
||||
Layout.topMargin: Kirigami.Units.gridUnit * 2
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: "white"
|
||||
visible: info.title
|
||||
text: PlayerUtils.formatTimestamp(audio.position)
|
||||
}
|
||||
|
||||
Slider {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.fillWidth: true
|
||||
from: 0
|
||||
to: audio.duration
|
||||
value: audio.position
|
||||
enabled: audio.seekable
|
||||
onMoved: {
|
||||
console.log("Value:", value);
|
||||
audio.seek(Math.floor(value));
|
||||
}
|
||||
|
||||
Behavior on value {
|
||||
NumberAnimation {
|
||||
duration: 1000
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: "white"
|
||||
visible: info.title
|
||||
text: PlayerUtils.formatTimestamp(audio.duration)
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.fillWidth: true
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
// ensure white icons
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
|
||||
Kirigami.Theme.inherit: false
|
||||
|
||||
ToolButton {
|
||||
id: favouriteButton
|
||||
readonly property QtObject favouriteWatcher: Library.favouriteWatcher(info.videoId)
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 2.5
|
||||
Layout.maximumWidth: height
|
||||
Layout.preferredWidth: height
|
||||
|
||||
onClicked: if (favouriteWatcher)
|
||||
favouriteWatcher.isFavourite
|
||||
? Library.removeFavourite(info.videoId)
|
||||
: Library.addFavourite(info.videoId, info.title)
|
||||
|
||||
icon.name: favouriteWatcher ? (favouriteWatcher.isFavourite ? "starred-symbolic" : "non-starred-symbolic") : ""
|
||||
icon.width: Kirigami.Units.gridUnit * 1.5
|
||||
icon.height: Kirigami.Units.gridUnit * 1.5
|
||||
icon.color: "white"
|
||||
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
|
||||
Kirigami.Theme.inherit: false
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
id: skipBackwardButton
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 2.5
|
||||
Layout.maximumWidth: height
|
||||
Layout.preferredWidth: height
|
||||
|
||||
enabled: false
|
||||
|
||||
icon.name: "media-skip-backward"
|
||||
icon.width: Kirigami.Units.gridUnit * 1.5
|
||||
icon.height: Kirigami.Units.gridUnit * 1.5
|
||||
icon.color: "white"
|
||||
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
|
||||
Kirigami.Theme.inherit: false
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
id: playPauseButton
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 4
|
||||
Layout.maximumWidth: height
|
||||
Layout.preferredWidth: height
|
||||
|
||||
enabled: info.audioUrl != ""
|
||||
onClicked: audio.playbackState === Audio.PlayingState ? audio.pause() : audio.play()
|
||||
|
||||
icon.name: audio.playbackState === Audio.PlayingState ? "media-playback-pause" : "media-playback-start"
|
||||
icon.width: Kirigami.Units.gridUnit * 3
|
||||
icon.height: Kirigami.Units.gridUnit * 3
|
||||
icon.color: "white"
|
||||
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
|
||||
Kirigami.Theme.inherit: false
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
id: skipForwardButton
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 2.5
|
||||
Layout.maximumWidth: height
|
||||
Layout.preferredWidth: height
|
||||
|
||||
enabled: UserPlaylistModel.canSkip
|
||||
onClicked: UserPlaylistModel.next()
|
||||
|
||||
icon.name: "media-skip-forward"
|
||||
icon.width: Kirigami.Units.gridUnit * 1.5
|
||||
icon.height: Kirigami.Units.gridUnit * 1.5
|
||||
icon.color: "white"
|
||||
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
|
||||
Kirigami.Theme.inherit: false
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
id: shuffleButton
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 2.5
|
||||
Layout.maximumWidth: height
|
||||
Layout.preferredWidth: height
|
||||
|
||||
onClicked: UserPlaylistModel.shufflePlaylist()
|
||||
|
||||
icon.name: "media-playlist-shuffle"
|
||||
icon.width: Kirigami.Units.gridUnit * 1.5
|
||||
icon.height: Kirigami.Units.gridUnit * 1.5
|
||||
icon.color: "white"
|
||||
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
|
||||
Kirigami.Theme.inherit: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
// SPDX-FileCopyrightText: 2020-2022 Devin Lin <[email protected]>
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtMultimedia 5.12
|
||||
|
||||
import org.kde.kirigami 2.19 as Kirigami
|
||||
import org.kde.ytmusic 1.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property int progressBarHeight
|
||||
required property int minimizedPlayerContentHeight
|
||||
|
||||
required property var info // VideoInfoExtractor object
|
||||
required property var audio // Audio object
|
||||
|
||||
readonly property bool isWidescreen: width >= Kirigami.Units.gridUnit * 40
|
||||
|
||||
signal requestOpen()
|
||||
|
||||
Rectangle {
|
||||
id: miniProgressBar
|
||||
z: 1
|
||||
visible: !root.isWidescreen
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
height: root.progressBarHeight
|
||||
color: Kirigami.Theme.highlightColor
|
||||
width: parent.width * (audio.position / audio.duration)
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
Rectangle {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: Kirigami.Units.gridUnit * 20
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 20
|
||||
color: Qt.rgba(0, 0, 0, trackClick.containsMouse ? 0.1 : trackClick.pressed ? 0.3 : 0)
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Kirigami.Units.largeSpacing
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
// track image
|
||||
Item {
|
||||
property double imageSize: root.height - Kirigami.Units.largeSpacing * 2
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.maximumWidth: imageSize
|
||||
Layout.preferredWidth: imageSize
|
||||
Layout.maximumHeight: imageSize
|
||||
Layout.minimumHeight: imageSize
|
||||
|
||||
Image {
|
||||
id: mainIcon
|
||||
anchors.fill: parent
|
||||
|
||||
visible: !loadingIndicator.visible
|
||||
asynchronous: true
|
||||
mipmap: true
|
||||
|
||||
source: info.thumbnail
|
||||
|
||||
sourceSize.width: 512
|
||||
sourceSize.height: 512
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
}
|
||||
|
||||
BusyIndicator {
|
||||
id: loadingIndicator
|
||||
anchors.centerIn: parent
|
||||
visible: UserPlaylistModel.loading
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
|
||||
Kirigami.Theme.inherit: false
|
||||
}
|
||||
}
|
||||
|
||||
// track information
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
Label {
|
||||
id: mainLabel
|
||||
text: info.title ? info.title : i18n("No media playing")
|
||||
textFormat: Text.PlainText
|
||||
wrapMode: Text.Wrap
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
// Hardcoded because the footerbar blur always makes a dark-ish
|
||||
// background, so we don't want to use a color scheme color that
|
||||
// might also be dark
|
||||
color: "white"
|
||||
font.weight: Font.Bold
|
||||
}
|
||||
|
||||
Label {
|
||||
id: authorLabel
|
||||
text: info.artist ? info.artist : info.channel
|
||||
textFormat: Text.PlainText
|
||||
wrapMode: Text.Wrap
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
// Hardcoded because the footerbar blur always makes a dark-ish
|
||||
// background, so we don't want to use a color scheme color that
|
||||
// might also be dark
|
||||
color: "white"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: trackClick
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: root.requestOpen()
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
visible: root.isWidescreen
|
||||
Layout.preferredWidth: 0
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: "white"
|
||||
visible: info.title
|
||||
text: PlayerUtils.formatTimestamp(audio.position)
|
||||
}
|
||||
|
||||
Slider {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.fillWidth: true
|
||||
from: 0
|
||||
to: audio.duration
|
||||
value: audio.position
|
||||
enabled: audio.seekable
|
||||
onMoved: {
|
||||
console.log("Value:", value);
|
||||
audio.seek(Math.floor(value));
|
||||
}
|
||||
|
||||
Behavior on value {
|
||||
NumberAnimation {
|
||||
duration: 1000
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: "white"
|
||||
visible: info.title
|
||||
text: PlayerUtils.formatTimestamp(audio.duration)
|
||||
}
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
id: playPauseButton
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 3
|
||||
Layout.maximumWidth: height
|
||||
Layout.preferredWidth: height
|
||||
|
||||
enabled: info.audioUrl != ""
|
||||
onClicked: audio.playbackState === Audio.PlayingState ? audio.pause() : audio.play()
|
||||
|
||||
icon.name: audio.playbackState === Audio.PlayingState ? "media-playback-pause" : "media-playback-start"
|
||||
icon.width: Kirigami.Units.gridUnit
|
||||
icon.height: Kirigami.Units.gridUnit
|
||||
icon.color: "white"
|
||||
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
|
||||
Kirigami.Theme.inherit: false
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
id: skipForwardButton
|
||||
Layout.rightMargin: Math.floor(Kirigami.Units.smallSpacing / 2)
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 3
|
||||
Layout.maximumWidth: height
|
||||
Layout.preferredWidth: height
|
||||
|
||||
enabled: UserPlaylistModel.canSkip
|
||||
onClicked: UserPlaylistModel.next()
|
||||
|
||||
icon.name: "media-skip-forward"
|
||||
icon.width: Kirigami.Units.gridUnit
|
||||
icon.height: Kirigami.Units.gridUnit
|
||||
icon.color: "white"
|
||||
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
|
||||
Kirigami.Theme.inherit: false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// 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.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQuick.Controls 2.15 as Controls
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.ytmusic 1.0
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
id: playbackHistoryPage
|
||||
title: i18n("Unknown list of songs")
|
||||
|
||||
property QtObject dataModel
|
||||
|
||||
ListView {
|
||||
id: listView
|
||||
reuseItems: true
|
||||
|
||||
Kirigami.PlaceholderMessage {
|
||||
visible: listView.count < 1
|
||||
text: i18n("No songs here yet")
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
model: playbackHistoryPage.dataModel
|
||||
|
||||
delegate: Kirigami.SwipeListItem {
|
||||
id: delegateItem
|
||||
required property string title
|
||||
required property string videoId
|
||||
required property string artists
|
||||
|
||||
onClicked: {
|
||||
play(videoId)
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
ThumbnailSource {
|
||||
id: thumbnailSource
|
||||
|
||||
videoId: delegateItem.videoId
|
||||
}
|
||||
|
||||
Item {
|
||||
width: Kirigami.Units.gridUnit * 3
|
||||
height: Kirigami.Units.gridUnit * 3
|
||||
|
||||
Image {
|
||||
sourceSize: "200x200"
|
||||
source: thumbnailSource.cachedPath
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
asynchronous: true
|
||||
|
||||
width: Kirigami.Units.gridUnit * 3
|
||||
height: Kirigami.Units.gridUnit * 3
|
||||
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
}
|
||||
}
|
||||
|
||||
Controls.Label {
|
||||
text: delegateItem.title
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
icon.name: "go-next"
|
||||
text: i18n("Play Next")
|
||||
onTriggered: UserPlaylistModel.playNext(delegateItem.videoId, delegateItem.title, [])
|
||||
},
|
||||
Kirigami.Action {
|
||||
icon.name: "media-playlist-append"
|
||||
text: i18n("Add to Playlist")
|
||||
onTriggered: UserPlaylistModel.append(delegateItem.videoId, delegateItem.title, [])
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
+399
-228
@@ -1,251 +1,422 @@
|
||||
// SPDX-FileCopyrightText: 2021 Jonah Brüchert <[email protected]>
|
||||
// SPDX-FileCopyrightText: 2021 Bart De Vries <[email protected]>
|
||||
// SPDX-FileCopyrightText: 2020-2022 Devin Lin <[email protected]>
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Controls 2.12 as Controls
|
||||
|
||||
import QtMultimedia 5.12
|
||||
import QtGraphicalEffects 1.0
|
||||
|
||||
import org.kde.kirigami 2.12 as Kirigami
|
||||
import org.kde.ytmusic 1.0
|
||||
import QtMultimedia 5.12
|
||||
|
||||
ColumnLayout {
|
||||
id: footerLayout
|
||||
|
||||
Flickable {
|
||||
id: footerItem
|
||||
|
||||
property bool maximized: false
|
||||
|
||||
readonly property int progressBarHeight: Kirigami.Units.gridUnit / 6
|
||||
readonly property int minimizedPlayerContentHeight: Math.round(Kirigami.Units.gridUnit * 3.5)
|
||||
readonly property int minimizedPlayerHeight: minimizedPlayerContentHeight + progressBarHeight
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: maximized ? applicationWindow().height : player.preferredHeight
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
function close() {
|
||||
toClose.restart();
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
id: playerLayout
|
||||
|
||||
readonly property bool mobile: width > height
|
||||
|
||||
Layout.fillHeight: footerLayout.maximized
|
||||
Layout.fillWidth: true
|
||||
flow: width > height ? GridLayout.LeftToRight : GridLayout.TopToBottom
|
||||
visible: footerLayout.maximized
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: info.thumbnail
|
||||
Layout.fillHeight: info.thumbnail
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
|
||||
source: info.thumbnail
|
||||
fillMode: Image.PreserveAspectFit
|
||||
function resetToBoundsOnFlick() {
|
||||
if (!atYBeginning || !atYEnd) {
|
||||
if (footerItem.verticalVelocity > 0) {
|
||||
toOpen.restart();
|
||||
} else if (footerItem.verticalVelocity < 0) {
|
||||
toClose.restart();
|
||||
} else { // i.e. when verticalVelocity === 0
|
||||
if (contentY > contentHeight / 4) {
|
||||
toOpen.restart();
|
||||
} else {
|
||||
toClose.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resetToBoundsOnResize() {
|
||||
if (contentY > contentHeight / 4) {
|
||||
contentY = contentHeight / 2;
|
||||
} else {
|
||||
contentY = 0;
|
||||
}
|
||||
}
|
||||
|
||||
NumberAnimation on contentY {
|
||||
id: toOpen
|
||||
from: contentY
|
||||
to: contentHeight / 2
|
||||
duration: Kirigami.Units.longDuration * 2
|
||||
easing.type: Easing.OutCubic
|
||||
running: false
|
||||
}
|
||||
|
||||
NumberAnimation on contentY {
|
||||
id: toClose
|
||||
from: contentY
|
||||
to: 0
|
||||
duration: Kirigami.Units.longDuration * 2
|
||||
easing.type: Easing.OutCubic
|
||||
running: false
|
||||
}
|
||||
|
||||
// snap to end
|
||||
MouseArea {
|
||||
anchors.fill: footerLayout
|
||||
propagateComposedEvents: true
|
||||
onPressed: {
|
||||
toOpen.stop();
|
||||
toClose.stop();
|
||||
propagateComposedEvents = true;
|
||||
}
|
||||
onReleased: footerItem.resetToBoundsOnFlick()
|
||||
}
|
||||
|
||||
onMovementStarted: {
|
||||
toOpen.stop();
|
||||
toClose.stop();
|
||||
}
|
||||
onFlickStarted: resetToBoundsOnFlick()
|
||||
onMovementEnded: resetToBoundsOnFlick()
|
||||
onHeightChanged: resetToBoundsOnResize()
|
||||
|
||||
property var videoInfoExtractor: VideoInfoExtractor {
|
||||
id: info
|
||||
|
||||
onSongChanged: audio.play()
|
||||
videoId: UserPlaylistModel.currentVideoId
|
||||
onTitleChanged: Library.addPlaybackHistoryItem(info.videoId, info.title)
|
||||
}
|
||||
|
||||
property var audioPlayer: Audio {
|
||||
id: audio
|
||||
|
||||
source: info.audioUrl
|
||||
onStatusChanged: {
|
||||
if (status === Audio.EndOfMedia) {
|
||||
console.log("Song ended");
|
||||
UserPlaylistModel.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contentHeight: height * 2
|
||||
|
||||
ColumnLayout {
|
||||
id: footerLayout
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
spacing: 0
|
||||
|
||||
// margin from top
|
||||
Item {
|
||||
Layout.minimumHeight: footerItem.height - footerItem.minimizedPlayerHeight
|
||||
}
|
||||
|
||||
Controls.Control {
|
||||
Layout.fillWidth: true
|
||||
padding: 0
|
||||
implicitHeight: footerItem.minimizedPlayerHeight
|
||||
|
||||
// minimized player background
|
||||
background: Item {
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Qt.rgba(25, 25, 30, 1)
|
||||
}
|
||||
|
||||
Image {
|
||||
opacity: 0.2
|
||||
source: info.thumbnail
|
||||
asynchronous: true
|
||||
|
||||
anchors.fill: parent
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
}
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: HueSaturation {
|
||||
cached: true
|
||||
|
||||
lightness: -0.5
|
||||
saturation: 1.9
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: FastBlur {
|
||||
cached: true
|
||||
radius: 64
|
||||
transparentBorder: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MinimizedPlayerControls {
|
||||
progressBarHeight: footerItem.progressBarHeight
|
||||
minimizedPlayerContentHeight: footerItem.minimizedPlayerContentHeight
|
||||
height: minimizedPlayerHeight
|
||||
|
||||
focus: true
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
|
||||
info: footerItem.videoInfoExtractor
|
||||
audio: footerItem.audioPlayer
|
||||
|
||||
onRequestOpen: toOpen.start();
|
||||
}
|
||||
}
|
||||
|
||||
MaximizedPlayerPage {
|
||||
id: maximizedPlayerPage
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
Controls.ScrollView {
|
||||
anchors.fill: parent
|
||||
|
||||
ListView {
|
||||
anchors.fill: parent
|
||||
id: playlistView
|
||||
|
||||
clip: true
|
||||
|
||||
Controls.BusyIndicator {
|
||||
anchors.centerIn: parent
|
||||
visible: UserPlaylistModel.loading || UserPlaylistModel.loading
|
||||
}
|
||||
|
||||
onCountChanged: {
|
||||
if (count < 1) {
|
||||
footerLayout.maximized = false
|
||||
}
|
||||
}
|
||||
|
||||
model: UserPlaylistModel
|
||||
|
||||
delegate: Kirigami.SwipeListItem {
|
||||
id: delegateItem
|
||||
required property string title
|
||||
required property string videoId
|
||||
required property string artists
|
||||
required property bool isCurrent
|
||||
|
||||
highlighted: isCurrent
|
||||
onClicked: UserPlaylistModel.skipTo(videoId)
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Kirigami.Heading {
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
level: 2
|
||||
text: title
|
||||
}
|
||||
|
||||
Controls.Label {
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
text: artists
|
||||
}
|
||||
}
|
||||
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
text: i18n("Remove Track")
|
||||
icon.name: "list-remove"
|
||||
onTriggered: UserPlaylistModel.remove(delegateItem.videoId)
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
header: Controls.ToolBar {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Controls.ToolButton {
|
||||
text: i18n("Clear")
|
||||
icon.name: "edit-clear-all"
|
||||
onClicked: UserPlaylistModel.clear()
|
||||
}
|
||||
Controls.ToolButton {
|
||||
text: i18n("Shuffle")
|
||||
icon.name: "media-playlist-shuffle"
|
||||
onClicked: UserPlaylistModel.shufflePlaylist()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
implicitHeight: footerItem.height
|
||||
|
||||
info: footerItem.videoInfoExtractor
|
||||
audio: footerItem.audioPlayer
|
||||
|
||||
onRequestClose: toClose.start();
|
||||
}
|
||||
|
||||
//GridLayout {
|
||||
//id: playerLayout
|
||||
|
||||
//readonly property bool mobile: width > height
|
||||
|
||||
//Layout.fillHeight: footerItem.maximized
|
||||
//Layout.fillWidth: true
|
||||
//flow: width > height ? GridLayout.LeftToRight : GridLayout.TopToBottom
|
||||
//visible: footerItem.maximized
|
||||
|
||||
//ColumnLayout {
|
||||
//Layout.preferredWidth: footerLayout.width * (playerLayout.mobile ? 1 : 0.5)
|
||||
//Layout.preferredHeight: footerLayout.height * (playerLayout.mobile ? 0.5 : 1)
|
||||
|
||||
//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
|
||||
|
||||
//ListView {
|
||||
//anchors.fill: parent
|
||||
//id: playlistView
|
||||
|
||||
//clip: true
|
||||
|
||||
//Controls.BusyIndicator {
|
||||
//anchors.centerIn: parent
|
||||
//visible: UserPlaylistModel.loading || UserPlaylistModel.loading
|
||||
//}
|
||||
|
||||
//onCountChanged: {
|
||||
//if (count < 1) {
|
||||
//footerItem.maximized = false
|
||||
//}
|
||||
//}
|
||||
|
||||
//model: UserPlaylistModel
|
||||
|
||||
//delegate: Kirigami.SwipeListItem {
|
||||
//id: delegateItem
|
||||
//required property string title
|
||||
//required property string videoId
|
||||
//required property string artists
|
||||
//required property bool isCurrent
|
||||
|
||||
//highlighted: isCurrent
|
||||
//onClicked: UserPlaylistModel.skipTo(videoId)
|
||||
|
||||
//ColumnLayout {
|
||||
//Layout.fillWidth: true
|
||||
//Kirigami.Heading {
|
||||
//elide: Text.ElideRight
|
||||
//Layout.fillWidth: true
|
||||
//level: 2
|
||||
//text: title
|
||||
//}
|
||||
|
||||
//Controls.Label {
|
||||
//elide: Text.ElideRight
|
||||
//Layout.fillWidth: true
|
||||
//text: artists
|
||||
//}
|
||||
//}
|
||||
|
||||
//actions: [
|
||||
//Kirigami.Action {
|
||||
//text: i18n("Remove Track")
|
||||
//icon.name: "list-remove"
|
||||
//onTriggered: UserPlaylistModel.remove(delegateItem.videoId)
|
||||
//}
|
||||
//]
|
||||
//}
|
||||
|
||||
//header: Controls.ToolBar {
|
||||
//anchors.left: parent.left
|
||||
//anchors.right: parent.right
|
||||
//RowLayout {
|
||||
//anchors.fill: parent
|
||||
//Item {
|
||||
//Layout.fillWidth: true
|
||||
//}
|
||||
|
||||
//Controls.ToolButton {
|
||||
//text: i18n("Clear")
|
||||
//icon.name: "edit-clear-all"
|
||||
//onClicked: UserPlaylistModel.clear()
|
||||
//}
|
||||
//Controls.ToolButton {
|
||||
//text: i18n("Shuffle")
|
||||
//icon.name: "media-playlist-shuffle"
|
||||
//onClicked: UserPlaylistModel.shufflePlaylist()
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
|
||||
//Kirigami.Separator {
|
||||
//Layout.fillWidth: true
|
||||
//}
|
||||
|
||||
//RowLayout {
|
||||
//id: player
|
||||
|
||||
//Layout.fillWidth: true
|
||||
//Layout.fillHeight: !footer.maximized
|
||||
|
||||
//Controls.ToolButton {
|
||||
//display: Controls.AbstractButton.IconOnly
|
||||
//Layout.preferredWidth: parent.height
|
||||
//Layout.fillHeight: true
|
||||
//enabled: info.audioUrl != ""
|
||||
//text: audio.playbackState === Audio.PlayingState ? i18n("Pause") : i18n("Play")
|
||||
//visible: !info.loading
|
||||
//icon.name: audio.playbackState === Audio.PlayingState ? "media-playback-pause" : "media-playback-start"
|
||||
//onClicked: audio.playbackState === Audio.PlayingState ? audio.pause() : audio.play()
|
||||
//}
|
||||
|
||||
//Controls.ToolButton {
|
||||
//display: Controls.AbstractButton.IconOnly
|
||||
//Layout.preferredWidth: parent.height
|
||||
//Layout.fillHeight: true
|
||||
//enabled: UserPlaylistModel.canSkip
|
||||
//visible: !info.loading
|
||||
//icon.name: "media-skip-forward"
|
||||
//onClicked: UserPlaylistModel.next()
|
||||
//}
|
||||
|
||||
//Controls.BusyIndicator {
|
||||
//Layout.fillHeight: true
|
||||
//visible: info.loading
|
||||
//}
|
||||
|
||||
//ColumnLayout {
|
||||
//Layout.fillHeight: true
|
||||
//Layout.topMargin: Kirigami.Units.smallSpacing
|
||||
//Layout.bottomMargin: Kirigami.Units.smallSpacing
|
||||
|
||||
//Kirigami.Heading {
|
||||
//level: 3
|
||||
//Layout.fillWidth: true
|
||||
//elide: Qt.ElideRight
|
||||
//text: info.title ? info.title : i18n("No media playing")
|
||||
//}
|
||||
|
||||
//RowLayout{
|
||||
|
||||
//Controls.Label{
|
||||
//visible: info.title
|
||||
//text: PlayerUtils.formatTimestamp(audio.position)
|
||||
//}
|
||||
|
||||
//Controls.Slider {
|
||||
//Layout.fillWidth: true
|
||||
//from: 0
|
||||
//to: audio.duration
|
||||
//value: audio.position
|
||||
//enabled: audio.seekable
|
||||
//onMoved: {
|
||||
//console.log("Value:", value);
|
||||
//audio.seek(Math.floor(value));
|
||||
//}
|
||||
|
||||
//Behavior on value {
|
||||
//NumberAnimation {
|
||||
//duration: 1000
|
||||
//}
|
||||
|
||||
//}
|
||||
|
||||
//}
|
||||
|
||||
//Controls.Label{
|
||||
//visible: info.title
|
||||
//text: PlayerUtils.formatTimestamp(audio.duration)
|
||||
//}
|
||||
|
||||
//}
|
||||
|
||||
|
||||
|
||||
//}
|
||||
|
||||
//Controls.ToolButton {
|
||||
//display: Controls.AbstractButton.IconOnly
|
||||
//Layout.preferredWidth: parent.height
|
||||
//text: i18n("Expand")
|
||||
//enabled: playlistView.count > 0
|
||||
//Layout.fillHeight: true
|
||||
//icon.name: footerItem.maximized ? "arrow-down" : "arrow-up"
|
||||
//onClicked: footerItem.maximized = !footerItem.maximized
|
||||
//}
|
||||
|
||||
//}
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: player
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: !footer.maximized
|
||||
|
||||
VideoInfoExtractor {
|
||||
id: info
|
||||
|
||||
onSongChanged: audio.play()
|
||||
videoId: UserPlaylistModel.currentVideoId
|
||||
}
|
||||
|
||||
Audio {
|
||||
id: audio
|
||||
|
||||
source: info.audioUrl
|
||||
onStatusChanged: {
|
||||
if (status === Audio.EndOfMedia) {
|
||||
console.log("Song ended");
|
||||
UserPlaylistModel.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Controls.ToolButton {
|
||||
display: Controls.AbstractButton.IconOnly
|
||||
Layout.preferredWidth: parent.height
|
||||
Layout.fillHeight: true
|
||||
enabled: info.audioUrl != ""
|
||||
text: audio.playbackState === Audio.PlayingState ? i18n("Pause") : i18n("Play")
|
||||
visible: !info.loading
|
||||
icon.name: audio.playbackState === Audio.PlayingState ? "media-playback-pause" : "media-playback-start"
|
||||
onClicked: audio.playbackState === Audio.PlayingState ? audio.pause() : audio.play()
|
||||
}
|
||||
|
||||
Controls.ToolButton {
|
||||
display: Controls.AbstractButton.IconOnly
|
||||
Layout.preferredWidth: parent.height
|
||||
Layout.fillHeight: true
|
||||
enabled: UserPlaylistModel.canSkip
|
||||
visible: !info.loading
|
||||
icon.name: "media-skip-forward"
|
||||
onClicked: UserPlaylistModel.next()
|
||||
}
|
||||
|
||||
Controls.BusyIndicator {
|
||||
Layout.fillHeight: true
|
||||
visible: info.loading
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillHeight: true
|
||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||
Layout.bottomMargin: Kirigami.Units.smallSpacing
|
||||
|
||||
Kirigami.Heading {
|
||||
level: 3
|
||||
Layout.fillWidth: true
|
||||
elide: Qt.ElideRight
|
||||
text: info.title ? info.title : i18n("No media playing")
|
||||
}
|
||||
|
||||
RowLayout{
|
||||
|
||||
Controls.Label{
|
||||
visible: info.title
|
||||
text: PlayerUtils.formatTimestamp(audio.position)
|
||||
}
|
||||
|
||||
Controls.Slider {
|
||||
Layout.fillWidth: true
|
||||
from: 0
|
||||
to: audio.duration
|
||||
value: audio.position
|
||||
enabled: audio.seekable
|
||||
onMoved: {
|
||||
console.log("Value:", value);
|
||||
audio.seek(Math.floor(value));
|
||||
}
|
||||
|
||||
Behavior on value {
|
||||
NumberAnimation {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Controls.Label{
|
||||
visible: info.title
|
||||
text: PlayerUtils.formatTimestamp(audio.duration)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
Controls.ToolButton {
|
||||
display: Controls.AbstractButton.IconOnly
|
||||
Layout.preferredWidth: parent.height
|
||||
text: i18n("Expand")
|
||||
enabled: playlistView.count > 0
|
||||
Layout.fillHeight: true
|
||||
icon.name: footerLayout.maximized ? "arrow-down" : "arrow-up"
|
||||
onClicked: footerLayout.maximized = !footerLayout.maximized
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Behavior on height {
|
||||
//NumberAnimation {
|
||||
//}
|
||||
//}
|
||||
}
|
||||
@@ -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 {
|
||||
@@ -118,9 +147,13 @@ Kirigami.ScrollablePage {
|
||||
text: i18n("Find music on YouTube Music")
|
||||
helpfulAction: Kirigami.Action {
|
||||
onTriggered: searchField.forceActiveFocus()
|
||||
text: "Search"
|
||||
text: i18n("Search")
|
||||
icon.name: "search"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer: ConfirmationMessage {
|
||||
id: videoPlayMessage
|
||||
}
|
||||
}
|
||||
+122
-27
@@ -12,7 +12,10 @@ import org.kde.ytmusic 1.0
|
||||
Kirigami.ApplicationWindow {
|
||||
id: root
|
||||
|
||||
property alias searchField: searchField
|
||||
property alias searchField: searchLoader.item // TODO
|
||||
|
||||
property bool wideScreen: width >= 600
|
||||
property bool showSearch: false // only applicable if not widescreen
|
||||
|
||||
header: Controls.Control {
|
||||
padding: Kirigami.Units.largeSpacing
|
||||
@@ -25,37 +28,67 @@ Kirigami.ApplicationWindow {
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Header
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
}
|
||||
contentItem: RowLayout{
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
|
||||
contentItem: RowLayout {
|
||||
Controls.ToolButton {
|
||||
id: searchBack
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
visible:
|
||||
(!root.wideScreen && root.showSearch && root.searchField.text === "")
|
||||
|| pageStack.layers.depth > 1
|
||||
text: i18n("Back")
|
||||
icon.name: "go-previous-view"
|
||||
display: Controls.ToolButton.IconOnly
|
||||
onClicked: if (root.showSearch)
|
||||
root.showSearch = false
|
||||
else
|
||||
pageStack.layers.pop()
|
||||
}
|
||||
Kirigami.SearchField {
|
||||
id: searchField
|
||||
delaySearch: true
|
||||
selectByMouse: true
|
||||
|
||||
Kirigami.Heading {
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||
level: 1
|
||||
text: "AudioTube"
|
||||
visible: !root.wideScreen && !root.showSearch
|
||||
}
|
||||
|
||||
// spacer
|
||||
Item {
|
||||
Layout.fillWidth: !root.wideScreen
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: searchLoader
|
||||
visible: root.wideScreen || root.showSearch
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: 400
|
||||
onAccepted: {
|
||||
pageStack.clear()
|
||||
pageStack.push("qrc:/SearchPage.qml", {
|
||||
"searchQuery": text})
|
||||
}
|
||||
sourceComponent: searchFieldComponent
|
||||
}
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Controls.ToolButton {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
visible: !root.wideScreen && !root.showSearch
|
||||
text: i18n("Search")
|
||||
icon.name: "search"
|
||||
display: Controls.ToolButton.TextBesideIcon
|
||||
onClicked: {
|
||||
root.showSearch = true
|
||||
root.searchField.forceActiveFocus();
|
||||
root.searchField.popup.open();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
title: i18n("AudioTube")
|
||||
|
||||
controlsVisible: !footer.maximized
|
||||
|
||||
contextDrawer: Kirigami.ContextDrawer {
|
||||
id: contextDrawer
|
||||
}
|
||||
|
||||
pageStack.initialPage: "qrc:/SearchPage.qml"
|
||||
pageStack.initialPage: "qrc:/LibraryPage.qml"
|
||||
|
||||
function play(videoId) {
|
||||
UserPlaylistModel.initialVideoId = videoId
|
||||
@@ -70,14 +103,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
|
||||
@@ -86,6 +111,76 @@ Kirigami.ApplicationWindow {
|
||||
showPassiveNotification(error)
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: searchFieldComponent
|
||||
|
||||
Kirigami.SearchField {
|
||||
id: searchField
|
||||
delaySearch: true
|
||||
selectByMouse: true
|
||||
onPressed: {
|
||||
popup.open()
|
||||
}
|
||||
|
||||
property alias popup: popup
|
||||
|
||||
footer: PlayerFooter {}
|
||||
Controls.Popup {
|
||||
id: popup
|
||||
x: searchField.y
|
||||
y: searchField.y + searchField.height
|
||||
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 {
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
||||
Kirigami.Theme.inherit: false
|
||||
id: completionDelegate
|
||||
width: parent.width
|
||||
height: Kirigami.Units.gridUnit * 2
|
||||
text: model.display
|
||||
onClicked: {
|
||||
searchField.text = model.display
|
||||
searchField.accepted()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onAccepted: {
|
||||
pageStack.clear()
|
||||
pageStack.layers.clear()
|
||||
|
||||
if (text) {
|
||||
Library.addSearch(text)
|
||||
pageStack.push("qrc:/SearchPage.qml", {
|
||||
"searchQuery": text})
|
||||
} else {
|
||||
pageStack.replace("qrc:/LibraryPage.qml")
|
||||
}
|
||||
|
||||
popup.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pageStack.anchors.bottomMargin: playerFooter.minimizedPlayerHeight + 1
|
||||
|
||||
// media player
|
||||
PlayerFooter {
|
||||
id: playerFooter
|
||||
anchors.topMargin: -root.header.height
|
||||
anchors.fill: parent
|
||||
|
||||
// only expand flicking area to full screen when it is open
|
||||
z: (contentY === 0) ? -1 : 999
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
+250
@@ -0,0 +1,250 @@
|
||||
// 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 {
|
||||
const auto databaseDirectory = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||
// Make sure the database directory exists
|
||||
QDir(databaseDirectory).mkpath(QStringLiteral("."));
|
||||
|
||||
DatabaseConfiguration config;
|
||||
config.setDatabaseName(databaseDirectory % 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 = m_database->getResults<Song>("select * from favourites natural join 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(m_database->getResults<SingleValue<QString>>("select (search_query) from searches order by search_id desc"), 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 = m_database->getResults<PlayedSong>("select * from played_songs natural join songs");
|
||||
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 = m_database->getResults<PlayedSong>("select * from played_songs natural join songs order by plays desc limit 10");
|
||||
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, "artists"},
|
||||
{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, "artists"}
|
||||
};
|
||||
}
|
||||
|
||||
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<SingleValue<int>>("select count(*) from favourites where video_id = ?", m_videoId), this, [this](auto count) {
|
||||
if (count) {
|
||||
m_isFavourite = count->value > 0;
|
||||
Q_EMIT isFavouriteChanged();
|
||||
}
|
||||
});
|
||||
};
|
||||
update();
|
||||
connect(library, &Library::favouritesChanged, this, update);
|
||||
}
|
||||
|
||||
bool FavouriteWatcher::isFavourite() const {
|
||||
return m_isFavourite;
|
||||
}
|
||||
|
||||
SearchHistoryModel::SearchHistoryModel(QFuture<std::vector<SingleValue<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()].value;
|
||||
}
|
||||
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
// 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 {
|
||||
using ColumnTypes = std::tuple<QString, QString, QString, QString>;
|
||||
|
||||
static Song fromSql(ColumnTypes 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 {
|
||||
using ColumnTypes = std::tuple<QString, int, QString, QString, QString>;
|
||||
|
||||
static PlayedSong fromSql(ColumnTypes 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<SingleValue<QString>>> &&historyFuture, QObject *parent = nullptr);
|
||||
int rowCount(const QModelIndex &parent) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
|
||||
private:
|
||||
std::vector<SingleValue<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;
|
||||
};
|
||||
@@ -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")));
|
||||
|
||||
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
@@ -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;
|
||||
};
|
||||
+12
-23
@@ -11,21 +11,21 @@
|
||||
#include "playlistutils.h"
|
||||
|
||||
PlaylistModel::PlaylistModel(QObject *parent)
|
||||
: QAbstractListModel(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();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -76,17 +76,6 @@ void PlaylistModel::setPlaylistId(const QString &playlistId)
|
||||
Q_EMIT playlistIdChanged();
|
||||
}
|
||||
|
||||
bool PlaylistModel::loading() const
|
||||
{
|
||||
return m_loading;
|
||||
}
|
||||
|
||||
void PlaylistModel::setLoading(bool loading)
|
||||
{
|
||||
m_loading = loading;
|
||||
Q_EMIT loadingChanged();
|
||||
}
|
||||
|
||||
QUrl PlaylistModel::thumbnailUrl() const
|
||||
{
|
||||
if (m_playlist.thumbnails.empty()) {
|
||||
|
||||
+3
-7
@@ -7,7 +7,9 @@
|
||||
#include <QAbstractListModel>
|
||||
#include <ytmusic.h>
|
||||
|
||||
class PlaylistModel : public QAbstractListModel
|
||||
#include "abstractytmusicmodel.h"
|
||||
|
||||
class PlaylistModel : public AbstractYTMusicModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
@@ -15,7 +17,6 @@ class PlaylistModel : public QAbstractListModel
|
||||
Q_PROPERTY(QString playlistId READ playlistId WRITE setPlaylistId NOTIFY playlistIdChanged REQUIRED)
|
||||
|
||||
// output
|
||||
Q_PROPERTY(bool loading READ loading WRITE setLoading NOTIFY loadingChanged)
|
||||
Q_PROPERTY(QUrl thumbnailUrl READ thumbnailUrl NOTIFY thumbnailUrlChanged)
|
||||
Q_PROPERTY(QString title READ title NOTIFY titleChanged)
|
||||
|
||||
@@ -36,10 +37,6 @@ public:
|
||||
void setPlaylistId(const QString &playlistId);
|
||||
Q_SIGNAL void playlistIdChanged();
|
||||
|
||||
bool loading() const;
|
||||
void setLoading(bool loading);
|
||||
Q_SIGNAL void loadingChanged();
|
||||
|
||||
QUrl thumbnailUrl() const;
|
||||
Q_SIGNAL void thumbnailUrlChanged();
|
||||
|
||||
@@ -48,7 +45,6 @@ public:
|
||||
|
||||
private:
|
||||
QString m_playlistId;
|
||||
bool m_loading = false;
|
||||
|
||||
playlist::Playlist m_playlist {};
|
||||
};
|
||||
@@ -15,11 +15,12 @@ namespace PlaylistUtils {
|
||||
QString artistsToString(const std::vector<meta::Artist> &artists)
|
||||
{
|
||||
if (!artists.empty()) {
|
||||
return std::accumulate(artists.begin() + 1, artists.end(),
|
||||
QString::fromStdString(artists.front().name),
|
||||
[](QString &string, const meta::Artist &artist) {
|
||||
return string.append(QStringLiteral(", %1").arg(QString::fromStdString(artist.name)));
|
||||
QString string = QString::fromStdString(artists.front().name);
|
||||
std::for_each(artists.begin() + 1, artists.end(),
|
||||
[&string](const meta::Artist &artist) {
|
||||
string.append(QStringLiteral(", %1").arg(QString::fromStdString(artist.name)));
|
||||
});
|
||||
return string;
|
||||
}
|
||||
|
||||
return {};
|
||||
|
||||
@@ -12,5 +12,12 @@ 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 alias="MinimizedPlayerControls.qml">contents/ui/MinimizedPlayerControls.qml</file>
|
||||
<file alias="MaximizedPlayerPage.qml">contents/ui/MaximizedPlayerPage.qml</file>
|
||||
<file>migrations/2022-05-20-194850_init/down.sql</file>
|
||||
<file>migrations/2022-05-20-194850_init/up.sql</file>
|
||||
<file alias="PlaybackHistory.qml">contents/ui/PlaybackHistory.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
+31
-21
@@ -5,9 +5,9 @@
|
||||
#include "searchmodel.h"
|
||||
|
||||
SearchModel::SearchModel(QObject *parent)
|
||||
: QAbstractListModel(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);
|
||||
});
|
||||
}
|
||||
@@ -146,17 +146,6 @@ void SearchModel::setSearchQuery(const QString &searchQuery)
|
||||
Q_EMIT searchQueryChanged();
|
||||
}
|
||||
|
||||
bool SearchModel::loading() const
|
||||
{
|
||||
return m_loading;
|
||||
}
|
||||
|
||||
void SearchModel::setLoading(bool loading)
|
||||
{
|
||||
m_loading = loading;
|
||||
Q_EMIT loadingChanged();
|
||||
}
|
||||
|
||||
void SearchModel::triggerItem(int row)
|
||||
{
|
||||
std::visit([&](auto&& arg) {
|
||||
@@ -176,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);
|
||||
}
|
||||
+3
-7
@@ -8,13 +8,13 @@
|
||||
#include <QThread>
|
||||
|
||||
#include "asyncytmusic.h"
|
||||
#include "abstractytmusicmodel.h"
|
||||
|
||||
class SearchModel : public QAbstractListModel
|
||||
class SearchModel : public AbstractYTMusicModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QString searchQuery READ searchQuery WRITE setSearchQuery NOTIFY searchQueryChanged)
|
||||
Q_PROPERTY(bool loading READ loading WRITE setLoading NOTIFY loadingChanged)
|
||||
|
||||
public:
|
||||
enum Type {
|
||||
@@ -44,10 +44,6 @@ public:
|
||||
void setSearchQuery(const QString &searchQuery);
|
||||
Q_SIGNAL void searchQueryChanged();
|
||||
|
||||
bool loading() const;
|
||||
void setLoading(bool loading);
|
||||
Q_SIGNAL void loadingChanged();
|
||||
|
||||
Q_INVOKABLE void triggerItem(int row);
|
||||
|
||||
Q_SIGNAL void openAlbum(const QString &browseId);
|
||||
@@ -59,5 +55,5 @@ public:
|
||||
private:
|
||||
QString m_searchQuery;
|
||||
std::vector<search::SearchResultItem> m_searchResults;
|
||||
bool m_loading = false;
|
||||
static int itemType(search::SearchResultItem const &item);
|
||||
};
|
||||
+27
-35
@@ -9,29 +9,14 @@
|
||||
#include <random>
|
||||
|
||||
#include <QRandomGenerator>
|
||||
#include <QFutureWatcher>
|
||||
|
||||
#include "playlistutils.h"
|
||||
|
||||
UserPlaylistModel::UserPlaylistModel(QObject *parent)
|
||||
: QAbstractListModel(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);
|
||||
});
|
||||
}
|
||||
@@ -98,21 +101,10 @@ void UserPlaylistModel::setInitialVideoId(const QString &videoId)
|
||||
Q_EMIT initialVideoIdChanged();
|
||||
}
|
||||
|
||||
bool UserPlaylistModel::loading() const
|
||||
{
|
||||
return m_loading;
|
||||
}
|
||||
|
||||
void UserPlaylistModel::setLoading(bool loading)
|
||||
{
|
||||
m_loading = loading;
|
||||
Q_EMIT loadingChanged();
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
@@ -140,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();
|
||||
});
|
||||
|
||||
@@ -168,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();
|
||||
});
|
||||
|
||||
@@ -250,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();
|
||||
});
|
||||
|
||||
@@ -268,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();
|
||||
});
|
||||
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
|
||||
#include <ytmusic.h>
|
||||
|
||||
class UserPlaylistModel : public QAbstractListModel
|
||||
#include "abstractytmusicmodel.h"
|
||||
|
||||
class UserPlaylistModel : public AbstractYTMusicModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
@@ -17,7 +19,6 @@ class UserPlaylistModel : public QAbstractListModel
|
||||
Q_PROPERTY(bool shuffle READ shuffle WRITE setShuffle NOTIFY shuffleChanged)
|
||||
|
||||
// output
|
||||
Q_PROPERTY(bool loading READ loading WRITE setLoading NOTIFY loadingChanged)
|
||||
Q_PROPERTY(QString currentVideoId READ currentVideoId NOTIFY currentVideoIdChanged)
|
||||
Q_PROPERTY(bool canSkip READ canSkip NOTIFY canSkipChanged)
|
||||
|
||||
@@ -39,10 +40,6 @@ public:
|
||||
void setInitialVideoId(const QString &videoId);
|
||||
Q_SIGNAL void initialVideoIdChanged();
|
||||
|
||||
bool loading() const;
|
||||
void setLoading(bool loading);
|
||||
Q_SIGNAL void loadingChanged();
|
||||
|
||||
QString nextVideoId() const;
|
||||
|
||||
QString currentVideoId() const;
|
||||
@@ -74,7 +71,6 @@ private:
|
||||
QString m_initialVideoId;
|
||||
QString m_playlistId;
|
||||
QString m_currentVideoId;
|
||||
bool m_loading = false;
|
||||
bool m_shuffle = false;
|
||||
|
||||
watch::Playlist m_playlist;
|
||||
|
||||
+38
-22
@@ -4,39 +4,46 @@
|
||||
|
||||
#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();
|
||||
Q_EMIT titleChanged();
|
||||
Q_EMIT artistChanged();
|
||||
Q_EMIT channelChanged();
|
||||
Q_EMIT songChanged();
|
||||
Q_EMIT thumbnailChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
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 artistChanged();
|
||||
Q_EMIT channelChanged();
|
||||
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 +55,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 +73,9 @@ QString VideoInfoExtractor::videoId() const
|
||||
|
||||
void VideoInfoExtractor::setVideoId(const QString &videoId)
|
||||
{
|
||||
if (m_videoId == videoId || videoId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
m_videoId = videoId;
|
||||
Q_EMIT videoIdChanged();
|
||||
}
|
||||
@@ -79,9 +85,19 @@ QString VideoInfoExtractor::title() const
|
||||
return QString::fromStdString(m_videoInfo.title);
|
||||
}
|
||||
|
||||
QString VideoInfoExtractor::artist() const
|
||||
{
|
||||
return QString::fromStdString(m_videoInfo.artist);
|
||||
}
|
||||
|
||||
QString VideoInfoExtractor::channel() const
|
||||
{
|
||||
return QString::fromStdString(m_videoInfo.channel);
|
||||
}
|
||||
|
||||
QString VideoInfoExtractor::thumbnail() const
|
||||
{
|
||||
return QString::fromStdString(m_videoInfo.thumnnail);
|
||||
return QString::fromStdString(m_videoInfo.thumbnail);
|
||||
}
|
||||
|
||||
bool VideoInfoExtractor::loading() const
|
||||
|
||||
@@ -17,6 +17,8 @@ class VideoInfoExtractor : public QObject
|
||||
|
||||
Q_PROPERTY(QUrl audioUrl READ audioUrl NOTIFY audioUrlChanged)
|
||||
Q_PROPERTY(QString title READ title NOTIFY titleChanged)
|
||||
Q_PROPERTY(QString artist READ artist NOTIFY artistChanged) // may be empty if it is a video
|
||||
Q_PROPERTY(QString channel READ channel NOTIFY channelChanged)
|
||||
Q_PROPERTY(QString thumbnail READ thumbnail NOTIFY thumbnailChanged)
|
||||
Q_PROPERTY(bool loading READ loading WRITE setLoading NOTIFY loadingChanged)
|
||||
|
||||
@@ -32,6 +34,12 @@ public:
|
||||
|
||||
QString title() const;
|
||||
Q_SIGNAL void titleChanged();
|
||||
|
||||
QString artist() const;
|
||||
Q_SIGNAL void artistChanged();
|
||||
|
||||
QString channel() const;
|
||||
Q_SIGNAL void channelChanged();
|
||||
|
||||
QString thumbnail() const;
|
||||
Q_SIGNAL void thumbnailChanged();
|
||||
|
||||
+55
-45
@@ -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.17.3";
|
||||
constexpr auto TESTED_YTMUSICAPI_VERSION = "0.22.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,11 +354,11 @@ 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>(),
|
||||
artist["subscribers"].cast<std::string>(),
|
||||
artist["subscribers"].cast<std::optional<std::string>>(),
|
||||
artist["subscribed"].cast<bool>(),
|
||||
extract_py_list<meta::Thumbnail>(artist["thumbnails"]),
|
||||
extract_artist_section<artist::Artist::Song>(artist, "songs"),
|
||||
@@ -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>(),
|
||||
@@ -452,10 +460,12 @@ video_info::VideoInfo YTMusic::extract_video_info(const std::string &video_id) c
|
||||
using namespace pybind11::literals;
|
||||
|
||||
const auto info = d->get_ytdl().attr("extract_info")(video_id, "download"_a=py::bool_(false));
|
||||
|
||||
|
||||
return {
|
||||
info["id"].cast<std::string>(),
|
||||
info["title"].cast<std::string>(),
|
||||
info.contains("artist") ? info["artist"].cast<std::string>() : "",
|
||||
info.contains("channel") ? info["channel"].cast<std::string>() : "",
|
||||
extract_py_list<video_info::Format>(info["formats"]),
|
||||
info["thumbnail"].cast<std::string>()
|
||||
};
|
||||
|
||||
+18
-13
@@ -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;
|
||||
};
|
||||
@@ -131,7 +131,7 @@ struct Artist {
|
||||
std::optional<std::string> views;
|
||||
std::string name;
|
||||
std::string channel_id;
|
||||
std::string subscribers;
|
||||
std::optional<std::string> subscribers;
|
||||
bool subscribed;
|
||||
std::vector<meta::Thumbnail> thumbnails;
|
||||
std::optional<Section<Song>> songs;
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -239,8 +241,10 @@ struct Format {
|
||||
struct VideoInfo {
|
||||
std::string id;
|
||||
std::string title;
|
||||
std::string artist;
|
||||
std::string channel;
|
||||
std::vector<Format> formats;
|
||||
std::string thumnnail;
|
||||
std::string thumbnail;
|
||||
|
||||
// More, but not interesting for us right now
|
||||
};
|
||||
@@ -277,6 +281,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 +292,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;
|
||||
|
||||
Reference in new issue
Block a user