Compare commits

..
1 Commits
Author SHA1 Message Date
Jonah Brüchert 38726a655e mpris shi 2021-05-14 21:48:08 +02:00
104 changed files with 2699 additions and 9637 deletions

No files matched your search

-7
View File
@@ -1,9 +1,2 @@
# 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/
-6
View File
@@ -1,6 +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
include:
- 'https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/reuse-lint.yml'
-25
View File
@@ -1,25 +0,0 @@
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
+5 -19
View File
@@ -1,23 +1,18 @@
# SPDX-FileCopyrightText: 2021 Jonah Brüchert <[email protected]>
#
# SPDX-License-Identifier: BSD-2-Clause
cmake_minimum_required(VERSION 3.16.0)
cmake_minimum_required(VERSION 3.14)
project(audiotube)
include(FeatureSummary)
set(PROJECT_VERSION "22.09")
set(QT5_MIN_VERSION 5.15)
set(KF5_MIN_VERSION 5.81)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH})
include(KDEInstallDirs)
include(KDECMakeSettings)
@@ -26,20 +21,11 @@ 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 Sql)
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Kirigami2 I18n CoreAddons Crash)
find_package(Qt5 ${QT5_MIN_VERSION} REQUIRED COMPONENTS Core Gui Qml QuickControls2 Svg DBus)
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Kirigami2 I18n)
find_package(pybind11 REQUIRED)
find_package(Ytdlp REQUIRED RUNTIME)
find_package(YTMusicAPI REQUIRED RUNTIME)
ki18n_install(po)
add_subdirectory(src)
-22
View File
@@ -1,22 +0,0 @@
Copyright (c) <year> <owner>. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-119
View File
@@ -1,119 +0,0 @@
Creative Commons Legal Code
CC0 1.0 Universal CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES
NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE
AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION
ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE
OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS
LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION
OR WORKS PROVIDED HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer exclusive
Copyright and Related Rights (defined below) upon the creator and subsequent
owner(s) (each and all, an "owner") of an original work of authorship and/or
a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for the
purpose of contributing to a commons of creative, cultural and scientific
works ("Commons") that the public can reliably and without fear of later claims
of infringement build upon, modify, incorporate in other works, reuse and
redistribute as freely as possible in any form whatsoever and for any purposes,
including without limitation commercial purposes. These owners may contribute
to the Commons to promote the ideal of a free culture and the further production
of creative, cultural and scientific works, or to gain reputation or greater
distribution for their Work in part through the use and efforts of others.
For these and/or other purposes and motivations, and without any expectation
of additional consideration or compensation, the person associating CC0 with
a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
and publicly distribute the Work under its terms, with knowledge of his or
her Copyright and Related Rights in the Work and the meaning and intended
legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be protected
by copyright and related or neighboring rights ("Copyright and Related Rights").
Copyright and Related Rights include, but are not limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display, communicate,
and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or likeness
depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work, subject
to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal protection
of databases, and under any national implementation thereof, including any
amended or successor version of such directive); and
vii. other similar, equivalent or corresponding rights throughout the world
based on applicable law or treaty, and any national implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention of,
applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
and Related Rights and associated claims and causes of action, whether now
known or unknown (including existing as well as future claims and causes of
action), in the Work (i) in all territories worldwide, (ii) for the maximum
duration provided by applicable law or treaty (including future time extensions),
(iii) in any current or future medium and for any number of copies, and (iv)
for any purpose whatsoever, including without limitation commercial, advertising
or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the
benefit of each member of the public at large and to the detriment of Affirmer's
heirs and successors, fully intending that such Waiver shall not be subject
to revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason be
judged legally invalid or ineffective under applicable law, then the Waiver
shall be preserved to the maximum extent permitted taking into account Affirmer's
express Statement of Purpose. In addition, to the extent the Waiver is so
judged Affirmer hereby grants to each affected person a royalty-free, non
transferable, non sublicensable, non exclusive, irrevocable and unconditional
license to exercise Affirmer's Copyright and Related Rights in the Work (i)
in all territories worldwide, (ii) for the maximum duration provided by applicable
law or treaty (including future time extensions), (iii) in any current or
future medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional purposes
(the "License"). The License shall be deemed effective as of the date CC0
was applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder of
the License, and in such case Affirmer hereby affirms that he or she will
not (i) exercise any of his or her remaining Copyright and Related Rights
in the Work or (ii) assert any associated claims and causes of action with
respect to the Work, in either case contrary to Affirmer's express Statement
of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered,
licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or warranties
of any kind concerning the Work, express, implied, statutory or otherwise,
including without limitation warranties of title, merchantability, fitness
for a particular purpose, non infringement, or the absence of latent or other
defects, accuracy, or the present or absence of errors, whether or not discoverable,
all to the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without limitation
any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims
responsibility for obtaining any necessary consents, permissions or other
rights required for any use of the Work.
d. Affirmer understands and acknowledges that Creative Commons is not a party
to this document and has no duty or obligation with respect to this CC0 or
use of the Work.
+2 -2
View File
@@ -4,9 +4,9 @@ AudioTube is a client for YouTube Music.
# Dependencies
It depends on Qt, Kirigami, KI18n, KCoreAddons and KCrash in addition to a few python packages:
It depends on Qt and Kirigami in addition to a few python packages:
* ytmusicapi
* yt_dlp
* youtube_dl
YouTube uses webp images for the cover art, so you will need to install qtimageformats.
-11
View File
@@ -1,11 +0,0 @@
# SPDX-FileCopyrightText: 2021 Jonah Brüchert <[email protected]>
#
# SPDX-License-Identifier: BSD-2-Clause
find_package(Python3 REQUIRED COMPONENTS Interpreter)
execute_process(COMMAND ${Python3_EXECUTABLE} -c "import ytmusicapi" RESULT_VARIABLE YTMUSICAPI_CHECK_RESULT)
if (${YTMUSICAPI_CHECK_RESULT} EQUAL 0)
set(YTMusicAPI_FOUND TRUE)
endif()
-11
View File
@@ -1,11 +0,0 @@
# SPDX-FileCopyrightText: 2021 Jonah Brüchert <[email protected]>
#
# SPDX-License-Identifier: BSD-2-Clause
find_package(Python3 REQUIRED COMPONENTS Interpreter)
execute_process(COMMAND ${Python3_EXECUTABLE} -c "import yt_dlp" RESULT_VARIABLE YTDLP_CHECK_RESULT)
if (${YTDLP_CHECK_RESULT} EQUAL 0)
set(Ytdlp_FOUND TRUE)
endif()
+14
View File
@@ -0,0 +1,14 @@
{
"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/94/a0/b8325b524700daae1c04a6156473ab7091d44071353f1d7c9e66b9c7f019/youtube_dl-2021.4.26.tar.gz",
"sha256": "6f311ffaf8b88cdcf27a2301a2272455e213bdb780aa447246933a3da4532879"
}
]
}
-39
View File
@@ -1,39 +0,0 @@
{
"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/10/a5/6e8b3a3f9caa535543f342254ee15e711cb26e974b54f998e90e17e1fe09/yt_dlp-2022.9.1-py2.py3-none-any.whl",
"sha256": "af3721ecb286a95272008add3b9bebde4f370fb3bdd1bb6402727af9e2e3aa2b"
},
{
"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/e9/06/d3d367b7af6305b16f0d28ae2aaeb86154fa91f144f036c2d5002a5a202b/certifi-2022.6.15-py3-none-any.whl",
"sha256": "fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"
},
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/52/0d/6cc95a83f6961a1ca041798d222240890af79b381e97eda3b9b538dba16f/pycryptodomex-3.15.0.tar.gz",
"sha256": "7341f1bb2dadb0d1a0047f34c3a58208a92423cdbd3244d998e4b28df5eac0ed"
}
]
}
-3
View File
@@ -1,3 +0,0 @@
SPDX-FileCopyrightText: 2021 Jonah Brüchert <[email protected]>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
+12 -12
View File
@@ -7,33 +7,33 @@
"sources": [
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/db/51/a507c856293ab05cdc1db77ff4bc1268ddd39f29e7dc4919aa497f0adbec/charset_normalizer-2.1.1-py3-none-any.whl",
"sha256": "83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"
"url": "https://files.pythonhosted.org/packages/cb/cf/871177f1fc795c6c10787bc0e1f27bb6cf7b81dbde399fd35860472cecbc/urllib3-1.26.4.tar.gz",
"sha256": "e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937"
},
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/6f/de/5be2e3eed8426f871b170663333a0f627fc2924cc386cd41be065e7ea870/urllib3-1.26.12-py2.py3-none-any.whl",
"sha256": "b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"
"url": "https://files.pythonhosted.org/packages/ea/b7/e0e3c1c467636186c39925827be42f16fee389dc404ac29e930e9136be70/idna-2.10.tar.gz",
"sha256": "b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"
},
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/ca/91/6d9b8ccacd0412c08820f72cebaa4f0c0441b5cda699c90f618b6f8a1b42/requests-2.28.1-py3-none-any.whl",
"sha256": "8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"
"url": "https://files.pythonhosted.org/packages/ee/2d/9cdc2b527e127b4c9db64b86647d567985940ac3698eeabc7ffaccb4ea61/chardet-4.0.0.tar.gz",
"sha256": "0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"
},
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/0e/5a/4a5316bddbb697d4b1787159291edfeffd10f73c08b59c7248b15291b601/ytmusicapi-0.22.0-py3-none-any.whl",
"sha256": "d042520f3f76eac2c1498130dbc4e8a4af21ca73496590a5ff054fc4da0af1d8"
"url": "https://files.pythonhosted.org/packages/06/a9/cd1fd8ee13f73a4d4f491ee219deeeae20afefa914dfb4c130cfc9dc397a/certifi-2020.12.5.tar.gz",
"sha256": "1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"
},
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/04/a2/d918dcd22354d8958fe113e1a3630137e0fc8b44859ade3063982eacd2a4/idna-3.3-py3-none-any.whl",
"sha256": "84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"
"url": "https://files.pythonhosted.org/packages/6b/47/c14abc08432ab22dc18b9892252efaf005ab44066de871e72a38d6af464b/requests-2.25.1.tar.gz",
"sha256": "27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"
},
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/e9/06/d3d367b7af6305b16f0d28ae2aaeb86154fa91f144f036c2d5002a5a202b/certifi-2022.6.15-py3-none-any.whl",
"sha256": "fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"
"url": "https://files.pythonhosted.org/packages/61/b5/da439d5f75db7ff17790b19136c835f9a315530436ee7bc9d75aeea2f6e5/ytmusicapi-0.16.0.tar.gz",
"sha256": "ffde3fb5a78123ac59dee37fc1f32793f34f9a4fa3d3e69a1fc08064112c2b5d"
}
]
}
-3
View File
@@ -1,3 +0,0 @@
SPDX-FileCopyrightText: 2021 Jonah Brüchert <[email protected]>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
+1 -5
View File
@@ -1,9 +1,5 @@
#!/usr/bin/env bash
# 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
set -e
export GIT_CLONE_ARGS="--depth 1 --single-branch"
@@ -16,5 +12,5 @@ else
git -C flatpak-builder-tools pull
fi
./flatpak-builder-tools/pip/flatpak-pip-generator yt-dlp
./flatpak-builder-tools/pip/flatpak-pip-generator youtube_dl
./flatpak-builder-tools/pip/flatpak-pip-generator ytmusicapi
-167
View File
@@ -1,179 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
SPDX-FileCopyrightText: 2021 Jonah Brüchert <[email protected]>
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>
<name xml:lang="de">AudioTube</name>
<name xml:lang="en-GB">AudioTube</name>
<name xml:lang="es">AudioTube</name>
<name xml:lang="eu">AudioTube</name>
<name xml:lang="fi">AudioTube</name>
<name xml:lang="fr">AudioTube</name>
<name xml:lang="hi">ओडियो-ट्यूब</name>
<name xml:lang="it">AudioTube</name>
<name xml:lang="ko">AudioTube</name>
<name xml:lang="nl">AudioTube</name>
<name xml:lang="nn">AudioTube</name>
<name xml:lang="pa">ਆਡੀਓ-ਟਿਊਬ</name>
<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="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>
<summary xml:lang="eu">YouTube Music bezero konbergentea</summary>
<summary xml:lang="fi">Mukautuva YouTube Music -asiakas</summary>
<summary xml:lang="fr">Client convergent pour musique sur YouTube</summary>
<summary xml:lang="hi">अभिसृत यूट्यूब संगीत क्लाएंट</summary>
<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="nn">YouTube Music-klient, tilpassa ulike brukarflater</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>
<developer_name xml:lang="de">KDE-Gemeinschaft</developer_name>
<developer_name xml:lang="en-GB">KDE Community</developer_name>
<developer_name xml:lang="es">Comunidad KDE</developer_name>
<developer_name xml:lang="eu">KDE komunitatea</developer_name>
<developer_name xml:lang="fi">KDE-yhteisö</developer_name>
<developer_name xml:lang="fr">Communauté de KDE</developer_name>
<developer_name xml:lang="hi">केडीई समुदाय</developer_name>
<developer_name xml:lang="it">Comunità KDE</developer_name>
<developer_name xml:lang="ko">KDE 커뮤니티</developer_name>
<developer_name xml:lang="nl">KDE-gemeenschap</developer_name>
<developer_name xml:lang="nn">KDE-fellesskapet</developer_name>
<developer_name xml:lang="pa">ਕੇਡੀਈ ਕਮਿਊਨਟੀ</developer_name>
<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.09" date="2022-09-27">
<url>https://www.plasma-mobile.org/2022/09/27/plasma-mobile-gear-22-09/</url>
</release>
<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>
<image>https://cdn.kde.org/screenshots/audiotube/library.png</image>
</screenshot>
<screenshot>
<image>https://cdn.kde.org/screenshots/audiotube/mobile_playlist.png</image>
</screenshot>
<screenshot>
<image>https://cdn.kde.org/screenshots/audiotube/desktop.png</image>
</screenshot>
</screenshots>
<recommends>
<control>pointing</control>
<control>keyboard</control>
<control>touch</control>
</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 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>
<p xml:lang="eu">AudioTube-k YouTube Music-en bilatu dezake, albumak eta artistak zerrendatu, automatikoki sortutako jotze-zerrendak eta albumak jo, eta zure jotze-zerrenda osatzeko aukera eskaintzen dizu.</p>
<p xml:lang="fi">AudioTube osaa etsiä YouTube Music -haun, albumien ja artistien luettelun, luotujen soittolistojen automaattisen toiston ja omien soittolistojen kokoamisen.</p>
<p xml:lang="fr">AudioTube peut effectuer des recherches de musiques sur YouTube Music, établir une liste d'albums et d'artistes, lire des listes de lecture générées automatiquement, des albums et permet de composer sa propre liste de lecture.</p>
<p xml:lang="hi">ऑडियोट्यूब यूट्यूब संगीत खोज सकता है, एल्बम और कलाकारों की सूची बना सकता है, स्वचालित रूप से उत्पन्न की गई गीतसूची, एल्बम बजा सकता है और आपकी अपनी खुद की प्लेलिस्ट को एक साथ रखने में मदद करता है।</p>
<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="nn">Med AudioTube kan du søkja på YouTube Music, visa album og artistar, spela automatiske genererte spelelister og album samt laga eigne spelelister.</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>
-56
View File
@@ -1,62 +1,6 @@
[Desktop Entry]
Name=AudioTube
Name[ar]=تيوب الصوتي
Name[az]=AudioTube
Name[ca]=AudioTube
Name[ca@valencia]=AudioTube
Name[cs]=AudioTube
Name[de]=AudioTube
Name[en_GB]=AudioTube
Name[es]=AudioTube
Name[eu]=AudioTube
Name[fi]=AudioTube
Name[fr]=AudioTube
Name[hi]=ओडियो-ट्यूब
Name[it]=AudioTube
Name[ko]=AudioTube
Name[nl]=AudioTube
Name[nn]=AudioTube
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
Comment[de]=YouTube-Musikprogramm
Comment[en_GB]=YouTube Music client
Comment[es]=Cliente de música de YouTube
Comment[eu]=YouTube Music bezeroa
Comment[fi]=YouTube Music -asiakas
Comment[fr]=Client de musique sur Youtube
Comment[hi]=यूट्यूब संगीत क्लाएंट
Comment[it]=Client YouTube Music
Comment[ko]=YouTube Music 클라이언트
Comment[nl]=Muziekclient van YouTube
Comment[nn]=YouTube Music-klient
Comment[pa]=ਯੂਟਿਊਬ ਸੰਗੀਤ ਕਲਾਈਂਟ
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
+3 -3
View File
@@ -1,7 +1,7 @@
{
"id": "org.kde.audiotube",
"runtime": "org.kde.Platform",
"runtime-version": "5.15-21.08",
"runtime-version": "5.15",
"sdk": "org.kde.Sdk",
"command": "audiotube",
"tags": ["nightly"],
@@ -21,14 +21,14 @@
],
"modules": [
"flatpak/python3-yt-dlp.json",
"flatpak/python3-youtube_dl.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.9.1" } ]
"sources": [ { "type": "git", "url": "https://github.com/pybind/pybind11", "tag": "v2.6.2" } ]
},
{
"name": "org.kde.audiotube",
-204
View File
@@ -1,204 +0,0 @@
# 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-08-27 00:50+0000\n"
"PO-Revision-Date: 2022-08-28 22:19+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:35 contents/ui/ArtistPage.qml:19
#, kde-format
msgid "Open in Browser"
msgstr "افتح في المتصفّح"
#: contents/ui/AlbumPage.qml:77 contents/ui/ArtistPage.qml:109
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "شغل التالي"
#: contents/ui/AlbumPage.qml:82 contents/ui/ArtistPage.qml:115
#: 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:47 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:63 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "الألبومات"
#: contents/ui/ArtistPage.qml:65
#, kde-format
msgid "Singles"
msgstr "فردي"
#: contents/ui/ArtistPage.qml:67 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "أغاني"
#: contents/ui/ArtistPage.qml:69 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:92
#, kde-format
msgid "Now Playing"
msgstr "المشغل الآن"
#: contents/ui/MaximizedPlayerPage.qml:117
#, kde-format
msgid "Queue"
msgstr "صُفّ"
#: contents/ui/MaximizedPlayerPage.qml:222
#, kde-format
msgid "Remove Track"
msgstr "أزل المسار"
#: contents/ui/MaximizedPlayerPage.qml:242
#: 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 "وسع"
-205
View File
@@ -1,205 +0,0 @@
# 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-08-27 00:50+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:35 contents/ui/ArtistPage.qml:19
#, kde-format
msgid "Open in Browser"
msgstr ""
#: contents/ui/AlbumPage.qml:77 contents/ui/ArtistPage.qml:109
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Sonrakını oxudun"
#: contents/ui/AlbumPage.qml:82 contents/ui/ArtistPage.qml:115
#: 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:47 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:63 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Albomlar"
#: contents/ui/ArtistPage.qml:65
#, kde-format
msgid "Singles"
msgstr "Musiqilər"
#: contents/ui/ArtistPage.qml:67 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Mahnılar"
#: contents/ui/ArtistPage.qml:69 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:92
#, kde-format
msgid "Now Playing"
msgstr "İndi səslənir"
#: contents/ui/MaximizedPlayerPage.qml:117
#, kde-format
msgid "Queue"
msgstr "Növbə"
#: contents/ui/MaximizedPlayerPage.qml:222
#, kde-format
msgid "Remove Track"
msgstr "Mahnını silin"
#: contents/ui/MaximizedPlayerPage.qml:242
#: 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"
-199
View File
@@ -1,199 +0,0 @@
# 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-08-27 00:50+0000\n"
"PO-Revision-Date: 2022-08-27 10:31+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:35 contents/ui/ArtistPage.qml:19
#, kde-format
msgid "Open in Browser"
msgstr "Obre en el navegador"
#: contents/ui/AlbumPage.qml:77 contents/ui/ArtistPage.qml:109
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Reprodueix el següent"
#: contents/ui/AlbumPage.qml:82 contents/ui/ArtistPage.qml:115
#: 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:47 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:63 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Àlbums"
#: contents/ui/ArtistPage.qml:65
#, kde-format
msgid "Singles"
msgstr "Individuals"
#: contents/ui/ArtistPage.qml:67 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Cançons"
#: contents/ui/ArtistPage.qml:69 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:92
#, kde-format
msgid "Now Playing"
msgstr "Ara s'està reproduint"
#: contents/ui/MaximizedPlayerPage.qml:117
#, kde-format
msgid "Queue"
msgstr "Cua"
#: contents/ui/MaximizedPlayerPage.qml:222
#, kde-format
msgid "Remove Track"
msgstr "Elimina la pista"
#: contents/ui/MaximizedPlayerPage.qml:242
#: 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"
-199
View File
@@ -1,199 +0,0 @@
# 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-08-27 00:50+0000\n"
"PO-Revision-Date: 2022-08-27 10:31+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:35 contents/ui/ArtistPage.qml:19
#, kde-format
msgid "Open in Browser"
msgstr "Obri en el navegador"
#: contents/ui/AlbumPage.qml:77 contents/ui/ArtistPage.qml:109
#: 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:82 contents/ui/ArtistPage.qml:115
#: 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:47 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:63 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Àlbums"
#: contents/ui/ArtistPage.qml:65
#, kde-format
msgid "Singles"
msgstr "Individuals"
#: contents/ui/ArtistPage.qml:67 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Cançons"
#: contents/ui/ArtistPage.qml:69 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 "Arrere"
#: 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:92
#, kde-format
msgid "Now Playing"
msgstr "Ara s'està reproduint"
#: contents/ui/MaximizedPlayerPage.qml:117
#, kde-format
msgid "Queue"
msgstr "Cua"
#: contents/ui/MaximizedPlayerPage.qml:222
#, kde-format
msgid "Remove Track"
msgstr "Elimina la pista"
#: contents/ui/MaximizedPlayerPage.qml:242
#: 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 en la Música de Youtube"
-196
View File
@@ -1,196 +0,0 @@
# 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-08-27 00:50+0000\n"
"PO-Revision-Date: 2022-06-28 13:58+0200\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 22.04.2\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:35 contents/ui/ArtistPage.qml:19
#, kde-format
msgid "Open in Browser"
msgstr "Otevřít v prohlížeči"
#: contents/ui/AlbumPage.qml:77 contents/ui/ArtistPage.qml:109
#: 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:82 contents/ui/ArtistPage.qml:115
#: 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:47 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:63 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Alba"
#: contents/ui/ArtistPage.qml:65
#, kde-format
msgid "Singles"
msgstr "Samotné"
#: contents/ui/ArtistPage.qml:67 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Písně"
#: contents/ui/ArtistPage.qml:69 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 "Oblíbené"
#: contents/ui/LibraryPage.qml:37 contents/ui/LibraryPage.qml:108
#, kde-format
msgid "Show All"
msgstr "Zobrazit vše"
#: contents/ui/LibraryPage.qml:42
#, kde-format
msgid "Favourite Songs"
msgstr "Oblíbené skladby"
#: contents/ui/LibraryPage.qml:98
#, kde-format
msgid "Most played"
msgstr "Nejčastěji hrané"
#: contents/ui/LibraryPage.qml:113
#, kde-format
msgid "Played Songs"
msgstr "Přehrané skladby"
#: contents/ui/main.qml:39
#, kde-format
msgid "Back"
msgstr "Zpět"
#: 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:92
#, kde-format
msgid "Now Playing"
msgstr "Nyní přehrávám"
#: contents/ui/MaximizedPlayerPage.qml:117
#, kde-format
msgid "Queue"
msgstr "Fronta"
#: contents/ui/MaximizedPlayerPage.qml:222
#, kde-format
msgid "Remove Track"
msgstr "Odstranit skladbu"
#: contents/ui/MaximizedPlayerPage.qml:242
#: 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 "Neznámý seznam skladeb"
#: contents/ui/PlaybackHistory.qml:25
#, kde-format
msgid "No songs here yet"
msgstr "Zatím zde nejsou žádné skladby"
#: 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"
-209
View File
@@ -1,209 +0,0 @@
# 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-08-27 00:50+0000\n"
"PO-Revision-Date: 2022-07-02 12:04+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:35 contents/ui/ArtistPage.qml:19
#, kde-format
msgid "Open in Browser"
msgstr ""
#: contents/ui/AlbumPage.qml:77 contents/ui/ArtistPage.qml:109
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Nächste Wiedergabe"
#: contents/ui/AlbumPage.qml:82 contents/ui/ArtistPage.qml:115
#: 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:47 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:63 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Alben"
#: contents/ui/ArtistPage.qml:65
#, kde-format
msgid "Singles"
msgstr "Singles"
#: contents/ui/ArtistPage.qml:67 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Songs"
#: contents/ui/ArtistPage.qml:69 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:92
#, kde-format
msgid "Now Playing"
msgstr "Aktueller Titel"
#: contents/ui/MaximizedPlayerPage.qml:117
#, kde-format
msgid "Queue"
msgstr "Warteschlange"
#: contents/ui/MaximizedPlayerPage.qml:222
#, kde-format
msgid "Remove Track"
msgstr "Stück entfernen"
#: contents/ui/MaximizedPlayerPage.qml:242
#: 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 "Unbekannte Liste von Liedern"
#: contents/ui/PlaybackHistory.qml:25
#, kde-format
msgid "No songs here yet"
msgstr "Bisher keine Lieder hier"
#: 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"
-208
View File
@@ -1,208 +0,0 @@
# 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-08-27 00:50+0000\n"
"PO-Revision-Date: 2022-08-29 12:04+0100\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 22.08.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:35 contents/ui/ArtistPage.qml:19
#, kde-format
msgid "Open in Browser"
msgstr "Open in Browser"
#: contents/ui/AlbumPage.qml:77 contents/ui/ArtistPage.qml:109
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Play Next"
#: contents/ui/AlbumPage.qml:82 contents/ui/ArtistPage.qml:115
#: 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:47 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:63 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Albums"
#: contents/ui/ArtistPage.qml:65
#, kde-format
msgid "Singles"
msgstr "Singles"
#: contents/ui/ArtistPage.qml:67 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Songs"
#: contents/ui/ArtistPage.qml:69 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 "Favourites"
#: contents/ui/LibraryPage.qml:37 contents/ui/LibraryPage.qml:108
#, kde-format
msgid "Show All"
msgstr "Show All"
#: contents/ui/LibraryPage.qml:42
#, kde-format
msgid "Favourite Songs"
msgstr "Favourite Songs"
#: contents/ui/LibraryPage.qml:98
#, kde-format
msgid "Most played"
msgstr "Most played"
#: contents/ui/LibraryPage.qml:113
#, kde-format
msgid "Played Songs"
msgstr "Played Songs"
#: contents/ui/main.qml:39
#, kde-format
msgid "Back"
msgstr "Back"
#: 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:92
#, kde-format
msgid "Now Playing"
msgstr "Now Playing"
#: contents/ui/MaximizedPlayerPage.qml:117
#, kde-format
msgid "Queue"
msgstr "Queue"
#: contents/ui/MaximizedPlayerPage.qml:222
#, kde-format
msgid "Remove Track"
msgstr "Remove Track"
#: contents/ui/MaximizedPlayerPage.qml:242
#: 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 "Unknown list of songs"
#: contents/ui/PlaybackHistory.qml:25
#, kde-format
msgid "No songs here yet"
msgstr "No songs here yet"
#: 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"
-210
View File
@@ -1,210 +0,0 @@
# 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-08-27 00:50+0000\n"
"PO-Revision-Date: 2022-08-30 04:36+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.08.0\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:35 contents/ui/ArtistPage.qml:19
#, kde-format
msgid "Open in Browser"
msgstr "Abrir en el navegador"
#: contents/ui/AlbumPage.qml:77 contents/ui/ArtistPage.qml:109
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Reproducir siguiente"
#: contents/ui/AlbumPage.qml:82 contents/ui/ArtistPage.qml:115
#: 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:47 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:63 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Álbumes"
#: contents/ui/ArtistPage.qml:65
#, kde-format
msgid "Singles"
msgstr "Sencillos"
#: contents/ui/ArtistPage.qml:67 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Canciones"
#: contents/ui/ArtistPage.qml:69 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:92
#, kde-format
msgid "Now Playing"
msgstr "Reproduciendo"
#: contents/ui/MaximizedPlayerPage.qml:117
#, kde-format
msgid "Queue"
msgstr "Cola"
#: contents/ui/MaximizedPlayerPage.qml:222
#, kde-format
msgid "Remove Track"
msgstr "Eliminar pista"
#: contents/ui/MaximizedPlayerPage.qml:242
#: 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"
-211
View File
@@ -1,211 +0,0 @@
# 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-08-27 00:50+0000\n"
"PO-Revision-Date: 2022-08-27 07:27+0200\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 22.04.3\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:35 contents/ui/ArtistPage.qml:19
#, kde-format
msgid "Open in Browser"
msgstr "Ireki arakatzailean"
#: contents/ui/AlbumPage.qml:77 contents/ui/ArtistPage.qml:109
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Jo hurrengoa"
#: contents/ui/AlbumPage.qml:82 contents/ui/ArtistPage.qml:115
#: 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:47 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:63 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Albumak"
#: contents/ui/ArtistPage.qml:65
#, kde-format
msgid "Singles"
msgstr "Banakoa"
#: contents/ui/ArtistPage.qml:67 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Abestiak"
#: contents/ui/ArtistPage.qml:69 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 "Gogokoak"
#: contents/ui/LibraryPage.qml:37 contents/ui/LibraryPage.qml:108
#, kde-format
msgid "Show All"
msgstr "Erakutsi denak"
#: contents/ui/LibraryPage.qml:42
#, kde-format
msgid "Favourite Songs"
msgstr "Gogoko abestiak"
#: contents/ui/LibraryPage.qml:98
#, kde-format
msgid "Most played"
msgstr "Gehien jotakoa"
#: contents/ui/LibraryPage.qml:113
#, kde-format
msgid "Played Songs"
msgstr "Jotako abestiak"
#: contents/ui/main.qml:39
#, kde-format
msgid "Back"
msgstr "Atzera"
#: 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:92
#, kde-format
msgid "Now Playing"
msgstr "Orain jotzen"
#: contents/ui/MaximizedPlayerPage.qml:117
#, kde-format
msgid "Queue"
msgstr "Ilara"
#: contents/ui/MaximizedPlayerPage.qml:222
#, kde-format
msgid "Remove Track"
msgstr "Kendu pista"
#: contents/ui/MaximizedPlayerPage.qml:242
#: 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 "Abesti zerrenda ezezaguna"
#: contents/ui/PlaybackHistory.qml:25
#, kde-format
msgid "No songs here yet"
msgstr "Hemen oraindik ez dago abestirik"
#: 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"
-207
View File
@@ -1,207 +0,0 @@
# 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-08-27 00:50+0000\n"
"PO-Revision-Date: 2022-06-13 19:11+0300\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 21.12.3\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:35 contents/ui/ArtistPage.qml:19
#, kde-format
msgid "Open in Browser"
msgstr ""
#: contents/ui/AlbumPage.qml:77 contents/ui/ArtistPage.qml:109
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Toista seuraava"
#: contents/ui/AlbumPage.qml:82 contents/ui/ArtistPage.qml:115
#: 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:47 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:63 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Albumit"
#: contents/ui/ArtistPage.qml:65
#, kde-format
msgid "Singles"
msgstr "Singlet"
#: contents/ui/ArtistPage.qml:67 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Kappaleet"
#: contents/ui/ArtistPage.qml:69 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 "Suosikit"
#: contents/ui/LibraryPage.qml:37 contents/ui/LibraryPage.qml:108
#, kde-format
msgid "Show All"
msgstr "Näytä kaikki"
#: contents/ui/LibraryPage.qml:42
#, kde-format
msgid "Favourite Songs"
msgstr "Suosikkikappaleet"
#: contents/ui/LibraryPage.qml:98
#, kde-format
msgid "Most played"
msgstr "Soitetuimmat"
#: contents/ui/LibraryPage.qml:113
#, kde-format
msgid "Played Songs"
msgstr "Soitetut kappaleet"
#: contents/ui/main.qml:39
#, kde-format
msgid "Back"
msgstr "Takaisin"
#: 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:92
#, kde-format
msgid "Now Playing"
msgstr "Nyt soi"
#: contents/ui/MaximizedPlayerPage.qml:117
#, kde-format
msgid "Queue"
msgstr "Jono"
#: contents/ui/MaximizedPlayerPage.qml:222
#, kde-format
msgid "Remove Track"
msgstr "Poista raita"
#: contents/ui/MaximizedPlayerPage.qml:242
#: 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 "Tuntematon kappaleluettelo"
#: contents/ui/PlaybackHistory.qml:25
#, kde-format
msgid "No songs here yet"
msgstr "Ei vielä kappaleita"
#: 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"
-205
View File
@@ -1,205 +0,0 @@
# 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-08-27 00:50+0000\n"
"PO-Revision-Date: 2022-08-29 12:39+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.08.0\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:35 contents/ui/ArtistPage.qml:19
#, kde-format
msgid "Open in Browser"
msgstr "Ouvrir dans un navigateur"
#: contents/ui/AlbumPage.qml:77 contents/ui/ArtistPage.qml:109
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Lire le suivant"
#: contents/ui/AlbumPage.qml:82 contents/ui/ArtistPage.qml:115
#: 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:47 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:63 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Albums"
#: contents/ui/ArtistPage.qml:65
#, kde-format
msgid "Singles"
msgstr "Titres uniques"
#: contents/ui/ArtistPage.qml:67 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Chansons"
#: contents/ui/ArtistPage.qml:69 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:92
#, kde-format
msgid "Now Playing"
msgstr "En cours de lecture"
#: contents/ui/MaximizedPlayerPage.qml:117
#, kde-format
msgid "Queue"
msgstr "Mettre en file d'attente"
#: contents/ui/MaximizedPlayerPage.qml:222
#, kde-format
msgid "Remove Track"
msgstr "Supprimer une piste"
#: contents/ui/MaximizedPlayerPage.qml:242
#: 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"
-208
View File
@@ -1,208 +0,0 @@
# 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-08-27 00:50+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:35 contents/ui/ArtistPage.qml:19
#, kde-format
msgid "Open in Browser"
msgstr ""
#: contents/ui/AlbumPage.qml:77 contents/ui/ArtistPage.qml:109
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "अगला बजाएँ"
#: contents/ui/AlbumPage.qml:82 contents/ui/ArtistPage.qml:115
#: 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:47 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:63 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "एल्बम्स"
#: contents/ui/ArtistPage.qml:65
#, kde-format
msgid "Singles"
msgstr "एक गीत"
#: contents/ui/ArtistPage.qml:67 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "गीत"
#: contents/ui/ArtistPage.qml:69 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:92
#, fuzzy, kde-format
#| msgid "No media playing"
msgid "Now Playing"
msgstr "कोई मीडिया नहीं बजा रहा है"
#: contents/ui/MaximizedPlayerPage.qml:117
#, kde-format
msgid "Queue"
msgstr ""
#: contents/ui/MaximizedPlayerPage.qml:222
#, kde-format
msgid "Remove Track"
msgstr "गाना हटाएँ"
#: contents/ui/MaximizedPlayerPage.qml:242
#: 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 "केवल ध्वनी चलाएँ"
-208
View File
@@ -1,208 +0,0 @@
# 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-08-27 00:50+0000\n"
"PO-Revision-Date: 2022-08-29 15:18+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.08.0\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:35 contents/ui/ArtistPage.qml:19
#, kde-format
msgid "Open in Browser"
msgstr "Apri nel browser"
#: contents/ui/AlbumPage.qml:77 contents/ui/ArtistPage.qml:109
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Riproduci successivo"
#: contents/ui/AlbumPage.qml:82 contents/ui/ArtistPage.qml:115
#: 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:47 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:63 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Album"
#: contents/ui/ArtistPage.qml:65
#, kde-format
msgid "Singles"
msgstr "Singoli"
#: contents/ui/ArtistPage.qml:67 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Brani"
#: contents/ui/ArtistPage.qml:69 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:92
#, kde-format
msgid "Now Playing"
msgstr "In riproduzione"
#: contents/ui/MaximizedPlayerPage.qml:117
#, kde-format
msgid "Queue"
msgstr "Coda"
#: contents/ui/MaximizedPlayerPage.qml:222
#, kde-format
msgid "Remove Track"
msgstr "Rimuovi traccia"
#: contents/ui/MaximizedPlayerPage.qml:242
#: 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"
-206
View File
@@ -1,206 +0,0 @@
# Copyright (C) YEAR This file is copyright:
# This file is distributed under the same license as the audiotube package.
# Shinjo Park <[email protected]>, 2021, 2022.
#
msgid ""
msgstr ""
"Project-Id-Version: audiotube\n"
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
"POT-Creation-Date: 2022-08-27 00:50+0000\n"
"PO-Revision-Date: 2022-07-13 15:34+0200\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.12.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:35 contents/ui/ArtistPage.qml:19
#, kde-format
msgid "Open in Browser"
msgstr ""
#: contents/ui/AlbumPage.qml:77 contents/ui/ArtistPage.qml:109
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "다음 재생"
#: contents/ui/AlbumPage.qml:82 contents/ui/ArtistPage.qml:115
#: 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:47 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:63 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "앨범"
#: contents/ui/ArtistPage.qml:65
#, kde-format
msgid "Singles"
msgstr "싱글"
#: contents/ui/ArtistPage.qml:67 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "곡"
#: contents/ui/ArtistPage.qml:69 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:92
#, kde-format
msgid "Now Playing"
msgstr "지금 재생 중"
#: contents/ui/MaximizedPlayerPage.qml:117
#, kde-format
msgid "Queue"
msgstr "대기열에 추가"
#: contents/ui/MaximizedPlayerPage.qml:222
#, kde-format
msgid "Remove Track"
msgstr "트랙 삭제"
#: contents/ui/MaximizedPlayerPage.qml:242
#: 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 "오디오만 재생"
-208
View File
@@ -1,208 +0,0 @@
# 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-08-27 00:50+0000\n"
"PO-Revision-Date: 2022-08-27 16:52+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.08.0\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:35 contents/ui/ArtistPage.qml:19
#, kde-format
msgid "Open in Browser"
msgstr "In browser openen"
#: contents/ui/AlbumPage.qml:77 contents/ui/ArtistPage.qml:109
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Volgende afspelen"
#: contents/ui/AlbumPage.qml:82 contents/ui/ArtistPage.qml:115
#: 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:47 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:63 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Albums"
#: contents/ui/ArtistPage.qml:65
#, kde-format
msgid "Singles"
msgstr "Enkelvoudige"
#: contents/ui/ArtistPage.qml:67 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Songs"
#: contents/ui/ArtistPage.qml:69 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:92
#, kde-format
msgid "Now Playing"
msgstr "Speelt nu"
#: contents/ui/MaximizedPlayerPage.qml:117
#, kde-format
msgid "Queue"
msgstr "Wachtrij"
#: contents/ui/MaximizedPlayerPage.qml:222
#, kde-format
msgid "Remove Track"
msgstr "Track verwijderen"
#: contents/ui/MaximizedPlayerPage.qml:242
#: 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"
-197
View File
@@ -1,197 +0,0 @@
# Translation of audiotube to Norwegian Nynorsk
#
# Øystein Steffensen-Alværvik <[email protected]>, 2022.
# Karl Ove Hufthammer <[email protected]>, 2022.
msgid ""
msgstr ""
"Project-Id-Version: audiotube\n"
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
"POT-Creation-Date: 2022-08-27 00:50+0000\n"
"PO-Revision-Date: 2022-09-11 19:45+0200\n"
"Last-Translator: Karl Ove Hufthammer <[email protected]>\n"
"Language-Team: Norwegian Nynorsk <[email protected]>\n"
"Language: nn\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.08.1\n"
"X-Environment: kde\n"
"X-Accelerator-Marker: &\n"
"X-Text-Markup: kde4\n"
#, kde-format
msgctxt "NAME OF TRANSLATORS"
msgid "Your names"
msgstr "Øystein Steffensen-Alværvik"
#, 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 "Spel av"
#: contents/ui/AlbumPage.qml:26 contents/ui/PlaylistPage.qml:26
#, kde-format
msgid "Shuffle"
msgstr "Stokk om"
#: contents/ui/AlbumPage.qml:35 contents/ui/ArtistPage.qml:19
#, kde-format
msgid "Open in Browser"
msgstr "Opna i nettlesar"
#: contents/ui/AlbumPage.qml:77 contents/ui/ArtistPage.qml:109
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Spel som neste"
#: contents/ui/AlbumPage.qml:82 contents/ui/ArtistPage.qml:115
#: contents/ui/PlaybackHistory.qml:79 contents/ui/PlaylistPage.qml:72
#: contents/ui/SearchPage.qml:123
#, kde-format
msgid "Add to Playlist"
msgstr "Legg til speleliste"
#: contents/ui/ArtistPage.qml:47 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 "Videoavspeling er endå ikkje støtta. Vil du spela av lyden til «%1»?"
#: contents/ui/ArtistPage.qml:63 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Album"
#: contents/ui/ArtistPage.qml:65
#, kde-format
msgid "Singles"
msgstr "Singelplater"
#: contents/ui/ArtistPage.qml:67 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Songar"
#: contents/ui/ArtistPage.qml:69 contents/ui/SearchPage.qml:33
#, kde-format
msgid "Videos"
msgstr "Videoar"
#: 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 "Favorittar"
#: contents/ui/LibraryPage.qml:37 contents/ui/LibraryPage.qml:108
#, kde-format
msgid "Show All"
msgstr "Vis alle"
#: contents/ui/LibraryPage.qml:42
#, kde-format
msgid "Favourite Songs"
msgstr "Favorittsongar"
#: contents/ui/LibraryPage.qml:98
#, kde-format
msgid "Most played"
msgstr "Oftast avspela"
#: contents/ui/LibraryPage.qml:113
#, kde-format
msgid "Played Songs"
msgstr "Avspela songar"
#: contents/ui/main.qml:39
#, kde-format
msgid "Back"
msgstr "Tilbake"
#: 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:92
#, kde-format
msgid "Now Playing"
msgstr "Spelar no"
#: contents/ui/MaximizedPlayerPage.qml:117
#, kde-format
msgid "Queue"
msgstr "Kø"
#: contents/ui/MaximizedPlayerPage.qml:222
#, kde-format
msgid "Remove Track"
msgstr "Fjern spor"
#: contents/ui/MaximizedPlayerPage.qml:242
#: contents/ui/MinimizedPlayerControls.qml:96
#, kde-format
msgid "No media playing"
msgstr "Inkje medium vert spela"
#: contents/ui/PlaybackHistory.qml:15
#, kde-format
msgid "Unknown list of songs"
msgstr "Ukjend songliste"
#: contents/ui/PlaybackHistory.qml:25
#, kde-format
msgid "No songs here yet"
msgstr "Ingen songar enno"
#: contents/ui/SearchPage.qml:27
#, kde-format
msgid "Artists"
msgstr "Artistar"
#: contents/ui/SearchPage.qml:29
#, kde-format
msgid "Playlists"
msgstr "Spelelister"
#: contents/ui/SearchPage.qml:36
#, kde-format
msgid "Unknown"
msgstr "Ukjend"
#: contents/ui/SearchPage.qml:117
#, kde-format
msgid "Play next"
msgstr "Spel som neste"
#: 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 etter musikk på YouTube Music"
-207
View File
@@ -1,207 +0,0 @@
# 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-08-27 00:50+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:35 contents/ui/ArtistPage.qml:19
#, kde-format
msgid "Open in Browser"
msgstr ""
#: contents/ui/AlbumPage.qml:77 contents/ui/ArtistPage.qml:109
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "ਅਗਲਾ ਚਲਾਓ"
#: contents/ui/AlbumPage.qml:82 contents/ui/ArtistPage.qml:115
#: 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:47 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:63 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "ਐਲਬਮਾਂ"
#: contents/ui/ArtistPage.qml:65
#, kde-format
msgid "Singles"
msgstr "ਇਕੱਲੇ"
#: contents/ui/ArtistPage.qml:67 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "ਗੀਤ"
#: contents/ui/ArtistPage.qml:69 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:92
#, fuzzy, kde-format
#| msgid "No media playing"
msgid "Now Playing"
msgstr "ਕੋਈ ਮੀਡੀਆ ਨਹੀਂ ਚੱਲਦਾ"
#: contents/ui/MaximizedPlayerPage.qml:117
#, kde-format
msgid "Queue"
msgstr ""
#: contents/ui/MaximizedPlayerPage.qml:222
#, kde-format
msgid "Remove Track"
msgstr "ਟਰੈਕ ਨੂੰ ਹਟਾਓ"
#: contents/ui/MaximizedPlayerPage.qml:242
#: 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 "ਸਿਰਫ਼ ਆਡੀਓ ਹੀ ਚਲਾਓ"
-210
View File
@@ -1,210 +0,0 @@
# 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, 2022.
# 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-08-27 00:50+0000\n"
"PO-Revision-Date: 2022-06-26 20:24+0200\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:35 contents/ui/ArtistPage.qml:19
#, kde-format
msgid "Open in Browser"
msgstr ""
#: contents/ui/AlbumPage.qml:77 contents/ui/ArtistPage.qml:109
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Odtwórz następny"
#: contents/ui/AlbumPage.qml:82 contents/ui/ArtistPage.qml:115
#: 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:47 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:63 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Albumy"
#: contents/ui/ArtistPage.qml:65
#, kde-format
msgid "Singles"
msgstr "Single"
#: contents/ui/ArtistPage.qml:67 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Piosenki"
#: contents/ui/ArtistPage.qml:69 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 "Ulubione"
#: contents/ui/LibraryPage.qml:37 contents/ui/LibraryPage.qml:108
#, kde-format
msgid "Show All"
msgstr "Pokaż wszystkie"
#: contents/ui/LibraryPage.qml:42
#, kde-format
msgid "Favourite Songs"
msgstr "Ulubione utwory"
#: contents/ui/LibraryPage.qml:98
#, kde-format
msgid "Most played"
msgstr "Najczęściej odtwarzane"
#: contents/ui/LibraryPage.qml:113
#, kde-format
msgid "Played Songs"
msgstr "Odtwarzane utwory"
#: contents/ui/main.qml:39
#, kde-format
msgid "Back"
msgstr "Wstecz"
#: 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:92
#, kde-format
msgid "Now Playing"
msgstr "Teraz odtwarzany"
#: contents/ui/MaximizedPlayerPage.qml:117
#, kde-format
msgid "Queue"
msgstr "Kolejka"
#: contents/ui/MaximizedPlayerPage.qml:222
#, kde-format
msgid "Remove Track"
msgstr "Usuń utwór"
#: contents/ui/MaximizedPlayerPage.qml:242
#: 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 "Nieznana lista utworów"
#: contents/ui/PlaybackHistory.qml:25
#, kde-format
msgid "No songs here yet"
msgstr "Jeszcze nie ma tutaj utworów"
#: 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"
-198
View File
@@ -1,198 +0,0 @@
# 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-08-27 00:50+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:35 contents/ui/ArtistPage.qml:19
#, kde-format
msgid "Open in Browser"
msgstr "Abrir no Navegador"
#: contents/ui/AlbumPage.qml:77 contents/ui/ArtistPage.qml:109
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Reproduzir o Seguinte"
#: contents/ui/AlbumPage.qml:82 contents/ui/ArtistPage.qml:115
#: 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:47 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:63 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Álbuns"
#: contents/ui/ArtistPage.qml:65
#, kde-format
msgid "Singles"
msgstr "'Singles'"
#: contents/ui/ArtistPage.qml:67 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Músicas"
#: contents/ui/ArtistPage.qml:69 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:92
#, kde-format
msgid "Now Playing"
msgstr "Agora a Ouvir"
#: contents/ui/MaximizedPlayerPage.qml:117
#, kde-format
msgid "Queue"
msgstr "Fila"
#: contents/ui/MaximizedPlayerPage.qml:222
#, kde-format
msgid "Remove Track"
msgstr "Remover a Faixa"
#: contents/ui/MaximizedPlayerPage.qml:242
#: 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"
-209
View File
@@ -1,209 +0,0 @@
# 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, 2022.
# 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-08-27 00:50+0000\n"
"PO-Revision-Date: 2022-07-13 14:59-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 21.12.3\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:35 contents/ui/ArtistPage.qml:19
#, kde-format
msgid "Open in Browser"
msgstr ""
#: contents/ui/AlbumPage.qml:77 contents/ui/ArtistPage.qml:109
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Reproduzir próxima"
#: contents/ui/AlbumPage.qml:82 contents/ui/ArtistPage.qml:115
#: 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:47 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:63 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Álbuns"
#: contents/ui/ArtistPage.qml:65
#, kde-format
msgid "Singles"
msgstr "Singles"
#: contents/ui/ArtistPage.qml:67 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Músicas"
#: contents/ui/ArtistPage.qml:69 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 reproduzidas"
#: contents/ui/LibraryPage.qml:113
#, kde-format
msgid "Played Songs"
msgstr "Músicas reproduzidas"
#: contents/ui/main.qml:39
#, kde-format
msgid "Back"
msgstr "Voltar"
#: 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:92
#, kde-format
msgid "Now Playing"
msgstr "Reproduzindo agora"
#: contents/ui/MaximizedPlayerPage.qml:117
#, kde-format
msgid "Queue"
msgstr "Fila"
#: contents/ui/MaximizedPlayerPage.qml:222
#, kde-format
msgid "Remove Track"
msgstr "Remover faixa"
#: contents/ui/MaximizedPlayerPage.qml:242
#: 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 "Lista de músicas desconhecida"
#: contents/ui/PlaybackHistory.qml:25
#, kde-format
msgid "No songs here yet"
msgstr "Nenhuma música ainda"
#: 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"
-201
View File
@@ -1,201 +0,0 @@
# 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-08-27 00:50+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:35 contents/ui/ArtistPage.qml:19
#, kde-format
msgid "Open in Browser"
msgstr ""
#: contents/ui/AlbumPage.qml:77 contents/ui/ArtistPage.qml:109
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Воспроизвести следующий"
#: contents/ui/AlbumPage.qml:82 contents/ui/ArtistPage.qml:115
#: 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:47 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:63 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Альбомы"
#: contents/ui/ArtistPage.qml:65
#, kde-format
msgid "Singles"
msgstr ""
#: contents/ui/ArtistPage.qml:67 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr ""
#: contents/ui/ArtistPage.qml:69 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:92
#, kde-format
msgid "Now Playing"
msgstr ""
#: contents/ui/MaximizedPlayerPage.qml:117
#, kde-format
msgid "Queue"
msgstr ""
#: contents/ui/MaximizedPlayerPage.qml:222
#, kde-format
msgid "Remove Track"
msgstr "Удалить дорожку"
#: contents/ui/MaximizedPlayerPage.qml:242
#: 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 "Приостановить"
-203
View File
@@ -1,203 +0,0 @@
# 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-08-27 00:50+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:35 contents/ui/ArtistPage.qml:19
#, kde-format
msgid "Open in Browser"
msgstr ""
#: contents/ui/AlbumPage.qml:77 contents/ui/ArtistPage.qml:109
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
#, fuzzy, kde-format
#| msgid "Play"
msgid "Play Next"
msgstr "Prehrať"
#: contents/ui/AlbumPage.qml:82 contents/ui/ArtistPage.qml:115
#: 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:47 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:63 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Albumy"
#: contents/ui/ArtistPage.qml:65
#, kde-format
msgid "Singles"
msgstr "Samotné"
#: contents/ui/ArtistPage.qml:67 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr ""
#: contents/ui/ArtistPage.qml:69 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:92
#, fuzzy, kde-format
#| msgid "No media playing"
msgid "Now Playing"
msgstr "Neprehráva sa žiadne médium"
#: contents/ui/MaximizedPlayerPage.qml:117
#, kde-format
msgid "Queue"
msgstr ""
#: contents/ui/MaximizedPlayerPage.qml:222
#, kde-format
msgid "Remove Track"
msgstr ""
#: contents/ui/MaximizedPlayerPage.qml:242
#: 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úť"
-207
View File
@@ -1,207 +0,0 @@
# 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-08-27 00:50+0000\n"
"PO-Revision-Date: 2022-09-03 08:19+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 22.04.1\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:35 contents/ui/ArtistPage.qml:19
#, kde-format
msgid "Open in Browser"
msgstr "Odpri v brskalniku"
#: contents/ui/AlbumPage.qml:77 contents/ui/ArtistPage.qml:109
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Predvajaj naslednjo"
#: contents/ui/AlbumPage.qml:82 contents/ui/ArtistPage.qml:115
#: 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:47 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:63 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Albumi"
#: contents/ui/ArtistPage.qml:65
#, kde-format
msgid "Singles"
msgstr "Singli"
#: contents/ui/ArtistPage.qml:67 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Glasba"
#: contents/ui/ArtistPage.qml:69 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:92
#, kde-format
msgid "Now Playing"
msgstr "Pravkar predvajano"
#: contents/ui/MaximizedPlayerPage.qml:117
#, kde-format
msgid "Queue"
msgstr "Čakalna vrsta"
#: contents/ui/MaximizedPlayerPage.qml:222
#, kde-format
msgid "Remove Track"
msgstr "Odstrani sled"
#: contents/ui/MaximizedPlayerPage.qml:242
#: 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"
-206
View File
@@ -1,206 +0,0 @@
# 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-08-27 00:50+0000\n"
"PO-Revision-Date: 2022-09-24 14:57+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:35 contents/ui/ArtistPage.qml:19
#, kde-format
msgid "Open in Browser"
msgstr "Öppna i webbläsare"
#: contents/ui/AlbumPage.qml:77 contents/ui/ArtistPage.qml:109
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Spela nästa"
#: contents/ui/AlbumPage.qml:82 contents/ui/ArtistPage.qml:115
#: 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:47 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:63 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Album"
#: contents/ui/ArtistPage.qml:65
#, kde-format
msgid "Singles"
msgstr "Singlar"
#: contents/ui/ArtistPage.qml:67 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Låtar"
#: contents/ui/ArtistPage.qml:69 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:92
#, kde-format
msgid "Now Playing"
msgstr "Spelar nu"
#: contents/ui/MaximizedPlayerPage.qml:117
#, kde-format
msgid "Queue"
msgstr "Köa"
#: contents/ui/MaximizedPlayerPage.qml:222
#, kde-format
msgid "Remove Track"
msgstr "Ta bort spår"
#: contents/ui/MaximizedPlayerPage.qml:242
#: 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"
-205
View File
@@ -1,205 +0,0 @@
# 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-08-27 00:50+0000\n"
"PO-Revision-Date: 2022-08-27 05:01+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.3\n"
#, kde-format
msgctxt "NAME OF TRANSLATORS"
msgid "Your names"
msgstr "Emir SARI"
#, kde-format
msgctxt "EMAIL OF TRANSLATORS"
msgid "Your emails"
msgstr "[email protected]"
#: contents/ui/AlbumPage.qml:19 contents/ui/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:35 contents/ui/ArtistPage.qml:19
#, kde-format
msgid "Open in Browser"
msgstr "Tarayıcıda Aç"
#: contents/ui/AlbumPage.qml:77 contents/ui/ArtistPage.qml:109
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Sonrakini Çal"
#: contents/ui/AlbumPage.qml:82 contents/ui/ArtistPage.qml:115
#: 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:47 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:63 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Albümler"
#: contents/ui/ArtistPage.qml:65
#, kde-format
msgid "Singles"
msgstr "Tekliler"
#: contents/ui/ArtistPage.qml:67 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Şarkılar"
#: contents/ui/ArtistPage.qml:69 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:92
#, kde-format
msgid "Now Playing"
msgstr "Şimdi Çalınıyor"
#: contents/ui/MaximizedPlayerPage.qml:117
#, kde-format
msgid "Queue"
msgstr "Kuyruk"
#: contents/ui/MaximizedPlayerPage.qml:222
#, kde-format
msgid "Remove Track"
msgstr "Parçayı Kaldır"
#: contents/ui/MaximizedPlayerPage.qml:242
#: 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"
-209
View File
@@ -1,209 +0,0 @@
# 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-08-27 00:50+0000\n"
"PO-Revision-Date: 2022-08-27 09:13+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:35 contents/ui/ArtistPage.qml:19
#, kde-format
msgid "Open in Browser"
msgstr "Відкрити у переглядачі"
#: contents/ui/AlbumPage.qml:77 contents/ui/ArtistPage.qml:109
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Відтворити наступну"
#: contents/ui/AlbumPage.qml:82 contents/ui/ArtistPage.qml:115
#: 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:47 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:63 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Альбоми"
#: contents/ui/ArtistPage.qml:65
#, kde-format
msgid "Singles"
msgstr "Одинарні"
#: contents/ui/ArtistPage.qml:67 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Пісні"
#: contents/ui/ArtistPage.qml:69 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:92
#, kde-format
msgid "Now Playing"
msgstr "Зараз відтворюємо"
#: contents/ui/MaximizedPlayerPage.qml:117
#, kde-format
msgid "Queue"
msgstr "Додати до черги"
#: contents/ui/MaximizedPlayerPage.qml:222
#, kde-format
msgid "Remove Track"
msgstr "Вилучити композицію"
#: contents/ui/MaximizedPlayerPage.qml:242
#: 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 "Відтворити лише звук"
-194
View File
@@ -1,194 +0,0 @@
msgid ""
msgstr ""
"Project-Id-Version: kdeorg\n"
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
"POT-Creation-Date: 2022-08-27 00:50+0000\n"
"PO-Revision-Date: 2022-09-20 01:57\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:35 contents/ui/ArtistPage.qml:19
#, kde-format
msgid "Open in Browser"
msgstr "在浏览器中打开"
#: contents/ui/AlbumPage.qml:77 contents/ui/ArtistPage.qml:109
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "播放下一首"
#: contents/ui/AlbumPage.qml:82 contents/ui/ArtistPage.qml:115
#: 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:47 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:63 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "专辑"
#: contents/ui/ArtistPage.qml:65
#, kde-format
msgid "Singles"
msgstr "单曲"
#: contents/ui/ArtistPage.qml:67 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "曲目"
#: contents/ui/ArtistPage.qml:69 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:92
#, kde-format
msgid "Now Playing"
msgstr "正在播放"
#: contents/ui/MaximizedPlayerPage.qml:117
#, kde-format
msgid "Queue"
msgstr "播放队列"
#: contents/ui/MaximizedPlayerPage.qml:222
#, kde-format
msgid "Remove Track"
msgstr "移除曲目"
#: contents/ui/MaximizedPlayerPage.qml:242
#: 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 中的音乐"
-195
View File
@@ -1,195 +0,0 @@
# 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-08-27 00:50+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:35 contents/ui/ArtistPage.qml:19
#, kde-format
msgid "Open in Browser"
msgstr ""
#: contents/ui/AlbumPage.qml:77 contents/ui/ArtistPage.qml:109
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr ""
#: contents/ui/AlbumPage.qml:82 contents/ui/ArtistPage.qml:115
#: 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:47 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:63 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr ""
#: contents/ui/ArtistPage.qml:65
#, kde-format
msgid "Singles"
msgstr ""
#: contents/ui/ArtistPage.qml:67 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr ""
#: contents/ui/ArtistPage.qml:69 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:92
#, kde-format
msgid "Now Playing"
msgstr ""
#: contents/ui/MaximizedPlayerPage.qml:117
#, kde-format
msgid "Queue"
msgstr ""
#: contents/ui/MaximizedPlayerPage.qml:222
#, kde-format
msgid "Remove Track"
msgstr ""
#: contents/ui/MaximizedPlayerPage.qml:242
#: 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 -7
View File
@@ -1,7 +1,2 @@
# SPDX-FileCopyrightText: 2021 Jonah Brüchert <[email protected]>
# SPDX-FileCopyrightText: 2021 Kuba Prussak <[email protected]>
#
# SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
ytmusicapi==0.22.0
yt-dlp
ytmusicapi==0.16.0
youtube_dl
+2 -18
View File
@@ -1,6 +1,4 @@
# SPDX-FileCopyrightText: 2021 Jonah Brüchert <[email protected]>
#
# SPDX-License-Identifier: BSD-2-Clause
add_subdirectory(mpris2)
add_library(ytm STATIC
ytmusic.cpp
@@ -10,14 +8,6 @@ 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
@@ -31,10 +21,7 @@ add_executable(audiotube
playlistmodel.cpp
playlistutils.cpp
errorhandler.cpp
playerutils.cpp
abstractytmusicmodel.cpp
multiiterableview.h
library.cpp
resources.qrc
)
@@ -46,12 +33,9 @@ target_link_libraries(audiotube
Qt5::QuickControls2
Qt5::Svg
KF5::I18n
KF5::CoreAddons
KF5::Crash
ytm
asyncdatabase
mpris2
)
target_compile_definitions(audiotube PRIVATE -DQT_NO_KEYWORDS -DQT_NO_URL_CAST_FROM_STRING)
install(TARGETS audiotube ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
-4
View File
@@ -1,4 +0,0 @@
#! /usr/bin/env bash
# SPDX-FileCopyrightText: 2020 Tobias Fella <[email protected]>
# SPDX-License-Identifier: CC0-1.0
$XGETTEXT `find -name \*.cpp -o -name \*.qml -o -name \*.js` -o $podir/audiotube.pot
-27
View File
@@ -1,27 +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
#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();
}
-28
View File
@@ -1,28 +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
#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;
};
+35 -24
View File
@@ -4,29 +4,28 @@
#include "albummodel.h"
#include <QStringBuilder>
#include <QDebug>
AlbumModel::AlbumModel(QObject *parent)
: AbstractYTMusicModel(parent)
: QAbstractListModel(parent)
{
connect(this, &AlbumModel::browseIdChanged, this, [this] {
connect(this, &AlbumModel::browseIdChanged, this, [=] {
setLoading(true);
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());
Q_EMIT titleChanged();
Q_EMIT thumbnailUrlChanged();
Q_EMIT playlistIdChanged();
});
YTMusicThread::instance()->fetchAlbum(m_browseId);
});
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::fetchAlbumFinished, this, [=](const album::Album &album) {
setLoading(false);
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::errorOccurred, this, [=, this] {
beginResetModel();
m_album = album;
endResetModel();
std::sort(m_album.thumbnails.begin(), m_album.thumbnails.end());
Q_EMIT titleChanged();
Q_EMIT thumbnailUrlChanged();
Q_EMIT playlistIdChanged();
});
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::errorOccurred, this, [=] {
setLoading(false);
});
}
@@ -44,7 +43,12 @@ 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(m_album.tracks[index.row()].artists);
return QVariant::fromValue(std::vector<meta::Artist> {
{
m_album.tracks[index.row()].artists,
std::nullopt
}
});
}
Q_UNREACHABLE();
@@ -77,6 +81,18 @@ 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()) {
@@ -88,10 +104,5 @@ QUrl AlbumModel::thumbnailUrl() const
QString AlbumModel::playlistId() const
{
return QString::fromStdString(m_album.audio_playlist_id);
}
QUrl AlbumModel::webUrl() const
{
return QUrl(YTMUSIC_WEB_BASE_URL % "/playlist?list=" % QString::fromStdString(m_album.audio_playlist_id));
return QString::fromStdString(m_album.playlist_id);
}
+7 -6
View File
@@ -8,17 +8,16 @@
#include <QUrl>
#include "asyncytmusic.h"
#include "abstractytmusicmodel.h"
class AlbumModel : public AbstractYTMusicModel
class AlbumModel : public QAbstractListModel
{
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)
Q_PROPERTY(QUrl webUrl READ webUrl NOTIFY webUrlChanged)
public:
enum Role {
@@ -40,17 +39,19 @@ 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();
QString playlistId() const;
Q_SIGNAL void playlistIdChanged();
QUrl webUrl() const;
Q_SIGNAL void webUrlChanged();
private:
QString m_browseId;
bool m_loading = false;
album::Album m_album;
};
+52 -46
View File
@@ -4,54 +4,52 @@
#include "artistmodel.h"
#include <QStringBuilder>
#include <asyncytmusic.h>
ArtistModel::ArtistModel(QObject *parent)
: AbstractYTMusicModel(parent)
, m_view(albums, singles, songs, videos)
: QAbstractListModel(parent)
{
connect(this, &ArtistModel::channelIdChanged, this, [this] {
connect(this, &ArtistModel::channelIdChanged, this, [=] {
if (m_channelId.isEmpty()) {
return;
}
setLoading(true);
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();
});
YTMusicThread::instance()->fetchArtist(m_channelId);
});
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::errorOccurred, this, [this] {
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());
m_view = std::optional(MultiIterableView(m_artist.albums->results,
m_artist.singles->results,
m_artist.songs->results,
m_artist.videos->results));
endResetModel();
Q_EMIT titleChanged();
Q_EMIT thumbnailUrlChanged();
});
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::errorOccurred, this, [=] {
setLoading(false);
});
}
int ArtistModel::rowCount(const QModelIndex &parent) const
{
return parent.isValid() ? 0 : m_view.size();
return parent.isValid() ? 0 : m_view ? int(m_view->size()) : 0;
}
QVariant ArtistModel::data(const QModelIndex &index, int role) const
{
if (!m_view) {
return {};
}
switch (role) {
case Title:
return QString::fromStdString(std::visit([&](auto&& item) {
@@ -67,7 +65,7 @@ QVariant ArtistModel::data(const QModelIndex &index, int role) const
}
Q_UNREACHABLE();
}, m_view[index.row()]));
}, m_view.value()[index.row()]));
case Type:
return std::visit([&](auto&& item) {
using T = std::decay_t<decltype(item)>;
@@ -82,7 +80,7 @@ QVariant ArtistModel::data(const QModelIndex &index, int role) const
}
Q_UNREACHABLE();
}, m_view[index.row()]);
}, m_view.value()[index.row()]);
case Artists:
return QVariant::fromValue(std::vector<meta::Artist> {
{
@@ -104,7 +102,7 @@ QVariant ArtistModel::data(const QModelIndex &index, int role) const
}
Q_UNREACHABLE();
}, m_view[index.row()]);
}, m_view.value()[index.row()]);
}
Q_UNREACHABLE();
@@ -147,25 +145,33 @@ QUrl ArtistModel::thumbnailUrl() const
return QUrl(QString::fromStdString(m_artist.thumbnails.back().url));
}
QUrl ArtistModel::webUrl() const
bool ArtistModel::loading() const
{
return QUrl(YTMUSIC_WEB_BASE_URL % u"channel/" % QString::fromStdString(m_artist.channel_id));
return m_loading;
}
void ArtistModel::setLoading(bool loading)
{
m_loading = loading;
Q_EMIT loadingChanged();
}
void ArtistModel::triggerItem(int 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]);
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));
} else {
Q_UNREACHABLE();
}
}, m_view.value()[row]);
}
}
+9 -16
View File
@@ -10,12 +10,9 @@
#include <optional>
#include <ytmusic.h>
#include "multiiterableview.h"
#include "abstractytmusicmodel.h"
class ArtistModel : public AbstractYTMusicModel
class ArtistModel : public QAbstractListModel
{
Q_OBJECT
@@ -23,7 +20,7 @@ class ArtistModel : public AbstractYTMusicModel
Q_PROPERTY(QString title READ title NOTIFY titleChanged)
Q_PROPERTY(QUrl thumbnailUrl READ thumbnailUrl NOTIFY thumbnailUrlChanged)
Q_PROPERTY(QUrl webUrl READ webUrl NOTIFY webUrlChanged)
Q_PROPERTY(bool loading READ loading WRITE setLoading NOTIFY loadingChanged)
public:
enum Type {
@@ -57,26 +54,22 @@ public:
QUrl thumbnailUrl() const;
Q_SIGNAL void thumbnailUrlChanged();
QUrl webUrl() const;
Q_SIGNAL void webUrlChanged();
bool loading() const;
void setLoading(bool loading);
Q_SIGNAL void loadingChanged();
Q_INVOKABLE void triggerItem(int row);
Q_SIGNAL void openAlbum(const QString &browseId);
Q_SIGNAL void openSong(const QString &videoId);
Q_SIGNAL void openVideo(const QString &videoId, const QString &title);
Q_SIGNAL void openVideo(const QString &videoId);
private:
QString m_channelId;
bool m_loading = false;
artist::Artist m_artist;
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<
std::optional<MultiIterableView<
artist::Artist::Album, artist::Artist::Single, artist::Artist::Song, artist::Artist::Video
> m_view;
>> m_view;
};
-309
View File
@@ -1,309 +0,0 @@
// 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;
}
-152
View File
@@ -1,152 +0,0 @@
// 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;
};
-179
View File
@@ -1,179 +0,0 @@
// 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;
};
}
+106 -58
View File
@@ -6,10 +6,6 @@
#include <QThread>
#include <QDebug>
#include <QJsonObject>
#include <QJsonArray>
#include <QFutureInterface>
#include <QCoreApplication>
#include <pybind11/embed.h>
@@ -17,21 +13,10 @@
namespace py = pybind11;
#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) {
template <typename R, typename T, typename OP>
std::optional<R> map_optional(const std::optional<T> &optional, OP op) {
if (optional.has_value()) {
if constexpr (std::is_member_function_pointer<OP>::value) {
return (&optional.value()->*op)();
} else {
return op(optional.value());
}
return op(optional.value());
}
return std::nullopt;
@@ -52,6 +37,15 @@ 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);
});
@@ -60,83 +54,139 @@ AsyncYTMusic::AsyncYTMusic(QObject *parent)
//
// search
//
QFuture<std::vector<search::SearchResultItem>> AsyncYTMusic::search(const QString &query)
void AsyncYTMusic::search(const QString &query)
{
return invokeAndCatchOnThread([=, this]() {
return m_ytm->search(query.toStdString());
});
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()));
}
}
//
// fetchArtist
//
QFuture<artist::Artist> AsyncYTMusic::fetchArtist(const QString &channelId)
void AsyncYTMusic::fetchArtist(const QString &channelId)
{
return invokeAndCatchOnThread([=, this]() {
return m_ytm->get_artist(channelId.toStdString());
});
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()));
}
}
//
// fetchAlbum
//
QFuture<album::Album> AsyncYTMusic::fetchAlbum(const QString &browseId)
void AsyncYTMusic::fetchAlbum(const QString &browseId)
{
return invokeAndCatchOnThread([=, this]() {
return m_ytm->get_album(browseId.toStdString());
});
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()));
}
}
//
// fetchSong
//
QFuture<std::optional<song::Song>> AsyncYTMusic::fetchSong(const QString &videoId)
void AsyncYTMusic::fetchSong(const QString &videoId)
{
return invokeAndCatchOnThread([=, this]() {
return m_ytm->get_song(videoId.toStdString());
});
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()));
}
}
//
// fetchPlaylist
//
QFuture<playlist::Playlist> AsyncYTMusic::fetchPlaylist(const QString &playlistId) {
return invokeAndCatchOnThread([=, this]() {
return m_ytm->get_playlist(playlistId.toStdString());
});
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()));
}
}
//
// fetchArtistAlbum
//
QFuture<std::vector<artist::Artist::Album>> AsyncYTMusic::fetchArtistAlbums(const QString &channelId, const QString &params)
void AsyncYTMusic::fetchArtistAlbums(const QString &channelId, const QString &params)
{
return invokeAndCatchOnThread([=, this]() {
return m_ytm->get_artist_albums(channelId.toStdString(), params.toStdString());
});
Q_EMIT startFetchArtistAlbums(channelId, params);
}
void AsyncYTMusic::internalFetchArtistAlbums(const QString &channelid, const QString &params)
{
try {
Q_EMIT fetchArtistAlbumsFinished(m_ytm->get_artist_albums(channelid.toStdString(), params.toStdString()));
} catch (const py::error_already_set &err) {
Q_EMIT errorOccurred(QString::fromLocal8Bit(err.what()));
}
}
//
// extractVideoInfo
//
QFuture<video_info::VideoInfo> AsyncYTMusic::extractVideoInfo(const QString &videoId)
void AsyncYTMusic::extractVideoInfo(const QString &videoId)
{
return invokeAndCatchOnThread([=, this]() {
return m_ytm->extract_video_info(videoId.toStdString());
});
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()));
}
}
//
// fetchWatchPlaylist
//
QFuture<watch::Playlist> AsyncYTMusic::fetchWatchPlaylist(const std::optional<QString> &videoId, const std::optional<QString> &playlistId)
void AsyncYTMusic::fetchWatchPlaylist(const std::optional<QString> &videoId, const std::optional<QString> &playlistId)
{
return invokeAndCatchOnThread([=, this]() {
return m_ytm->get_watch_playlist(
mapOptional(videoId, &QString::toStdString),
mapOptional(playlistId, &QString::toStdString)
);
});
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()));
}
}
YTMusicThread &YTMusicThread::instance()
@@ -153,19 +203,17 @@ 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();
}
+42 -80
View File
@@ -6,15 +6,11 @@
#include <QObject>
#include <QThread>
#include <QFuture>
#include <QFutureWatcher>
#include <vector>
#include <ytmusic.h>
constexpr QStringView YTMUSIC_WEB_BASE_URL = u"https://music.youtube.com/";
Q_DECLARE_METATYPE(std::vector<artist::Artist::Album>);
Q_DECLARE_METATYPE(std::vector<search::SearchResultItem>)
Q_DECLARE_METATYPE(artist::Artist)
@@ -41,7 +37,6 @@ public:
if (!m_item) {
m_item = std::make_unique<T>();
}
Q_ASSERT(m_item);
return m_item;
}
@@ -49,53 +44,6 @@ 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 {
Q_UNUSED(self);
fun();
}
} else if (future.resultCount() > 0) {
if constexpr (std::is_member_function_pointer_v<Function>) {
(self->*fun)(watcher->result());
} else {
Q_UNUSED(self);
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;
@@ -104,22 +52,30 @@ class AsyncYTMusic : public QObject
public:
// public functions need to be thread safe
QFuture<std::vector<search::SearchResultItem>> search(const QString &query);
void search(const QString &query);
Q_SIGNAL void searchFinished(std::vector<search::SearchResultItem>);
QFuture<artist::Artist> fetchArtist(const QString &channelId);
void fetchArtist(const QString &channelId);
Q_SIGNAL void fetchArtistFinished(artist::Artist);
QFuture<album::Album> fetchAlbum(const QString &browseId);
void fetchAlbum(const QString &browseId);
Q_SIGNAL void fetchAlbumFinished(album::Album);
QFuture<std::optional<song::Song> > fetchSong(const QString &videoId);
void fetchSong(const QString &videoId);
Q_SIGNAL void fetchSongFinished(song::Song);
QFuture<playlist::Playlist> fetchPlaylist(const QString &playlistId);
void fetchPlaylist(const QString &playlistId);
Q_SIGNAL void fetchPlaylistFinished(playlist::Playlist);
QFuture<std::vector<artist::Artist::Album>> fetchArtistAlbums(const QString &channelId, const QString &params);
void fetchArtistAlbums(const QString &channelId, const QString &params);
Q_SIGNAL void fetchArtistAlbumsFinished(std::vector<artist::Artist::Album>);
QFuture<video_info::VideoInfo> extractVideoInfo(const QString &videoId);
void extractVideoInfo(const QString &videoId);
Q_SIGNAL void extractVideoInfoFinished(video_info::VideoInfo);
QFuture<watch::Playlist> fetchWatchPlaylist(const std::optional<QString> &videoId = std::nullopt ,
void 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);
@@ -127,32 +83,38 @@ protected:
explicit AsyncYTMusic(QObject *parent = nullptr);
private:
/// 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();
}
Q_SIGNAL void startSearch(const QString &query);
Q_SLOT void internalSearch(const QString &query);
// Python interpreter will be initialized from the thread calling the methods
Q_SIGNAL void startFetchArtist(const QString &channelId);
Q_SLOT void internalFetchArtist(const QString &channelId);
Q_SIGNAL void startFetchAlbum(const QString &browseId);
Q_SLOT void internalFetchAlbum(const QString &browseId);
Q_SIGNAL void startFetchSong(const QString &videoId);
Q_SLOT void internalFetchSong(const QString &videoId);
Q_SIGNAL void startFetchPlaylist(const QString &playlistId);
Q_SLOT void internalFetchPlaylist(const QString &playlistId);
Q_SIGNAL void startFetchArtistAlbums(const QString &channelid, const QString &params);
Q_SLOT void internalFetchArtistAlbums(const QString &channelid, const QString &params);
Q_SIGNAL void startExtractVideoInfo(const QString &videoId);
Q_SLOT void internalExtractVideoInfo(const QString &videoId);
Q_SIGNAL void startFetchWatchPlaylist(const std::optional<QString> &videoId, const std::optional<QString> &playlistId);
Q_SLOT void internalFetchWatchPlaylist(const std::optional<QString> &videoId, const std::optional<QString> &playlistId);
// Python interpreter will be ininitalized from the thread calling the methods
Lazy<YTMusic> m_ytm;
};
class YTMusicThread : private QThread {
public:
static YTMusicThread &instance();
~YTMusicThread() override;
~YTMusicThread();
AsyncYTMusic *operator->();
AsyncYTMusic &get();
@@ -160,5 +122,5 @@ public:
private:
YTMusicThread();
AsyncYTMusic *m_ytm;
AsyncYTMusic m_ytm;
};
+3 -21
View File
@@ -2,7 +2,7 @@
//
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
import QtQuick 2.15
import QtQuick 2.1
import org.kde.kirigami 2.14 as Kirigami
import QtQuick.Controls 2.14 as Controls
import QtQuick.Layouts 1.3
@@ -21,32 +21,14 @@ Kirigami.ScrollablePage {
applicationWindow().playPlaylist(albumModel.playlistId)
}
}
right: Kirigami.Action {
icon.name: "media-playlist-shuffle"
text: i18n("Shuffle")
onTriggered: {
applicationWindow().playShufflePlaylist(albumModel.playlistId)
}
}
}
contextualActions: [
Kirigami.Action {
text: i18n("Open in Browser")
icon.name: "internet-services"
onTriggered: Qt.openUrlExternally(albumModel.webUrl)
}
]
ListView {
header: Kirigami.ItemViewHeader {
backgroundImage.source: albumModel.thumbnailUrl
title: albumModel.title
}
reuseItems: true
model: AlbumModel {
id: albumModel
}
@@ -74,12 +56,12 @@ Kirigami.ScrollablePage {
actions: [
Kirigami.Action {
icon.name: "go-next"
text: i18n("Play Next")
text: i18n("Play next")
onTriggered: UserPlaylistModel.playNext(delegateItem.videoId, delegateItem.title, delegateItem.artists)
},
Kirigami.Action {
icon.name: "media-playlist-append"
text: i18n("Add to Playlist")
text: i18n("Add to playlist")
onTriggered: UserPlaylistModel.append(delegateItem.videoId, delegateItem.title, delegateItem.artists)
}
]
+5 -28
View File
@@ -2,7 +2,7 @@
//
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
import QtQuick 2.15
import QtQuick 2.1
import org.kde.kirigami 2.14 as Kirigami
import QtQuick.Controls 2.14 as Controls
import QtQuick.Layouts 1.3
@@ -14,22 +14,11 @@ Kirigami.ScrollablePage {
title: artistModel.title
contextualActions: [
Kirigami.Action {
text: i18n("Open in Browser")
icon.name: "internet-services"
onTriggered: Qt.openUrlExternally(artistModel.webUrl)
}
]
ListView {
header: Kirigami.ItemViewHeader {
backgroundImage.source: artistModel.thumbnailUrl
title: artistModel.title
}
reuseItems: true
model: ArtistModel {
id: artistModel
@@ -43,16 +32,8 @@ Kirigami.ScrollablePage {
play(videoId)
}
onOpenVideo: (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
}
onOpenVideo: (videoId) => {
askPlayVideoAudio(videoId)
}
}
section.property: "type"
@@ -106,13 +87,13 @@ Kirigami.ScrollablePage {
actions: [
Kirigami.Action {
icon.name: "go-next"
text: i18n("Play Next")
text: i18n("Play next")
visible: type === ArtistModel.Song
onTriggered: UserPlaylistModel.playNext(delegateItem.videoId, delegateItem.title, delegateItem.artists)
},
Kirigami.Action {
icon.name: "media-playlist-append"
text: i18n("Add to Playlist")
text: i18n("Add to playlist")
visible: type === ArtistModel.Song
onTriggered: UserPlaylistModel.append(delegateItem.videoId, delegateItem.title, delegateItem.artists)
}
@@ -126,8 +107,4 @@ Kirigami.ScrollablePage {
visible: artistModel.loading
}
}
footer: ConfirmationMessage {
id: videoPlayMessage
}
}
-26
View File
@@ -1,26 +0,0 @@
// 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()
}
]
}
-159
View File
@@ -1,159 +0,0 @@
// 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
}
}
}
}
}
}
}
-425
View File
@@ -1,425 +0,0 @@
// 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
padding: Kirigami.Units.largeSpacing
onClicked: swipeView.setCurrentIndex(0)
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
Layout.preferredWidth: Kirigami.Units.gridUnit * 9
contentItem: Label {
text: i18n("Now Playing")
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
padding: Kirigami.Units.largeSpacing
onClicked: swipeView.setCurrentIndex(1)
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
Layout.preferredWidth: Kirigami.Units.gridUnit * 9
contentItem: Label {
text: i18n("Queue")
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
}
}
}
}
}
-217
View File
@@ -1,217 +0,0 @@
// 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
}
}
}
-85
View File
@@ -1,85 +0,0 @@
// 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, [])
}
]
}
}
}
+206 -395
View File
@@ -1,422 +1,233 @@
// 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
boundsBehavior: Flickable.StopAtBounds
function close() {
toClose.restart();
}
anchors.left: parent.left
anchors.right: parent.right
height: maximized ? applicationWindow().height : player.preferredHeight
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();
}
}
}
}
GridLayout {
id: playerLayout
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
}
readonly property bool mobile: width > height
// snap to end
MouseArea {
anchors.fill: footerLayout
propagateComposedEvents: true
onPressed: {
toOpen.stop();
toClose.stop();
propagateComposedEvents = true;
}
onReleased: footerItem.resetToBoundsOnFlick()
}
Layout.fillHeight: footerLayout.maximized
Layout.fillWidth: true
flow: width > height ? GridLayout.LeftToRight : GridLayout.TopToBottom
visible: footerLayout.maximized
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
Layout.fillHeight: true
Image {
anchors.fill: parent
lightness: -0.5
saturation: 1.9
source: info.thumbnail
fillMode: Image.PreserveAspectFit
}
}
layer.enabled: true
layer.effect: FastBlur {
cached: true
radius: 64
transparentBorder: false
Item {
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 {
icon.name: "edit-clear-all"
onClicked: UserPlaylistModel.clear()
}
Controls.ToolButton {
icon.name: "media-playlist-shuffle"
onClicked: UserPlaylistModel.shufflePlaylist()
}
}
}
}
}
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
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
//}
//}
}
//Behavior on height {
//NumberAnimation {
//}
//}
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")
}
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.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 {
}
}
}
+4 -9
View File
@@ -2,7 +2,7 @@
//
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
import QtQuick 2.15
import QtQuick 2.0
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.12 as Controls
import org.kde.kirigami 2.12 as Kirigami
@@ -38,14 +38,11 @@ Kirigami.ScrollablePage {
model: PlaylistModel {
id: playlistModel
}
reuseItems: true
delegate: Kirigami.SwipeListItem {
id: delegateItem
required property string title
required property string videoId
required property var artists
required property string artists
RowLayout {
Layout.fillHeight: true
@@ -64,17 +61,15 @@ Kirigami.ScrollablePage {
actions: [
Kirigami.Action {
icon.name: "go-next"
text: i18n("Play Next")
text: i18n("Play next")
onTriggered: UserPlaylistModel.playNext(delegateItem.videoId, delegateItem.title, delegateItem.artists)
},
Kirigami.Action {
icon.name: "media-playlist-append"
text: i18n("Add to Playlist")
text: i18n("Add to playlist")
onTriggered: UserPlaylistModel.append(delegateItem.videoId, delegateItem.title, delegateItem.artists)
}
]
onClicked: play(videoId)
}
Controls.BusyIndicator {
+16 -62
View File
@@ -2,7 +2,7 @@
//
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
import QtQuick 2.15
import QtQuick 2.0
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.14 as Controls
import org.kde.kirigami 2.14 as Kirigami
@@ -10,34 +10,20 @@ import org.kde.kirigami 2.14 as Kirigami
import org.kde.ytmusic 1.0
Kirigami.ScrollablePage {
property alias searchQuery: searchModel.searchQuery
title: i18n("Youtube Music")
title: searchQuery
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")
header: Controls.Control {
padding: Kirigami.Units.largeSpacing
contentItem: Kirigami.SearchField {
delaySearch: true
selectByMouse: true
onAccepted: {
searchModel.searchQuery = text
}
}
section.property: "type"
}
ListView {
model: SearchModel {
id: searchModel
@@ -63,16 +49,8 @@ Kirigami.ScrollablePage {
play(videoId)
}
onOpenVideo: (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
}
onOpenVideo: (videoId) => {
askPlayVideoAudio(videoId)
}
}
delegate: Kirigami.SwipeListItem {
@@ -82,7 +60,6 @@ Kirigami.ScrollablePage {
required property int type
required property string videoId
required property var artists
required property string radioPlaylistId
RowLayout {
Layout.fillHeight: true
@@ -115,20 +92,14 @@ Kirigami.ScrollablePage {
Kirigami.Action {
icon.name: "go-next"
text: i18n("Play next")
visible: delegateItem.type === SearchModel.Song || delegateItem.type == SearchModel.Video
visible: delegateItem.type === SearchModel.Song
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 || delegateItem.type == SearchModel.Video
text: i18n("Add to playlist")
visible: delegateItem.type === SearchModel.Song
onTriggered: UserPlaylistModel.append(delegateItem.videoId, delegateItem.title, delegateItem.artists)
},
Kirigami.Action {
icon.name: "radio"
text: i18n("Radio")
visible: delegateItem.type === SearchModel.Artist && delegateItem.radioPlaylistId
onTriggered: playPlaylist(delegateItem.radioPlaylistId)
}
]
@@ -138,22 +109,5 @@ Kirigami.ScrollablePage {
anchors.centerIn: parent
visible: searchModel.loading
}
Kirigami.PlaceholderMessage {
anchors.centerIn: parent
width: parent.width - (Kirigami.Units.largeSpacing * 4)
visible: parent.count === 0 && !searchModel.loading
text: i18n("Find music on YouTube Music")
helpfulAction: Kirigami.Action {
onTriggered: searchField.forceActiveFocus()
text: i18n("Search")
icon.name: "search"
}
}
}
footer: ConfirmationMessage {
id: videoPlayMessage
}
}
+12 -144
View File
@@ -3,8 +3,6 @@
// 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
@@ -12,83 +10,15 @@ import org.kde.ytmusic 1.0
Kirigami.ApplicationWindow {
id: root
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
background: Rectangle {
width: parent.width
height: parent.height
anchors.fill: parent
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.Header
color: Kirigami.Theme.backgroundColor
}
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.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
sourceComponent: searchFieldComponent
}
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:/LibraryPage.qml"
pageStack.initialPage: "qrc:/SearchPage.qml"
function play(videoId) {
UserPlaylistModel.initialVideoId = videoId
@@ -103,6 +33,14 @@ Kirigami.ApplicationWindow {
UserPlaylistModel.playlistId = playlistId
}
function askPlayVideoAudio(videoId) {
showPassiveNotification(i18n("Video playback is not supported yet."),
"long",
i18n("Play only audio"),
function() {
play(videoId)
})
}
Connections {
target: ErrorHandler
@@ -111,76 +49,6 @@ Kirigami.ApplicationWindow {
showPassiveNotification(error)
}
}
Component {
id: searchFieldComponent
Kirigami.SearchField {
id: searchField
delaySearch: true
selectByMouse: true
onPressed: {
popup.open()
}
property alias popup: popup
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
}
footer: PlayerFooter {}
}
-4
View File
@@ -1,6 +1,2 @@
# SPDX-FileCopyrightText: 2021 Jonah Brüchert <[email protected]>
#
# SPDX-License-Identifier: BSD-2-Clause
add_executable(ytm_example main.cpp)
target_link_libraries(ytm_example PRIVATE ytm)
+2 -2
View File
@@ -8,7 +8,7 @@
int main() {
const auto ytm = YTMusic();
const auto results = ytm.search("Another one bites the dust");
const auto results = ytm.search("arte concert");
std::cout << "Found " << results.size() << " results." << std::endl;
@@ -42,7 +42,7 @@ int main() {
} else if constexpr (std::is_same_v<T, search::Video>) {
std::cout << arg.title << std::endl;
} else {
std::cout << "non exaustive visitor";
std::cout << "non excaustive visitor";
}
}, result);
}
-250
View File
@@ -1,250 +0,0 @@
// 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 order by favourites.rowid desc");
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<bool>>("select count(*) > 0 from favourites where video_id = ?", m_videoId), this, [this](auto count) {
if (count) {
m_isFavourite = count->value;
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
View File
@@ -1,187 +0,0 @@
// 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;
};
-18
View File
@@ -7,9 +7,6 @@
#include <QtQml>
#include <QUrl>
#include <KLocalizedContext>
#include <KLocalizedString>
#include <KCrash>
#include <KAboutData>
#include "searchmodel.h"
#include "albummodel.h"
@@ -18,10 +15,6 @@
#include "userplaylistmodel.h"
#include "errorhandler.h"
#include "playlistmodel.h"
#include "playerutils.h"
#include "library.h"
#include "asyncdatabase.h"
constexpr auto URI = "org.kde.ytmusic";
@@ -33,11 +26,6 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
QCoreApplication::setOrganizationDomain(QStringLiteral("kde.org"));
QCoreApplication::setApplicationName(QStringLiteral("audiotube"));
KLocalizedString::setApplicationDomain("audiotube");
KAboutData::setApplicationData(KAboutData(QStringLiteral("audiotube"), QStringLiteral("AudioTube")));
KCrash::initialize();
QQmlApplicationEngine engine;
qmlRegisterType<SearchModel>(URI, 1, 0, "SearchModel");
@@ -51,12 +39,6 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
qmlRegisterSingletonType<ErrorHandler>(URI, 1, 0, "ErrorHandler", [](QQmlEngine *, QJSEngine *) {
return new ErrorHandler();
});
qmlRegisterSingletonType<PlayerUtils>(URI, 1, 0, "PlayerUtils", [](QQmlEngine *, QJSEngine *) {
return new PlayerUtils();
});
qmlRegisterSingletonInstance<Library>(URI, 1, 0, "Library", &Library::instance());
qmlRegisterType<ThumbnailSource>(URI, 1, 0, "ThumbnailSource");
qmlRegisterAnonymousType<FavouriteWatcher>(URI, 1);
engine.rootContext()->setContextObject(new KLocalizedContext(&engine));
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
View File
Whitespace-only changes.
@@ -1,8 +0,0 @@
-- 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;
@@ -1,26 +0,0 @@
-- 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
);
+10
View File
@@ -0,0 +1,10 @@
qt5_add_dbus_adaptor(DBUS_SRCS
dbus/org.mpris.MediaPlayer2.xml
mprisapplication.h MprisApplication)
qt5_add_dbus_adaptor(DBUS_SRCS
dbus/org.mpris.MediaPlayer2.Player.xml
mprisplayer.h MprisPlayer)
add_library(mpris2 STATIC mprisapplication.cpp mprisplayer.cpp ${DBUS_SRCS})
target_link_libraries(mpris2 PUBLIC Qt5::Core Qt5::DBus)
@@ -0,0 +1,672 @@
<?xml version="1.0" ?>
<node name="/Player_Interface" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
<interface name="org.mpris.MediaPlayer2.Player">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>
This interface implements the methods for querying and providing basic
control over what is currently playing.
</p>
</tp:docstring>
<tp:enum name="Playback_Status" tp:name-for-bindings="Playback_Status" type="s">
<tp:enumvalue suffix="Playing" value="Playing">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>A track is currently playing.</p>
</tp:docstring>
</tp:enumvalue>
<tp:enumvalue suffix="Paused" value="Paused">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>A track is currently paused.</p>
</tp:docstring>
</tp:enumvalue>
<tp:enumvalue suffix="Stopped" value="Stopped">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>There is no track currently playing.</p>
</tp:docstring>
</tp:enumvalue>
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>A playback state.</p>
</tp:docstring>
</tp:enum>
<tp:enum name="Loop_Status" tp:name-for-bindings="Loop_Status" type="s">
<tp:enumvalue suffix="None" value="None">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>The playback will stop when there are no more tracks to play</p>
</tp:docstring>
</tp:enumvalue>
<tp:enumvalue suffix="Track" value="Track">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>The current track will start again from the begining once it has finished playing</p>
</tp:docstring>
</tp:enumvalue>
<tp:enumvalue suffix="Playlist" value="Playlist">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>The playback loops through a list of tracks</p>
</tp:docstring>
</tp:enumvalue>
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>A repeat / loop status</p>
</tp:docstring>
</tp:enum>
<tp:simple-type name="Track_Id" type="o" array-name="Track_Id_List">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>Unique track identifier.</p>
<p>
If the media player implements the TrackList interface and allows
the same track to appear multiple times in the tracklist,
this must be unique within the scope of the tracklist.
</p>
<p>
Note that this should be a valid D-Bus object id, although clients
should not assume that any object is actually exported with any
interfaces at that path.
</p>
<p>
Media players may not use any paths starting with
<literal>/org/mpris</literal> unless explicitly allowed by this specification.
Such paths are intended to have special meaning, such as
<literal>/org/mpris/MediaPlayer2/TrackList/NoTrack</literal>
to indicate "no track".
</p>
<tp:rationale>
<p>
This is a D-Bus object id as that is the definitive way to have
unique identifiers on D-Bus. It also allows for future optional
expansions to the specification where tracks are exported to D-Bus
with an interface similar to org.gnome.UPnP.MediaItem2.
</p>
</tp:rationale>
</tp:docstring>
</tp:simple-type>
<tp:simple-type name="Playback_Rate" type="d">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>A playback rate</p>
<p>
This is a multiplier, so a value of 0.5 indicates that playback is
happening at half speed, while 1.5 means that 1.5 seconds of "track time"
is consumed every second.
</p>
</tp:docstring>
</tp:simple-type>
<tp:simple-type name="Volume" type="d">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>Audio volume level</p>
<ul>
<li>0.0 means mute.</li>
<li>1.0 is a sensible maximum volume level (ex: 0dB).</li>
</ul>
<p>
Note that the volume may be higher than 1.0, although generally
clients should not attempt to set it above 1.0.
</p>
</tp:docstring>
</tp:simple-type>
<tp:simple-type name="Time_In_Us" type="x">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>Time in microseconds.</p>
</tp:docstring>
</tp:simple-type>
<method name="Next" tp:name-for-bindings="Next">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>Skips to the next track in the tracklist.</p>
<p>
If there is no next track (and endless playback and track
repeat are both off), stop playback.
</p>
<p>If playback is paused or stopped, it remains that way.</p>
<p>
If <tp:member-ref>CanGoNext</tp:member-ref> is
<strong>false</strong>, attempting to call this method should have
no effect.
</p>
</tp:docstring>
</method>
<method name="Previous" tp:name-for-bindings="Previous">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>Skips to the previous track in the tracklist.</p>
<p>
If there is no previous track (and endless playback and track
repeat are both off), stop playback.
</p>
<p>If playback is paused or stopped, it remains that way.</p>
<p>
If <tp:member-ref>CanGoPrevious</tp:member-ref> is
<strong>false</strong>, attempting to call this method should have
no effect.
</p>
</tp:docstring>
</method>
<method name="Pause" tp:name-for-bindings="Pause">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>Pauses playback.</p>
<p>If playback is already paused, this has no effect.</p>
<p>
Calling Play after this should cause playback to start again
from the same position.
</p>
<p>
If <tp:member-ref>CanPause</tp:member-ref> is
<strong>false</strong>, attempting to call this method should have
no effect.
</p>
</tp:docstring>
</method>
<method name="PlayPause" tp:name-for-bindings="PlayPause">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>Pauses playback.</p>
<p>If playback is already paused, resumes playback.</p>
<p>If playback is stopped, starts playback.</p>
<p>
If <tp:member-ref>CanPause</tp:member-ref> is
<strong>false</strong>, attempting to call this method should have
no effect and raise an error.
</p>
</tp:docstring>
</method>
<method name="Stop" tp:name-for-bindings="Stop">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>Stops playback.</p>
<p>If playback is already stopped, this has no effect.</p>
<p>
Calling Play after this should cause playback to
start again from the beginning of the track.
</p>
<p>
If <tp:member-ref>CanControl</tp:member-ref> is
<strong>false</strong>, attempting to call this method should have
no effect and raise an error.
</p>
</tp:docstring>
</method>
<method name="Play" tp:name-for-bindings="Play">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>Starts or resumes playback.</p>
<p>If already playing, this has no effect.</p>
<p>If paused, playback resumes from the current position.</p>
<p>If there is no track to play, this has no effect.</p>
<p>
If <tp:member-ref>CanPlay</tp:member-ref> is
<strong>false</strong>, attempting to call this method should have
no effect.
</p>
</tp:docstring>
</method>
<method name="Seek" tp:name-for-bindings="Seek">
<arg direction="in" type="x" name="Offset" tp:type="Time_In_Us">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>The number of microseconds to seek forward.</p>
</tp:docstring>
</arg>
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>
Seeks forward in the current track by the specified number
of microseconds.
</p>
<p>
A negative value seeks back. If this would mean seeking
back further than the start of the track, the position
is set to 0.
</p>
<p>
If the value passed in would mean seeking beyond the end
of the track, acts like a call to Next.
</p>
<p>
If the <tp:member-ref>CanSeek</tp:member-ref> property is false,
this has no effect.
</p>
</tp:docstring>
</method>
<method name="SetPosition" tp:name-for-bindings="Set_Position">
<arg direction="in" type="o" tp:type="Track_Id" name="TrackId">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>The currently playing track's identifier.</p>
<p>
If this does not match the id of the currently-playing track,
the call is ignored as "stale".
</p>
<p>
<literal>/org/mpris/MediaPlayer2/TrackList/NoTrack</literal>
is <em>not</em> a valid value for this argument.
</p>
</tp:docstring>
</arg>
<arg direction="in" type="x" tp:type="Time_In_Us" name="Position">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>Track position in microseconds.</p>
<p>This must be between 0 and &lt;track_length&gt;.</p>
</tp:docstring>
</arg>
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>Sets the current track position in microseconds.</p>
<p>If the Position argument is less than 0, do nothing.</p>
<p>
If the Position argument is greater than the track length,
do nothing.
</p>
<p>
If the <tp:member-ref>CanSeek</tp:member-ref> property is false,
this has no effect.
</p>
<tp:rationale>
<p>
The reason for having this method, rather than making
<tp:member-ref>Position</tp:member-ref> writable, is to include
the TrackId argument to avoid race conditions where a client tries
to seek to a position when the track has already changed.
</p>
</tp:rationale>
</tp:docstring>
</method>
<method name="OpenUri" tp:name-for-bindings="Open_Uri">
<arg direction="in" type="s" tp:type="Uri" name="Uri">
<tp:docstring>
<p>
Uri of the track to load. Its uri scheme should be an element of the
<literal>org.mpris.MediaPlayer2.SupportedUriSchemes</literal>
property and the mime-type should match one of the elements of the
<literal>org.mpris.MediaPlayer2.SupportedMimeTypes</literal>.
</p>
</tp:docstring>
</arg>
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>Opens the Uri given as an argument</p>
<p>If the playback is stopped, starts playing</p>
<p>
If the uri scheme or the mime-type of the uri to open is not supported,
this method does nothing and may raise an error. In particular, if the
list of available uri schemes is empty, this method may not be
implemented.
</p>
<p>Clients should not assume that the Uri has been opened as soon as this
method returns. They should wait until the mpris:trackid field in the
<tp:member-ref>Metadata</tp:member-ref> property changes.
</p>
<p>
If the media player implements the TrackList interface, then the
opened track should be made part of the tracklist, the
<literal>org.mpris.MediaPlayer2.TrackList.TrackAdded</literal> or
<literal>org.mpris.MediaPlayer2.TrackList.TrackListReplaced</literal>
signal should be fired, as well as the
<literal>org.freedesktop.DBus.Properties.PropertiesChanged</literal>
signal on the tracklist interface.
</p>
</tp:docstring>
</method>
<property name="PlaybackStatus" tp:name-for-bindings="Playback_Status" type="s" tp:type="Playback_Status" access="read">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>The current playback status.</p>
<p>
May be "Playing", "Paused" or "Stopped".
</p>
</tp:docstring>
</property>
<property name="LoopStatus" type="s" access="readwrite"
tp:name-for-bindings="Loop_Status" tp:type="Loop_Status">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
<annotation name="org.mpris.MediaPlayer2.property.optional" value="true"/>
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>The current loop / repeat status</p>
<p>May be:
<ul>
<li>"None" if the playback will stop when there are no more tracks to play</li>
<li>"Track" if the current track will start again from the begining once it has finished playing</li>
<li>"Playlist" if the playback loops through a list of tracks</li>
</ul>
</p>
<p>
If <tp:member-ref>CanControl</tp:member-ref> is
<strong>false</strong>, attempting to set this property should have
no effect and raise an error.
</p>
</tp:docstring>
</property>
<property name="Rate" tp:name-for-bindings="Rate" type="d" tp:type="Playback_Rate" access="readwrite">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>The current playback rate.</p>
<p>
The value must fall in the range described by
<tp:member-ref>MinimumRate</tp:member-ref> and
<tp:member-ref>MaximumRate</tp:member-ref>, and must not be 0.0. If
playback is paused, the <tp:member-ref>PlaybackStatus</tp:member-ref>
property should be used to indicate this. A value of 0.0 should not
be set by the client. If it is, the media player should act as
though <tp:member-ref>Pause</tp:member-ref> was called.
</p>
<p>
If the media player has no ability to play at speeds other than the
normal playback rate, this must still be implemented, and must
return 1.0. The <tp:member-ref>MinimumRate</tp:member-ref> and
<tp:member-ref>MaximumRate</tp:member-ref> properties must also be
set to 1.0.
</p>
<p>
Not all values may be accepted by the media player. It is left to
media player implementations to decide how to deal with values they
cannot use; they may either ignore them or pick a "best fit" value.
Clients are recommended to only use sensible fractions or multiples
of 1 (eg: 0.5, 0.25, 1.5, 2.0, etc).
</p>
<tp:rationale>
<p>
This allows clients to display (reasonably) accurate progress bars
without having to regularly query the media player for the current
position.
</p>
</tp:rationale>
</tp:docstring>
</property>
<property name="Shuffle" tp:name-for-bindings="Shuffle" type="b" access="readwrite">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
<annotation name="org.mpris.MediaPlayer2.property.optional" value="true"/>
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>
A value of <strong>false</strong> indicates that playback is
progressing linearly through a playlist, while <strong>true</strong>
means playback is progressing through a playlist in some other order.
</p>
<p>
If <tp:member-ref>CanControl</tp:member-ref> is
<strong>false</strong>, attempting to set this property should have
no effect and raise an error.
</p>
</tp:docstring>
</property>
<property name="Metadata" tp:name-for-bindings="Metadata" type="a{sv}" tp:type="Metadata_Map" access="read">
<annotation name="org.qtproject.QtDBus.QtTypeName" value="QMap<QString, QString>"/>
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>The metadata of the current element.</p>
<p>
If there is a current track, this must have a "mpris:trackid" entry
(of D-Bus type "o") at the very least, which contains a D-Bus path that
uniquely identifies this track.
</p>
<p>
See the type documentation for more details.
</p>
</tp:docstring>
</property>
<property name="Volume" type="d" tp:type="Volume" tp:name-for-bindings="Volume" access="readwrite">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true" />
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>The volume level.</p>
<p>
When setting, if a negative value is passed, the volume
should be set to 0.0.
</p>
<p>
If <tp:member-ref>CanControl</tp:member-ref> is
<strong>false</strong>, attempting to set this property should have
no effect and raise an error.
</p>
</tp:docstring>
</property>
<property name="Position" type="x" tp:type="Time_In_Us" tp:name-for-bindings="Position" access="read">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>
The current track position in microseconds, between 0 and
the 'mpris:length' metadata entry (see Metadata).
</p>
<p>
Note: If the media player allows it, the current playback position
can be changed either the SetPosition method or the Seek method on
this interface. If this is not the case, the
<tp:member-ref>CanSeek</tp:member-ref> property is false, and
setting this property has no effect and can raise an error.
</p>
<p>
If the playback progresses in a way that is inconstistant with the
<tp:member-ref>Rate</tp:member-ref> property, the
<tp:member-ref>Seeked</tp:member-ref> signal is emited.
</p>
</tp:docstring>
</property>
<property name="MinimumRate" tp:name-for-bindings="Minimum_Rate" type="d" tp:type="Playback_Rate" access="read">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>
The minimum value which the <tp:member-ref>Rate</tp:member-ref>
property can take.
Clients should not attempt to set the
<tp:member-ref>Rate</tp:member-ref> property below this value.
</p>
<p>
Note that even if this value is 0.0 or negative, clients should
not attempt to set the <tp:member-ref>Rate</tp:member-ref> property
to 0.0.
</p>
<p>This value should always be 1.0 or less.</p>
</tp:docstring>
</property>
<property name="MaximumRate" tp:name-for-bindings="Maximum_Rate" type="d" tp:type="Playback_Rate" access="read">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>
The maximum value which the <tp:member-ref>Rate</tp:member-ref>
property can take.
Clients should not attempt to set the
<tp:member-ref>Rate</tp:member-ref> property above this value.
</p>
<p>
This value should always be 1.0 or greater.
</p>
</tp:docstring>
</property>
<property name="CanGoNext" tp:name-for-bindings="Can_Go_Next" type="b" access="read">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>
Whether the client can call the <tp:member-ref>Next</tp:member-ref>
method on this interface and expect the current track to change.
</p>
<p>
If it is unknown whether a call to <tp:member-ref>Next</tp:member-ref> will
be successful (for example, when streaming tracks), this property should
be set to <strong>true</strong>.
</p>
<p>
If <tp:member-ref>CanControl</tp:member-ref> is
<strong>false</strong>, this property should also be
<strong>false</strong>.
</p>
<tp:rationale>
<p>
Even when playback can generally be controlled, there may not
always be a next track to move to.
</p>
</tp:rationale>
</tp:docstring>
</property>
<property name="CanGoPrevious" tp:name-for-bindings="Can_Go_Previous" type="b" access="read">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>
Whether the client can call the
<tp:member-ref>Previous</tp:member-ref> method on this interface and
expect the current track to change.
</p>
<p>
If it is unknown whether a call to <tp:member-ref>Previous</tp:member-ref>
will be successful (for example, when streaming tracks), this property
should be set to <strong>true</strong>.
</p>
<p>
If <tp:member-ref>CanControl</tp:member-ref> is
<strong>false</strong>, this property should also be
<strong>false</strong>.
</p>
<tp:rationale>
<p>
Even when playback can generally be controlled, there may not
always be a next previous to move to.
</p>
</tp:rationale>
</tp:docstring>
</property>
<property name="CanPlay" tp:name-for-bindings="Can_Play" type="b" access="read">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>Whether playback can be started using
<tp:member-ref>Play</tp:member-ref> or
<tp:member-ref>PlayPause</tp:member-ref>.
</p>
<p>
Note that this is related to whether there is a "current track": the
value should not depend on whether the track is currently paused or
playing. In fact, if a track is currently playing (and
<tp:member-ref>CanControl</tp:member-ref> is <strong>true</strong>),
this should be <strong>true</strong>.
</p>
<p>
If <tp:member-ref>CanControl</tp:member-ref> is
<strong>false</strong>, this property should also be
<strong>false</strong>.
</p>
<tp:rationale>
<p>
Even when playback can generally be controlled, it may not be
possible to enter a "playing" state, for example if there is no
"current track".
</p>
</tp:rationale>
</tp:docstring>
</property>
<property name="CanPause" tp:name-for-bindings="Can_Pause" type="b" access="read">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>Whether playback can be paused using
<tp:member-ref>Pause</tp:member-ref> or
<tp:member-ref>PlayPause</tp:member-ref>.
</p>
<p>
Note that this is an intrinsic property of the current track: its
value should not depend on whether the track is currently paused or
playing. In fact, if playback is currently paused (and
<tp:member-ref>CanControl</tp:member-ref> is <strong>true</strong>),
this should be <strong>true</strong>.
</p>
<p>
If <tp:member-ref>CanControl</tp:member-ref> is
<strong>false</strong>, this property should also be
<strong>false</strong>.
</p>
<tp:rationale>
<p>
Not all media is pausable: it may not be possible to pause some
streamed media, for example.
</p>
</tp:rationale>
</tp:docstring>
</property>
<property name="CanSeek" tp:name-for-bindings="Can_Seek" type="b" access="read">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>
Whether the client can control the playback position using
<tp:member-ref>Seek</tp:member-ref> and
<tp:member-ref>SetPosition</tp:member-ref>. This may be different for
different tracks.
</p>
<p>
If <tp:member-ref>CanControl</tp:member-ref> is
<strong>false</strong>, this property should also be
<strong>false</strong>.
</p>
<tp:rationale>
<p>
Not all media is seekable: it may not be possible to seek when
playing some streamed media, for example.
</p>
</tp:rationale>
</tp:docstring>
</property>
<property name="CanControl" tp:name-for-bindings="Can_Control" type="b" access="read">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>Whether the media player may be controlled over this interface.</p>
<p>
This property is not expected to change, as it describes an intrinsic
capability of the implementation.
</p>
<p>
If this is <strong>false</strong>, clients should assume that all
properties on this interface are read-only (and will raise errors
if writing to them is attempted), no methods are implemented
and all other properties starting with "Can" are also
<strong>false</strong>.
</p>
<tp:rationale>
<p>
This allows clients to determine whether to present and enable
controls to the user in advance of attempting to call methods
and write to properties.
</p>
</tp:rationale>
</tp:docstring>
</property>
<signal name="Seeked" tp:name-for-bindings="Seeked">
<arg name="Position" type="x" tp:type="Time_In_Us">
<tp:docstring>
<p>The new position, in microseconds.</p>
</tp:docstring>
</arg>
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>
Indicates that the track position has changed in a way that is
inconsistant with the current playing state.
</p>
<p>When this signal is not received, clients should assume that:</p>
<ul>
<li>
When playing, the position progresses according to the rate property.
</li>
<li>When paused, it remains constant.</li>
</ul>
<p>
This signal does not need to be emitted when playback starts
or when the track changes, unless the track is starting at an
unexpected position. An expected position would be the last
known one when going from Paused to Playing, and 0 when going from
Stopped to Playing.
</p>
</tp:docstring>
</signal>
</interface>
</node>
<!-- vim:set sw=2 sts=2 et ft=xml: -->
@@ -0,0 +1,253 @@
<?xml version="1.0" ?>
<node name="/Playlists_Interface" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
<interface name="org.mpris.MediaPlayer2.Playlists">
<tp:added version="2.1" />
<tp:docstring>
<p>Provides access to the media player's playlists.</p>
<p>
Since D-Bus does not provide an easy way to check for what interfaces
are exported on an object, clients should attempt to get one of the
properties on this interface to see if it is implemented.
</p>
</tp:docstring>
<tp:simple-type name="Playlist_Id" type="o" array-name="Playlist_Id_List">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>Unique playlist identifier.</p>
<tp:rationale>
<p>
Multiple playlists may have the same name.
</p>
<p>
This is a D-Bus object id as that is the definitive way to have
unique identifiers on D-Bus. It also allows for future optional
expansions to the specification where tracks are exported to D-Bus
with an interface similar to org.gnome.UPnP.MediaItem2.
</p>
</tp:rationale>
</tp:docstring>
</tp:simple-type>
<tp:simple-type name="Uri" type="s" array-name="Uri_List">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>A URI.</p>
</tp:docstring>
</tp:simple-type>
<tp:struct name="Playlist" array-name="Playlist_List">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>A data structure describing a playlist.</p>
</tp:docstring>
<tp:member type="o" tp:type="Playlist_Id" name="Id">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>A unique identifier for the playlist.</p>
<p>This should remain the same if the playlist is renamed.</p>
</tp:docstring>
</tp:member>
<tp:member type="s" name="Name">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>The name of the playlist, typically given by the user.</p>
</tp:docstring>
</tp:member>
<tp:member type="s" tp:type="Uri" name="Icon">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>The URI of an (optional) icon.</p>
</tp:docstring>
</tp:member>
</tp:struct>
<tp:struct name="Maybe_Playlist">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>A data structure describing a playlist, or nothing.</p>
<tp:rationale>
<p>
D-Bus does not (at the time of writing) support a MAYBE type,
so we are forced to invent our own.
</p>
</tp:rationale>
</tp:docstring>
<tp:member type="b" name="Valid">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>Whether this structure refers to a valid playlist.</p>
</tp:docstring>
</tp:member>
<tp:member type="(oss)" tp:type="Playlist" name="Playlist">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>The playlist, providing Valid is true, otherwise undefined.</p>
<p>
When constructing this type, it should be noted that the playlist
ID must be a valid object path, or D-Bus implementations may reject
it. This is true even when Valid is false. It is suggested that
"/" is used as the playlist ID in this case.
</p>
</tp:docstring>
</tp:member>
</tp:struct>
<tp:enum name="Playlist_Ordering" array-name="Playlist_Ordering_List" type="s">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>Specifies the ordering of returned playlists.</p>
</tp:docstring>
<tp:enumvalue suffix="Alphabetical" value="Alphabetical">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>Alphabetical ordering by name, ascending.</p>
</tp:docstring>
</tp:enumvalue>
<tp:enumvalue suffix="CreationDate" value="Created">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>Ordering by creation date, oldest first.</p>
</tp:docstring>
</tp:enumvalue>
<tp:enumvalue suffix="ModifiedDate" value="Modified">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>Ordering by last modified date, oldest first.</p>
</tp:docstring>
</tp:enumvalue>
<tp:enumvalue suffix="LastPlayDate" value="Played">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>Ordering by date of last playback, oldest first.</p>
</tp:docstring>
</tp:enumvalue>
<tp:enumvalue suffix="UserDefined" value="User">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>A user-defined ordering.</p>
<tp:rationale>
<p>
Some media players may allow users to order playlists as they
wish. This ordering allows playlists to be retreived in that
order.
</p>
</tp:rationale>
</tp:docstring>
</tp:enumvalue>
</tp:enum>
<method name="ActivatePlaylist" tp:name-for-bindings="Activate_Playlist">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>
Starts playing the given playlist.
</p>
<p>
Note that this must be implemented. If the media player does not
allow clients to change the playlist, it should not implement this
interface at all.
</p>
<p>
It is up to the media player whether this completely replaces the
current tracklist, or whether it is merely inserted into the
tracklist and the first track starts. For example, if the media
player is operating in a "jukebox" mode, it may just append the
playlist to the list of upcoming tracks, and skip to the first
track in the playlist.
</p>
</tp:docstring>
<arg direction="in" name="PlaylistId" type="o">
<tp:docstring>
<p>The id of the playlist to activate.</p>
</tp:docstring>
</arg>
</method>
<method name="GetPlaylists" tp:name-for-bindings="Get_Playlists">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>Gets a set of playlists.</p>
</tp:docstring>
<arg direction="in" name="Index" type="u">
<tp:docstring>
<p>The index of the first playlist to be fetched (according to the ordering).</p>
</tp:docstring>
</arg>
<arg direction="in" name="MaxCount" type="u">
<tp:docstring>
<p>The maximum number of playlists to fetch.</p>
</tp:docstring>
</arg>
<arg direction="in" name="Order" type="s" tp:type="Playlist_Ordering">
<tp:docstring>
<p>The ordering that should be used.</p>
</tp:docstring>
</arg>
<arg direction="in" name="ReverseOrder" type="b">
<tp:docstring>
<p>Whether the order should be reversed.</p>
</tp:docstring>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="PlaylistList"/>
<arg direction="out" name="Playlists" type="a(oss)" tp:type="Playlist[]">
<tp:docstring>
<p>A list of (at most MaxCount) playlists.</p>
</tp:docstring>
</arg>
</method>
<property name="PlaylistCount" type="u" tp:name-for-bindings="Playlist_Count" access="read">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>
The number of playlists available.
</p>
</tp:docstring>
</property>
<property name="Orderings" tp:name-for-bindings="Orderings" type="as" tp:type="Playlist_Ordering[]" access="read">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>
The available orderings. At least one must be offered.
</p>
<tp:rationale>
<p>
Media players may not have access to all the data required for some
orderings. For example, creation times are not available on UNIX
filesystems (don't let the ctime fool you!). On the other hand,
clients should have some way to get the "most recent" playlists.
</p>
</tp:rationale>
</tp:docstring>
</property>
<property name="ActivePlaylist" type="(b(oss))" tp:name-for-bindings="Active_Playlist" tp:type="Maybe_Playlist" access="read">
<annotation name="org.qtproject.QtDBus.QtTypeName" value="PlaylistType"/>
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>
The currently-active playlist.
</p>
<p>
If there is no currently-active playlist, the structure's Valid field
will be false, and the Playlist details are undefined.
</p>
<p>
Note that this may not have a value even after ActivatePlaylist is
called with a valid playlist id as ActivatePlaylist implementations
have the option of simply inserting the contents of the playlist into
the current tracklist.
</p>
</tp:docstring>
</property>
<signal name="PlaylistChanged" tp:name-for-bindings="Playlist_Changed">
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="Playlist"/>
<arg name="Playlist" type="(oss)" tp:type="Playlist">
<tp:docstring>
The playlist which details have changed.
</tp:docstring>
</arg>
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>Indicates that either the Name or Icon attribute of a
playlist has changed.
</p>
<p>Client implementations should be aware that this signal
may not be implemented.
</p>
<tp:rationale>
Without this signal, media players have no way to notify clients
of a change in the attributes of a playlist other than the active one
</tp:rationale>
</tp:docstring>
</signal>
</interface>
</node>
<!-- vim:set sw=2 sts=2 et ft=xml: -->
@@ -0,0 +1,352 @@
<?xml version="1.0" ?>
<node name="/Track_List_Interface" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
<interface name="org.mpris.MediaPlayer2.TrackList">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>
Provides access to a short list of tracks which were recently played or
will be played shortly. This is intended to provide context to the
currently-playing track, rather than giving complete access to the
media player's playlist.
</p>
<p>
Example use cases are the list of tracks from the same album as the
currently playing song or the
<a href="http://projects.gnome.org/rhythmbox/">Rhythmbox</a> play queue.
</p>
<p>
Each track in the tracklist has a unique identifier.
The intention is that this uniquely identifies the track within
the scope of the tracklist. In particular, if a media item
(a particular music file, say) occurs twice in the track list, each
occurrence should have a different identifier. If a track is removed
from the middle of the playlist, it should not affect the track ids
of any other tracks in the tracklist.
</p>
<p>
As a result, the traditional track identifiers of URLs and position
in the playlist cannot be used. Any scheme which satisfies the
uniqueness requirements is valid, as clients should not make any
assumptions about the value of the track id beyond the fact
that it is a unique identifier.
</p>
<p>
Note that the (memory and processing) burden of implementing the
TrackList interface and maintaining unique track ids for the
playlist can be mitigated by only exposing a subset of the playlist when
it is very long (the 20 or so tracks around the currently playing
track, for example). This is a recommended practice as the tracklist
interface is not designed to enable browsing through a large list of tracks,
but rather to provide clients with context about the currently playing track.
</p>
</tp:docstring>
<tp:mapping name="Metadata_Map" array-name="Metadata_Map_List">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>A mapping from metadata attribute names to values.</p>
<p>
The <b>mpris:trackid</b> attribute must always be present, and must be
of D-Bus type "o". This contains a D-Bus path that uniquely identifies
the track within the scope of the playlist. There may or may not be
an actual D-Bus object at that path; this specification says nothing
about what interfaces such an object may implement.
</p>
<p>
If the length of the track is known, it should be provided in the
metadata property with the "mpris:length" key. The length must be
given in microseconds, and be represented as a signed 64-bit integer.
</p>
<p>
If there is an image associated with the track, a URL for it may be
provided using the "mpris:artUrl" key. For other metadata, fields
defined by the
<a href="http://xesam.org/main/XesamOntology">Xesam ontology</a>
should be used, prefixed by "xesam:". See the
<a href="http://www.freedesktop.org/wiki/Specifications/mpris-spec/metadata">metadata page on the freedesktop.org wiki</a>
for a list of common fields.
</p>
<p>
Lists of strings should be passed using the array-of-string ("as")
D-Bus type. Dates should be passed as strings using the ISO 8601
extended format (eg: 2007-04-29T14:35:51). If the timezone is
known, RFC 3339's internet profile should be used (eg:
2007-04-29T14:35:51+02:00).
</p>
</tp:docstring>
<tp:member type="s" name="Attribute">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>
The name of the attribute; see the
<a href="http://www.freedesktop.org/wiki/Specifications/mpris-spec/metadata">metadata page</a>
for guidelines on names to use.
</p>
</tp:docstring>
</tp:member>
<tp:member type="v" name="Value">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>The value of the attribute, in the most appropriate format.</p>
</tp:docstring>
</tp:member>
</tp:mapping>
<tp:simple-type name="Uri" type="s">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>A unique resource identifier.</p>
</tp:docstring>
</tp:simple-type>
<method name="GetTracksMetadata" tp:name-for-bindings="Get_Tracks_Metadata">
<arg direction="in" name="TrackIds" type="ao" tp:type="Track_Id[]">
<tp:docstring>
<p>The list of track ids for which metadata is requested.</p>
</tp:docstring>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="MetadataMap"/>
<arg direction="out" type="aa{sv}" tp:type="Metadata_Map[]" name="Metadata">
<tp:docstring>
<p>Metadata of the set of tracks given as input.</p>
<p>See the type documentation for more details.</p>
</tp:docstring>
</arg>
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>Gets all the metadata available for a set of tracks.</p>
<p>
Each set of metadata must have a "mpris:trackid" entry at the very least,
which contains a string that uniquely identifies this track within
the scope of the tracklist.
</p>
</tp:docstring>
</method>
<method name="AddTrack" tp:name-for-bindings="Add_Track">
<arg direction="in" type="s" tp:type="Uri" name="Uri">
<tp:docstring>
<p>
The uri of the item to add. Its uri scheme should be an element of the
<strong>org.mpris.MediaPlayer2.SupportedUriSchemes</strong>
property and the mime-type should match one of the elements of the
<strong>org.mpris.MediaPlayer2.SupportedMimeTypes</strong>
</p>
</tp:docstring>
</arg>
<arg direction="in" type="o" tp:type="Track_Id" name="AfterTrack">
<tp:docstring>
<p>
The identifier of the track after which
the new item should be inserted. The path
<literal>/org/mpris/MediaPlayer2/TrackList/NoTrack</literal>
indicates that the track should be inserted at the
start of the track list.
</p>
</tp:docstring>
</arg>
<arg direction="in" type="b" name="SetAsCurrent">
<tp:docstring>
<p>
Whether the newly inserted track should be considered as
the current track. Setting this to true has the same effect as
calling GoTo afterwards.
</p>
</tp:docstring>
</arg>
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>Adds a URI in the TrackList.</p>
<p>
If the <tp:member-ref>CanEditTracks</tp:member-ref> property is false,
this has no effect.
</p>
<p>
Note: Clients should not assume that the track has been added at the
time when this method returns. They should wait for a TrackAdded (or
TrackListReplaced) signal.
</p>
</tp:docstring>
</method>
<method name="RemoveTrack" tp:name-for-bindings="Remove__Track">
<arg direction="in" type="o" tp:type="Track_Id" name="TrackId">
<tp:docstring>
<p>Identifier of the track to be removed.</p>
<p>
<literal>/org/mpris/MediaPlayer2/TrackList/NoTrack</literal>
is <em>not</em> a valid value for this argument.
</p>
</tp:docstring>
</arg>
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>Removes an item from the TrackList.</p>
<p>If the track is not part of this tracklist, this has no effect.</p>
<p>
If the <tp:member-ref>CanEditTracks</tp:member-ref> property is false,
this has no effect.
</p>
<p>
Note: Clients should not assume that the track has been removed at the
time when this method returns. They should wait for a TrackRemoved (or
TrackListReplaced) signal.
</p>
</tp:docstring>
</method>
<method name="GoTo" tp:name-for-bindings="Go_To">
<arg direction="in" type="o" tp:type="Track_Id" name="TrackId">
<tp:docstring>
<p>Identifier of the track to skip to.</p>
<p>
<literal>/org/mpris/MediaPlayer2/TrackList/NoTrack</literal>
is <em>not</em> a valid value for this argument.
</p>
</tp:docstring>
</arg>
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>Skip to the specified TrackId.</p>
<p>If the track is not part of this tracklist, this has no effect.</p>
<p>
If this object is not <strong>/org/mpris/MediaPlayer2</strong>,
the current TrackList's tracks should be replaced with the contents of
this TrackList, and the TrackListReplaced signal should be fired from
<strong>/org/mpris/MediaPlayer2</strong>.
</p>
</tp:docstring>
</method>
<property name="Tracks" type="ao" tp:type="Track_Id[]" tp:name-for-bindings="Tracks" access="read">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="invalidates"/>
<tp:docstring>
<p>
An array which contains the identifier of each track
in the tracklist, in order.
</p>
<p>
The <literal>org.freedesktop.DBus.Properties.PropertiesChanged</literal>
signal is emited every time this property changes, but the signal
message does not contain the new value.
Client implementations should rather rely on the
<tp:member-ref>TrackAdded</tp:member-ref>,
<tp:member-ref>TrackRemoved</tp:member-ref> and
<tp:member-ref>TrackListReplaced</tp:member-ref> signals to keep their
representation of the tracklist up to date.
</p>
</tp:docstring>
</property>
<property name="CanEditTracks" type="b" tp:name-for-bindings="Can_Edit_Tracks" access="read">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>
If <strong>false</strong>, calling
<tp:member-ref>AddTrack</tp:member-ref> or
<tp:member-ref>RemoveTrack</tp:member-ref> will have no effect,
and may raise a NotSupported error.
</p>
</tp:docstring>
</property>
<signal name="TrackListReplaced" tp:name-for-bindings="Track_List_Replaced">
<arg name="Tracks" type="ao" tp:type="Track_Id[]">
<tp:docstring>
<p>The new content of the tracklist.</p>
</tp:docstring>
</arg>
<arg name="CurrentTrack" type="o" tp:type="Track_Id">
<tp:docstring>
<p>The identifier of the track to be considered as current.</p>
<p>
<literal>/org/mpris/MediaPlayer2/TrackList/NoTrack</literal>
indicates that there is no current track.
</p>
<p>
This should correspond to the <literal>mpris:trackid</literal> field of the
Metadata property of the <literal>org.mpris.MediaPlayer2.Player</literal>
interface.
</p>
</tp:docstring>
</arg>
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>Indicates that the entire tracklist has been replaced.</p>
<p>
It is left up to the implementation to decide when
a change to the track list is invasive enough that
this signal should be emitted instead of a series of
TrackAdded and TrackRemoved signals.
</p>
</tp:docstring>
</signal>
<signal name="TrackAdded" tp:name-for-bindings="Track_Added">
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="MetadataMap"/>
<arg type="a{sv}" tp:type="Metadata_Map" name="Metadata">
<tp:docstring>
<p>The metadata of the newly added item.</p>
<p>This must include a mpris:trackid entry.</p>
<p>See the type documentation for more details.</p>
</tp:docstring>
</arg>
<arg type="o" tp:type="Track_Id" name="AfterTrack">
<tp:docstring>
<p>
The identifier of the track after which the new track
was inserted. The path
<literal>/org/mpris/MediaPlayer2/TrackList/NoTrack</literal>
indicates that the track was inserted at the
start of the track list.
</p>
</tp:docstring>
</arg>
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>Indicates that a track has been added to the track list.</p>
</tp:docstring>
</signal>
<signal name="TrackRemoved" tp:name-for-bindings="Track_Removed">
<arg type="o" tp:type="Track_Id" name="TrackId">
<tp:docstring>
<p>The identifier of the track being removed.</p>
<p>
<literal>/org/mpris/MediaPlayer2/TrackList/NoTrack</literal>
is <em>not</em> a valid value for this argument.
</p>
</tp:docstring>
</arg>
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>Indicates that a track has been removed from the track list.</p>
</tp:docstring>
</signal>
<signal name="TrackMetadataChanged" tp:name-for-bindings="Track_Metadata_Changed">
<arg type="o" tp:type="Track_Id" name="TrackId">
<tp:docstring>
<p>The id of the track which metadata has changed.</p>
<p>If the track id has changed, this will be the old value.</p>
<p>
<literal>/org/mpris/MediaPlayer2/TrackList/NoTrack</literal>
is <em>not</em> a valid value for this argument.
</p>
</tp:docstring>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="MetadataMap"/>
<arg type="a{sv}" tp:type="Metadata_Map" name="Metadata">
<tp:docstring>
<p>The new track metadata.</p>
<p>
This must include a mpris:trackid entry. If the track id has
changed, this will be the new value.
</p>
<p>See the type documentation for more details.</p>
</tp:docstring>
</arg>
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>
Indicates that the metadata of a track in the tracklist has changed.
</p>
<p>
This may indicate that a track has been replaced, in which case the
mpris:trackid metadata entry is different from the TrackId argument.
</p>
</tp:docstring>
</signal>
</interface>
</node>
<!-- vim:set sw=2 sts=2 et ft=xml: -->
+198
View File
@@ -0,0 +1,198 @@
<?xml version="1.0" ?>
<node name="/Media_Player" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
<interface name="org.mpris.MediaPlayer2">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
<method name="Raise" tp:name-for-bindings="Raise">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>
Brings the media player's user interface to the front using any
appropriate mechanism available.
</p>
<p>
The media player may be unable to control how its user interface
is displayed, or it may not have a graphical user interface at all.
In this case, the <tp:member-ref>CanRaise</tp:member-ref> property is
<strong>false</strong> and this method does nothing.
</p>
</tp:docstring>
</method>
<method name="Quit" tp:name-for-bindings="Quit">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>Causes the media player to stop running.</p>
<p>
The media player may refuse to allow clients to shut it down.
In this case, the <tp:member-ref>CanQuit</tp:member-ref> property is
<strong>false</strong> and this method does nothing.
</p>
<p>
Note: Media players which can be D-Bus activated, or for which there is
no sensibly easy way to terminate a running instance (via the main
interface or a notification area icon for example) should allow clients
to use this method. Otherwise, it should not be needed.
</p>
<p>If the media player does not have a UI, this should be implemented.</p>
</tp:docstring>
</method>
<property name="CanQuit" type="b" tp:name-for-bindings="Can_Quit" access="read">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>
If <strong>false</strong>, calling
<tp:member-ref>Quit</tp:member-ref> will have no effect, and may
raise a NotSupported error. If <strong>true</strong>, calling
<tp:member-ref>Quit</tp:member-ref> will cause the media application
to attempt to quit (although it may still be prevented from quitting
by the user, for example).
</p>
</tp:docstring>
</property>
<property name="Fullscreen" type="b" tp:name-for-bindings="Fullscreen" access="readwrite">
<tp:added version="2.2" />
<annotation name="org.mpris.MediaPlayer2.property.optional" value="true"/>
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>Whether the media player is occupying the fullscreen.</p>
<p>
This is typically used for videos. A value of <strong>true</strong>
indicates that the media player is taking up the full screen.
</p>
<p>
Media centre software may well have this value fixed to <strong>true</strong>
</p>
<p>
If <tp:member-ref>CanSetFullscreen</tp:member-ref> is <strong>true</strong>,
clients may set this property to <strong>true</strong> to tell the media player
to enter fullscreen mode, or to <strong>false</strong> to return to windowed
mode.
</p>
<p>
If <tp:member-ref>CanSetFullscreen</tp:member-ref> is <strong>false</strong>,
then attempting to set this property should have no effect, and may raise
an error. However, even if it is <strong>true</strong>, the media player
may still be unable to fulfil the request, in which case attempting to set
this property will have no effect (but should not raise an error).
</p>
<tp:rationale>
<p>
This allows remote control interfaces, such as LIRC or mobile devices like
phones, to control whether a video is shown in fullscreen.
</p>
</tp:rationale>
</tp:docstring>
</property>
<property name="CanSetFullscreen" type="b" tp:name-for-bindings="Can_Set_Fullscreen" access="read">
<tp:added version="2.2" />
<annotation name="org.mpris.MediaPlayer2.property.optional" value="true"/>
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>
If <strong>false</strong>, attempting to set
<tp:member-ref>Fullscreen</tp:member-ref> will have no effect, and may
raise an error. If <strong>true</strong>, attempting to set
<tp:member-ref>Fullscreen</tp:member-ref> will not raise an error, and (if it
is different from the current value) will cause the media player to attempt to
enter or exit fullscreen mode.
</p>
<p>
Note that the media player may be unable to fulfil the request.
In this case, the value will not change. If the media player knows in
advance that it will not be able to fulfil the request, however, this
property should be <strong>false</strong>.
</p>
<tp:rationale>
<p>
This allows clients to choose whether to display controls for entering
or exiting fullscreen mode.
</p>
</tp:rationale>
</tp:docstring>
</property>
<property name="CanRaise" type="b" tp:name-for-bindings="Can_Raise" access="read">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>
If <strong>false</strong>, calling
<tp:member-ref>Raise</tp:member-ref> will have no effect, and may
raise a NotSupported error. If <strong>true</strong>, calling
<tp:member-ref>Raise</tp:member-ref> will cause the media application
to attempt to bring its user interface to the front, although it may
be prevented from doing so (by the window manager, for example).
</p>
</tp:docstring>
</property>
<property name="HasTrackList" type="b" tp:name-for-bindings="Has_TrackList" access="read">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>
Indicates whether the <strong>/org/mpris/MediaPlayer2</strong>
object implements the <strong>org.mpris.MediaPlayer2.TrackList</strong>
interface.
</p>
</tp:docstring>
</property>
<property name="Identity" type="s" tp:name-for-bindings="Identity" access="read">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>A friendly name to identify the media player to users.</p>
<p>This should usually match the name found in .desktop files</p>
<p>(eg: "VLC media player").</p>
</tp:docstring>
</property>
<property name="DesktopEntry" type="s" tp:name-for-bindings="Desktop_Entry" access="read">
<annotation name="org.mpris.MediaPlayer2.property.optional" value="true"/>
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>The basename of an installed .desktop file which complies with the <a href="http://standards.freedesktop.org/desktop-entry-spec/latest/">Desktop entry specification</a>,
with the ".desktop" extension stripped.</p>
<p>
Example: The desktop entry file is "/usr/share/applications/vlc.desktop",
and this property contains "vlc"
</p>
</tp:docstring>
</property>
<property name="SupportedUriSchemes" type="as" tp:name-for-bindings="Supported_Uri_Schemes" access="read">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>
The URI schemes supported by the media player.
</p>
<p>
This can be viewed as protocols supported by the player in almost
all cases. Almost every media player will include support for the
"file" scheme. Other common schemes are "http" and "rtsp".
</p>
<p>
Note that URI schemes should be lower-case.
</p>
<tp:rationale>
<p>
This is important for clients to know when using the editing
capabilities of the Playlist interface, for example.
</p>
</tp:rationale>
</tp:docstring>
</property>
<property name="SupportedMimeTypes" type="as" tp:name-for-bindings="Supported_Mime_Types" access="read">
<tp:docstring xmlns="http://www.w3.org/1999/xhtml">
<p>
The mime-types supported by the media player.
</p>
<p>
Mime-types should be in the standard format (eg: audio/mpeg or
application/ogg).
</p>
<tp:rationale>
<p>
This is important for clients to know when using the editing
capabilities of the Playlist interface, for example.
</p>
</tp:rationale>
</tp:docstring>
</property>
</interface>
</node>
<!-- vim:set sw=2 sts=2 et ft=xml: -->
+176
View File
@@ -0,0 +1,176 @@
#include "mprisapplication.h"
#include <QDBusConnection>
#include "mediaplayer2adaptor.h"
#include "playeradaptor.h"
struct MprisApplicationPrivate {
std::unique_ptr<MediaPlayer2Adaptor> adaptor;
bool canQuit = false;
bool canRaise = false;
bool canSetFullscreen = false;
QString desktopEntry;
bool fullscreen = false;
bool hasTrackList = false;
QString identity;
QStringList supportedMimeTypes;
QStringList supportedUriSchemes;
};
MprisApplication::MprisApplication(const QString &appId, QObject *parent)
: QObject(parent)
, d(std::make_unique<MprisApplicationPrivate>())
{
d->adaptor = std::make_unique<MediaPlayer2Adaptor>(this);
QDBusConnection::sessionBus().registerService(QStringLiteral("org.mpris.MediaPlayer2.") + appId);
if (!QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/mpris/MediaPlayer2"), this)) {
qWarning() << "Failed to register MPRIS interface";
}
}
MprisApplication::~MprisApplication() = default;
bool MprisApplication::canQuit() const
{
return d->canQuit;
}
void MprisApplication::setCanQuit(bool canQuit)
{
if (d->canQuit == canQuit) {
return;
}
d->canQuit = canQuit;
Q_EMIT canQuitChanged();
}
bool MprisApplication::canRaise() const
{
return d->canRaise;
}
void MprisApplication::setCanRaise(bool canRaise)
{
if (d->canRaise == canRaise) {
return;
}
d->canRaise = canRaise;
Q_EMIT canRaiseChanged();
}
bool MprisApplication::canSetFullscreen() const
{
return d->canSetFullscreen;
}
void MprisApplication::setCanSetFullscreen(bool can)
{
if (d->canSetFullscreen == can) {
return;
}
d->canSetFullscreen = can;
Q_EMIT canSetFullscreenChanged();
}
QString MprisApplication::desktopEntry() const
{
return d->desktopEntry;
}
void MprisApplication::setDesktopEntry(const QString &entry)
{
if (d->desktopEntry == entry) {
return;
}
d->desktopEntry = entry;
Q_EMIT desktopEntryChanged();
}
bool MprisApplication::fullscreen() const
{
return d->fullscreen;
}
void MprisApplication::setFullscreen(bool value)
{
if (d->fullscreen == value) {
return;
}
d->fullscreen = value;
Q_EMIT fullscreenChanged();
}
bool MprisApplication::hasTrackList() const
{
return d->hasTrackList;
}
void MprisApplication::setHasTrackList(bool has)
{
if (d->hasTrackList == has) {
return;
}
d->hasTrackList = has;
Q_EMIT hasTrackListChanged();
}
QString MprisApplication::identity() const
{
return d->identity;
}
void MprisApplication::setIdentity(const QString &appName)
{
if (d->identity == appName) {
return;
}
d->identity = appName;
Q_EMIT identityChanged();
}
QStringList MprisApplication::supportedMimeTypes() const
{
return d->supportedMimeTypes;
}
void MprisApplication::setSupportedMimeTypes(const QStringList &types) {
if (d->supportedMimeTypes == types) {
return;
}
d->supportedMimeTypes = types;
Q_EMIT supportedMimeTypesChanged();
}
QStringList MprisApplication::supportedUriSchemes() const
{
return d->supportedUriSchemes;
}
void MprisApplication::setSupportedUriSchemes(const QStringList &schemes)
{
if (d->supportedUriSchemes == schemes) {
return;
}
d->supportedMimeTypes = schemes;
Q_EMIT supportedUriSchemesChanged();
}
void MprisApplication::Quit()
{
Q_EMIT quitRequested();
}
void MprisApplication::Raise()
{
Q_EMIT raiseRequested();
}
+90
View File
@@ -0,0 +1,90 @@
#ifndef MPRISAPPLICATION_H
#define MPRISAPPLICATION_H
#include <QObject>
#include <memory>
struct MprisApplicationPrivate;
class MprisApplication : public QObject
{
Q_OBJECT
// DBus property names
Q_PROPERTY(bool CanQuit READ canQuit NOTIFY canQuitChanged)
Q_PROPERTY(bool CanRaise READ canRaise NOTIFY canRaiseChanged)
Q_PROPERTY(bool CanSetFullscreen READ canSetFullscreen NOTIFY canSetFullscreenChanged)
Q_PROPERTY(QString DesktopEntry READ desktopEntry NOTIFY desktopEntryChanged)
Q_PROPERTY(bool Fullscreen READ fullscreen WRITE setFullscreen NOTIFY fullscreenChanged)
Q_PROPERTY(bool HasTrackList READ hasTrackList NOTIFY hasTrackListChanged)
Q_PROPERTY(QString Identity READ identity NOTIFY identityChanged)
Q_PROPERTY(QStringList SupportedMimeTypes READ supportedMimeTypes NOTIFY supportedMimeTypesChanged)
Q_PROPERTY(QStringList SupportedUriSchemes READ supportedUriSchemes NOTIFY supportedUriSchemes)
// QML property names
Q_PROPERTY(bool canQuit READ canQuit WRITE setCanQuit NOTIFY canQuitChanged)
Q_PROPERTY(bool canRaise READ canRaise WRITE setCanRaise NOTIFY canRaiseChanged)
Q_PROPERTY(bool canSetFullscreen READ canSetFullscreen NOTIFY canSetFullscreenChanged)
Q_PROPERTY(QString desktopEntry READ desktopEntry NOTIFY desktopEntryChanged)
Q_PROPERTY(bool fullscreen READ fullscreen WRITE setFullscreen NOTIFY fullscreenChanged)
Q_PROPERTY(bool hasTrackList READ hasTrackList NOTIFY hasTrackListChanged)
Q_PROPERTY(QString identity READ identity NOTIFY identityChanged)
Q_PROPERTY(QStringList supportedMimeTypes READ supportedMimeTypes NOTIFY supportedMimeTypesChanged)
Q_PROPERTY(QStringList supportedUriSchemes READ supportedUriSchemes NOTIFY supportedUriSchemes)
friend class MediaPlayer2Adaptor;
public:
explicit MprisApplication(const QString &appId, QObject *parent = nullptr);
~MprisApplication();
bool canQuit() const;
void setCanQuit(bool canQuit);
Q_SIGNAL void canQuitChanged();
bool canRaise() const;
void setCanRaise(bool canRaise);
Q_SIGNAL void canRaiseChanged();
bool canSetFullscreen() const;
void setCanSetFullscreen(bool can);
Q_SIGNAL void canSetFullscreenChanged();
QString desktopEntry() const;
void setDesktopEntry(const QString &entry);
Q_SIGNAL void desktopEntryChanged();
bool fullscreen() const;
void setFullscreen(bool value);
Q_SIGNAL void fullscreenChanged();
bool hasTrackList() const;
void setHasTrackList(bool has);
Q_SIGNAL void hasTrackListChanged();
QString identity() const;
void setIdentity(const QString &appName);
Q_SIGNAL void identityChanged();
QStringList supportedMimeTypes() const;
void setSupportedMimeTypes(const QStringList &types);
Q_SIGNAL void supportedMimeTypesChanged();
QStringList supportedUriSchemes() const;
void setSupportedUriSchemes(const QStringList &schemes);
Q_SIGNAL void supportedUriSchemesChanged();
protected:
void Quit();
void Raise();
Q_SIGNALS:
void quitRequested();
void raiseRequested();
private:
std::unique_ptr<MprisApplicationPrivate> d;
};
#endif // MPRISAPPLICATION_H
+60
View File
@@ -0,0 +1,60 @@
#include "mprisplayer.h"
#include <playeradaptor.h>
MprisPlayer::MprisPlayer(QObject *parent)
: QObject(parent)
, m_adaptor(std::make_unique<PlayerAdaptor>(this))
{
QDBusConnection::sessionBus().registerService(QStringLiteral("org.mpris.MediaPlayer2.") + "audiotube");
if (!QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/mpris/MediaPlayer2"), this)) {
qWarning() << "Failed to register MPRIS interface";
}
}
void MprisPlayer::Next()
{
Q_EMIT nextRequested();
}
void MprisPlayer::OpenUri(const QString &uri)
{
Q_EMIT openUriRequested(uri);
}
void MprisPlayer::Pause()
{
Q_EMIT pauseRequested();
}
void MprisPlayer::Play()
{
Q_EMIT playRequested();
}
void MprisPlayer::PlayPause()
{
Q_EMIT playPauseRequested();
}
void MprisPlayer::Previous()
{
Q_EMIT previousRequested();
}
void MprisPlayer::Seek(qlonglong offset)
{
Q_EMIT seekRequested(offset);
}
void MprisPlayer::SetPosition(const QDBusObjectPath &trackId, qlonglong position)
{
Q_EMIT setPositionRequested(MprisTrack {}, position); // TODO write MprisTrack class
}
void MprisPlayer::Stop()
{
Q_EMIT stopRequested();
}
MprisPlayer::~MprisPlayer() = default;
+59
View File
@@ -0,0 +1,59 @@
#ifndef MPRISPLAYER_H
#define MPRISPLAYER_H
#include <QObject>
#include <QString>
#include <memory>
class PlayerAdaptor;
class QDBusObjectPath;
class MprisTrack {};
class MprisPlayer : public QObject
{
Q_OBJECT
friend class PlayerAdaptor;
public:
explicit MprisPlayer(QObject *parent = nullptr);
~MprisPlayer();
void setCanQuit(bool canQuit);
void setCanRaise(bool canRaise);
void setCanSetFullscreen(bool canSetFullscreen);
void setDesktopEntry(const QString &entry);
void setFullscreen(bool fullscreen);
void setHasTrackList(bool hasTrackList);
void setIdentity(const QString &playerName);
void setSupportedMimeTypes(const QStringList &mimeTypes);
void setSupportedUriSchemes(const QStringList &uriSchemes);
protected:
void Next();
void OpenUri(const QString &uri);
void Pause();
void Play();
void PlayPause();
void Previous();
void Seek(qlonglong offset);
void SetPosition(const QDBusObjectPath &trackId, qlonglong position);
void Stop();
Q_SIGNALS:
void nextRequested();
void openUriRequested(const QString &uri);
void pauseRequested();
void playRequested();
void playPauseRequested();
void previousRequested();
void seekRequested(qlonglong offset);
void setPositionRequested(const MprisTrack &track, qlonglong position);
void stopRequested();
private:
std::unique_ptr<PlayerAdaptor> m_adaptor;
};
#endif // MPRISPLAYER_H
+55 -52
View File
@@ -6,68 +6,71 @@
#include <variant>
#include <tuple>
#include <span>
#include <vector>
#include <QtGlobal>
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);
}
}
// 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>
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 {
public:
explicit MultiIterableView(std::span<Arguments>... lists)
: m_vectors(std::forward_as_tuple(lists...))
{
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;
}
}
explicit MultiIterableView(std::vector<Arguments> &...lists)
: MultiIterableView(std::span{lists}...)
{
}
[[nodiscard]] constexpr size_t size() const {
size_t s = 0;
iterate_tuple(m_vectors, [&](auto &vec) {
s += vec.size();
});
return s;
}
[[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];
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());
}
s += vec.size();
});
return out;
} else {
Q_UNREACHABLE();
}
}
[[nodiscard]] constexpr bool empty() const {
bool empty = true;
iterate_tuple(m_vectors, [&](auto &elem) {
empty &= elem.empty();
});
return empty;
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(const std::vector<Arguments> &...lists)
: m_vectors(std::make_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);
}
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);
}
bool empty() const {
constexpr size_t nvecs = std::tuple_size_v<decltype(m_vectors)>;
return everything_empty<decltype(m_vectors), 0, nvecs>(m_vectors);
}
private:
std::tuple<std::span<Arguments>...> m_vectors;
std::tuple<std::vector<Arguments>...> m_vectors;
};
-15
View File
@@ -1,15 +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
#include "playerutils.h"
PlayerUtils::PlayerUtils(QObject *parent) : QObject(parent)
{
}
QString PlayerUtils::formatTimestamp(quint64 stamp)
{
return m_format.formatDuration(stamp, KFormat::FoldHours);
}
-22
View File
@@ -1,22 +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
#pragma once
#include <QObject>
#include <KFormat>
class PlayerUtils : public QObject
{
Q_OBJECT
public:
explicit PlayerUtils(QObject *parent = nullptr);
Q_INVOKABLE QString formatTimestamp(quint64 stamp);
private:
KFormat m_format;
};
+24 -13
View File
@@ -11,21 +11,21 @@
#include "playlistutils.h"
PlaylistModel::PlaylistModel(QObject *parent)
: AbstractYTMusicModel(parent)
: QAbstractListModel(parent)
{
connect(this, &PlaylistModel::playlistIdChanged, this, [=, this] {
connect(this, &PlaylistModel::playlistIdChanged, this, [=] {
setLoading(true);
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();
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();
Q_EMIT titleChanged();
Q_EMIT thumbnailUrlChanged();
});
Q_EMIT titleChanged();
Q_EMIT thumbnailUrlChanged();
});
}
@@ -49,7 +49,7 @@ QVariant PlaylistModel::data(const QModelIndex &index, int role) const
case Title:
return QString::fromStdString(m_playlist.tracks[index.row()].title);
case Artists:
return QVariant::fromValue(m_playlist.tracks[index.row()].artists);
return PlaylistUtils::artistsToString(m_playlist.tracks[index.row()].artists);
case VideoId:
if (m_playlist.tracks[index.row()].video_id) {
return QString::fromStdString(*m_playlist.tracks[index.row()].video_id);
@@ -76,6 +76,17 @@ 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()) {
+7 -3
View File
@@ -7,9 +7,7 @@
#include <QAbstractListModel>
#include <ytmusic.h>
#include "abstractytmusicmodel.h"
class PlaylistModel : public AbstractYTMusicModel
class PlaylistModel : public QAbstractListModel
{
Q_OBJECT
@@ -17,6 +15,7 @@ class PlaylistModel : public AbstractYTMusicModel
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)
@@ -37,6 +36,10 @@ 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();
@@ -45,6 +48,7 @@ public:
private:
QString m_playlistId;
bool m_loading = false;
playlist::Playlist m_playlist {};
};
+5 -10
View File
@@ -10,20 +10,15 @@
#include <ytmusic.h>
namespace PlaylistUtils {
QString artistsToString(const std::vector<meta::Artist> &artists)
QString PlaylistUtils::artistsToString(const std::vector<meta::Artist> &artists)
{
if (!artists.empty()) {
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 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)));
});
return string;
}
return {};
}
}
+6 -2
View File
@@ -11,6 +11,10 @@ namespace meta {
struct Artist;
}
namespace PlaylistUtils {
QString artistsToString(const std::vector<meta::Artist> &artists);
class PlaylistUtils
{
public:
PlaylistUtils() = delete;
static QString artistsToString(const std::vector<meta::Artist> &artists);
};
-13
View File
@@ -1,9 +1,3 @@
<!--
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
-->
<RCC>
<qresource prefix="/">
<file alias="main.qml">contents/ui/main.qml</file>
@@ -12,12 +6,5 @@ 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>
+23 -46
View File
@@ -5,9 +5,9 @@
#include "searchmodel.h"
SearchModel::SearchModel(QObject *parent)
: AbstractYTMusicModel(parent)
: QAbstractListModel(parent)
{
connect(this, &SearchModel::searchQueryChanged, this, [this] {
connect(this, &SearchModel::searchQueryChanged, this, [=] {
if (m_searchQuery.isEmpty()) {
beginResetModel();
m_searchResults.clear();
@@ -16,15 +16,15 @@ SearchModel::SearchModel(QObject *parent)
}
setLoading(true);
auto future = YTMusicThread::instance()->search(m_searchQuery);
connectFuture(future, this, [=, this](const std::vector<search::SearchResultItem> &results) {
beginResetModel();
setLoading(false);
m_searchResults = results;
endResetModel();
});
YTMusicThread::instance()->search(m_searchQuery);
});
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::errorOccurred, this, [this] {
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, [=] {
setLoading(false);
});
}
@@ -105,18 +105,6 @@ QVariant SearchModel::data(const QModelIndex &index, int role) const
Q_UNREACHABLE();
}
}, m_searchResults.at(index.row())));
case RadioPlaylistId:
return std::visit([&](auto &&arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, search::Artist>) {
if (arg.radio_id) {
return QString::fromStdString(*arg.radio_id);
}
}
return QString();
}, m_searchResults.at(index.row()));
}
Q_UNREACHABLE();
@@ -130,8 +118,7 @@ QHash<int, QByteArray> SearchModel::roleNames() const
{Title, "title"},
{Type, "type"},
{VideoId, "videoId"},
{Artists, "artists"},
{RadioPlaylistId, "radioPlaylistId"},
{Artists, "artists"}
};
}
@@ -146,6 +133,17 @@ 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) {
@@ -159,30 +157,9 @@ void SearchModel::triggerItem(int row)
} else if constexpr (std::is_same_v<T, search::Song>) {
Q_EMIT openSong(QString::fromStdString(arg.video_id));
} else if constexpr (std::is_same_v<T, search::Video>) {
Q_EMIT openVideo(QString::fromStdString(arg.video_id), QString::fromStdString(arg.title));
Q_EMIT openVideo(QString::fromStdString(arg.video_id));
} else {
Q_UNREACHABLE();
}
}, 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);
}
+9 -6
View File
@@ -8,13 +8,13 @@
#include <QThread>
#include "asyncytmusic.h"
#include "abstractytmusicmodel.h"
class SearchModel : public AbstractYTMusicModel
class SearchModel : public QAbstractListModel
{
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 {
@@ -30,8 +30,7 @@ public:
Title = Qt::UserRole + 1,
Type,
VideoId,
Artists,
RadioPlaylistId
Artists
};
explicit SearchModel(QObject *parent = nullptr);
@@ -44,16 +43,20 @@ 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);
Q_SIGNAL void openArtist(const QString &browseId);
Q_SIGNAL void openPlaylist(const QString &browseId);
Q_SIGNAL void openSong(const QString &videoId);
Q_SIGNAL void openVideo(const QString &videoId, const QString &title);
Q_SIGNAL void openVideo(const QString &videoId);
private:
QString m_searchQuery;
std::vector<search::SearchResultItem> m_searchResults;
static int itemType(search::SearchResultItem const &item);
bool m_loading = false;
};
+41 -27
View File
@@ -9,14 +9,31 @@
#include <random>
#include <QRandomGenerator>
#include <QFutureWatcher>
#include "playlistutils.h"
UserPlaylistModel::UserPlaylistModel(QObject *parent)
: AbstractYTMusicModel(parent)
: QAbstractListModel(parent)
, m_mprisApp(QStringLiteral("audiotube"))
, m_mprisPlayer()
{
auto handleResult = [=, this](const watch::Playlist &playlist) {
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) {
setLoading(false);
beginResetModel();
@@ -33,28 +50,14 @@ 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(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] {
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::errorOccurred, this, [=] {
setLoading(false);
});
m_mprisApp.setCanQuit(true);
m_mprisApp.setDesktopEntry("org.kde.audiotube.desktop");
m_mprisApp.setIdentity(QStringLiteral("AudioTube"));
}
int UserPlaylistModel::rowCount(const QModelIndex &parent) const
@@ -101,10 +104,21 @@ 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(),
[this](const watch::Playlist::Track &track) {
[=](const watch::Playlist::Track &track) {
return track.video_id == m_currentVideoId.toStdString();
});
@@ -132,7 +146,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(),
[this](const watch::Playlist::Track &track) {
[=](const watch::Playlist::Track &track) {
return track.video_id == m_currentVideoId.toStdString();
});
@@ -160,7 +174,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(),
[this](const watch::Playlist::Track &track) {
[=](const watch::Playlist::Track &track) {
return track.video_id == m_currentVideoId.toStdString();
});
@@ -242,7 +256,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(),
[this](const watch::Playlist::Track &track) {
[=](const watch::Playlist::Track &track) {
return track.video_id == m_currentVideoId.toStdString();
});
@@ -260,7 +274,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(),
[this](const watch::Playlist::Track &track) {
[=](const watch::Playlist::Track &track) {
return track.video_id == m_currentVideoId.toStdString();
});
+12 -2
View File
@@ -7,9 +7,10 @@
#include <ytmusic.h>
#include "abstractytmusicmodel.h"
#include "mprisapplication.h"
#include "mprisplayer.h"
class UserPlaylistModel : public AbstractYTMusicModel
class UserPlaylistModel : public QAbstractListModel
{
Q_OBJECT
@@ -19,6 +20,7 @@ class UserPlaylistModel : public AbstractYTMusicModel
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)
@@ -40,6 +42,10 @@ 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;
@@ -71,7 +77,11 @@ private:
QString m_initialVideoId;
QString m_playlistId;
QString m_currentVideoId;
bool m_loading = false;
bool m_shuffle = false;
watch::Playlist m_playlist;
MprisApplication m_mprisApp;
MprisPlayer m_mprisPlayer;
};
Loaded 100 of 104 files, more files were not shown because too many files have changed in this diff. Show more