Compare commits

..
170 Commits
Author SHA1 Message Date
Bhushan Shah 630b54933d GIT_SILENT: bump version to 22.09 2022-09-27 20:20:12 +05:30
l10n daemon script 2293280316 GIT_SILENT Sync po/docbooks with svn 2022-09-25 01:52:51 +00:00
l10n daemon script 14116b9cd2 GIT_SILENT Sync po/docbooks with svn 2022-09-24 01:53:06 +00:00
l10n daemon script 4c7db5d5c9 GIT_SILENT Sync po/docbooks with svn 2022-09-21 01:52:39 +00:00
l10n daemon script f26bc80421 GIT_SILENT Sync po/docbooks with svn 2022-09-12 01:48:30 +00:00
l10n daemon script f934e99254 GIT_SILENT Sync po/docbooks with svn 2022-09-09 01:53:23 +00:00
l10n daemon script b6cd3c4a2b GIT_SILENT Sync po/docbooks with svn 2022-09-04 01:51:30 +00:00
Jonah Brüchert 76763b9be1 Update python dependencies 2022-09-01 23:35:44 +02:00
l10n daemon script 1606f91124 GIT_SILENT Sync po/docbooks with svn 2022-08-31 01:47:29 +00:00
l10n daemon script cf8c982228 GIT_SILENT Sync po/docbooks with svn 2022-08-30 01:49:27 +00:00
l10n daemon script e94362854d GIT_SILENT Sync po/docbooks with svn 2022-08-29 01:55:05 +00:00
l10n daemon script 40f3d5eab1 GIT_SILENT Sync po/docbooks with svn 2022-08-28 01:49:25 +00:00
l10n daemon script dc3b35bffa GIT_SILENT Sync po/docbooks with svn 2022-08-27 01:57:17 +00:00
Jonah Brüchert 52c654179f Add buttons for opening some pages in the web ui 2022-08-26 21:03:49 +02:00
Jonah Brüchert a07a3c162f Show newer favourites first 2022-08-24 09:31:59 +02:00
Jonah Brüchert e4f8f96517 Fix new unused variable warning on current gcc 12
The warning occurs in a constexpr if, so even though the variable is
used in the function, it may not be after the constexpr ifs are
evaluated.
2022-08-24 09:30:12 +02:00
l10n daemon script 6fdb0321d6 GIT_SILENT Sync po/docbooks with svn 2022-08-21 01:55:06 +00:00
l10n daemon script be9b0d57e9 GIT_SILENT Sync po/docbooks with svn 2022-08-14 01:51:10 +00:00
l10n daemon script b4c6749a76 GIT_SILENT Sync po/docbooks with svn 2022-08-08 01:57:12 +00:00
l10n daemon script a98e3c7b92 SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2022-08-08 01:51:52 +00:00
l10n daemon script 26e7bbbdb8 GIT_SILENT made messages (after extraction) 2022-08-08 00:48:13 +00:00
l10n daemon script efa7dd674a GIT_SILENT Sync po/docbooks with svn 2022-08-07 01:54:32 +00:00
l10n daemon script ef59e9d418 GIT_SILENT Sync po/docbooks with svn 2022-07-31 01:57:02 +00:00
l10n daemon script e1678a76ba GIT_SILENT Sync po/docbooks with svn 2022-07-23 01:54:34 +00:00
Jonah Brüchert 56ffb52ce5 flatpak: Update python packages 2022-07-19 15:16:41 +02:00
l10n daemon script f0a2d2e79a GIT_SILENT Sync po/docbooks with svn 2022-07-19 01:45:53 +00:00
l10n daemon script 182e77a68d GIT_SILENT Sync po/docbooks with svn 2022-07-17 01:52:45 +00:00
Jonah Brüchert 98ccbfe99d ytmusic: Include iostream
Previously this was not necessary, but now it fails to compile.
I suspect this to be related to a gcc update.
2022-07-16 19:31:40 +02:00
l10n daemon script 53bfc641b4 GIT_SILENT Sync po/docbooks with svn 2022-07-15 01:54:45 +00:00
l10n daemon script 1aabf2eefc GIT_SILENT Sync po/docbooks with svn 2022-07-14 01:58:13 +00:00
l10n daemon script 0d54dfb660 GIT_SILENT Sync po/docbooks with svn 2022-07-12 01:55:23 +00:00
Jonah Brüchert 8093f16f16 player: Set text directly on label instead of on the button
I suspect this to be the cause of the duplicated label. However I'm not
fully certain this fixes it. Please re-open the bug if the issue
persists.

BUG: 456597
2022-07-11 22:16:16 +02:00
Jonah Brüchert bac6ac3362 (very) minor database optimization 2022-07-11 22:09:59 +02:00
Jonah Brüchert 2f914d9f60 appstream: Add library screenshot 2022-07-11 15:44:12 +02:00
l10n daemon script d3293d22ab GIT_SILENT Sync po/docbooks with svn 2022-07-11 01:55:30 +00:00
l10n daemon script 4de3fdae81 GIT_SILENT Sync po/docbooks with svn 2022-07-10 01:57:26 +00:00
l10n daemon script 9518c73415 GIT_SILENT Sync po/docbooks with svn 2022-07-09 01:56:21 +00:00
l10n daemon script 05be539411 GIT_SILENT Sync po/docbooks with svn 2022-07-03 02:04:59 +00:00
l10n daemon script f12c4e3109 GIT_SILENT Sync po/docbooks with svn 2022-06-28 01:48:54 +00:00
l10n daemon script 004ef28a15 GIT_SILENT Sync po/docbooks with svn 2022-06-26 01:47:08 +00:00
l10n daemon script afa9b25aff GIT_SILENT Sync po/docbooks with svn 2022-06-25 02:06:45 +00:00
Bhushan Shah 303ce7c816 GIT_SILENT: bump version to 22.06 2022-06-23 21:11:20 +05:30
l10n daemon script e7cbd07b80 GIT_SILENT Sync po/docbooks with svn 2022-06-20 02:02:25 +00:00
l10n daemon script 923ae5fc96 GIT_SILENT Sync po/docbooks with svn 2022-06-18 01:47:06 +00:00
l10n daemon script 51a3b3c68e GIT_SILENT Sync po/docbooks with svn 2022-06-17 01:46:44 +00:00
l10n daemon script ef1582c67c GIT_SILENT Sync po/docbooks with svn 2022-06-16 02:01:08 +00:00
l10n daemon script 2091e39b90 GIT_SILENT Sync po/docbooks with svn 2022-06-13 02:49:49 +00:00
l10n daemon script ab02e72f1f GIT_SILENT Sync po/docbooks with svn 2022-06-12 01:50:20 +00:00
l10n daemon script d498d6a407 GIT_SILENT Sync po/docbooks with svn 2022-06-07 02:32:41 +00:00
l10n daemon script 6dc6e9538c GIT_SILENT Sync po/docbooks with svn 2022-06-06 01:47:22 +00:00
l10n daemon script 3b0cbefe85 GIT_SILENT Sync po/docbooks with svn 2022-06-05 01:48:47 +00:00
l10n daemon script 38e152fc52 GIT_SILENT Sync po/docbooks with svn 2022-06-04 01:49:02 +00:00
l10n daemon script 0f111b43be GIT_SILENT Sync po/docbooks with svn 2022-06-01 02:04:45 +00:00
l10n daemon script ffbeab0b14 GIT_SILENT Sync po/docbooks with svn 2022-05-30 01:48:55 +00:00
Jonah Brüchert 8860b62eec PlaybackHistory: Load album covers asynchronously 2022-05-30 00:52:05 +02:00
l10n daemon script 93433d8714 GIT_SILENT Sync po/docbooks with svn 2022-05-29 01:50:37 +00:00
l10n daemon script 211a0c6f93 GIT_SILENT Sync po/docbooks with svn 2022-05-28 02:33:52 +00:00
Jonah Brüchert aadfc71200 Fix crash on exit 2022-05-28 00:31:22 +02:00
l10n daemon script 1532adedb1 GIT_SILENT Sync po/docbooks with svn 2022-05-27 01:47:15 +00:00
Jonah Brüchert 16ce656201 Remove QT_QML_DEBUG 2022-05-26 14:26:46 +02:00
l10n daemon script a398723bdb GIT_SILENT Sync po/docbooks with svn 2022-05-26 01:47:23 +00:00
Jonah Brüchert f7a4788e73 Fix opening the 'Leoniden' artist page 2022-05-25 23:12:20 +02:00
Jonah Brüchert 15425c7b01 Use std::invoke_result_t to simplify template code 2022-05-25 23:00:22 +02:00
Jonah Brüchert 96c2a505a7 Fix up previous commit 2022-05-25 23:00:22 +02:00
Jonah Brüchert b58e3fc2d4 Allow to show all favourites / history as a list 2022-05-25 22:36:06 +02:00
Jonah Brüchert 4b1cbf11ad Display AudioTube label just on mobile, and move the search bar back to the center 2022-05-25 15:33:35 +02:00
Devin Lin 1037ac8c02 app header: Move search bar to side, add app header, add search button for mobile 2022-05-25 15:33:32 +02:00
l10n daemon script 572f3c4c94 GIT_SILENT Sync po/docbooks with svn 2022-05-25 01:52:03 +00:00
Devin Lin e48d6c8bb6 Add loading indicator to minimized bar when loading track 2022-05-24 14:52:20 +00:00
l10n daemon script d717e7dff9 GIT_SILENT Sync po/docbooks with svn 2022-05-24 01:50:35 +00:00
Jonah Brüchert cb16ebb812 Crop album art to square 2022-05-23 21:42:23 +02:00
Jonah Brüchert 2c1df5e9e4 asyncdatabase: Remove unused includes 2022-05-23 21:16:43 +02:00
Jonah Brüchert c3873d05ac asyncdatabase: Move parseRows into private API
It is no longer useful externally, as the database API doesn't return
unparsed QVariants any more.
2022-05-23 21:16:43 +02:00
Jonah Brüchert 4fca0ca0c5 Show mini progress bar when there is no space for the full one 2022-05-23 16:06:00 +00:00
Jonah Brüchert c02f369a0f asyncdatabase: Fix mistake in concept checks 2022-05-23 18:04:28 +02:00
Jonah Brüchert 2e68ec0880 Remove remove action from most viewed 2022-05-23 17:31:21 +02:00
Jonah Brüchert c7b22b348e asyncdatabase: Use concepts to define the interface of types that can be deserialized from SQL 2022-05-23 16:12:25 +02:00
Devin Lin f4c8172201 Revamp media player UI
Reworks the media player UI for mobile devices. Adds a swipe gesture to open the media player, and reworks the UI to be more... *hip?*

TODO: haven't implemented a "previous song" feature, is that possible?

![image](/uploads/d329da0ea548eed466227ef39f25301f/image.png)

![image](/uploads/7b3cf83204a1df13b5b166d24d0c2a91/image.png)

![image](/uploads/74c641c544a130eb5e692fb1ae733e37/image.png)
2022-05-23 12:36:35 +00:00
Jonah Brüchert 9fa7757efd Make database query result deserialization more automatic 2022-05-23 13:24:07 +02:00
l10n daemon script fc2c359bd7 GIT_SILENT Sync po/docbooks with svn 2022-05-23 01:49:32 +00:00
Jonah Brüchert f17987b3e7 Update ytmusicapi to 0.22.0 2022-05-23 01:11:37 +02:00
Jonah Brüchert 7c2bbf3a98 library: Create database directory before opening database 2022-05-23 01:11:10 +02:00
Jonah Brüchert 4320921a96 asyncdatabase: Log error if database could not be opened 2022-05-23 01:10:45 +02:00
Jonah Brüchert 306a4c866c Fix background color of the search popup 2022-05-23 00:41:46 +02:00
Jonah Brüchert 2bfd142300 asyncdatabase: Move as much as possible out of the header
In preparation for making it a shared library in the future.
2022-05-23 00:41:06 +02:00
Jonah Brüchert bec70c37db Run migrations in a transaction
This should be slightly faster (although not a problem right now) and
more importantly allow to rollback if transactions go wrong.

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

No files matched your search

+3
View File
@@ -1,6 +1,9 @@
# SPDX-FileCopyrightText: 2021 Jonah Brüchert <[email protected]>
# SPDX-FileCopyrightText: 2021 Bhushan Shah <[email protected]>
#
# SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*.kdev4
CMakeLists.txt.user
flatpak/flatpak-builder-tools/
build/
+1 -1
View File
@@ -3,4 +3,4 @@
# SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
include:
- 'https://invent.kde.org/sysadmin/ci-tooling/raw/master/invent/ci-reuse.yml'
- 'https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/reuse-lint.yml'
+21
View File
@@ -2,3 +2,24 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: AudioTube
Upstream-Contact: Jonah Brüchert <[email protected]>
Source: https://invent.kde.org/plasma-mobile/audiotube
# Sample paragraph, commented out:
#
# Files: ...
# Copyright: $YEAR $NAME <$CONTACT>
# License: ...
# Comment: ...
Files: README.md
Copyright: 2021 Jonah Brüchert <[email protected]>
License: CC0-1.0
Files: org.kde.audiotube.desktop
org.kde.audiotube.json
Copyright: 2021 Jonah Brüchert <[email protected]>
License: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
Files: logo.png
org.kde.audiotube.svg
Copyright: 2021 Mathis Brüchert <[email protected]>
License: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
+11 -3
View File
@@ -8,10 +8,11 @@ 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 17)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE)
@@ -25,14 +26,21 @@ include(ECMSetupVersion)
include(ECMGenerateHeaders)
include(ECMPoQmTools)
ecm_setup_version(${PROJECT_VERSION}
VARIABLE_PREFIX AUDIOTUBE
VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/version.h
)
kde_enable_exceptions()
find_package(Qt5 ${QT5_MIN_VERSION} REQUIRED COMPONENTS Core Gui Qml QuickControls2 Svg)
find_package(Qt5 ${QT5_MIN_VERSION} REQUIRED COMPONENTS Core Gui Qml QuickControls2 Svg Sql)
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Kirigami2 I18n CoreAddons Crash)
find_package(pybind11 REQUIRED)
find_package(YoutubeDL REQUIRED RUNTIME)
find_package(Ytdlp REQUIRED RUNTIME)
find_package(YTMusicAPI REQUIRED RUNTIME)
ki18n_install(po)
add_subdirectory(src)
install(PROGRAMS org.kde.audiotube.desktop DESTINATION ${KDE_INSTALL_APPDIR})
+2 -2
View File
@@ -4,9 +4,9 @@ AudioTube is a client for YouTube Music.
# Dependencies
It depends on Qt and Kirigami in addition to a few python packages:
It depends on Qt, Kirigami, KI18n, KCoreAddons and KCrash in addition to a few python packages:
* ytmusicapi
* youtube_dl
* yt_dlp
YouTube uses webp images for the cover art, so you will need to install qtimageformats.
-3
View File
@@ -1,3 +0,0 @@
SPDX-FileCopyrightText: 2021 Jonah Brüchert <[email protected]>
SPDX-License-Identifier: CC0-1.0
-4
View File
@@ -1,4 +0,0 @@
[Project]
CreatedFrom=CMakeLists.txt
Manager=KDevCMakeManager
Name=audiotube
-3
View File
@@ -1,3 +0,0 @@
SPDX-FileCopyrightText: 2021 Jonah Brüchert <[email protected]>
SPDX-License-Identifier: CC0-1.0
@@ -4,8 +4,8 @@
find_package(Python3 REQUIRED COMPONENTS Interpreter)
execute_process(COMMAND ${Python3_EXECUTABLE} -c "import youtube_dl" RESULT_VARIABLE YTDL_CHECK_RESULT)
execute_process(COMMAND ${Python3_EXECUTABLE} -c "import yt_dlp" RESULT_VARIABLE YTDLP_CHECK_RESULT)
if (${YTDL_CHECK_RESULT} EQUAL 0)
set(YoutubeDL_FOUND TRUE)
if (${YTDLP_CHECK_RESULT} EQUAL 0)
set(Ytdlp_FOUND TRUE)
endif()
-14
View File
@@ -1,14 +0,0 @@
{
"name": "python3-youtube_dl",
"buildsystem": "simple",
"build-commands": [
"pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"youtube_dl\" --no-build-isolation"
],
"sources": [
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/c6/75/05979677d9abc76851d13d8db3951e39017ac223545adab6e8576fa0cbe7/youtube_dl-2021.6.6.tar.gz",
"sha256": "cb2d3ee002158ede783e97a82c95f3817594df54367ea6a77ce5ceea4772f0ab"
}
]
}
+39
View File
@@ -0,0 +1,39 @@
{
"name": "python3-yt-dlp",
"buildsystem": "simple",
"build-commands": [
"pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"yt-dlp\" --no-build-isolation"
],
"sources": [
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/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"
}
]
}
File renamed without changes.
+12 -12
View File
@@ -7,33 +7,33 @@
"sources": [
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/4f/5a/597ef5911cb8919efe4d86206aa8b2658616d676a7088f0825ca08bd7cb8/urllib3-1.26.6.tar.gz",
"sha256": "f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"
"url": "https://files.pythonhosted.org/packages/db/51/a507c856293ab05cdc1db77ff4bc1268ddd39f29e7dc4919aa497f0adbec/charset_normalizer-2.1.1-py3-none-any.whl",
"sha256": "83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"
},
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/ea/b7/e0e3c1c467636186c39925827be42f16fee389dc404ac29e930e9136be70/idna-2.10.tar.gz",
"sha256": "b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"
"url": "https://files.pythonhosted.org/packages/6f/de/5be2e3eed8426f871b170663333a0f627fc2924cc386cd41be065e7ea870/urllib3-1.26.12-py2.py3-none-any.whl",
"sha256": "b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"
},
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/ee/2d/9cdc2b527e127b4c9db64b86647d567985940ac3698eeabc7ffaccb4ea61/chardet-4.0.0.tar.gz",
"sha256": "0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"
"url": "https://files.pythonhosted.org/packages/ca/91/6d9b8ccacd0412c08820f72cebaa4f0c0441b5cda699c90f618b6f8a1b42/requests-2.28.1-py3-none-any.whl",
"sha256": "8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"
},
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/6d/78/f8db8d57f520a54f0b8a438319c342c61c22759d8f9a1cd2e2180b5e5ea9/certifi-2021.5.30.tar.gz",
"sha256": "2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"
"url": "https://files.pythonhosted.org/packages/0e/5a/4a5316bddbb697d4b1787159291edfeffd10f73c08b59c7248b15291b601/ytmusicapi-0.22.0-py3-none-any.whl",
"sha256": "d042520f3f76eac2c1498130dbc4e8a4af21ca73496590a5ff054fc4da0af1d8"
},
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/6b/47/c14abc08432ab22dc18b9892252efaf005ab44066de871e72a38d6af464b/requests-2.25.1.tar.gz",
"sha256": "27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"
"url": "https://files.pythonhosted.org/packages/04/a2/d918dcd22354d8958fe113e1a3630137e0fc8b44859ade3063982eacd2a4/idna-3.3-py3-none-any.whl",
"sha256": "84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"
},
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/1b/8e/2bc4c8160083bbfaac215637f1e9f4a43263833c2e81212cba3073aeaa25/ytmusicapi-0.17.3.tar.gz",
"sha256": "9a249cc53efd6408034f401a9a4375272a9b339e8eb64f3a2e713a1ccd06d55b"
"url": "https://files.pythonhosted.org/packages/e9/06/d3d367b7af6305b16f0d28ae2aaeb86154fa91f144f036c2d5002a5a202b/certifi-2022.6.15-py3-none-any.whl",
"sha256": "fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"
}
]
}
+1 -1
View File
@@ -16,5 +16,5 @@ else
git -C flatpak-builder-tools pull
fi
./flatpak-builder-tools/pip/flatpak-pip-generator youtube_dl
./flatpak-builder-tools/pip/flatpak-pip-generator yt-dlp
./flatpak-builder-tools/pip/flatpak-pip-generator ytmusicapi
-3
View File
@@ -1,3 +0,0 @@
SPDX-FileCopyrightText: 2021 Mathis Brüchert <[email protected]>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
+70 -6
View File
@@ -7,6 +7,8 @@ SPDX-License-Identifier: CC0-1.0
<component type="desktop-application">
<id>org.kde.audiotube</id>
<name>AudioTube</name>
<name xml:lang="ar">تيوب الصوتي</name>
<name xml:lang="az">AudioTube</name>
<name xml:lang="ca">AudioTube</name>
<name xml:lang="ca-valencia">AudioTube</name>
<name xml:lang="cs">AudioTube</name>
@@ -20,18 +22,24 @@ SPDX-License-Identifier: CC0-1.0
<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="ca">Client convergent per a la música de YouTube</summary>
<summary xml:lang="ca-valencia">Client convergent per a la música de YouTube</summary>
<summary xml:lang="ar">عميل موسيقى يوتيوب متعدد المنصات</summary>
<summary xml:lang="az">Birləşmiş YouTube Musiqi müştərisi</summary>
<summary xml:lang="ca">Client convergent de música de YouTube</summary>
<summary xml:lang="ca-valencia">Client convergent de música de YouTube</summary>
<summary xml:lang="de">Konvergentes YouTube-Musikprogramm</summary>
<summary xml:lang="en-GB">Convergent YouTube Music client</summary>
<summary xml:lang="es">Cliente de música de YouTube convergente</summary>
@@ -42,16 +50,22 @@ SPDX-License-Identifier: CC0-1.0
<summary xml:lang="it">Client YouTube Music convergente</summary>
<summary xml:lang="ko">올인원 YouTube Music 클라이언트</summary>
<summary xml:lang="nl">Convergente muziekclient van YouTube</summary>
<summary xml:lang="pl">Całościowy program do muzyki z YouTube</summary>
<summary xml:lang="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>
@@ -65,25 +79,70 @@ SPDX-License-Identifier: CC0-1.0
<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/desktop.png</image>
<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>
@@ -92,8 +151,10 @@ SPDX-License-Identifier: CC0-1.0
</recommends>
<description>
<p>AudioTube can search YouTube Music, list albums and artists, play automatically generated playlists, albums and allows to put your own playlist together.</p>
<p xml:lang="ar">يمكن لتيوب الصوتي البحث في موسيقى يوتيوب، وعرض الألبومات والفنانين، وتشغيل القوائم والألبومات المولدة تلقائيًا، ويسمح لك بوضع قائمة التشغيل الخاصة بك معًا.</p>
<p xml:lang="az">AudioTube YouTube Musiqi, albomların və ifaçıların siyahısı üzrə axtarış etməyə, yaradılmış mahnı siyahısı və albomları avtomatik oxutmağa və öz mahnı siyahınızı yaratmağa imkan verir.</p>
<p xml:lang="ca">L'AudioTube pot cercar música a YouTube, llistar àlbums i artistes, reproduir llistes de reproducció generades automàticament, àlbums i permet crear la vostra pròpia llista de reproducció.</p>
<p xml:lang="ca-valencia">L'AudioTube pot cercar música a YouTube, llistar àlbums i artistes, reproduir llistes de reproducció generades automàticament, àlbums i permet crear la vostra pròpia llista de reproducció.</p>
<p xml:lang="ca-valencia">L'AudioTube pot buscar música a YouTube, llistar àlbums i artistes, reproduir llistes de reproducció generades automàticament, àlbums i permet crear la vostra pròpia llista de reproducció.</p>
<p xml:lang="de">AudioTube kann Musik auf YouTube durchsuchen, Alben und Interpreten auflisten, automatisch generierte Wiedergabelisten und Alben abspielen und erlaubt es, eine eigene Wiedergabeliste zusammenzustellen.</p>
<p xml:lang="en-GB">AudioTube can search YouTube Music, list albums and artists, play automatically generated playlists, albums and allows to put your own playlist together.</p>
<p xml:lang="es">AudioTube puede buscar música de YouTube, listar álbumes y artistas, reproducir las listas de reproducción generadas, álbumes y le permite crear su propia lista de reproducción.</p>
@@ -104,12 +165,15 @@ SPDX-License-Identifier: CC0-1.0
<p xml:lang="it">AudioTube può eseguire ricerche su YouTube Music, elencare album e artisti, riprodurre scalette e album generati automaticamente e ti consente di creare la tua scaletta.</p>
<p xml:lang="ko">AudioTube는 YouTube Music을 검색하고, 앨범과 아티스트를 표시하고, 자동 생성된 재생 목록, 앨범을 재생하고, 사용자 정의 재생 목록을 만들 수 있습니다.</p>
<p xml:lang="nl">AudioTube kan in YouTube Music zoeken, een lijst maken van albums en artiesten, automatically gegenereerde afspeellijsten, albums afspelen en biedt u het samenstellen van uw eigen afspeellijst.</p>
<p xml:lang="pl">AudioTube może wyszukać muzyki na YouTube, wypisać albumy i wykonawców, odtwarzać samoczynnie utworzone listy odtwarzania, albumy oraz umożliwia tworzenie własnych list odtwarzania.</p>
<p xml:lang="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>
+13 -1
View File
@@ -1,5 +1,7 @@
[Desktop Entry]
Name=AudioTube
Name[ar]=تيوب الصوتي
Name[az]=AudioTube
Name[ca]=AudioTube
Name[ca@valencia]=AudioTube
Name[cs]=AudioTube
@@ -13,16 +15,22 @@ 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
@@ -36,15 +44,19 @@ 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 muzyki z YouTube
Comment[pl]=Program do YouTube Music
Comment[pt]=Cliente do YouTube Music
Comment[pt_BR]=Cliente YouTube Music
Comment[ro]=Client de muzică pentru YouTube
Comment[sk]=Hudobný klient YouTube
Comment[sl]=Glasbeni odjemalec Youtube
Comment[sv]=YouTube-musikprogram
Comment[tr]=YouTube Müzik istemcisi
Comment[uk]=Музичний клієнт YouTube
Comment[x-test]=xxYouTube Music clientxx
Comment[zh_CN]=YouTube Music 客户端
Version=1.0
Exec=audiotube
Icon=org.kde.audiotube
-3
View File
@@ -1,3 +0,0 @@
SPDX-FileCopyrightText: 2021 Jonah Brüchert <[email protected]>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
+3 -3
View File
@@ -1,7 +1,7 @@
{
"id": "org.kde.audiotube",
"runtime": "org.kde.Platform",
"runtime-version": "5.15",
"runtime-version": "5.15-21.08",
"sdk": "org.kde.Sdk",
"command": "audiotube",
"tags": ["nightly"],
@@ -21,14 +21,14 @@
],
"modules": [
"flatpak/python3-youtube_dl.json",
"flatpak/python3-yt-dlp.json",
"flatpak/python3-ytmusicapi.json",
{
"name": "pybind11",
"config-opts": ["-DPYBIND11_FINDPYTHON=ON", "-DPYBIND11_TEST=OFF"],
"buildsystem": "cmake-ninja",
"builddir": true,
"sources": [ { "type": "git", "url": "https://github.com/pybind/pybind11", "tag": "v2.6.2" } ]
"sources": [ { "type": "git", "url": "https://github.com/pybind/pybind11", "tag": "v2.9.1" } ]
},
{
"name": "org.kde.audiotube",
-3
View File
@@ -1,3 +0,0 @@
SPDX-FileCopyrightText: 2021 Jonah Brüchert <[email protected]>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
-3
View File
@@ -1,3 +0,0 @@
SPDX-FileCopyrightText: 2021 Mathis Brüchert <[email protected]>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
+204
View File
@@ -0,0 +1,204 @@
# 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
@@ -0,0 +1,205 @@
# 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
@@ -0,0 +1,199 @@
# 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
@@ -0,0 +1,199 @@
# 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
@@ -0,0 +1,196 @@
# 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
@@ -0,0 +1,209 @@
# 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
@@ -0,0 +1,208 @@
# 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
@@ -0,0 +1,210 @@
# 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
@@ -0,0 +1,211 @@
# 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
@@ -0,0 +1,207 @@
# 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
@@ -0,0 +1,205 @@
# 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
@@ -0,0 +1,208 @@
# 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
@@ -0,0 +1,208 @@
# 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
@@ -0,0 +1,206 @@
# 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
@@ -0,0 +1,208 @@
# 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
@@ -0,0 +1,197 @@
# 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
@@ -0,0 +1,207 @@
# 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
@@ -0,0 +1,210 @@
# 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
@@ -0,0 +1,198 @@
# 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
@@ -0,0 +1,209 @@
# 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
@@ -0,0 +1,201 @@
# 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
@@ -0,0 +1,203 @@
# 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
@@ -0,0 +1,207 @@
# 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
@@ -0,0 +1,206 @@
# 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
@@ -0,0 +1,205 @@
# 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
@@ -0,0 +1,209 @@
# 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
@@ -0,0 +1,194 @@
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
@@ -0,0 +1,195 @@
# 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 -2
View File
@@ -3,5 +3,5 @@
#
# SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
ytmusicapi==0.17.3
youtube_dl
ytmusicapi==0.22.0
yt-dlp
+12
View File
@@ -10,6 +10,14 @@ target_link_libraries(ytm PUBLIC pybind11::embed)
target_include_directories(ytm PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_definitions(ytm PRIVATE -DRANDALL_WAS_HERE)
add_library(asyncdatabase STATIC
asyncdatabase.cpp
asyncdatabase_p.h
)
target_include_directories(asyncdatabase PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(asyncdatabase PUBLIC Qt5::Sql)
add_subdirectory(example)
add_executable(audiotube
@@ -24,7 +32,9 @@ add_executable(audiotube
playlistutils.cpp
errorhandler.cpp
playerutils.cpp
abstractytmusicmodel.cpp
multiiterableview.h
library.cpp
resources.qrc
)
@@ -39,7 +49,9 @@ target_link_libraries(audiotube
KF5::CoreAddons
KF5::Crash
ytm
asyncdatabase
)
target_compile_definitions(audiotube PRIVATE -DQT_NO_KEYWORDS -DQT_NO_URL_CAST_FROM_STRING)
install(TARGETS audiotube ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
+27
View File
@@ -0,0 +1,27 @@
// SPDX-FileCopyrightText: 2021 Jonah Brüchert <[email protected]>
//
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "abstractytmusicmodel.h"
#include "asyncytmusic.h"
AbstractYTMusicModel::AbstractYTMusicModel(QObject *parent)
: QAbstractListModel(parent)
{
}
bool AbstractYTMusicModel::loading() const
{
return m_loading;
}
void AbstractYTMusicModel::setLoading(bool loading)
{
if (m_loading == loading)
return;
m_loading = loading;
Q_EMIT loadingChanged();
}
+28
View File
@@ -0,0 +1,28 @@
// SPDX-FileCopyrightText: 2021 Jonah Brüchert <[email protected]>
//
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <QAbstractListModel>
///
/// Base class for all models that work with data from the AsyncYTMusic class.
/// It provides a loading property, that is automatically set to false if a query fails.
///
class AbstractYTMusicModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(bool loading READ loading WRITE setLoading NOTIFY loadingChanged)
public:
explicit AbstractYTMusicModel(QObject *parent = nullptr);
bool loading() const;
void setLoading(bool loading);
Q_SIGNAL void loadingChanged();
private:
bool m_loading = false;
};
+23 -34
View File
@@ -4,28 +4,29 @@
#include "albummodel.h"
#include <QDebug>
#include <QStringBuilder>
AlbumModel::AlbumModel(QObject *parent)
: QAbstractListModel(parent)
: AbstractYTMusicModel(parent)
{
connect(this, &AlbumModel::browseIdChanged, this, [=] {
connect(this, &AlbumModel::browseIdChanged, this, [this] {
setLoading(true);
YTMusicThread::instance()->fetchAlbum(m_browseId);
});
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::fetchAlbumFinished, this, [=](const album::Album &album) {
setLoading(false);
auto future = YTMusicThread::instance()->fetchAlbum(m_browseId);
connectFuture(future, this, [=, this](const album::Album &album) {
setLoading(false);
beginResetModel();
m_album = album;
endResetModel();
std::sort(m_album.thumbnails.begin(), m_album.thumbnails.end());
beginResetModel();
m_album = album;
endResetModel();
std::sort(m_album.thumbnails.begin(), m_album.thumbnails.end());
Q_EMIT titleChanged();
Q_EMIT thumbnailUrlChanged();
Q_EMIT playlistIdChanged();
Q_EMIT titleChanged();
Q_EMIT thumbnailUrlChanged();
Q_EMIT playlistIdChanged();
});
});
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::errorOccurred, this, [=] {
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::errorOccurred, this, [=, this] {
setLoading(false);
});
}
@@ -43,12 +44,7 @@ QVariant AlbumModel::data(const QModelIndex &index, int role) const
case VideoId:
return QString::fromStdString(m_album.tracks[index.row()].video_id.value_or(std::string()));
case Artists:
return QVariant::fromValue(std::vector<meta::Artist> {
{
m_album.tracks[index.row()].artists,
std::nullopt
}
});
return QVariant::fromValue(m_album.tracks[index.row()].artists);
}
Q_UNREACHABLE();
@@ -81,18 +77,6 @@ QString AlbumModel::title() const
return QString::fromStdString(m_album.title);
}
bool AlbumModel::loading() const
{
return m_loading;
}
void AlbumModel::setLoading(bool loading)
{
m_loading = loading;
Q_EMIT loadingChanged();
}
QUrl AlbumModel::thumbnailUrl() const
{
if (m_album.thumbnails.empty()) {
@@ -104,5 +88,10 @@ QUrl AlbumModel::thumbnailUrl() const
QString AlbumModel::playlistId() const
{
return QString::fromStdString(m_album.playlist_id);
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));
}
+6 -7
View File
@@ -8,16 +8,17 @@
#include <QUrl>
#include "asyncytmusic.h"
#include "abstractytmusicmodel.h"
class AlbumModel : public QAbstractListModel
class AlbumModel : public AbstractYTMusicModel
{
Q_OBJECT
Q_PROPERTY(QString browseId READ browseId WRITE setBrowseId NOTIFY browseIdChanged REQUIRED)
Q_PROPERTY(QString title READ title NOTIFY titleChanged)
Q_PROPERTY(QUrl thumbnailUrl READ thumbnailUrl NOTIFY thumbnailUrlChanged)
Q_PROPERTY(bool loading READ loading WRITE setLoading NOTIFY loadingChanged)
Q_PROPERTY(QString playlistId READ playlistId NOTIFY playlistIdChanged)
Q_PROPERTY(QUrl webUrl READ webUrl NOTIFY webUrlChanged)
public:
enum Role {
@@ -39,19 +40,17 @@ 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;
};
+46 -58
View File
@@ -4,58 +4,54 @@
#include "artistmodel.h"
#include <QStringBuilder>
#include <asyncytmusic.h>
ArtistModel::ArtistModel(QObject *parent)
: QAbstractListModel(parent)
: AbstractYTMusicModel(parent)
, m_view(albums, singles, songs, videos)
{
connect(this, &ArtistModel::channelIdChanged, this, [=] {
connect(this, &ArtistModel::channelIdChanged, this, [this] {
if (m_channelId.isEmpty()) {
return;
}
setLoading(true);
YTMusicThread::instance()->fetchArtist(m_channelId);
auto future = YTMusicThread::instance()->fetchArtist(m_channelId);
connectFuture(future, this, [=, this](const artist::Artist &artist) {
setLoading(false);
beginResetModel();
m_artist = artist;
std::sort(m_artist.thumbnails.begin(), m_artist.thumbnails.end());
albums = m_artist.albums ? m_artist.albums->results : std::vector<artist::Artist::Album>();
singles = m_artist.singles ? m_artist.singles->results : std::vector<artist::Artist::Single>();
songs = m_artist.songs ? m_artist.songs->results : std::vector<artist::Artist::Song>();
videos = m_artist.videos ? m_artist.videos->results : std::vector<artist::Artist::Video>();
// std::span can't know if the data pointer underneath it was changed, so re-create
m_view = MultiIterableView(albums, singles, songs, videos);
endResetModel();
Q_EMIT titleChanged();
Q_EMIT thumbnailUrlChanged();
});
});
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::fetchArtistFinished, this, [=](const artist::Artist &artist) {
setLoading(false);
beginResetModel();
m_artist = artist;
std::sort(m_artist.thumbnails.begin(), m_artist.thumbnails.end());
auto albums = m_artist.albums ? m_artist.albums->results : std::vector<artist::Artist::Album>();
auto singles = m_artist.singles ? m_artist.singles->results : std::vector<artist::Artist::Single>();
auto songs = m_artist.songs ? m_artist.songs->results : std::vector<artist::Artist::Song>();
auto videos = m_artist.videos ? m_artist.videos->results : std::vector<artist::Artist::Video>();
m_view = std::optional(MultiIterableView(std::move(albums),
std::move(singles),
std::move(songs),
std::move(videos)));
endResetModel();
Q_EMIT titleChanged();
Q_EMIT thumbnailUrlChanged();
});
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::errorOccurred, this, [=] {
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::errorOccurred, this, [this] {
setLoading(false);
});
}
int ArtistModel::rowCount(const QModelIndex &parent) const
{
return parent.isValid() ? 0 : m_view ? int(m_view->size()) : 0;
return parent.isValid() ? 0 : m_view.size();
}
QVariant ArtistModel::data(const QModelIndex &index, int role) const
{
if (!m_view) {
return {};
}
switch (role) {
case Title:
return QString::fromStdString(std::visit([&](auto&& item) {
@@ -71,7 +67,7 @@ QVariant ArtistModel::data(const QModelIndex &index, int role) const
}
Q_UNREACHABLE();
}, m_view.value()[index.row()]));
}, m_view[index.row()]));
case Type:
return std::visit([&](auto&& item) {
using T = std::decay_t<decltype(item)>;
@@ -86,7 +82,7 @@ QVariant ArtistModel::data(const QModelIndex &index, int role) const
}
Q_UNREACHABLE();
}, m_view.value()[index.row()]);
}, m_view[index.row()]);
case Artists:
return QVariant::fromValue(std::vector<meta::Artist> {
{
@@ -108,7 +104,7 @@ QVariant ArtistModel::data(const QModelIndex &index, int role) const
}
Q_UNREACHABLE();
}, m_view.value()[index.row()]);
}, m_view[index.row()]);
}
Q_UNREACHABLE();
@@ -151,33 +147,25 @@ QUrl ArtistModel::thumbnailUrl() const
return QUrl(QString::fromStdString(m_artist.thumbnails.back().url));
}
bool ArtistModel::loading() const
QUrl ArtistModel::webUrl() const
{
return m_loading;
}
void ArtistModel::setLoading(bool loading)
{
m_loading = loading;
Q_EMIT loadingChanged();
return QUrl(YTMUSIC_WEB_BASE_URL % u"channel/" % QString::fromStdString(m_artist.channel_id));
}
void ArtistModel::triggerItem(int row)
{
if (m_view) {
std::visit([&](auto&& item) {
using T = std::decay_t<decltype(item)>;
if constexpr(std::is_same_v<T, artist::Artist::Album>) {
Q_EMIT openAlbum(QString::fromStdString(item.browse_id));
} else if constexpr(std::is_same_v<T, artist::Artist::Single>) {
Q_EMIT openAlbum(QString::fromStdString(item.browse_id));
} else if constexpr(std::is_same_v<T, artist::Artist::Song>) {
Q_EMIT openSong(QString::fromStdString(item.video_id));
} else if constexpr(std::is_same_v<T, artist::Artist::Video>) {
Q_EMIT openVideo(QString::fromStdString(item.video_id), QString::fromStdString(item.title));
} else {
Q_UNREACHABLE();
}
}, m_view.value()[row]);
}
std::visit([&](auto&& item) {
using T = std::decay_t<decltype(item)>;
if constexpr(std::is_same_v<T, artist::Artist::Album>) {
Q_EMIT openAlbum(QString::fromStdString(item.browse_id));
} else if constexpr(std::is_same_v<T, artist::Artist::Single>) {
Q_EMIT openAlbum(QString::fromStdString(item.browse_id));
} else if constexpr(std::is_same_v<T, artist::Artist::Song>) {
Q_EMIT openSong(QString::fromStdString(item.video_id));
} else if constexpr(std::is_same_v<T, artist::Artist::Video>) {
Q_EMIT openVideo(QString::fromStdString(item.video_id), QString::fromStdString(item.title));
} else {
Q_UNREACHABLE();
}
}, m_view[row]);
}
+16 -9
View File
@@ -10,9 +10,12 @@
#include <optional>
#include <ytmusic.h>
#include "multiiterableview.h"
class ArtistModel : public QAbstractListModel
#include "multiiterableview.h"
#include "abstractytmusicmodel.h"
class ArtistModel : public AbstractYTMusicModel
{
Q_OBJECT
@@ -20,7 +23,7 @@ class ArtistModel : public QAbstractListModel
Q_PROPERTY(QString title READ title NOTIFY titleChanged)
Q_PROPERTY(QUrl thumbnailUrl READ thumbnailUrl NOTIFY thumbnailUrlChanged)
Q_PROPERTY(bool loading READ loading WRITE setLoading NOTIFY loadingChanged)
Q_PROPERTY(QUrl webUrl READ webUrl NOTIFY webUrlChanged)
public:
enum Type {
@@ -54,9 +57,8 @@ public:
QUrl thumbnailUrl() const;
Q_SIGNAL void thumbnailUrlChanged();
bool loading() const;
void setLoading(bool loading);
Q_SIGNAL void loadingChanged();
QUrl webUrl() const;
Q_SIGNAL void webUrlChanged();
Q_INVOKABLE void triggerItem(int row);
@@ -66,10 +68,15 @@ public:
private:
QString m_channelId;
bool m_loading = false;
artist::Artist m_artist;
std::optional<MultiIterableView<
std::vector<artist::Artist::Album> albums;
std::vector<artist::Artist::Single> singles;
std::vector<artist::Artist::Song> songs;
std::vector<artist::Artist::Video> videos;
MultiIterableView<
artist::Artist::Album, artist::Artist::Single, artist::Artist::Song, artist::Artist::Video
>> m_view;
> m_view;
};
+309
View File
@@ -0,0 +1,309 @@
// SPDX-FileCopyrightText: 2022 Jonah Brüchert <[email protected]>
//
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "asyncdatabase.h"
#include <QDir>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QUrl>
#include <QStringBuilder>
#include <QVariant>
#include <QSqlResult>
#include <QSqlError>
#include <QLoggingCategory>
#define SCHAMA_MIGRATIONS_TABLE "__qt_schema_migrations"
Q_DECLARE_LOGGING_CATEGORY(asyncdatabase)
Q_LOGGING_CATEGORY(asyncdatabase, "asyncdatabase")
namespace asyncdatabase_private {
// migrations
void createInternalTable(QSqlDatabase &database) {
QSqlQuery query(QStringLiteral("create table if not exists " SCHAMA_MIGRATIONS_TABLE " ("
"version Text primary key not null, "
"run_on timestamp not null default current_timestamp)"), database);
if (!query.exec()) {
printSqlError(query);
}
}
void markMigrationRun(QSqlDatabase &database, const QString &name) {
qCDebug(asyncdatabase) << "Marking migration" << name << "as done.";
QSqlQuery query(database);
if (!query.prepare(QStringLiteral("insert into " SCHAMA_MIGRATIONS_TABLE " (version) values (:name)"))) {
printSqlError(query);
}
query.bindValue(QStringLiteral(":name"), QVariant(name));
if (!query.exec()) {
printSqlError(query);
}
}
bool checkMigrationAlreadyRun(QSqlDatabase &database, const QString &name) {
qCDebug(asyncdatabase) << "Checking whether migration" << name << "is already applied";
QSqlQuery query(database);
query.prepare(QStringLiteral("select count(*) from " SCHAMA_MIGRATIONS_TABLE " where version = :name"));
query.bindValue(QStringLiteral(":name"), QVariant(name));
query.exec();
query.next();
int count = query.value(0).toInt();
bool isApplied = count > 0;
if (isApplied) {
qCDebug(asyncdatabase) << "… yes";
} else {
qDebug(asyncdatabase) << "… no";
}
return isApplied;
}
void runDatabaseMigrations(QSqlDatabase &database, const QString &migrationDirectory)
{
createInternalTable(database);
QDir dir(migrationDirectory);
auto entries = dir.entryList(QDir::Filter::Dirs | QDir::Filter::NoDotAndDotDot, QDir::SortFlag::Name);
for (const auto &entry : entries) {
QDir subdir(entry);
if (!checkMigrationAlreadyRun(database, subdir.dirName())) {
QFile file(migrationDirectory % QDir::separator() % entry % QDir::separator() % "up.sql");
if (!file.open(QFile::ReadOnly)) {
qCDebug(asyncdatabase) << "Failed to open migration file" << file.fileName();
}
qCDebug(asyncdatabase) << "Running migration" << subdir.dirName();
database.transaction();
// Hackish
const auto statements = file.readAll().split(';');
bool migrationSuccessful = true;
for (const QByteArray &statement : statements) {
const auto trimmedStatement = statement.trimmed();
QSqlQuery query(database);
if (!trimmedStatement.isEmpty()) {
qCDebug(asyncdatabase) << "Running" << trimmedStatement;
if (!query.prepare(trimmedStatement)) {
printSqlError(query);
} else {
bool success = query.exec();
migrationSuccessful &= success;
if (!success) {
printSqlError(query);
}
}
}
}
if (migrationSuccessful) {
database.commit();
markMigrationRun(database, subdir.dirName());
} else {
database.rollback();
}
}
}
qCDebug(asyncdatabase) << "Migrations finished";
}
struct AsyncSqlDatabasePrivate {
QSqlDatabase database;
};
// Internal asynchronous database class
QFuture<void> AsyncSqlDatabase::establishConnection(const DatabaseConfiguration &configuration)
{
return runAsync([=, this] {
d->database = QSqlDatabase::addDatabase(configuration.type());
if (configuration.databaseName()) {
d->database.setDatabaseName(*configuration.databaseName());
}
if (configuration.hostName()) {
d->database.setHostName(*configuration.hostName());
}
if (configuration.userName()) {
d->database.setUserName(*configuration.userName());
}
if (configuration.password()) {
d->database.setPassword(*configuration.password());
}
if (!d->database.open()) {
qCDebug(asyncdatabase) << "Failed to open database" << d->database.lastError().text();
if (configuration.databaseName()) {
qCDebug(asyncdatabase) << "Tried to use database" << *configuration.databaseName();
}
}
});
}
AsyncSqlDatabase::AsyncSqlDatabase()
: QObject()
, d(std::make_unique<AsyncSqlDatabasePrivate>())
{
}
Row AsyncSqlDatabase::retrieveRow(const QSqlQuery &query) {
Row row;
int i = 0;
while (true) {
if (query.isValid()) {
QVariant value = query.value(i);
if (value.isValid()) {
row.push_back(std::move(value));
i++;
} else {
break;
}
} else {
break;
}
}
return row;
}
Rows AsyncSqlDatabase::retrieveRows(QSqlQuery &query)
{
Rows rows;
while (query.next()) {
rows.push_back(retrieveRow(query));
}
return rows;
}
std::optional<Row> AsyncSqlDatabase::retrieveOptionalRow(QSqlQuery &query)
{
query.next();
if (query.isValid()) {
return retrieveRow(query);
} else {
return std::nullopt;
}
}
QSqlDatabase &AsyncSqlDatabase::db()
{
return d->database;
}
AsyncSqlDatabase::~AsyncSqlDatabase() = default;
void printSqlError(const QSqlQuery &query)
{
qCDebug(asyncdatabase) << "SQL error:" << query.lastError().text();
}
QSqlQuery prepareQuery(const QSqlDatabase &database, const QString &sqlQuery)
{
qCDebug(asyncdatabase) << "Running" << sqlQuery;
QSqlQuery query(database);
if (!query.prepare(sqlQuery)) {
printSqlError(query);
}
return query;
}
QSqlQuery runQuery(QSqlQuery &query)
{
if (!query.exec()) {
printSqlError(query);
}
return query;
}
}
struct DatabaseConfigurationPrivate : public QSharedData {
QString type;
std::optional<QString> hostName;
std::optional<QString> databaseName;
std::optional<QString> userName;
std::optional<QString> password;
};
DatabaseConfiguration::DatabaseConfiguration() : d(new DatabaseConfigurationPrivate)
{}
DatabaseConfiguration::~DatabaseConfiguration() = default;
DatabaseConfiguration::DatabaseConfiguration(const DatabaseConfiguration &) = default;
void DatabaseConfiguration::setType(const QString &type) {
d->type = type;
}
const QString &DatabaseConfiguration::type() const {
return d->type;
}
void DatabaseConfiguration::setHostName(const QString &hostName) {
d->hostName = hostName;
}
const std::optional<QString> &DatabaseConfiguration::hostName() const {
return d->hostName;
}
void DatabaseConfiguration::setDatabaseName(const QString &databaseName) {
d->databaseName = databaseName;
}
const std::optional<QString> &DatabaseConfiguration::databaseName() const {
return d->databaseName;
}
void DatabaseConfiguration::setUserName(const QString &userName) {
d->userName = userName;
}
const std::optional<QString> &DatabaseConfiguration::userName() const {
return d->userName;
}
void DatabaseConfiguration::setPassword(const QString &password) {
d->password = password;
}
const std::optional<QString> &DatabaseConfiguration::password() const {
return d->password;
}
struct ThreadedDatabasePrivate {
asyncdatabase_private::AsyncSqlDatabase db;
};
std::unique_ptr<ThreadedDatabase> ThreadedDatabase::establishConnection(DatabaseConfiguration config) {
auto threadedDb = std::make_unique<ThreadedDatabase>();
threadedDb->setObjectName(QStringLiteral("database thread"));
threadedDb->d->db.moveToThread(&*threadedDb);
threadedDb->start();
threadedDb->d->db.establishConnection(config);
return threadedDb;
}
ThreadedDatabase::ThreadedDatabase()
: QThread()
, d(std::make_unique<ThreadedDatabasePrivate>())
{
}
ThreadedDatabase::~ThreadedDatabase()
{
quit();
wait();
}
asyncdatabase_private::AsyncSqlDatabase &ThreadedDatabase::db()
{
return d->db;
}
+152
View File
@@ -0,0 +1,152 @@
// SPDX-FileCopyrightText: 2022 Jonah Brüchert <[email protected]>
//
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
class QUrl;
class QSqlDatabase;
#include <QString>
#include <QObject>
#include <QFuture>
#include <QSharedDataPointer>
#include <memory>
#include <optional>
#include <tuple>
#include <vector>
#include "asyncdatabase_p.h"
struct DatabaseConfigurationPrivate;
///
/// Options for connecting to a database
///
class DatabaseConfiguration {
public:
DatabaseConfiguration();
DatabaseConfiguration(const DatabaseConfiguration &);
~DatabaseConfiguration();
/// Set the name of the database driver, for example DATABASE_TYPE_SQLITE or a custom string.
void setType(const QString &type);
const QString &type() const;
/// Set the hostname
void setHostName(const QString &hostName);
const std::optional<QString> &hostName() const;
/// Set the name of the database (path of the file for SQLite)
void setDatabaseName(const QString &databaseName);
const std::optional<QString> &databaseName() const;
/// Set user name
void setUserName(const QString &userName);
const std::optional<QString> &userName() const;
/// Set password
void setPassword(const QString &password);
const std::optional<QString> &password() const;
private:
QSharedDataPointer<DatabaseConfigurationPrivate> d;
};
///
/// The SQLite database driver
///
const QString DATABASE_TYPE_SQLITE = QStringLiteral("QSQLITE");
template <typename T>
concept FromSql = requires(T v, typename T::ColumnTypes row)
{
typename T::ColumnTypes;
{ std::tuple(row) } -> std::same_as<typename T::ColumnTypes>;
{ T::fromSql(row) } -> std::same_as<T>;
};
struct ThreadedDatabasePrivate;
class ThreadedDatabase : public QThread {
public:
///
/// \brief Connect to a database
/// \param configuration of the database connection
/// \return
///
static std::unique_ptr<ThreadedDatabase> establishConnection(DatabaseConfiguration config);
///
/// \brief Execute an SQL query on the database, ignoring the result.
/// \param SQL query string to execute
/// \param Parameters to bind to the placeholders in the SQL Query
/// \return
///
template <typename ...Args>
requires std::conjunction_v<std::is_convertible<Args, QVariant>...>
auto execute(const QString &sqlQuery, Args... args) -> QFuture<void> {
return db().execute(sqlQuery, args...);
}
///
/// Run database migrations in the given directory.
/// The directory needs to contain a subdirectory for each migration.
/// The subdirectories need to be named so that when sorted alphabetically the migrations will be run in the correct order.
/// Each subdirectory needs to contain a file named up.sql.
///
/// \param Directory which contains the migrations.
/// \return
///
auto runMigrations(const QString &migrationDirectory) -> QFuture<void> {
return db().runMigrations(migrationDirectory);
}
///
/// \brief Execute an SQL query on the database, retrieving the result.
/// \param SQL Query to execute
/// \param parameters to bind to the placeholders in the SQL query.
/// \return Future of a list of lists of variants.
///
/// T must provide a tuple of the column types as `using ColumnTypes = std::tuple<...>`
/// and a `static T fromSql(ColumnTypes tuple)` deserialization method.
///
template <typename T, typename ...Args>
requires FromSql<T> && std::conjunction_v<std::is_convertible<Args, QVariant>...>
auto getResults(const QString &sqlQuery, Args... args) -> QFuture<std::vector<T>> {
return db().getResults<T, Args...>(sqlQuery, args...);
}
///
/// \brief Like getResults, but for retrieving just one row.
///
template <typename T, typename ...Args>
requires FromSql<T> && std::conjunction_v<std::is_convertible<Args, QVariant>...>
auto getResult(const QString &sqlQuery, Args... args) -> QFuture<std::optional<T>> {
return db().getResult<T, Args...>(sqlQuery, args...);
}
ThreadedDatabase();
~ThreadedDatabase();
private:
asyncdatabase_private::AsyncSqlDatabase &db();
std::unique_ptr<ThreadedDatabasePrivate> d;
};
///
/// \brief Deserialize just a single value from a query result.
///
template <typename T>
struct SingleValue {
using ColumnTypes = std::tuple<T>;
static SingleValue fromSql(ColumnTypes tuple) {
auto [value] = tuple;
return SingleValue { value };
}
T value;
};
+179
View File
@@ -0,0 +1,179 @@
// SPDX-FileCopyrightText: 2022 Jonah Brüchert <[email protected]>
//
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <memory>
#include <tuple>
#include <optional>
#include <QFuture>
#include <QFutureWatcher>
#include <QSqlQuery>
#include <QSqlDatabase>
#include <QThread>
class DatabaseConfiguration;
namespace asyncdatabase_private {
// Helpers
template <typename Tuple, typename Func, std::size_t i>
inline constexpr void iterate_impl(Tuple &tup, Func fun)
{
if constexpr(i >= std::tuple_size_v<std::decay_t<decltype(tup)>>) {
return;
} else {
fun(std::get<i>(tup));
return asyncdatabase_private::iterate_impl<Tuple, Func, i + 1>(tup, fun);
}
}
template <typename Tuple, typename Func>
inline constexpr void iterate_tuple(Tuple &tup, Func fun)
{
asyncdatabase_private::iterate_impl<Tuple, Func, 0>(tup, fun);
}
template <typename T, typename QObjectDerivedType, typename Function>
void connectFuture(const QFuture<T> &future, QObjectDerivedType *self, const Function &fun) {
auto watcher = std::make_shared<QFutureWatcher<T>>();
watcher->setFuture(future);
QObject::connect(watcher.get(), &QFutureWatcherBase::finished, self, [self, watcher, fun, future] {
if constexpr (std::is_same_v<void, T>) {
if constexpr (std::is_member_function_pointer_v<Function>) {
fun->*(self);
} else {
fun();
}
} else if (future.resultCount() > 0) {
if constexpr (std::is_member_function_pointer_v<Function>) {
(self->*fun)(watcher->result());
} else {
fun(watcher->result());
}
}
});
}
using Row = std::vector<QVariant>;
using Rows = std::vector<Row>;
template <typename RowTypesTuple>
auto parseRow(const Row &row) -> RowTypesTuple
{
auto tuple = RowTypesTuple();
int i = 0;
asyncdatabase_private::iterate_tuple(tuple, [&](auto &elem) {
elem = row.at(i).value<std::decay_t<decltype(elem)>>();
i++;
});
return tuple;
}
template <typename RowTypesTuple>
auto parseRows(const Rows &rows) -> std::vector<RowTypesTuple> {
std::vector<RowTypesTuple> parsedRows;
parsedRows.reserve(rows.size());
std::ranges::transform(rows, std::back_inserter(parsedRows), parseRow<RowTypesTuple>);
return parsedRows;
}
void runDatabaseMigrations(QSqlDatabase &database, const QString &migrationDirectory);
void printSqlError(const QSqlQuery &query);
// non-template helper functions to allow patching a much as possible in the shared library
QSqlQuery prepareQuery(const QSqlDatabase &database, const QString &sqlQuery);
QSqlQuery runQuery(QSqlQuery &query);
struct AsyncSqlDatabasePrivate;
class AsyncSqlDatabase : public QObject {
Q_OBJECT
public:
QFuture<void> establishConnection(const DatabaseConfiguration &configuration);
template <typename T, typename ...Args>
auto getResults(const QString &sqlQuery, Args... args) -> QFuture<std::vector<T>> {
return runAsync([=, this] {
auto query = executeQuery(sqlQuery, args...);
const auto rows = parseRows<typename T::ColumnTypes>(retrieveRows(query));
std::vector<T> deserializedRows;
std::ranges::transform(rows, std::back_inserter(deserializedRows), T::fromSql);
return deserializedRows;
});
}
template <typename T, typename ...Args>
auto getResult(const QString &sqlQuery, Args... args) -> QFuture<std::optional<T>> {
return runAsync([=, this]() -> std::optional<T> {
auto query = executeQuery(sqlQuery, args...);
if (const auto row = retrieveOptionalRow(query)) {
return T::fromSql(parseRow<typename T::ColumnTypes>(*row));
}
return std::nullopt;
});
}
template <typename ...Args>
auto execute(const QString &sqlQuery, Args... args) -> QFuture<void> {
return runAsync([=, this] {
executeQuery(sqlQuery, args...);
});
}
auto runMigrations(const QString &migrationDirectory) -> QFuture<void> {
return runAsync([=, this] {
runDatabaseMigrations(db(), migrationDirectory);
});
}
AsyncSqlDatabase();
~AsyncSqlDatabase();
private:
template <typename ...Args>
QSqlQuery executeQuery(const QString &sqlQuery, Args... args) {
auto query = prepareQuery(db(), sqlQuery);
auto argsTuple = std::make_tuple<Args...>(std::move(args)...);
int i = 0;
asyncdatabase_private::iterate_tuple(argsTuple, [&](auto &arg) {
query.bindValue(i, arg);
i++;
});
return runQuery(query);
}
template <typename Functor>
QFuture<std::invoke_result_t<Functor>> runAsync(Functor func) {
using ReturnType = std::invoke_result_t<Functor>;
auto interface = std::make_shared<QFutureInterface<ReturnType>>();
QMetaObject::invokeMethod(this, [interface, func] {
if constexpr (!std::is_same_v<ReturnType, void>) {
auto result = func();
interface->reportResult(result);
} else {
func();
}
interface->reportFinished();
});
return interface->future();
}
Row retrieveRow(const QSqlQuery &query);
Rows retrieveRows(QSqlQuery &query);
std::optional<Row> retrieveOptionalRow(QSqlQuery &query);
QSqlDatabase &db();
std::unique_ptr<AsyncSqlDatabasePrivate> d;
};
}
+58 -106
View File
@@ -6,6 +6,10 @@
#include <QThread>
#include <QDebug>
#include <QJsonObject>
#include <QJsonArray>
#include <QFutureInterface>
#include <QCoreApplication>
#include <pybind11/embed.h>
@@ -13,10 +17,21 @@
namespace py = pybind11;
template <typename R, typename T, typename OP>
std::optional<R> map_optional(const std::optional<T> &optional, OP op) {
#include <iostream>
#include <algorithm>
#include <unordered_map>
#include <ranges>
#include <type_traits>
#include <memory>
template <typename T, typename OP>
std::optional<std::invoke_result_t<OP, T>> mapOptional(const std::optional<T> &optional, OP op) {
if (optional.has_value()) {
return op(optional.value());
if constexpr (std::is_member_function_pointer<OP>::value) {
return (&optional.value()->*op)();
} else {
return op(optional.value());
}
}
return std::nullopt;
@@ -37,15 +52,6 @@ AsyncYTMusic::AsyncYTMusic(QObject *parent)
qRegisterMetaType<std::vector<meta::Artist>>();
qRegisterMetaType<meta::Artist>();
connect(this, &AsyncYTMusic::startSearch, this, &AsyncYTMusic::internalSearch);
connect(this, &AsyncYTMusic::startFetchArtist, this, &AsyncYTMusic::internalFetchArtist);
connect(this, &AsyncYTMusic::startFetchAlbum, this, &AsyncYTMusic::internalFetchAlbum);
connect(this, &AsyncYTMusic::startFetchSong, this, &AsyncYTMusic::internalFetchSong);
connect(this, &AsyncYTMusic::startFetchPlaylist, this, &AsyncYTMusic::internalFetchPlaylist);
connect(this, &AsyncYTMusic::startFetchArtistAlbums, this, &AsyncYTMusic::internalFetchArtistAlbums);
connect(this, &AsyncYTMusic::startExtractVideoInfo, this, &AsyncYTMusic::internalExtractVideoInfo);
connect(this, &AsyncYTMusic::startFetchWatchPlaylist, this, &AsyncYTMusic::internalFetchWatchPlaylist);
connect(this, &AsyncYTMusic::errorOccurred, this, [](const QString &err) {
std::cerr << qPrintable(err);
});
@@ -54,139 +60,83 @@ AsyncYTMusic::AsyncYTMusic(QObject *parent)
//
// search
//
void AsyncYTMusic::search(const QString &query)
QFuture<std::vector<search::SearchResultItem>> AsyncYTMusic::search(const QString &query)
{
Q_EMIT startSearch(query);
}
void AsyncYTMusic::internalSearch(const QString &query)
{
try {
Q_EMIT searchFinished(m_ytm->search(query.toStdString()));
} catch (const py::error_already_set &err) {
Q_EMIT errorOccurred(QString::fromLocal8Bit(err.what()));
}
return invokeAndCatchOnThread([=, this]() {
return m_ytm->search(query.toStdString());
});
}
//
// fetchArtist
//
void AsyncYTMusic::fetchArtist(const QString &channelId)
QFuture<artist::Artist> AsyncYTMusic::fetchArtist(const QString &channelId)
{
Q_EMIT startFetchArtist(channelId);
}
void AsyncYTMusic::internalFetchArtist(const QString &channelId)
{
try {
Q_EMIT fetchArtistFinished(m_ytm->get_artist(channelId.toStdString()));
} catch (const py::error_already_set &err) {
Q_EMIT errorOccurred(QString::fromLocal8Bit(err.what()));
}
return invokeAndCatchOnThread([=, this]() {
return m_ytm->get_artist(channelId.toStdString());
});
}
//
// fetchAlbum
//
void AsyncYTMusic::fetchAlbum(const QString &browseId)
QFuture<album::Album> AsyncYTMusic::fetchAlbum(const QString &browseId)
{
Q_EMIT startFetchAlbum(browseId);
}
void AsyncYTMusic::internalFetchAlbum(const QString &browseId)
{
try {
Q_EMIT fetchAlbumFinished(m_ytm->get_album(browseId.toStdString()));
} catch (const py::error_already_set &err) {
Q_EMIT errorOccurred(QString::fromLocal8Bit(err.what()));
}
return invokeAndCatchOnThread([=, this]() {
return m_ytm->get_album(browseId.toStdString());
});
}
//
// fetchSong
//
void AsyncYTMusic::fetchSong(const QString &videoId)
QFuture<std::optional<song::Song>> AsyncYTMusic::fetchSong(const QString &videoId)
{
Q_EMIT startFetchSong(videoId);
}
void AsyncYTMusic::internalFetchSong(const QString &videoId)
{
try {
Q_EMIT fetchSongFinished(m_ytm->get_song(videoId.toStdString()));
} catch (const py::error_already_set &err) {
Q_EMIT errorOccurred(QString::fromLocal8Bit(err.what()));
}
return invokeAndCatchOnThread([=, this]() {
return m_ytm->get_song(videoId.toStdString());
});
}
//
// fetchPlaylist
//
void AsyncYTMusic::fetchPlaylist(const QString &playlistId) {
Q_EMIT startFetchPlaylist(playlistId);
}
void AsyncYTMusic::internalFetchPlaylist(const QString &playlistId)
{
try {
Q_EMIT fetchPlaylistFinished(m_ytm->get_playlist(playlistId.toStdString()));
} catch (const py::error_already_set &err) {
Q_EMIT errorOccurred(QString::fromLocal8Bit(err.what()));
}
QFuture<playlist::Playlist> AsyncYTMusic::fetchPlaylist(const QString &playlistId) {
return invokeAndCatchOnThread([=, this]() {
return m_ytm->get_playlist(playlistId.toStdString());
});
}
//
// fetchArtistAlbum
//
void AsyncYTMusic::fetchArtistAlbums(const QString &channelId, const QString &params)
QFuture<std::vector<artist::Artist::Album>> AsyncYTMusic::fetchArtistAlbums(const QString &channelId, const QString &params)
{
Q_EMIT startFetchArtistAlbums(channelId, params);
}
void AsyncYTMusic::internalFetchArtistAlbums(const QString &channelid, const QString &params)
{
try {
Q_EMIT fetchArtistAlbumsFinished(m_ytm->get_artist_albums(channelid.toStdString(), params.toStdString()));
} catch (const py::error_already_set &err) {
Q_EMIT errorOccurred(QString::fromLocal8Bit(err.what()));
}
return invokeAndCatchOnThread([=, this]() {
return m_ytm->get_artist_albums(channelId.toStdString(), params.toStdString());
});
}
//
// extractVideoInfo
//
void AsyncYTMusic::extractVideoInfo(const QString &videoId)
QFuture<video_info::VideoInfo> AsyncYTMusic::extractVideoInfo(const QString &videoId)
{
Q_EMIT startExtractVideoInfo(videoId);
}
void AsyncYTMusic::internalExtractVideoInfo(const QString &videoId)
{
try {
Q_EMIT extractVideoInfoFinished(m_ytm->extract_video_info(videoId.toStdString()));
} catch (const py::error_already_set &err) {
Q_EMIT errorOccurred(QString::fromLocal8Bit(err.what()));
}
return invokeAndCatchOnThread([=, this]() {
return m_ytm->extract_video_info(videoId.toStdString());
});
}
//
// fetchWatchPlaylist
//
void AsyncYTMusic::fetchWatchPlaylist(const std::optional<QString> &videoId, const std::optional<QString> &playlistId)
QFuture<watch::Playlist> AsyncYTMusic::fetchWatchPlaylist(const std::optional<QString> &videoId, const std::optional<QString> &playlistId)
{
Q_EMIT startFetchWatchPlaylist(videoId, playlistId);
}
void AsyncYTMusic::internalFetchWatchPlaylist(const std::optional<QString> &videoId, const std::optional<QString> &playlistId)
{
try {
Q_EMIT fetchWatchPlaylistFinished(m_ytm->get_watch_playlist(
map_optional<std::string>(videoId, [](const QString &value) { return value.toStdString(); }),
map_optional<std::string>(playlistId, [](const QString &value) { return value.toStdString(); })
));
} catch (const py::error_already_set &err) {
Q_EMIT errorOccurred(QString::fromLocal8Bit(err.what()));
}
return invokeAndCatchOnThread([=, this]() {
return m_ytm->get_watch_playlist(
mapOptional(videoId, &QString::toStdString),
mapOptional(playlistId, &QString::toStdString)
);
});
}
YTMusicThread &YTMusicThread::instance()
@@ -203,17 +153,19 @@ YTMusicThread::~YTMusicThread()
AsyncYTMusic *YTMusicThread::operator->()
{
return &m_ytm;
return m_ytm;
}
AsyncYTMusic &YTMusicThread::get()
{
return m_ytm;
return *m_ytm;
}
YTMusicThread::YTMusicThread()
: m_ytm(new AsyncYTMusic())
{
connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, m_ytm, &QObject::deleteLater);
setObjectName(QStringLiteral("YTMusicAPI"));
m_ytm.moveToThread(this);
m_ytm->moveToThread(this);
start();
}
+77 -40
View File
@@ -6,11 +6,15 @@
#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)
@@ -45,6 +49,53 @@ 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;
@@ -53,30 +104,22 @@ class AsyncYTMusic : public QObject
public:
// public functions need to be thread safe
void search(const QString &query);
Q_SIGNAL void searchFinished(std::vector<search::SearchResultItem>);
QFuture<std::vector<search::SearchResultItem>> search(const QString &query);
void fetchArtist(const QString &channelId);
Q_SIGNAL void fetchArtistFinished(artist::Artist);
QFuture<artist::Artist> fetchArtist(const QString &channelId);
void fetchAlbum(const QString &browseId);
Q_SIGNAL void fetchAlbumFinished(album::Album);
QFuture<album::Album> fetchAlbum(const QString &browseId);
void fetchSong(const QString &videoId);
Q_SIGNAL void fetchSongFinished(song::Song);
QFuture<std::optional<song::Song> > fetchSong(const QString &videoId);
void fetchPlaylist(const QString &playlistId);
Q_SIGNAL void fetchPlaylistFinished(playlist::Playlist);
QFuture<playlist::Playlist> fetchPlaylist(const QString &playlistId);
void fetchArtistAlbums(const QString &channelId, const QString &params);
Q_SIGNAL void fetchArtistAlbumsFinished(std::vector<artist::Artist::Album>);
QFuture<std::vector<artist::Artist::Album>> fetchArtistAlbums(const QString &channelId, const QString &params);
void extractVideoInfo(const QString &videoId);
Q_SIGNAL void extractVideoInfoFinished(video_info::VideoInfo);
QFuture<video_info::VideoInfo> extractVideoInfo(const QString &videoId);
void fetchWatchPlaylist(const std::optional<QString> &videoId = std::nullopt ,
QFuture<watch::Playlist> fetchWatchPlaylist(const std::optional<QString> &videoId = std::nullopt ,
const std::optional<QString> &playlistId = std::nullopt);
Q_SIGNAL void fetchWatchPlaylistFinished(watch::Playlist);
Q_SIGNAL void errorOccurred(const QString &error);
@@ -84,29 +127,23 @@ protected:
explicit AsyncYTMusic(QObject *parent = nullptr);
private:
Q_SIGNAL void startSearch(const QString &query);
Q_SLOT void internalSearch(const QString &query);
Q_SIGNAL void startFetchArtist(const QString &channelId);
Q_SLOT void internalFetchArtist(const QString &channelId);
Q_SIGNAL void startFetchAlbum(const QString &browseId);
Q_SLOT void internalFetchAlbum(const QString &browseId);
Q_SIGNAL void startFetchSong(const QString &videoId);
Q_SLOT void internalFetchSong(const QString &videoId);
Q_SIGNAL void startFetchPlaylist(const QString &playlistId);
Q_SLOT void internalFetchPlaylist(const QString &playlistId);
Q_SIGNAL void startFetchArtistAlbums(const QString &channelid, const QString &params);
Q_SLOT void internalFetchArtistAlbums(const QString &channelid, const QString &params);
Q_SIGNAL void startExtractVideoInfo(const QString &videoId);
Q_SLOT void internalExtractVideoInfo(const QString &videoId);
Q_SIGNAL void startFetchWatchPlaylist(const std::optional<QString> &videoId, const std::optional<QString> &playlistId);
Q_SLOT void internalFetchWatchPlaylist(const std::optional<QString> &videoId, const std::optional<QString> &playlistId);
/// Invokes the given function on the thread of the YTMusic object, and handles exceptions that occur while invoking it.
template <typename Func>
QFuture<std::invoke_result_t<Func>> invokeAndCatchOnThread(Func fun) {
using ReturnType = std::invoke_result_t<Func>;
auto interface = std::make_shared<QFutureInterface<ReturnType>>();
QMetaObject::invokeMethod(this, [=, this]() {
try {
ReturnType val = fun();
interface->reportResult(val);
interface->reportFinished();
} catch (const std::exception &err) {
interface->reportFinished();
Q_EMIT errorOccurred(QString::fromLocal8Bit(err.what()));
}
});
return interface->future();
}
// Python interpreter will be initialized from the thread calling the methods
Lazy<YTMusic> m_ytm;
@@ -123,5 +160,5 @@ public:
private:
YTMusicThread();
AsyncYTMusic m_ytm;
AsyncYTMusic *m_ytm;
};
+16
View File
@@ -21,8 +21,24 @@ 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
+21 -1
View File
@@ -14,6 +14,14 @@ 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
@@ -36,7 +44,15 @@ Kirigami.ScrollablePage {
}
onOpenVideo: (videoId, title) => {
askPlayVideoAudio(videoId, title)
videoPlayMessage.text = i18n("Video playback is not supported yet. Do you want to play only the audio of \"%1\"?", title)
videoPlayMessage.visible = true
videoPlayMessage.okAction = function() {
play(videoId)
videoPlayMessage.visible = false
}
videoPlayMessage.cancelAction = function() {
videoPlayMessage.visible = false
}
}
}
section.property: "type"
@@ -110,4 +126,8 @@ Kirigami.ScrollablePage {
visible: artistModel.loading
}
}
footer: ConfirmationMessage {
id: videoPlayMessage
}
}
+26
View File
@@ -0,0 +1,26 @@
// SPDX-FileCopyrightText: 2021 Dimitris Kardarakos <[email protected]>
//
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
import QtQuick 2.15
import org.kde.kirigami 2.14 as Kirigami
Kirigami.InlineMessage {
property var okAction
property var cancelAction
visible: false
actions: [
Kirigami.Action {
text: i18n("OK")
onTriggered: okAction()
},
Kirigami.Action {
text: i18n("Cancel")
onTriggered: cancelAction()
}
]
}
+159
View File
@@ -0,0 +1,159 @@
// SPDX-FileCopyrightText: 2022 Jonah Brüchert <[email protected]>
//
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
import QtQuick 2.1
import QtQuick.Controls 2.12 as Controls
import QtQuick.Layouts 1.3
import org.kde.kirigami 2.14 as Kirigami
import org.kde.ytmusic 1.0
Kirigami.ScrollablePage {
globalToolBarStyle: Kirigami.ApplicationHeaderStyle.None
// so that there is still a separator, since the header style is none
Kirigami.Separator {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
}
ColumnLayout {
RowLayout {
Layout.fillWidth: true
Kirigami.Heading {
text: i18n("Favourites")
Layout.alignment: Qt.AlignLeft
}
// Spacer
Item {
Layout.fillWidth: true
}
Controls.ToolButton {
text: i18n("Show All")
Layout.alignment: Qt.AlignRight
icon.name: "arrow-right"
onClicked: pageStack.layers.push("qrc:/PlaybackHistory.qml", {
"dataModel": Library.favourites,
"title": i18n("Favourite Songs")
})
}
}
Controls.ScrollView {
Layout.fillWidth: true
RowLayout {
Repeater {
Layout.fillWidth: true
model: Library.favourites
delegate: ColumnLayout {
id: delegateItem
required property string title
required property string artists
required property string videoId
Layout.fillWidth: false
Layout.maximumWidth: 200
Kirigami.Card {
id: card
Layout.preferredHeight: 200
onClicked: play(delegateItem.videoId)
ThumbnailSource {
id: thumbnailSource
videoId: delegateItem.videoId
}
header: Image {
sourceSize: "200x200"
source: thumbnailSource.cachedPath
fillMode: Image.PreserveAspectCrop
asynchronous: true
}
actions: [
Kirigami.Action {
icon.name: "delete"
onTriggered: Library.removeFavourite(delegateItem.videoId)
}
]
}
Controls.Label {
Layout.maximumWidth: 200
text: delegateItem.title
elide: Qt.ElideRight
}
}
}
}
}
RowLayout {
Layout.fillWidth: true
Kirigami.Heading {
text: i18n("Most played")
Layout.alignment: Qt.AlignLeft
}
// Spacer
Item {
Layout.fillWidth: true
}
Controls.ToolButton {
text: i18n("Show All")
Layout.alignment: Qt.AlignRight
icon.name: "arrow-right"
onClicked: pageStack.layers.push("qrc:/PlaybackHistory.qml", {
"dataModel": Library.playbackHistory,
"title": i18n("Played Songs")
})
}
}
Controls.ScrollView {
Layout.fillWidth: true
RowLayout {
Repeater {
Layout.fillWidth: true
model: Library.mostPlayed
delegate: ColumnLayout {
id: mpdelegateItem
required property string title
required property string artists
required property string videoId
Layout.fillWidth: false
Layout.maximumWidth: 200
Kirigami.Card {
id: mpcard
Layout.preferredHeight: 200
onClicked: play(mpdelegateItem.videoId)
ThumbnailSource {
id: mpthumbnailSource
videoId: mpdelegateItem.videoId
}
header: Image {
sourceSize: "200x200"
source: mpthumbnailSource.cachedPath
fillMode: Image.PreserveAspectCrop
asynchronous: true
}
}
Controls.Label {
Layout.maximumWidth: 200
text: mpdelegateItem.title
elide: Qt.ElideRight
}
}
}
}
}
}
}
+425
View File
@@ -0,0 +1,425 @@
// 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
@@ -0,0 +1,217 @@
// SPDX-FileCopyrightText: 2020-2022 Devin Lin <[email protected]>
//
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtGraphicalEffects 1.0
import QtMultimedia 5.12
import org.kde.kirigami 2.19 as Kirigami
import org.kde.ytmusic 1.0
Item {
id: root
required property int progressBarHeight
required property int minimizedPlayerContentHeight
required property var info // VideoInfoExtractor object
required property var audio // Audio object
readonly property bool isWidescreen: width >= Kirigami.Units.gridUnit * 40
signal requestOpen()
Rectangle {
id: miniProgressBar
z: 1
visible: !root.isWidescreen
anchors.top: parent.top
anchors.left: parent.left
height: root.progressBarHeight
color: Kirigami.Theme.highlightColor
width: parent.width * (audio.position / audio.duration)
}
RowLayout {
anchors.fill: parent
spacing: 0
Rectangle {
Layout.fillHeight: true
Layout.fillWidth: true
Layout.maximumWidth: Kirigami.Units.gridUnit * 20
Layout.preferredWidth: Kirigami.Units.gridUnit * 20
color: Qt.rgba(0, 0, 0, trackClick.containsMouse ? 0.1 : trackClick.pressed ? 0.3 : 0)
RowLayout {
anchors.fill: parent
anchors.leftMargin: Kirigami.Units.largeSpacing
spacing: Kirigami.Units.largeSpacing
// track image
Item {
property double imageSize: root.height - Kirigami.Units.largeSpacing * 2
Layout.alignment: Qt.AlignVCenter
Layout.maximumWidth: imageSize
Layout.preferredWidth: imageSize
Layout.maximumHeight: imageSize
Layout.minimumHeight: imageSize
Image {
id: mainIcon
anchors.fill: parent
visible: !loadingIndicator.visible
asynchronous: true
mipmap: true
source: info.thumbnail
sourceSize.width: 512
sourceSize.height: 512
fillMode: Image.PreserveAspectCrop
}
BusyIndicator {
id: loadingIndicator
anchors.centerIn: parent
visible: UserPlaylistModel.loading
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
Kirigami.Theme.inherit: false
}
}
// track information
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
Label {
id: mainLabel
text: info.title ? info.title : i18n("No media playing")
textFormat: Text.PlainText
wrapMode: Text.Wrap
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
Layout.fillWidth: true
horizontalAlignment: Text.AlignLeft
elide: Text.ElideRight
maximumLineCount: 1
// Hardcoded because the footerbar blur always makes a dark-ish
// background, so we don't want to use a color scheme color that
// might also be dark
color: "white"
font.weight: Font.Bold
}
Label {
id: authorLabel
text: info.artist ? info.artist : info.channel
textFormat: Text.PlainText
wrapMode: Text.Wrap
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
Layout.fillWidth: true
horizontalAlignment: Text.AlignLeft
elide: Text.ElideRight
maximumLineCount: 1
// Hardcoded because the footerbar blur always makes a dark-ish
// background, so we don't want to use a color scheme color that
// might also be dark
color: "white"
}
}
}
MouseArea {
id: trackClick
anchors.fill: parent
hoverEnabled: true
onClicked: root.requestOpen()
}
}
RowLayout {
visible: root.isWidescreen
Layout.preferredWidth: 0
Layout.fillHeight: true
Layout.fillWidth: true
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.rightMargin: Kirigami.Units.largeSpacing
spacing: Kirigami.Units.smallSpacing
Label {
Layout.alignment: Qt.AlignVCenter
color: "white"
visible: info.title
text: PlayerUtils.formatTimestamp(audio.position)
}
Slider {
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
from: 0
to: audio.duration
value: audio.position
enabled: audio.seekable
onMoved: {
console.log("Value:", value);
audio.seek(Math.floor(value));
}
Behavior on value {
NumberAnimation {
duration: 1000
}
}
}
Label {
Layout.alignment: Qt.AlignVCenter
color: "white"
visible: info.title
text: PlayerUtils.formatTimestamp(audio.duration)
}
}
ToolButton {
id: playPauseButton
Layout.preferredHeight: Kirigami.Units.gridUnit * 3
Layout.maximumWidth: height
Layout.preferredWidth: height
enabled: info.audioUrl != ""
onClicked: audio.playbackState === Audio.PlayingState ? audio.pause() : audio.play()
icon.name: audio.playbackState === Audio.PlayingState ? "media-playback-pause" : "media-playback-start"
icon.width: Kirigami.Units.gridUnit
icon.height: Kirigami.Units.gridUnit
icon.color: "white"
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
Kirigami.Theme.inherit: false
}
ToolButton {
id: skipForwardButton
Layout.rightMargin: Math.floor(Kirigami.Units.smallSpacing / 2)
Layout.preferredHeight: Kirigami.Units.gridUnit * 3
Layout.maximumWidth: height
Layout.preferredWidth: height
enabled: UserPlaylistModel.canSkip
onClicked: UserPlaylistModel.next()
icon.name: "media-skip-forward"
icon.width: Kirigami.Units.gridUnit
icon.height: Kirigami.Units.gridUnit
icon.color: "white"
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
Kirigami.Theme.inherit: false
}
}
}
+85
View File
@@ -0,0 +1,85 @@
// SPDX-FileCopyrightText: 2022 Jonah Brüchert <[email protected]>
//
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15 as Controls
import org.kde.kirigami 2.15 as Kirigami
import org.kde.ytmusic 1.0
Kirigami.ScrollablePage {
id: playbackHistoryPage
title: i18n("Unknown list of songs")
property QtObject dataModel
ListView {
id: listView
reuseItems: true
Kirigami.PlaceholderMessage {
visible: listView.count < 1
text: i18n("No songs here yet")
anchors.centerIn: parent
}
model: playbackHistoryPage.dataModel
delegate: Kirigami.SwipeListItem {
id: delegateItem
required property string title
required property string videoId
required property string artists
onClicked: {
play(videoId)
}
RowLayout {
ThumbnailSource {
id: thumbnailSource
videoId: delegateItem.videoId
}
Item {
width: Kirigami.Units.gridUnit * 3
height: Kirigami.Units.gridUnit * 3
Image {
sourceSize: "200x200"
source: thumbnailSource.cachedPath
fillMode: Image.PreserveAspectCrop
asynchronous: true
width: Kirigami.Units.gridUnit * 3
height: Kirigami.Units.gridUnit * 3
Layout.alignment: Qt.AlignLeft
}
}
Controls.Label {
text: delegateItem.title
Layout.fillWidth: true
}
}
actions: [
Kirigami.Action {
icon.name: "go-next"
text: i18n("Play Next")
onTriggered: UserPlaylistModel.playNext(delegateItem.videoId, delegateItem.title, [])
},
Kirigami.Action {
icon.name: "media-playlist-append"
text: i18n("Add to Playlist")
onTriggered: UserPlaylistModel.append(delegateItem.videoId, delegateItem.title, [])
}
]
}
}
}
+399 -228
View File
@@ -1,251 +1,422 @@
// SPDX-FileCopyrightText: 2021 Jonah Brüchert <[email protected]>
// SPDX-FileCopyrightText: 2021 Bart De Vries <[email protected]>
// SPDX-FileCopyrightText: 2020-2022 Devin Lin <[email protected]>
//
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
import QtQuick 2.7
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.12 as Controls
import QtMultimedia 5.12
import QtGraphicalEffects 1.0
import org.kde.kirigami 2.12 as Kirigami
import org.kde.ytmusic 1.0
import QtMultimedia 5.12
ColumnLayout {
id: footerLayout
Flickable {
id: footerItem
property bool maximized: false
readonly property int progressBarHeight: Kirigami.Units.gridUnit / 6
readonly property int minimizedPlayerContentHeight: Math.round(Kirigami.Units.gridUnit * 3.5)
readonly property int minimizedPlayerHeight: minimizedPlayerContentHeight + progressBarHeight
anchors.left: parent.left
anchors.right: parent.right
height: maximized ? applicationWindow().height : player.preferredHeight
boundsBehavior: Flickable.StopAtBounds
function close() {
toClose.restart();
}
GridLayout {
id: playerLayout
readonly property bool mobile: width > height
Layout.fillHeight: footerLayout.maximized
Layout.fillWidth: true
flow: width > height ? GridLayout.LeftToRight : GridLayout.TopToBottom
visible: footerLayout.maximized
Item {
Layout.fillWidth: info.thumbnail
Layout.fillHeight: info.thumbnail
Image {
anchors.fill: parent
source: info.thumbnail
fillMode: Image.PreserveAspectFit
function resetToBoundsOnFlick() {
if (!atYBeginning || !atYEnd) {
if (footerItem.verticalVelocity > 0) {
toOpen.restart();
} else if (footerItem.verticalVelocity < 0) {
toClose.restart();
} else { // i.e. when verticalVelocity === 0
if (contentY > contentHeight / 4) {
toOpen.restart();
} else {
toClose.restart();
}
}
}
}
function resetToBoundsOnResize() {
if (contentY > contentHeight / 4) {
contentY = contentHeight / 2;
} else {
contentY = 0;
}
}
NumberAnimation on contentY {
id: toOpen
from: contentY
to: contentHeight / 2
duration: Kirigami.Units.longDuration * 2
easing.type: Easing.OutCubic
running: false
}
NumberAnimation on contentY {
id: toClose
from: contentY
to: 0
duration: Kirigami.Units.longDuration * 2
easing.type: Easing.OutCubic
running: false
}
// snap to end
MouseArea {
anchors.fill: footerLayout
propagateComposedEvents: true
onPressed: {
toOpen.stop();
toClose.stop();
propagateComposedEvents = true;
}
onReleased: footerItem.resetToBoundsOnFlick()
}
onMovementStarted: {
toOpen.stop();
toClose.stop();
}
onFlickStarted: resetToBoundsOnFlick()
onMovementEnded: resetToBoundsOnFlick()
onHeightChanged: resetToBoundsOnResize()
property var videoInfoExtractor: VideoInfoExtractor {
id: info
onSongChanged: audio.play()
videoId: UserPlaylistModel.currentVideoId
onTitleChanged: Library.addPlaybackHistoryItem(info.videoId, info.title)
}
property var audioPlayer: Audio {
id: audio
source: info.audioUrl
onStatusChanged: {
if (status === Audio.EndOfMedia) {
console.log("Song ended");
UserPlaylistModel.next();
}
}
}
contentHeight: height * 2
ColumnLayout {
id: footerLayout
anchors.left: parent.left
anchors.right: parent.right
spacing: 0
// margin from top
Item {
Layout.minimumHeight: footerItem.height - footerItem.minimizedPlayerHeight
}
Controls.Control {
Layout.fillWidth: true
padding: 0
implicitHeight: footerItem.minimizedPlayerHeight
// minimized player background
background: Item {
Rectangle {
anchors.fill: parent
color: Qt.rgba(25, 25, 30, 1)
}
Image {
opacity: 0.2
source: info.thumbnail
asynchronous: true
anchors.fill: parent
fillMode: Image.PreserveAspectCrop
}
layer.enabled: true
layer.effect: HueSaturation {
cached: true
lightness: -0.5
saturation: 1.9
layer.enabled: true
layer.effect: FastBlur {
cached: true
radius: 64
transparentBorder: false
}
}
}
MinimizedPlayerControls {
progressBarHeight: footerItem.progressBarHeight
minimizedPlayerContentHeight: footerItem.minimizedPlayerContentHeight
height: minimizedPlayerHeight
focus: true
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
info: footerItem.videoInfoExtractor
audio: footerItem.audioPlayer
onRequestOpen: toOpen.start();
}
}
MaximizedPlayerPage {
id: maximizedPlayerPage
Layout.fillWidth: true
Layout.fillHeight: true
Controls.ScrollView {
anchors.fill: parent
ListView {
anchors.fill: parent
id: playlistView
clip: true
Controls.BusyIndicator {
anchors.centerIn: parent
visible: UserPlaylistModel.loading || UserPlaylistModel.loading
}
onCountChanged: {
if (count < 1) {
footerLayout.maximized = false
}
}
model: UserPlaylistModel
delegate: Kirigami.SwipeListItem {
id: delegateItem
required property string title
required property string videoId
required property string artists
required property bool isCurrent
highlighted: isCurrent
onClicked: UserPlaylistModel.skipTo(videoId)
ColumnLayout {
Layout.fillWidth: true
Kirigami.Heading {
elide: Text.ElideRight
Layout.fillWidth: true
level: 2
text: title
}
Controls.Label {
elide: Text.ElideRight
Layout.fillWidth: true
text: artists
}
}
actions: [
Kirigami.Action {
text: i18n("Remove Track")
icon.name: "list-remove"
onTriggered: UserPlaylistModel.remove(delegateItem.videoId)
}
]
}
header: Controls.ToolBar {
anchors.left: parent.left
anchors.right: parent.right
RowLayout {
anchors.fill: parent
Item {
Layout.fillWidth: true
}
Controls.ToolButton {
text: i18n("Clear")
icon.name: "edit-clear-all"
onClicked: UserPlaylistModel.clear()
}
Controls.ToolButton {
text: i18n("Shuffle")
icon.name: "media-playlist-shuffle"
onClicked: UserPlaylistModel.shufflePlaylist()
}
}
}
}
}
implicitHeight: footerItem.height
info: footerItem.videoInfoExtractor
audio: footerItem.audioPlayer
onRequestClose: toClose.start();
}
//GridLayout {
//id: playerLayout
//readonly property bool mobile: width > height
//Layout.fillHeight: footerItem.maximized
//Layout.fillWidth: true
//flow: width > height ? GridLayout.LeftToRight : GridLayout.TopToBottom
//visible: footerItem.maximized
//ColumnLayout {
//Layout.preferredWidth: footerLayout.width * (playerLayout.mobile ? 1 : 0.5)
//Layout.preferredHeight: footerLayout.height * (playerLayout.mobile ? 0.5 : 1)
//Item {
//Layout.fillWidth: true
//Layout.fillHeight: true
//Image {
//anchors.fill: parent
//source: info.thumbnail
//fillMode: Image.PreserveAspectFit
//}
//}
//Controls.RoundButton {
//readonly property QtObject favouriteWatcher: Library.favouriteWatcher(info.videoId)
//Layout.alignment: Qt.AlignHCenter
//icon.name: favouriteWatcher ? (favouriteWatcher.isFavourite ? "starred-symbolic" : "non-starred-symbolic") : ""
//onClicked: if (favouriteWatcher)
//favouriteWatcher.isFavourite
//? Library.removeFavourite(info.videoId)
//: Library.addFavourite(info.videoId, info.title)
//}
//}
//Item {
//Layout.fillWidth: true
//Layout.fillHeight: true
//Layout.preferredWidth: footerLayout.width * (playerLayout.mobile ? 1 : 0.5)
//Layout.preferredHeight: footerLayout.height * (playerLayout.mobile ? 0.5 : 1)
//Controls.ScrollView {
//anchors.fill: parent
//ListView {
//anchors.fill: parent
//id: playlistView
//clip: true
//Controls.BusyIndicator {
//anchors.centerIn: parent
//visible: UserPlaylistModel.loading || UserPlaylistModel.loading
//}
//onCountChanged: {
//if (count < 1) {
//footerItem.maximized = false
//}
//}
//model: UserPlaylistModel
//delegate: Kirigami.SwipeListItem {
//id: delegateItem
//required property string title
//required property string videoId
//required property string artists
//required property bool isCurrent
//highlighted: isCurrent
//onClicked: UserPlaylistModel.skipTo(videoId)
//ColumnLayout {
//Layout.fillWidth: true
//Kirigami.Heading {
//elide: Text.ElideRight
//Layout.fillWidth: true
//level: 2
//text: title
//}
//Controls.Label {
//elide: Text.ElideRight
//Layout.fillWidth: true
//text: artists
//}
//}
//actions: [
//Kirigami.Action {
//text: i18n("Remove Track")
//icon.name: "list-remove"
//onTriggered: UserPlaylistModel.remove(delegateItem.videoId)
//}
//]
//}
//header: Controls.ToolBar {
//anchors.left: parent.left
//anchors.right: parent.right
//RowLayout {
//anchors.fill: parent
//Item {
//Layout.fillWidth: true
//}
//Controls.ToolButton {
//text: i18n("Clear")
//icon.name: "edit-clear-all"
//onClicked: UserPlaylistModel.clear()
//}
//Controls.ToolButton {
//text: i18n("Shuffle")
//icon.name: "media-playlist-shuffle"
//onClicked: UserPlaylistModel.shufflePlaylist()
//}
//}
//}
//}
//}
//}
//}
//Kirigami.Separator {
//Layout.fillWidth: true
//}
//RowLayout {
//id: player
//Layout.fillWidth: true
//Layout.fillHeight: !footer.maximized
//Controls.ToolButton {
//display: Controls.AbstractButton.IconOnly
//Layout.preferredWidth: parent.height
//Layout.fillHeight: true
//enabled: info.audioUrl != ""
//text: audio.playbackState === Audio.PlayingState ? i18n("Pause") : i18n("Play")
//visible: !info.loading
//icon.name: audio.playbackState === Audio.PlayingState ? "media-playback-pause" : "media-playback-start"
//onClicked: audio.playbackState === Audio.PlayingState ? audio.pause() : audio.play()
//}
//Controls.ToolButton {
//display: Controls.AbstractButton.IconOnly
//Layout.preferredWidth: parent.height
//Layout.fillHeight: true
//enabled: UserPlaylistModel.canSkip
//visible: !info.loading
//icon.name: "media-skip-forward"
//onClicked: UserPlaylistModel.next()
//}
//Controls.BusyIndicator {
//Layout.fillHeight: true
//visible: info.loading
//}
//ColumnLayout {
//Layout.fillHeight: true
//Layout.topMargin: Kirigami.Units.smallSpacing
//Layout.bottomMargin: Kirigami.Units.smallSpacing
//Kirigami.Heading {
//level: 3
//Layout.fillWidth: true
//elide: Qt.ElideRight
//text: info.title ? info.title : i18n("No media playing")
//}
//RowLayout{
//Controls.Label{
//visible: info.title
//text: PlayerUtils.formatTimestamp(audio.position)
//}
//Controls.Slider {
//Layout.fillWidth: true
//from: 0
//to: audio.duration
//value: audio.position
//enabled: audio.seekable
//onMoved: {
//console.log("Value:", value);
//audio.seek(Math.floor(value));
//}
//Behavior on value {
//NumberAnimation {
//duration: 1000
//}
//}
//}
//Controls.Label{
//visible: info.title
//text: PlayerUtils.formatTimestamp(audio.duration)
//}
//}
//}
//Controls.ToolButton {
//display: Controls.AbstractButton.IconOnly
//Layout.preferredWidth: parent.height
//text: i18n("Expand")
//enabled: playlistView.count > 0
//Layout.fillHeight: true
//icon.name: footerItem.maximized ? "arrow-down" : "arrow-up"
//onClicked: footerItem.maximized = !footerItem.maximized
//}
//}
}
Kirigami.Separator {
Layout.fillWidth: true
}
RowLayout {
id: player
Layout.fillWidth: true
Layout.fillHeight: !footer.maximized
VideoInfoExtractor {
id: info
onSongChanged: audio.play()
videoId: UserPlaylistModel.currentVideoId
}
Audio {
id: audio
source: info.audioUrl
onStatusChanged: {
if (status === Audio.EndOfMedia) {
console.log("Song ended");
UserPlaylistModel.next();
}
}
}
Controls.ToolButton {
display: Controls.AbstractButton.IconOnly
Layout.preferredWidth: parent.height
Layout.fillHeight: true
enabled: info.audioUrl != ""
text: audio.playbackState === Audio.PlayingState ? i18n("Pause") : i18n("Play")
visible: !info.loading
icon.name: audio.playbackState === Audio.PlayingState ? "media-playback-pause" : "media-playback-start"
onClicked: audio.playbackState === Audio.PlayingState ? audio.pause() : audio.play()
}
Controls.ToolButton {
display: Controls.AbstractButton.IconOnly
Layout.preferredWidth: parent.height
Layout.fillHeight: true
enabled: UserPlaylistModel.canSkip
visible: !info.loading
icon.name: "media-skip-forward"
onClicked: UserPlaylistModel.next()
}
Controls.BusyIndicator {
Layout.fillHeight: true
visible: info.loading
}
ColumnLayout {
Layout.fillHeight: true
Layout.topMargin: Kirigami.Units.smallSpacing
Layout.bottomMargin: Kirigami.Units.smallSpacing
Kirigami.Heading {
level: 3
Layout.fillWidth: true
elide: Qt.ElideRight
text: info.title ? info.title : i18n("No media playing")
}
RowLayout{
Controls.Label{
visible: info.title
text: PlayerUtils.formatTimestamp(audio.position)
}
Controls.Slider {
Layout.fillWidth: true
from: 0
to: audio.duration
value: audio.position
enabled: audio.seekable
onMoved: {
console.log("Value:", value);
audio.seek(Math.floor(value));
}
Behavior on value {
NumberAnimation {
}
}
}
Controls.Label{
visible: info.title
text: PlayerUtils.formatTimestamp(audio.duration)
}
}
}
Controls.ToolButton {
display: Controls.AbstractButton.IconOnly
Layout.preferredWidth: parent.height
text: i18n("Expand")
enabled: playlistView.count > 0
Layout.fillHeight: true
icon.name: footerLayout.maximized ? "arrow-down" : "arrow-up"
onClicked: footerLayout.maximized = !footerLayout.maximized
}
}
Behavior on height {
NumberAnimation {
}
}
//Behavior on height {
//NumberAnimation {
//}
//}
}
+37 -4
View File
@@ -17,6 +17,27 @@ Kirigami.ScrollablePage {
ListView {
reuseItems: true
section.delegate: Kirigami.ListSectionHeader {
required property string section
text: {
switch (parseInt(section)) {
case SearchModel.Album:
return i18n("Albums")
case SearchModel.Artist:
return i18n("Artists")
case SearchModel.Playlist:
return i18n("Playlists")
case SearchModel.Song:
return i18n("Songs")
case SearchModel.Video:
return i18n("Videos")
}
return i18n("Unknown")
}
}
section.property: "type"
model: SearchModel {
id: searchModel
@@ -43,7 +64,15 @@ Kirigami.ScrollablePage {
}
onOpenVideo: (videoId, title) => {
askPlayVideoAudio(videoId, title)
videoPlayMessage.text = i18n("Video playback is not supported yet. Do you want to play only the audio of \"%1\"?", title)
videoPlayMessage.visible = true
videoPlayMessage.okAction = function() {
play(videoId)
videoPlayMessage.visible = false
}
videoPlayMessage.cancelAction = function() {
videoPlayMessage.visible = false
}
}
}
delegate: Kirigami.SwipeListItem {
@@ -86,13 +115,13 @@ Kirigami.ScrollablePage {
Kirigami.Action {
icon.name: "go-next"
text: i18n("Play next")
visible: delegateItem.type === SearchModel.Song
visible: delegateItem.type === SearchModel.Song || delegateItem.type == SearchModel.Video
onTriggered: UserPlaylistModel.playNext(delegateItem.videoId, delegateItem.title, delegateItem.artists)
},
Kirigami.Action {
icon.name: "media-playlist-append"
text: i18n("Add to Playlist")
visible: delegateItem.type === SearchModel.Song
visible: delegateItem.type === SearchModel.Song || delegateItem.type == SearchModel.Video
onTriggered: UserPlaylistModel.append(delegateItem.videoId, delegateItem.title, delegateItem.artists)
},
Kirigami.Action {
@@ -118,9 +147,13 @@ Kirigami.ScrollablePage {
text: i18n("Find music on YouTube Music")
helpfulAction: Kirigami.Action {
onTriggered: searchField.forceActiveFocus()
text: "Search"
text: i18n("Search")
icon.name: "search"
}
}
}
footer: ConfirmationMessage {
id: videoPlayMessage
}
}
+122 -27
View File
@@ -12,7 +12,10 @@ import org.kde.ytmusic 1.0
Kirigami.ApplicationWindow {
id: root
property alias searchField: searchField
property alias searchField: searchLoader.item // TODO
property bool wideScreen: width >= 600
property bool showSearch: false // only applicable if not widescreen
header: Controls.Control {
padding: Kirigami.Units.largeSpacing
@@ -25,37 +28,67 @@ Kirigami.ApplicationWindow {
Kirigami.Theme.colorSet: Kirigami.Theme.Header
color: Kirigami.Theme.backgroundColor
}
contentItem: RowLayout{
Item {
Layout.fillWidth: true
contentItem: RowLayout {
Controls.ToolButton {
id: searchBack
Layout.alignment: Qt.AlignLeft
visible:
(!root.wideScreen && root.showSearch && root.searchField.text === "")
|| pageStack.layers.depth > 1
text: i18n("Back")
icon.name: "go-previous-view"
display: Controls.ToolButton.IconOnly
onClicked: if (root.showSearch)
root.showSearch = false
else
pageStack.layers.pop()
}
Kirigami.SearchField {
id: searchField
delaySearch: true
selectByMouse: true
Kirigami.Heading {
Layout.alignment: Qt.AlignLeft
Layout.leftMargin: Kirigami.Units.largeSpacing
level: 1
text: "AudioTube"
visible: !root.wideScreen && !root.showSearch
}
// spacer
Item {
Layout.fillWidth: !root.wideScreen
}
Loader {
id: searchLoader
visible: root.wideScreen || root.showSearch
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
Layout.maximumWidth: 400
onAccepted: {
pageStack.clear()
pageStack.push("qrc:/SearchPage.qml", {
"searchQuery": text})
}
sourceComponent: searchFieldComponent
}
Item {
Layout.fillWidth: true
Controls.ToolButton {
Layout.alignment: Qt.AlignRight
visible: !root.wideScreen && !root.showSearch
text: i18n("Search")
icon.name: "search"
display: Controls.ToolButton.TextBesideIcon
onClicked: {
root.showSearch = true
root.searchField.forceActiveFocus();
root.searchField.popup.open();
}
}
}
}
title: i18n("AudioTube")
controlsVisible: !footer.maximized
contextDrawer: Kirigami.ContextDrawer {
id: contextDrawer
}
pageStack.initialPage: "qrc:/SearchPage.qml"
pageStack.initialPage: "qrc:/LibraryPage.qml"
function play(videoId) {
UserPlaylistModel.initialVideoId = videoId
@@ -70,14 +103,6 @@ Kirigami.ApplicationWindow {
UserPlaylistModel.playlistId = playlistId
}
function askPlayVideoAudio(videoId, title) {
showPassiveNotification(i18n("Video playback is not supported yet. Do you want to play only the audio of \"%1\"?", title),
"long",
i18n("Play only Audio"),
function() {
play(videoId)
})
}
Connections {
target: ErrorHandler
@@ -86,6 +111,76 @@ Kirigami.ApplicationWindow {
showPassiveNotification(error)
}
}
Component {
id: searchFieldComponent
Kirigami.SearchField {
id: searchField
delaySearch: true
selectByMouse: true
onPressed: {
popup.open()
}
property alias popup: popup
footer: PlayerFooter {}
Controls.Popup {
id: popup
x: searchField.y
y: searchField.y + searchField.height
width: searchField.width
height: completionList
? Math.min(completionList.count * Kirigami.Units.gridUnit * 2 + Kirigami.Units.gridUnit * 2, Kirigami.Units.gridUnit * 20)
: Kirigami.Units.gridUnit * 20
contentItem: Controls.ScrollView {
Controls.ScrollBar.horizontal.policy: Controls.ScrollBar.AlwaysOff
ListView {
id: completionList
model: Library.searches
delegate: Controls.ItemDelegate {
Kirigami.Theme.colorSet: Kirigami.Theme.Window
Kirigami.Theme.inherit: false
id: completionDelegate
width: parent.width
height: Kirigami.Units.gridUnit * 2
text: model.display
onClicked: {
searchField.text = model.display
searchField.accepted()
}
}
}
}
}
onAccepted: {
pageStack.clear()
pageStack.layers.clear()
if (text) {
Library.addSearch(text)
pageStack.push("qrc:/SearchPage.qml", {
"searchQuery": text})
} else {
pageStack.replace("qrc:/LibraryPage.qml")
}
popup.close()
}
}
}
pageStack.anchors.bottomMargin: playerFooter.minimizedPlayerHeight + 1
// media player
PlayerFooter {
id: playerFooter
anchors.topMargin: -root.header.height
anchors.fill: parent
// only expand flicking area to full screen when it is open
z: (contentY === 0) ? -1 : 999
}
}
+1 -1
View File
@@ -8,7 +8,7 @@
int main() {
const auto ytm = YTMusic();
const auto results = ytm.search("Thomas Bangalter");
const auto results = ytm.search("Another one bites the dust");
std::cout << "Found " << results.size() << " results." << std::endl;
+250
View File
@@ -0,0 +1,250 @@
// SPDX-FileCopyrightText: 2022 Jonah Brüchert <[email protected]>
//
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "library.h"
#include <QStandardPaths>
#include <QDir>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QStringBuilder>
#include "asyncdatabase.h"
namespace ranges = std::ranges;
Library::Library(QObject *parent)
: QObject{parent}
, m_database(ThreadedDatabase::establishConnection([]() -> DatabaseConfiguration {
const auto databaseDirectory = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
// Make sure the database directory exists
QDir(databaseDirectory).mkpath(QStringLiteral("."));
DatabaseConfiguration config;
config.setDatabaseName(databaseDirectory % QDir::separator() % "library.sqlite");
config.setType(DATABASE_TYPE_SQLITE);
return config;
}()))
{
m_database->runMigrations(":/migrations/");
}
Library::~Library() = default;
Library &Library::instance()
{
static Library inst;
return inst;
}
FavouritesModel *Library::favourites()
{
auto future = m_database->getResults<Song>("select * from favourites natural join songs 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
@@ -0,0 +1,187 @@
// SPDX-FileCopyrightText: 2022 Jonah Brüchert <[email protected]>
//
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <QObject>
#include <QQuickAsyncImageProvider>
#include <QNetworkAccessManager>
#include <QAbstractListModel>
#include <memory>
#include "asyncytmusic.h"
#include "asyncdatabase.h"
class FavouriteWatcher;
struct Song {
using ColumnTypes = std::tuple<QString, QString, QString, QString>;
static Song fromSql(ColumnTypes tuple) {
auto [videoId, title, artist, album] = tuple;
return Song {videoId, title, artist, album};
}
QString videoId;
QString title;
QString artist;
QString album;
};
class FavouritesModel : public QAbstractListModel {
Q_OBJECT
enum Roles {
VideoId = Qt::UserRole + 1,
Title,
Artist,
};
public:
FavouritesModel(QFuture<std::vector<Song>> &&songs, QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent) const override;
QVariant data(const QModelIndex &index, int role) const override;
private:
std::vector<Song> m_favouriteSongs;
};
struct PlayedSong {
using ColumnTypes = std::tuple<QString, int, QString, QString, QString>;
static PlayedSong fromSql(ColumnTypes tuple) {
auto [videoId, plays, title, artist, album] = tuple;
return PlayedSong {videoId, title, artist, album, plays};
}
QString videoId;
QString title;
QString artist;
QString album;
int plays;
};
class PlaybackHistoryModel : public QAbstractListModel {
Q_OBJECT
enum Roles {
VideoId = Qt::UserRole + 1,
Title,
Artist,
Plays
};
public:
PlaybackHistoryModel(QFuture<std::vector<PlayedSong>> &&songs, QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent) const override;
QVariant data(const QModelIndex &index, int role) const override;
private:
std::vector<PlayedSong> m_playedSongs;
};
class SearchHistoryModel : public QAbstractListModel {
Q_OBJECT
public:
SearchHistoryModel(QFuture<std::vector<SingleValue<QString>>> &&historyFuture, QObject *parent = nullptr);
int rowCount(const QModelIndex &parent) const override;
QVariant data(const QModelIndex &index, int role) const override;
private:
std::vector<SingleValue<QString>> m_history;
};
class Library : public QObject
{
Q_OBJECT
Q_PROPERTY(QAbstractListModel *favourites READ favourites NOTIFY favouritesChanged)
Q_PROPERTY(QAbstractListModel *searches READ searches NOTIFY searchesChanged)
Q_PROPERTY(QAbstractListModel *playbackHistory READ playbackHistory NOTIFY playbackHistoryChanged)
Q_PROPERTY(QAbstractListModel *mostPlayed READ mostPlayed NOTIFY playbackHistoryChanged)
public:
explicit Library(QObject *parent = nullptr);
~Library();
static Library &instance();
FavouritesModel *favourites();
Q_SIGNAL void favouritesChanged();
Q_INVOKABLE void addFavourite(const QString &videoId, const QString &title);
Q_INVOKABLE void removeFavourite(const QString &videoId);
Q_INVOKABLE FavouriteWatcher *favouriteWatcher(const QString &videoId);
SearchHistoryModel *searches();
Q_SIGNAL void searchesChanged();
Q_INVOKABLE void addSearch(const QString &text);
PlaybackHistoryModel *playbackHistory();
Q_SIGNAL void playbackHistoryChanged();
Q_INVOKABLE void addPlaybackHistoryItem(const QString &videoId, const QString &title);
PlaybackHistoryModel *mostPlayed();
QNetworkAccessManager &nam();
ThreadedDatabase &database() {
return *m_database;
}
private:
QFuture<void> addSong(const QString &videoId, const QString &title);
QNetworkAccessManager m_networkImageCacher;
std::unique_ptr<ThreadedDatabase> m_database;
};
class ThumbnailSource : public QObject {
Q_OBJECT
Q_PROPERTY(QString videoId READ videoId WRITE setVideoId NOTIFY videoIdChanged)
Q_PROPERTY(QUrl cachedPath READ cachedPath NOTIFY cachedPathChanged)
QString videoId() const {
return m_videoId;
}
void setVideoId(const QString &id);
Q_SIGNAL void videoIdChanged();
QUrl cachedPath() const {
return m_cachedPath;
}
void setCachedPath(const QUrl &path) {
m_cachedPath = path;
cachedPathChanged();
}
Q_SIGNAL void cachedPathChanged();
private:
QString m_videoId;
QUrl m_cachedPath;
};
class FavouriteWatcher : public QObject {
Q_OBJECT
Q_PROPERTY(bool isFavourite READ isFavourite NOTIFY isFavouriteChanged)
public:
FavouriteWatcher(Library *library, const QString &videoId);
bool isFavourite() const;
Q_SIGNAL void isFavouriteChanged();
Q_SIGNAL void videoIdChanged();
private:
QString m_videoId;
Library *m_library;
bool m_isFavourite = false;
};
+6
View File
@@ -19,6 +19,9 @@
#include "errorhandler.h"
#include "playlistmodel.h"
#include "playerutils.h"
#include "library.h"
#include "asyncdatabase.h"
constexpr auto URI = "org.kde.ytmusic";
@@ -51,6 +54,9 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
qmlRegisterSingletonType<PlayerUtils>(URI, 1, 0, "PlayerUtils", [](QQmlEngine *, QJSEngine *) {
return new PlayerUtils();
});
qmlRegisterSingletonInstance<Library>(URI, 1, 0, "Library", &Library::instance());
qmlRegisterType<ThumbnailSource>(URI, 1, 0, "ThumbnailSource");
qmlRegisterAnonymousType<FavouriteWatcher>(URI, 1);
engine.rootContext()->setContextObject(new KLocalizedContext(&engine));
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
View File
Whitespace-only changes.
@@ -0,0 +1,8 @@
-- SPDX-FileCopyrightText: 2022 Jonah Brüchert <[email protected]>
--
-- SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
drop table songs;
drop table played_songs;
drop table favourites;
drop table searches;
@@ -0,0 +1,26 @@
-- SPDX-FileCopyrightText: 2022 Jonah Brüchert <[email protected]>
--
-- SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
create table songs (
video_id Text primary key not null,
title Text not null,
artist Text,
album Text
);
create table played_songs (
video_id Text primary key not null,
plays Int not null,
foreign key (video_id) references songs(video_id)
);
create table favourites (
video_id Text primary key not null,
foreign key (video_id) references songs(video_id)
);
create table searches (
search_id integer primary key autoincrement not null,
search_query Text not null
);
+49 -52
View File
@@ -6,71 +6,68 @@
#include <variant>
#include <tuple>
#include <vector>
#include <span>
#include <QtGlobal>
// MultiIterableView is usually used on a small number of vectors.
// The number is already known, so we can convert this into an if else if else etc tree
// instead of recursively calling the function
#ifdef __GNUC__
#define REMOVE_RECURSION __attribute__((always_inline))
#else
#define REMOVE_RECURSION
#endif
template <typename Tuple, typename Func, std::size_t i>
inline constexpr void iterate_impl(Tuple &tup, Func fun)
{
if constexpr(i >= std::tuple_size_v<std::decay_t<decltype(tup)>>) {
return;
} else {
fun(std::get<i>(tup));
return iterate_impl<Tuple, Func, i + 1>(tup, fun);
}
}
template <typename Tuple, typename Func>
inline constexpr void iterate_tuple(Tuple &tup, Func fun)
{
iterate_impl<Tuple, Func, 0>(tup, fun);
}
/// MultiIterableView is a view over multiple contigous memory arrays, like std::vector or std::array.
/// The elements of the arrays do not need to be of the same type.
template <typename... Arguments>
class MultiIterableView {
private:
template<typename T, size_t i, size_t len>
REMOVE_RECURSION inline size_t count(const T &tuple, int initial_size = 0) const {
if constexpr(i < len) {
return count<T, i + 1, len>(tuple, initial_size + std::get<i>(tuple).size());
} else {
return initial_size;
}
}
template<typename T, size_t tuple_i, size_t tuple_len>
REMOVE_RECURSION inline std::variant<Arguments...> get_item(const T &tuple, size_t i) const {
if constexpr(tuple_i < tuple_len) {
if (i < std::get<tuple_i>(tuple).size()) {
return std::get<tuple_i>(tuple)[i];
} else {
return get_item<T, tuple_i + 1, tuple_len>(tuple, i - std::get<tuple_i>(tuple).size());
}
} else {
Q_UNREACHABLE();
}
}
template<typename T, size_t i, size_t len>
REMOVE_RECURSION inline bool everything_empty(const T &tuple, bool initial_state = true) const {
if constexpr(i < len) {
return everything_empty<T, i + 1, len>(tuple, initial_state && std::get<i>(tuple).empty());
} else {
return initial_state;
}
}
public:
explicit MultiIterableView(std::vector<Arguments> &&...lists)
: m_vectors(std::make_tuple(lists...))
explicit MultiIterableView(std::span<Arguments>... lists)
: m_vectors(std::forward_as_tuple(lists...))
{
}
size_t size() const {
constexpr size_t nvecs = std::tuple_size_v<decltype(m_vectors)>;
return count<decltype(m_vectors), 0, nvecs>(m_vectors);
explicit MultiIterableView(std::vector<Arguments> &...lists)
: MultiIterableView(std::span{lists}...)
{
}
std::variant<Arguments...> operator[](size_t i) const {
constexpr size_t nvecs = std::tuple_size_v<decltype(m_vectors)>;
return get_item<decltype(m_vectors), 0, nvecs>(m_vectors, i);
[[nodiscard]] constexpr size_t size() const {
size_t s = 0;
iterate_tuple(m_vectors, [&](auto &vec) {
s += vec.size();
});
return s;
}
bool empty() const {
constexpr size_t nvecs = std::tuple_size_v<decltype(m_vectors)>;
return everything_empty<decltype(m_vectors), 0, nvecs>(m_vectors);
[[nodiscard]] constexpr std::variant<Arguments...> operator[](size_t i) const {
size_t s = 0;
std::variant<Arguments...> out;
iterate_tuple(m_vectors, [&](auto &vec) {
if (i >= s && i < s + vec.size()) {
out = vec[i - s];
}
s += vec.size();
});
return out;
}
[[nodiscard]] constexpr bool empty() const {
bool empty = true;
iterate_tuple(m_vectors, [&](auto &elem) {
empty &= elem.empty();
});
return empty;
}
private:
std::tuple<std::vector<Arguments>...> m_vectors;
std::tuple<std::span<Arguments>...> m_vectors;
};
+12 -23
View File
@@ -11,21 +11,21 @@
#include "playlistutils.h"
PlaylistModel::PlaylistModel(QObject *parent)
: QAbstractListModel(parent)
: AbstractYTMusicModel(parent)
{
connect(this, &PlaylistModel::playlistIdChanged, this, [=] {
connect(this, &PlaylistModel::playlistIdChanged, this, [=, this] {
setLoading(true);
YTMusicThread::instance()->fetchPlaylist(m_playlistId);
});
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::fetchPlaylistFinished, this, [=](const playlist::Playlist &playlist) {
setLoading(false);
beginResetModel();
m_playlist = playlist;
std::sort(m_playlist.thumbnails.begin(), m_playlist.thumbnails.end());
endResetModel();
auto future = YTMusicThread::instance()->fetchPlaylist(m_playlistId);
connectFuture(future, this, [=, this](const playlist::Playlist &playlist) {
setLoading(false);
beginResetModel();
m_playlist = playlist;
std::sort(m_playlist.thumbnails.begin(), m_playlist.thumbnails.end());
endResetModel();
Q_EMIT titleChanged();
Q_EMIT thumbnailUrlChanged();
Q_EMIT titleChanged();
Q_EMIT thumbnailUrlChanged();
});
});
}
@@ -76,17 +76,6 @@ void PlaylistModel::setPlaylistId(const QString &playlistId)
Q_EMIT playlistIdChanged();
}
bool PlaylistModel::loading() const
{
return m_loading;
}
void PlaylistModel::setLoading(bool loading)
{
m_loading = loading;
Q_EMIT loadingChanged();
}
QUrl PlaylistModel::thumbnailUrl() const
{
if (m_playlist.thumbnails.empty()) {
+3 -7
View File
@@ -7,7 +7,9 @@
#include <QAbstractListModel>
#include <ytmusic.h>
class PlaylistModel : public QAbstractListModel
#include "abstractytmusicmodel.h"
class PlaylistModel : public AbstractYTMusicModel
{
Q_OBJECT
@@ -15,7 +17,6 @@ class PlaylistModel : public QAbstractListModel
Q_PROPERTY(QString playlistId READ playlistId WRITE setPlaylistId NOTIFY playlistIdChanged REQUIRED)
// output
Q_PROPERTY(bool loading READ loading WRITE setLoading NOTIFY loadingChanged)
Q_PROPERTY(QUrl thumbnailUrl READ thumbnailUrl NOTIFY thumbnailUrlChanged)
Q_PROPERTY(QString title READ title NOTIFY titleChanged)
@@ -36,10 +37,6 @@ public:
void setPlaylistId(const QString &playlistId);
Q_SIGNAL void playlistIdChanged();
bool loading() const;
void setLoading(bool loading);
Q_SIGNAL void loadingChanged();
QUrl thumbnailUrl() const;
Q_SIGNAL void thumbnailUrlChanged();
@@ -48,7 +45,6 @@ public:
private:
QString m_playlistId;
bool m_loading = false;
playlist::Playlist m_playlist {};
};
+5 -4
View File
@@ -15,11 +15,12 @@ namespace PlaylistUtils {
QString artistsToString(const std::vector<meta::Artist> &artists)
{
if (!artists.empty()) {
return std::accumulate(artists.begin() + 1, artists.end(),
QString::fromStdString(artists.front().name),
[](QString &string, const meta::Artist &artist) {
return string.append(QStringLiteral(", %1").arg(QString::fromStdString(artist.name)));
QString string = QString::fromStdString(artists.front().name);
std::for_each(artists.begin() + 1, artists.end(),
[&string](const meta::Artist &artist) {
string.append(QStringLiteral(", %1").arg(QString::fromStdString(artist.name)));
});
return string;
}
return {};
+7
View File
@@ -12,5 +12,12 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
<file alias="PlayerFooter.qml">contents/ui/PlayerFooter.qml</file>
<file alias="SearchPage.qml">contents/ui/SearchPage.qml</file>
<file alias="PlaylistPage.qml">contents/ui/PlaylistPage.qml</file>
<file alias="ConfirmationMessage.qml">contents/ui/ConfirmationMessage.qml</file>
<file alias="LibraryPage.qml">contents/ui/LibraryPage.qml</file>
<file alias="MinimizedPlayerControls.qml">contents/ui/MinimizedPlayerControls.qml</file>
<file alias="MaximizedPlayerPage.qml">contents/ui/MaximizedPlayerPage.qml</file>
<file>migrations/2022-05-20-194850_init/down.sql</file>
<file>migrations/2022-05-20-194850_init/up.sql</file>
<file alias="PlaybackHistory.qml">contents/ui/PlaybackHistory.qml</file>
</qresource>
</RCC>
+31 -21
View File
@@ -5,9 +5,9 @@
#include "searchmodel.h"
SearchModel::SearchModel(QObject *parent)
: QAbstractListModel(parent)
: AbstractYTMusicModel(parent)
{
connect(this, &SearchModel::searchQueryChanged, this, [=] {
connect(this, &SearchModel::searchQueryChanged, this, [this] {
if (m_searchQuery.isEmpty()) {
beginResetModel();
m_searchResults.clear();
@@ -16,15 +16,15 @@ SearchModel::SearchModel(QObject *parent)
}
setLoading(true);
YTMusicThread::instance()->search(m_searchQuery);
auto future = YTMusicThread::instance()->search(m_searchQuery);
connectFuture(future, this, [=, this](const std::vector<search::SearchResultItem> &results) {
beginResetModel();
setLoading(false);
m_searchResults = results;
endResetModel();
});
});
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::searchFinished, this, [=](const std::vector<search::SearchResultItem> &results) {
beginResetModel();
setLoading(false);
m_searchResults = results;
endResetModel();
});
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::errorOccurred, this, [=] {
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::errorOccurred, this, [this] {
setLoading(false);
});
}
@@ -146,17 +146,6 @@ void SearchModel::setSearchQuery(const QString &searchQuery)
Q_EMIT searchQueryChanged();
}
bool SearchModel::loading() const
{
return m_loading;
}
void SearchModel::setLoading(bool loading)
{
m_loading = loading;
Q_EMIT loadingChanged();
}
void SearchModel::triggerItem(int row)
{
std::visit([&](auto&& arg) {
@@ -176,3 +165,24 @@ void SearchModel::triggerItem(int row)
}
}, m_searchResults.at(row));
}
int SearchModel::itemType(search::SearchResultItem const &item)
{
return std::visit([&](auto &&arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr(std::is_same_v<T, search::Album>) {
return SearchModel::Type::Album;
} else if constexpr(std::is_same_v<T, search::Artist>) {
return SearchModel::Type::Artist;
} else if constexpr(std::is_same_v<T, search::Playlist>) {
return SearchModel::Type::Playlist;
} else if constexpr(std::is_same_v<T, search::Song>) {
return SearchModel::Type::Song;
} else if constexpr(std::is_same_v<T, search::Video>) {
return SearchModel::Type::Video;
} else {
Q_UNREACHABLE();
}
}, item);
}
+3 -7
View File
@@ -8,13 +8,13 @@
#include <QThread>
#include "asyncytmusic.h"
#include "abstractytmusicmodel.h"
class SearchModel : public QAbstractListModel
class SearchModel : public AbstractYTMusicModel
{
Q_OBJECT
Q_PROPERTY(QString searchQuery READ searchQuery WRITE setSearchQuery NOTIFY searchQueryChanged)
Q_PROPERTY(bool loading READ loading WRITE setLoading NOTIFY loadingChanged)
public:
enum Type {
@@ -44,10 +44,6 @@ public:
void setSearchQuery(const QString &searchQuery);
Q_SIGNAL void searchQueryChanged();
bool loading() const;
void setLoading(bool loading);
Q_SIGNAL void loadingChanged();
Q_INVOKABLE void triggerItem(int row);
Q_SIGNAL void openAlbum(const QString &browseId);
@@ -59,5 +55,5 @@ public:
private:
QString m_searchQuery;
std::vector<search::SearchResultItem> m_searchResults;
bool m_loading = false;
static int itemType(search::SearchResultItem const &item);
};
+27 -35
View File
@@ -9,29 +9,14 @@
#include <random>
#include <QRandomGenerator>
#include <QFutureWatcher>
#include "playlistutils.h"
UserPlaylistModel::UserPlaylistModel(QObject *parent)
: QAbstractListModel(parent)
: AbstractYTMusicModel(parent)
{
connect(this, &UserPlaylistModel::initialVideoIdChanged, this, [=] {
if (m_initialVideoId.isEmpty()) {
return;
}
setLoading(true);
YTMusicThread::instance()->fetchWatchPlaylist(m_initialVideoId);
});
connect(this, &UserPlaylistModel::playlistIdChanged, this, [=] {
if (m_playlistId.isEmpty()) {
return;
}
setLoading(true);
YTMusicThread::instance()->fetchWatchPlaylist(std::nullopt, m_playlistId);
});
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::fetchWatchPlaylistFinished, this, [=](const watch::Playlist &playlist) {
auto handleResult = [=, this](const watch::Playlist &playlist) {
setLoading(false);
beginResetModel();
@@ -48,8 +33,26 @@ UserPlaylistModel::UserPlaylistModel(QObject *parent)
} else {
setCurrentVideoId({});
}
};
connect(this, &UserPlaylistModel::initialVideoIdChanged, this, [=, this] {
if (m_initialVideoId.isEmpty()) {
return;
}
setLoading(true);
auto future = YTMusicThread::instance()->fetchWatchPlaylist(m_initialVideoId);
connectFuture(future, this, handleResult);
});
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::errorOccurred, this, [=] {
connect(this, &UserPlaylistModel::playlistIdChanged, this, [=, this] {
if (m_playlistId.isEmpty()) {
return;
}
setLoading(true);
auto future = YTMusicThread::instance()->fetchWatchPlaylist(std::nullopt, m_playlistId);
connectFuture(future, this, handleResult);;
});
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::errorOccurred, this, [this] {
setLoading(false);
});
}
@@ -98,21 +101,10 @@ void UserPlaylistModel::setInitialVideoId(const QString &videoId)
Q_EMIT initialVideoIdChanged();
}
bool UserPlaylistModel::loading() const
{
return m_loading;
}
void UserPlaylistModel::setLoading(bool loading)
{
m_loading = loading;
Q_EMIT loadingChanged();
}
QString UserPlaylistModel::nextVideoId() const
{
auto currentTrack = std::find_if(m_playlist.tracks.begin(), m_playlist.tracks.end(),
[=](const watch::Playlist::Track &track) {
[this](const watch::Playlist::Track &track) {
return track.video_id == m_currentVideoId.toStdString();
});
@@ -140,7 +132,7 @@ void UserPlaylistModel::setCurrentVideoId(const QString &videoId)
bool UserPlaylistModel::canSkip() const
{
const auto currentTrackIt = std::find_if(m_playlist.tracks.begin(), m_playlist.tracks.end(),
[=](const watch::Playlist::Track &track) {
[this](const watch::Playlist::Track &track) {
return track.video_id == m_currentVideoId.toStdString();
});
@@ -168,7 +160,7 @@ void UserPlaylistModel::skipTo(const QString &videoId)
void UserPlaylistModel::playNext(const QString &videoId, const QString &title, const std::vector<meta::Artist> &artists)
{
const auto currentIt = std::find_if(m_playlist.tracks.begin(), m_playlist.tracks.end(),
[=](const watch::Playlist::Track &track) {
[this](const watch::Playlist::Track &track) {
return track.video_id == m_currentVideoId.toStdString();
});
@@ -250,7 +242,7 @@ void UserPlaylistModel::shufflePlaylist()
// Only shuffle playlist after current track
if (!m_currentVideoId.isEmpty()) {
const auto currentIt = std::find_if(m_playlist.tracks.begin(), m_playlist.tracks.end(),
[=](const watch::Playlist::Track &track) {
[this](const watch::Playlist::Track &track) {
return track.video_id == m_currentVideoId.toStdString();
});
@@ -268,7 +260,7 @@ void UserPlaylistModel::emitCurrentVideoChanged(const QString &oldVideoId)
return track.video_id == oldVideoId.toStdString();
});
const auto currentVideoIt = std::find_if(m_playlist.tracks.begin(), m_playlist.tracks.end(),
[=](const watch::Playlist::Track &track) {
[this](const watch::Playlist::Track &track) {
return track.video_id == m_currentVideoId.toStdString();
});
+3 -7
View File
@@ -7,7 +7,9 @@
#include <ytmusic.h>
class UserPlaylistModel : public QAbstractListModel
#include "abstractytmusicmodel.h"
class UserPlaylistModel : public AbstractYTMusicModel
{
Q_OBJECT
@@ -17,7 +19,6 @@ class UserPlaylistModel : public QAbstractListModel
Q_PROPERTY(bool shuffle READ shuffle WRITE setShuffle NOTIFY shuffleChanged)
// output
Q_PROPERTY(bool loading READ loading WRITE setLoading NOTIFY loadingChanged)
Q_PROPERTY(QString currentVideoId READ currentVideoId NOTIFY currentVideoIdChanged)
Q_PROPERTY(bool canSkip READ canSkip NOTIFY canSkipChanged)
@@ -39,10 +40,6 @@ public:
void setInitialVideoId(const QString &videoId);
Q_SIGNAL void initialVideoIdChanged();
bool loading() const;
void setLoading(bool loading);
Q_SIGNAL void loadingChanged();
QString nextVideoId() const;
QString currentVideoId() const;
@@ -74,7 +71,6 @@ private:
QString m_initialVideoId;
QString m_playlistId;
QString m_currentVideoId;
bool m_loading = false;
bool m_shuffle = false;
watch::Playlist m_playlist;
+38 -22
View File
@@ -4,39 +4,46 @@
#include "videoinfoextractor.h"
#include <asyncytmusic.h>
#include <QFutureWatcher>
#include "asyncytmusic.h"
#include "library.h"
VideoInfoExtractor::VideoInfoExtractor(QObject *parent)
: QObject(parent)
{
connect(this, &VideoInfoExtractor::videoIdChanged, this, [=] {
connect(this, &VideoInfoExtractor::videoIdChanged, this, [this] {
if (m_videoId.isEmpty()) {
m_videoInfo = {};
Q_EMIT audioUrlChanged();
Q_EMIT titleChanged();
Q_EMIT artistChanged();
Q_EMIT channelChanged();
Q_EMIT songChanged();
Q_EMIT thumbnailChanged();
return;
}
setLoading(true);
YTMusicThread::instance()->extractVideoInfo(QString::fromStdString(m_videoId.toStdString()));
});
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::extractVideoInfoFinished, this, [=](const video_info::VideoInfo &info) {
m_videoInfo = info;
setLoading(false);
Q_EMIT audioUrlChanged();
Q_EMIT titleChanged();
Q_EMIT songChanged();
Q_EMIT thumbnailChanged();
auto future = YTMusicThread::instance()->extractVideoInfo(QString::fromStdString(m_videoId.toStdString()));
connectFuture(future, this, [this](const video_info::VideoInfo &videoInfo) {
m_videoInfo = videoInfo;
setLoading(false);
Q_EMIT audioUrlChanged();
Q_EMIT titleChanged();
Q_EMIT artistChanged();
Q_EMIT channelChanged();
Q_EMIT songChanged();
Q_EMIT thumbnailChanged();
});
});
}
QUrl VideoInfoExtractor::audioUrl() const
{
if (m_videoInfo.formats.empty()) {
return QUrl();
return {};
}
std::vector<video_info::Format> audioFormats;
@@ -48,17 +55,13 @@ QUrl VideoInfoExtractor::audioUrl() const
});
if (audioFormats.empty()) {
return QUrl();
return {};
}
struct {
bool operator()(const video_info::Format &a, const video_info::Format &b) {
return a.quality > b.quality;
}
} qualitySort;
std::sort(audioFormats.begin(), audioFormats.end(), qualitySort);
std::sort(audioFormats.begin(), audioFormats.end(),
[](const video_info::Format &a, const video_info::Format &b) {
return a.quality > b.quality;
});
return QUrl(QString::fromStdString(audioFormats.front().url));
}
@@ -70,6 +73,9 @@ QString VideoInfoExtractor::videoId() const
void VideoInfoExtractor::setVideoId(const QString &videoId)
{
if (m_videoId == videoId || videoId.isEmpty()) {
return;
}
m_videoId = videoId;
Q_EMIT videoIdChanged();
}
@@ -79,9 +85,19 @@ QString VideoInfoExtractor::title() const
return QString::fromStdString(m_videoInfo.title);
}
QString VideoInfoExtractor::artist() const
{
return QString::fromStdString(m_videoInfo.artist);
}
QString VideoInfoExtractor::channel() const
{
return QString::fromStdString(m_videoInfo.channel);
}
QString VideoInfoExtractor::thumbnail() const
{
return QString::fromStdString(m_videoInfo.thumnnail);
return QString::fromStdString(m_videoInfo.thumbnail);
}
bool VideoInfoExtractor::loading() const
+8
View File
@@ -17,6 +17,8 @@ class VideoInfoExtractor : public QObject
Q_PROPERTY(QUrl audioUrl READ audioUrl NOTIFY audioUrlChanged)
Q_PROPERTY(QString title READ title NOTIFY titleChanged)
Q_PROPERTY(QString artist READ artist NOTIFY artistChanged) // may be empty if it is a video
Q_PROPERTY(QString channel READ channel NOTIFY channelChanged)
Q_PROPERTY(QString thumbnail READ thumbnail NOTIFY thumbnailChanged)
Q_PROPERTY(bool loading READ loading WRITE setLoading NOTIFY loadingChanged)
@@ -32,6 +34,12 @@ public:
QString title() const;
Q_SIGNAL void titleChanged();
QString artist() const;
Q_SIGNAL void artistChanged();
QString channel() const;
Q_SIGNAL void channelChanged();
QString thumbnail() const;
Q_SIGNAL void thumbnailChanged();
+56 -45
View File
@@ -5,6 +5,7 @@
#include "ytmusic.h"
#include <algorithm>
#include <iostream>
#include <pybind11/embed.h>
#include <pybind11/stl.h>
@@ -13,9 +14,15 @@ namespace py = pybind11;
using namespace py::literals;
/// Useful for debugging
void pyPrintPretty(py::handle obj) {
auto json = py::module::import("json");
py::print(json.attr("dumps")(obj, "indent"_a=py::int_(4)));
}
#define UNEXPORT __attribute__ ((visibility("hidden")))
constexpr auto TESTED_YTMUSICAPI_VERSION = "0.17.3";
constexpr auto TESTED_YTMUSICAPI_VERSION = "0.22.0";
struct UNEXPORT YTMusicPrivate {
py::scoped_interpreter guard {};
@@ -47,7 +54,7 @@ struct UNEXPORT YTMusicPrivate {
py::object get_ytdl() {
// lazy initialization
if (ytdl.is_none()) {
ytdl = py::module::import("youtube_dl").attr("YoutubeDL")(py::dict());
ytdl = py::module::import("yt_dlp").attr("YoutubeDL")(py::dict());
}
return ytdl;
@@ -64,7 +71,7 @@ std::optional<T> optional_key(py::handle obj, const char *name) {
return std::nullopt;
}
return obj[name].cast<T>();
return obj[name].cast<std::optional<T>>();
}
meta::Thumbnail extract_thumbnail(py::handle thumbnail) {
@@ -82,28 +89,10 @@ meta::Artist extract_meta_artist(py::handle artist) {
};
};
album::Track extract_album_track(py::handle track) {
return {
track["index"].cast<std::string>(),
track["title"].cast<std::string>(),
track["artists"].cast<std::string>(),
track["videoId"].cast<std::optional<std::string>>(), // E rated songs don't have a videoId
track["lengthMs"].cast<std::optional<std::string>>(), //
track["likeStatus"].cast<std::optional<std::string>>()
};
}
video_info::Format extract_format(py::handle format) {
return {
optional_key<int>(format, "quality"),
format["url"].cast<std::string>(),
format["vcodec"].cast<std::string>(),
format["acodec"].cast<std::string>()
};
}
playlist::Track extract_playlist_track(py::handle track);
watch::Playlist::Track extract_watch_track(py::handle track);
album::Track extract_album_track(py::handle track);
video_info::Format extract_format(py::handle format);
template <typename T>
inline auto extract_py_list(py::handle obj) {
@@ -135,6 +124,27 @@ inline auto extract_py_list(py::handle obj) {
return output;
}
album::Track extract_album_track(py::handle track) {
return {
track["isExplicit"].cast<bool>(),
track["title"].cast<std::string>(),
extract_py_list<meta::Artist>(track["artists"]),
track["album"].cast<std::optional<std::string>>(),
track["videoId"].cast<std::optional<std::string>>(), // E rated songs don't have a videoId
track["duration"].cast<std::optional<std::string>>(), //
track["likeStatus"].cast<std::optional<std::string>>()
};
}
video_info::Format extract_format(py::handle format) {
return {
optional_key<int>(format, "quality"),
format["url"].cast<std::string>(),
format["vcodec"].cast<std::string>(),
format["acodec"].cast<std::string>()
};
}
meta::Album extract_meta_album(py::handle album) {
return meta::Album {
album["name"].cast<std::string>(),
@@ -280,7 +290,7 @@ std::optional<search::SearchResultItem> extract_search_result(py::handle result)
result["title"].cast<std::string>(),
result["type"].cast<std::string>(),
extract_py_list<meta::Artist>(result["artists"]),
result["year"].cast<std::string>(),
result["year"].cast<std::optional<std::string>>(),
result["isExplicit"].cast<bool>()
};
} else if (resultType == "playlist") {
@@ -299,7 +309,8 @@ std::optional<search::SearchResultItem> extract_search_result(py::handle result)
};
} else {
std::cerr << "Warning: Unsupported search result type found" << std::endl;
std::cerr << "It's called: " << resultType;
std::cerr << "It's called: " << resultType << std::endl;
pyPrintPretty(result);
return std::nullopt;
}
}
@@ -324,10 +335,11 @@ YTMusic::~YTMusic() = default;
std::vector<search::SearchResultItem> YTMusic::search(
const std::string &query,
const std::optional<std::string> &filter,
const std::optional<std::string> &scope,
const int limit,
const bool ignore_spelling) const
{
const auto results = d->get_ytmusic().attr("search")(query, filter, limit, ignore_spelling).cast<py::list>();
const auto results = d->get_ytmusic().attr("search")("query"_a=query, "filter"_a=filter, "scope"_a=scope, "limit"_a = limit, "ignore_spelling"_a = ignore_spelling).cast<py::list>();
std::vector<search::SearchResultItem> output;
for (const auto &result : results) {
@@ -343,11 +355,11 @@ artist::Artist YTMusic::get_artist(const std::string &channel_id) const
{
const auto artist = d->get_ytmusic().attr("get_artist")(channel_id);
return artist::Artist {
artist["description"].cast<std::optional<std::string>>(),
optional_key<std::string>(artist, "description"),
artist["views"].cast<std::optional<std::string>>(),
artist["name"].cast<std::string>(),
artist["channelId"].cast<std::string>(),
artist["subscribers"].cast<std::string>(),
artist["subscribers"].cast<std::optional<std::string>>(),
artist["subscribed"].cast<bool>(),
extract_py_list<meta::Thumbnail>(artist["thumbnails"]),
extract_artist_section<artist::Artist::Song>(artist, "songs"),
@@ -360,31 +372,28 @@ artist::Artist YTMusic::get_artist(const std::string &channel_id) const
album::Album YTMusic::get_album(const std::string &browseId) const
{
const auto album = d->get_ytmusic().attr("get_album")(browseId);
return {
album["title"].cast<std::string>(),
album["trackCount"].cast<std::string>(),
album["durationMs"].cast<std::string>(),
album["playlistId"].cast<std::string>(),
[&]() -> album::Album::ReleaseDate {
const auto date = album["releaseDate"];
return {
date["year"].cast<int>(),
date["month"].cast<int>(),
date["day"].cast<int>()
};
}(),
album["description"].cast<std::string>(),
album["trackCount"].cast<int>(),
album["duration"].cast<std::string>(),
album["audioPlaylistId"].cast<std::string>(),
album["year"].cast<std::string>(),
optional_key<std::string>(album, "description"),
extract_py_list<meta::Thumbnail>(album["thumbnails"]),
extract_py_list<album::Track>(album["tracks"])
extract_py_list<album::Track>(album["tracks"]),
extract_py_list<meta::Artist>(album["artists"])
};
}
song::Song YTMusic::get_song(const std::string &video_id) const
std::optional<song::Song> YTMusic::get_song(const std::string &video_id) const
{
const auto song = d->get_ytmusic().attr("get_song")(video_id);
return {
if (!song.cast<py::dict>().contains("videoId")) {
return std::nullopt;
}
return song::Song {
song["videoId"].cast<std::string>(),
song["title"].cast<std::string>(),
song["lengthSeconds"].cast<std::string>(),
@@ -452,10 +461,12 @@ video_info::VideoInfo YTMusic::extract_video_info(const std::string &video_id) c
using namespace pybind11::literals;
const auto info = d->get_ytdl().attr("extract_info")(video_id, "download"_a=py::bool_(false));
return {
info["id"].cast<std::string>(),
info["title"].cast<std::string>(),
info.contains("artist") ? info["artist"].cast<std::string>() : "",
info.contains("channel") ? info["channel"].cast<std::string>() : "",
extract_py_list<video_info::Format>(info["formats"]),
info["thumbnail"].cast<std::string>()
};
+18 -13
View File
@@ -67,7 +67,7 @@ struct Album {
std::string title;
std::string type;
std::vector<meta::Artist> artists;
std::string year;
std::optional<std::string> year;
bool is_explicit;
};
@@ -122,7 +122,7 @@ struct Artist {
struct Single {
std::string title;
std::vector<meta::Thumbnail> thumnnails;
std::vector<meta::Thumbnail> thumbnails;
std::string year;
std::string browse_id;
};
@@ -131,7 +131,7 @@ struct Artist {
std::optional<std::string> views;
std::string name;
std::string channel_id;
std::string subscribers;
std::optional<std::string> subscribers;
bool subscribed;
std::vector<meta::Thumbnail> thumbnails;
std::optional<Section<Song>> songs;
@@ -143,11 +143,12 @@ struct Artist {
namespace album {
struct Track {
std::string index;
bool is_explicit;
std::string title;
std::string artists;
std::vector<meta::Artist> artists;
std::optional<std::string> album;
std::optional<std::string> video_id;
std::optional<std::string> length_ms;
std::optional<std::string> duration;
std::optional<std::string> like_status;
};
@@ -159,13 +160,14 @@ namespace album {
};
std::string title;
std::string track_count;
std::string duration_ms;
std::string playlist_id;
ReleaseDate release_date;
std::string description;
int track_count;
std::string duration;
std::string audio_playlist_id;
std::string year;
std::optional<std::string> description;
std::vector<meta::Thumbnail> thumbnails;
std::vector<Track> tracks;
std::vector<meta::Artist> artists;
};
}
@@ -239,8 +241,10 @@ struct Format {
struct VideoInfo {
std::string id;
std::string title;
std::string artist;
std::string channel;
std::vector<Format> formats;
std::string thumnnail;
std::string thumbnail;
// More, but not interesting for us right now
};
@@ -277,6 +281,7 @@ public:
std::vector<search::SearchResultItem> search(const std::string &query,
const std::optional<std::string> &filter = std::nullopt,
const std::optional<std::string> &scope = std::nullopt,
const int limit = 20,
const bool ignore_spelling = false) const;
@@ -287,7 +292,7 @@ public:
album::Album get_album(const std::string &browse_id) const;
/// https://ytmusicapi.readthedocs.io/en/latest/reference.html#ytmusicapi.YTMusic.get_song
song::Song get_song(const std::string &video_id) const;
std::optional<song::Song> get_song(const std::string &video_id) const;
/// https://ytmusicapi.readthedocs.io/en/latest/reference.html#ytmusicapi.YTMusic.get_playlist
playlist::Playlist get_playlist(const std::string &playlist_id, int limit = 100) const;