Compare commits

...

109 Commits

Author SHA1 Message Date
089eff3152 Revert "chore(deps): update dependency electron-builder to v25"
This reverts commit fe4c89c349.
2024-09-07 20:22:27 +09:00
306ee8dba5 Bump version to 3.5.2 2024-09-07 19:09:15 +09:00
a4992bafb2 fix(synced-lyric): fix timestamp
- Close #2323
- Close #2379
2024-09-07 19:06:52 +09:00
5b004eedff fix(song-info): fix regex 2024-09-07 18:40:24 +09:00
2a66076d31 fix(synced-lyric): optimize logic 2024-09-07 18:37:37 +09:00
f48e46d29c chore(deps): update dependency eslint-plugin-import to v2.30.0 2024-09-07 18:29:22 +09:00
20296f5463 chore(deps): update dependency rollup to v4.21.2 2024-09-07 18:29:16 +09:00
fe4c89c349 chore(deps): update dependency electron-builder to v25 2024-09-07 18:29:09 +09:00
3640527c8c fix(synced-lyrics): LRCLIB returns 0 results if album is undefined
- inspired by #2381
2024-09-07 18:28:13 +09:00
3326582a16 chore(deps): update dependency electron to v32 2024-09-07 18:21:41 +09:00
5dcb9fe9ba fix(deps): update dependency i18next to v23.14.0 2024-09-07 18:19:15 +09:00
336fa1e6fd fix(deps): update dependency youtubei.js to v10.4.0 2024-09-07 18:19:09 +09:00
3679a109d6 chore(deps): update dependency esbuild to v0.23.1 2024-09-07 18:15:01 +09:00
5290ed3de2 chore(deps): update playwright monorepo to v1.47.0 2024-09-07 18:14:40 +09:00
fe5195714f chore(deps): update typescript-eslint monorepo to v8.4.0 (#2401)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-07 18:14:27 +09:00
8eb846262d chore(deps): update dependency @total-typescript/ts-reset to v0.6.1 (#2396)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-07 18:09:00 +09:00
e9a1c2a91f chore(deps): update dependency electron to v31.5.0 (#2397)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-07 18:08:41 +09:00
2d1f78b383 chore(deps): update dependency eslint-import-resolver-typescript to v3.6.3 (#2376)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-07 18:06:07 +09:00
1899064fd3 chore(deps): update dependency discord-api-types to v0.37.100 (#2394)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-07 18:06:00 +09:00
e0280e5fe2 fix(deps): update dependency electron-updater to v6.3.4 (#2395)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-07 18:05:54 +09:00
d577e0fba6 chore(deps): update dependency @babel/runtime to v7.25.6 (#2388)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-07 17:26:24 +09:00
37577c2f7f chore(deps): update dependency vite-plugin-inspect to v0.8.7 (#2389)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-07 17:26:16 +09:00
c880f0a4eb chore(i18n): Translated using Weblate (Portuguese)
Currently translated at 100.0% (382 of 382 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pt/
2024-09-05 21:09:15 +02:00
4875955914 chore(deps): update dependency discord-api-types to v0.37.99 (#2374)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-04 23:47:50 +09:00
5b28c780bd chore(deps): update dependency vite to v5.4.3 (#2377)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-04 23:47:42 +09:00
4db2674b15 chore(i18n): Translated using Weblate (Greek)
Currently translated at 52.6% (201 of 382 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/el/
2024-09-02 23:09:12 +00:00
Sen
a454a0163f chore(i18n): Translated using Weblate (Dutch)
Currently translated at 74.0% (283 of 382 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/nl/
2024-08-31 21:09:31 +02:00
61eb28e780 fix: incorrect regex when splitting artistName (#2378)
Fixes the incorrect regex used to split a string in the form of `Name1 & Name2` or `Name1, Name2`.

Previously the regex was actually splitting on `Name1 &, Name2`...
2024-08-29 07:48:18 +09:00
e165e64952 chore(i18n): Translated using Weblate (Hungarian)
Currently translated at 93.7% (358 of 382 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/hu/
2024-08-28 13:09:22 +00:00
f380822e11 chore(deps): update dependency @babel/runtime to v7.25.4 (#2373)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-28 12:22:29 +09:00
6b1995145a synced-lyrics: make the lyrics search more reliable (#2343)
* fix: comparing multiple artists on a single track

* fix: get song info on startup

* chore: also split on commas

* chore: re-apply .toLowerCase() on the artist names

* chore: remove redundant code

* chore: attempt at improving the initial videodata

* oops

* eureka!

* stuff
2024-08-28 12:21:58 +09:00
9317e99f43 fix(deps): update dependency solid-js to v1.8.22 (#2354)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-28 12:20:37 +09:00
66d05d8683 chore(deps): update typescript-eslint monorepo to v8.3.0 (#2350)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-28 12:20:26 +09:00
545a3a4bb6 fix(deps): update dependency electron-debug to v4.0.1 (#2349)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-28 12:20:07 +09:00
04a6d16dbe chore(deps): update dependency electron to v31.4.0 (#2356)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-28 12:19:53 +09:00
3c36477b1e fix: hide native-controls on linux when in-app-menu is used (#2366) 2024-08-28 12:19:30 +09:00
c5c191492e fix: detect the upgrade btn using the icon (#2364) 2024-08-23 07:13:55 +09:00
b12e2f607c chore(i18n): Translated using Weblate (Filipino)
Currently translated at 86.6% (331 of 382 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/fil/
2024-08-20 03:09:10 +00:00
1d77ad6de4 fix: exclude build-id files from rpm (#2361) 2024-08-19 07:46:05 +09:00
25bd26d7f3 chore(i18n): Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (382 of 382 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2024-08-17 12:09:10 +02:00
d11d0abe73 chore(i18n): Translated using Weblate (Japanese)
Currently translated at 100.0% (382 of 382 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ja/
2024-08-17 12:09:10 +02:00
8a643c465d chore(i18n): Translated using Weblate (Estonian)
Currently translated at 18.8% (72 of 382 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/et/
2024-08-15 22:09:31 +00:00
233673b8d8 chore(i18n): Translated using Weblate (Estonian)
Currently translated at 14.1% (54 of 382 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/et/
2024-08-13 21:09:13 +02:00
5a448fab31 chore(i18n): Translated using Weblate (Catalan)
Currently translated at 100.0% (382 of 382 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ca/
2024-08-13 21:09:12 +02:00
42e8262cda fix(deps): update dependency i18next to v23.12.3 (#2352)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-13 11:57:27 +09:00
f64769b1d3 fix(deps): update dependency @floating-ui/dom to v1.6.10 (#2340)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-12 12:12:08 +09:00
e12998761b fix(deps): update dependency electron-updater to v6.3.3 (#2347)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-12 12:11:55 +09:00
2e20fa83b8 chore(i18n): Translated using Weblate (German)
Currently translated at 99.2% (379 of 382 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/de/
2024-08-11 10:09:24 +00:00
5149757af3 chore(i18n): Translated using Weblate (German)
Currently translated at 99.2% (379 of 382 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/de/
2024-08-11 10:09:24 +00:00
655741f108 chore(i18n): Translated using Weblate (German)
Currently translated at 96.3% (368 of 382 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/de/
2024-08-10 11:41:12 +02:00
4e58571ad0 chore(i18n): Translated using Weblate (German)
Currently translated at 96.3% (368 of 382 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/de/
2024-08-10 11:41:12 +02:00
1e4a615b47 chore(i18n): Translated using Weblate (German)
Currently translated at 94.7% (362 of 382 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/de/
2024-08-10 11:39:20 +02:00
dedcf0c9ff fix(deps): update dependency solid-js to v1.8.20 (#2345)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-10 10:58:28 +09:00
a84a7d236a chore(deps): update dependency vite to v5.4.0 (#2342)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-10 10:54:05 +09:00
e56b4b21f0 chore(i18n): Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/
2024-08-09 16:09:15 +02:00
361f9e42bd chore(i18n): Translated using Weblate (Malay)
Currently translated at 19.6% (75 of 382 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ms/
2024-08-09 16:09:13 +02:00
918736d2ca chore(i18n): Translated using Weblate (German)
Currently translated at 94.2% (360 of 382 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/de/
2024-08-09 16:09:12 +02:00
8f3d5b08ac chore(i18n): Translated using Weblate (Russian)
Currently translated at 97.3% (372 of 382 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ru/
2024-08-08 00:09:19 +02:00
4ca327d801 chore(deps): update typescript-eslint monorepo to v8.0.1 (#2335)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-07 23:03:13 +09:00
8d0aa057ad fix(deps): update dependency @floating-ui/dom to v1.6.9 (#2337)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-07 23:02:23 +09:00
b7ffee089b chore(deps): update playwright monorepo to v1.46.0 (#2336)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-07 23:02:12 +09:00
d72b994f66 chore(README): Translation README to Russian and adding Synced Lyrics to main README (#2338)
* Translation of README.md to Russian and adding Synced Lyrics to default README.md

* Syntax fixes

* Sorting of Synced Lyrics Alphabetically
2024-08-07 23:01:49 +09:00
e6dafdb068 chore(i18n): Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/
2024-08-06 11:09:21 +02:00
14886dd4bd chore(i18n): Translated using Weblate (Catalan)
Currently translated at 100.0% (382 of 382 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ca/
2024-08-06 11:09:17 +02:00
c9f2f88bac chore(i18n): Translated using Weblate (Russian)
Currently translated at 97.3% (372 of 382 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ru/
2024-08-06 11:09:17 +02:00
1eabbc0bbe chore(i18n): Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (382 of 382 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hans/
2024-08-05 09:09:19 +02:00
32a572b35a fix(downloader): fix playabilityStatus 2024-08-04 16:33:28 +09:00
59a5679cbb fix(cache): use cacheNoArgs for better performance 2024-08-04 16:31:07 +09:00
ac51f798c3 fix(synced-lyric): fix album_name 2024-08-04 15:49:30 +09:00
7599cc694a Revert "fix(MPRIS): Prevents player to start with invalid MPRIS interface (#1996)"
This reverts commit eaf9d310aa.

Fix #2225
2024-08-04 15:26:14 +09:00
53595654b1 chore(adblocker): add comment 2024-08-04 14:50:14 +09:00
7656c41dbc fix(adblocker/inplayer): fix Response.prototype.json
fix #2310
2024-08-04 14:36:10 +09:00
ff0c5b87c9 chore(i18n): Translated using Weblate (Estonian)
Currently translated at 2.8% (11 of 382 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/et/
2024-08-03 22:09:19 +00:00
506c95740a chore(i18n): Translated using Weblate (Vietnamese)
Currently translated at 99.4% (380 of 382 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/vi/
2024-08-03 22:09:18 +00:00
575a42e28a chore(i18n): Translated using Weblate (Indonesian)
Currently translated at 100.0% (382 of 382 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/id/
2024-08-03 22:09:17 +00:00
dcdc6a825f chore(i18n): Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (382 of 382 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2024-08-03 22:09:16 +00:00
0a41bb1cd6 chore(i18n): Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (382 of 382 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2024-08-03 22:09:16 +00:00
72a4736dc9 chore(i18n): Translated using Weblate (Greek)
Currently translated at 47.3% (181 of 382 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/el/
2024-08-03 22:09:15 +00:00
f2b1e6b6bf chore(deps): update dependency rollup to v4.20.0 (#2326)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-03 18:47:56 +09:00
bc0e28ad6d chore(i18n): Added translation using Weblate (Estonian) 2024-08-02 23:59:14 +02:00
8ebae91c02 chore(i18n): Translated using Weblate (Hungarian)
Currently translated at 93.7% (358 of 382 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/hu/
2024-08-02 23:59:13 +02:00
954ad90733 chore(i18n): Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (382 of 382 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2024-08-02 23:59:13 +02:00
5af0643788 chore(i18n): Translated using Weblate (French)
Currently translated at 100.0% (382 of 382 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/fr/
2024-08-02 23:59:13 +02:00
be633ac1f2 chore(i18n): Translated using Weblate (Chinese (Traditional))
Currently translated at 99.7% (381 of 382 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2024-08-02 21:58:41 +02:00
0d047c1fd5 chore(i18n): Translated using Weblate (Chinese (Traditional))
Currently translated at 99.7% (381 of 382 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2024-08-02 21:58:41 +02:00
e8b1aca629 chore(i18n): Translated using Weblate (Turkish)
Currently translated at 100.0% (382 of 382 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/tr/
2024-08-02 21:58:41 +02:00
5b9bacf390 chore(i18n): Translated using Weblate (Filipino)
Currently translated at 85.3% (326 of 382 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/fil/
2024-08-02 14:09:23 +02:00
ccd16f4a5f chore(i18n): Translated using Weblate (Chinese (Traditional))
Currently translated at 93.4% (357 of 382 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2024-08-02 14:09:22 +02:00
02e519dab3 chore(i18n): Translated using Weblate (Spanish)
Currently translated at 100.0% (382 of 382 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2024-08-02 14:09:21 +02:00
473ea78f12 synced-lyrics: fix album name? 2024-08-01 15:46:53 +03:00
e7f366b770 Update changelog for v3.5.1 2024-08-01 11:49:06 +00:00
66816ac42d Bump version to 3.5.1 2024-08-01 20:39:44 +09:00
08b985f2ab chore(i18n): Translated using Weblate (Korean)
Currently translated at 100.0% (382 of 382 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2024-08-01 13:38:36 +02:00
747bde2136 fix(synced-lyrics): fix lyric load
fix #2295
2024-08-01 20:19:08 +09:00
eabc28b39f fix(wait-for-element): add 100ms timeout for improve performance 2024-08-01 20:19:08 +09:00
3537dc19ee chore(i18n): Translated using Weblate (Hungarian)
Currently translated at 97.8% (358 of 366 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/hu/
2024-08-01 10:57:05 +00:00
6afeb60557 chore(i18n): Translated using Weblate (Vietnamese)
Currently translated at 100.0% (366 of 366 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/vi/
2024-08-01 10:57:04 +00:00
71115dedee chore(i18n): Translated using Weblate (Italian)
Currently translated at 100.0% (366 of 366 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2024-08-01 10:57:04 +00:00
8750b54f76 fix(synced-lyrics): fix i18n 2024-08-01 19:56:32 +09:00
482a1c5073 fix(deps): update dependency youtubei.js to v10.3.0 (#2306)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-01 19:27:58 +09:00
c8d516c40b fix: Window gets stuck offscreen in some instances (#2303)
Improved offscreen detection logic, fixes th-ch#1894
2024-08-01 19:27:01 +09:00
c1ad168c32 fix: Incorrect window size on multi-monitor scaled displays (#2302)
see discussion on th-ch#2258
2024-08-01 19:26:37 +09:00
5f5be5d02f chore(deps): update dependency rollup to v4.19.2 (#2304)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-01 19:25:59 +09:00
61ef56dccc chore: enable sourcemaps in dev 2024-08-01 11:02:21 +03:00
a73b5acc75 chore(deps): update typescript-eslint monorepo to v8 (major) (#2297)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-01 16:37:08 +09:00
877573532c ts-fix: disambiguate ElectronStore typings 2024-08-01 10:10:52 +03:00
7b033b5caf fix(ambient-mode): fix ambient-mode not working for videos after restart (#2294)
* Fix Ambient Mode not working for videos after restart (#2255)

This should fix https://github.com/th-ch/youtube-music/issues/1641

* fix: fix waitForElement

---------

Co-authored-by: craftgeil <80261988+craftgeil@users.noreply.github.com>
2024-07-31 22:08:45 +09:00
8924ec29d3 fix(deps): update dependency @xhayper/discord-rpc to v1.2.0 (#2291)
* fix(deps): update dependency @xhayper/discord-rpc to v1.2.0

* fix: discord-rpc

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: JellyBrick <shlee1503@naver.com>
2024-07-31 21:57:37 +09:00
23e688aaf8 Update changelog for v3.5.0 2024-07-31 12:04:22 +00:00
60 changed files with 3137 additions and 1140 deletions

View File

@ -21,7 +21,7 @@
</a> </a>
</div> </div>
Read this in other languages: [🇰🇷](./docs/readme/README-ko.md), [🇮🇸](./docs/readme/README-is.md), [🇨🇱 🇪🇸](./docs/readme/README-es.md) Read this in other languages: [🇰🇷](./docs/readme/README-ko.md), [🇮🇸](./docs/readme/README-is.md), [🇨🇱 🇪🇸](./docs/readme/README-es.md), [🇷🇺](./docs/readme/README-ru.md)
**Electron wrapper around YouTube Music featuring:** **Electron wrapper around YouTube Music featuring:**
@ -141,6 +141,8 @@ Read this in other languages: [🇰🇷](./docs/readme/README-ko.md), [🇮🇸]
- [**SponsorBlock**](https://github.com/ajayyy/SponsorBlock): Automatically Skips non-music parts like intro/outro or - [**SponsorBlock**](https://github.com/ajayyy/SponsorBlock): Automatically Skips non-music parts like intro/outro or
parts of music videos where the song isn't playing parts of music videos where the song isn't playing
- **Synced Lyrics**: Provides synced lyrics to songs, using providers like [LRClib](https://lrclib.net).
- **Taskbar Media Control**: Control playback from - **Taskbar Media Control**: Control playback from
your [Windows taskbar](https://user-images.githubusercontent.com/78568641/111916130-24a35e80-8a82-11eb-80c8-5021c1aa27f4.png) your [Windows taskbar](https://user-images.githubusercontent.com/78568641/111916130-24a35e80-8a82-11eb-80c8-5021c1aa27f4.png)
@ -159,6 +161,7 @@ Read this in other languages: [🇰🇷](./docs/readme/README-ko.md), [🇮🇸]
- **Visualizer**: Different music visualizers - **Visualizer**: Different music visualizers
## Translation ## Translation
You can help with translation on [Hosted Weblate](https://hosted.weblate.org/projects/youtube-music/). You can help with translation on [Hosted Weblate](https://hosted.weblate.org/projects/youtube-music/).

View File

@ -2,8 +2,62 @@
All notable changes to this project will be documented in this file. Dates are displayed in UTC. All notable changes to this project will be documented in this file. Dates are displayed in UTC.
#### [v3.5.1](https://github.com/th-ch/youtube-music/compare/v3.5.0...v3.5.1)
- fix(deps): update dependency youtubei.js to v10.3.0 [`#2306`](https://github.com/th-ch/youtube-music/pull/2306)
- fix: Window gets stuck offscreen in some instances [`#2303`](https://github.com/th-ch/youtube-music/pull/2303)
- fix: Incorrect window size on multi-monitor scaled displays [`#2302`](https://github.com/th-ch/youtube-music/pull/2302)
- chore(deps): update dependency rollup to v4.19.2 [`#2304`](https://github.com/th-ch/youtube-music/pull/2304)
- chore(deps): update typescript-eslint monorepo to v8 (major) [`#2297`](https://github.com/th-ch/youtube-music/pull/2297)
- fix(ambient-mode): fix ambient-mode not working for videos after restart [`#2294`](https://github.com/th-ch/youtube-music/pull/2294)
- fix(deps): update dependency @xhayper/discord-rpc to v1.2.0 [`#2291`](https://github.com/th-ch/youtube-music/pull/2291)
- fix(synced-lyrics): fix lyric load [`#2295`](https://github.com/th-ch/youtube-music/issues/2295)
- fix(ambient-mode): fix ambient-mode not working for videos after restart (#2294) [`#1641`](https://github.com/th-ch/youtube-music/issues/1641)
- fix(synced-lyrics): fix i18n [`8750b54`](https://github.com/th-ch/youtube-music/commit/8750b54f766c735ff039c6be454427f17d4737e2)
- ts-fix: disambiguate ElectronStore typings [`8775735`](https://github.com/th-ch/youtube-music/commit/877573532c1b68af861a3fdc44d093f3097d36ab)
- chore(i18n): Translated using Weblate (Hungarian) [`3537dc1`](https://github.com/th-ch/youtube-music/commit/3537dc19eecce7f7deb2478942f70d3c7b72148d)
#### [v3.5.0](https://github.com/th-ch/youtube-music/compare/v3.4.1...v3.5.0)
> 31 July 2024
- plugin: Synced Lyrics [`#2207`](https://github.com/th-ch/youtube-music/pull/2207)
- chore(deps): update dependency electron to v31.3.1 [`#2290`](https://github.com/th-ch/youtube-music/pull/2290)
- chore(deps): update typescript-eslint monorepo to v7.18.0 [`#2292`](https://github.com/th-ch/youtube-music/pull/2292)
- fix(deps): update dependency youtubei.js to v10.2.0 [`#2285`](https://github.com/th-ch/youtube-music/pull/2285)
- chore(deps): update dependency electron to v31.3.0 [`#2282`](https://github.com/th-ch/youtube-music/pull/2282)
- chore(deps): update typescript-eslint monorepo to v7.17.0 [`#2283`](https://github.com/th-ch/youtube-music/pull/2283)
- fix(deps): update dependency solid-js to v1.8.19 [`#2280`](https://github.com/th-ch/youtube-music/pull/2280)
- fix(deps): update dependency @xhayper/discord-rpc to v1.1.4 [`#2279`](https://github.com/th-ch/youtube-music/pull/2279)
- chore(deps): update dependency @babel/runtime to v7.25.0 [`#2281`](https://github.com/th-ch/youtube-music/pull/2281)
- fix(deps): update dependency @floating-ui/dom to v1.6.8 [`#2278`](https://github.com/th-ch/youtube-music/pull/2278)
- Fix: Incorrect window size on scaled displays [`#2258`](https://github.com/th-ch/youtube-music/pull/2258)
- chore(deps): update dependency vite-plugin-resolve to v2.5.2 [`#2276`](https://github.com/th-ch/youtube-music/pull/2276)
- chore(deps): update playwright monorepo to v1.45.3 [`#2277`](https://github.com/th-ch/youtube-music/pull/2277)
- fix(deps): update dependency deepmerge-ts to v7.1.0 [`#2263`](https://github.com/th-ch/youtube-music/pull/2263)
- chore(deps): update dependency typescript to v5.5.4 [`#2274`](https://github.com/th-ch/youtube-music/pull/2274)
- chore(deps): update dependency vite to v5.3.5 [`#2275`](https://github.com/th-ch/youtube-music/pull/2275)
- fix(deps): update dependency i18next to v23.12.2 [`#2260`](https://github.com/th-ch/youtube-music/pull/2260)
- chore(deps): update dependency discord-api-types to v0.37.93 [`#2273`](https://github.com/th-ch/youtube-music/pull/2273)
- chore(deps): update dependency rollup to v4.19.1 [`#2261`](https://github.com/th-ch/youtube-music/pull/2261)
- fix(deps): update dependency custom-electron-prompt to v1.5.8 [`#2262`](https://github.com/th-ch/youtube-music/pull/2262)
- feat(adblocker): add new option AdSpeedup [`#2235`](https://github.com/th-ch/youtube-music/pull/2235)
- fix: disable multi-plane format for software video [`#2254`](https://github.com/th-ch/youtube-music/pull/2254)
- chore(deps): update dependency eslint-plugin-prettier to v5.2.1 [`#2253`](https://github.com/th-ch/youtube-music/pull/2253)
- chore(deps): update dependency vite to v5.3.4 [`#2243`](https://github.com/th-ch/youtube-music/pull/2243)
- chore(deps): update typescript-eslint monorepo to v7.16.1 [`#2239`](https://github.com/th-ch/youtube-music/pull/2239)
- chore(deps): update playwright monorepo to v1.45.2 [`#2244`](https://github.com/th-ch/youtube-music/pull/2244)
- chore(deps): update dependency vite-plugin-inspect to v0.8.5 [`#2252`](https://github.com/th-ch/youtube-music/pull/2252)
- fix(deps): update dependency semver to v7.6.3 [`#2250`](https://github.com/th-ch/youtube-music/pull/2250)
- chore(deps): update dependency electron to v31.2.1 [`#2241`](https://github.com/th-ch/youtube-music/pull/2241)
- chore(i18n): Translated using Weblate (Catalan) [`4a8440c`](https://github.com/th-ch/youtube-music/commit/4a8440c281c341977ab3687982cec8cbc5af6cf7)
- Update changelog for v3.4.1 [`18e0b1b`](https://github.com/th-ch/youtube-music/commit/18e0b1b86341b13f1cbc713bfbd7b5d7a45ee392)
- fix(synced-lyrics): fix type error [`9357a15`](https://github.com/th-ch/youtube-music/commit/9357a15116a8526d22ba6142c0a02f31688743f2)
#### [v3.4.1](https://github.com/th-ch/youtube-music/compare/v3.4.0...v3.4.1) #### [v3.4.1](https://github.com/th-ch/youtube-music/compare/v3.4.0...v3.4.1)
> 15 July 2024
- fix(mpris): fix mpris position [`#2225`](https://github.com/th-ch/youtube-music/issues/2225) - fix(mpris): fix mpris position [`#2225`](https://github.com/th-ch/youtube-music/issues/2225)
- fix(deb): fix depends [`#1983`](https://github.com/th-ch/youtube-music/issues/1983) - fix(deb): fix depends [`#1983`](https://github.com/th-ch/youtube-music/issues/1983)
- fix: fix touchbar icon [`#2183`](https://github.com/th-ch/youtube-music/issues/2183) - fix: fix touchbar icon [`#2183`](https://github.com/th-ch/youtube-music/issues/2183)

374
docs/readme/README-ru.md Normal file
View File

@ -0,0 +1,374 @@
<div align="center">
# YouTube Music
[![GitHub release](https://img.shields.io/github/release/th-ch/youtube-music.svg?style=for-the-badge&logo=youtube-music)](https://github.com/th-ch/youtube-music/releases/)
[![GitHub license](https://img.shields.io/github/license/th-ch/youtube-music.svg?style=for-the-badge)](https://github.com/th-ch/youtube-music/blob/master/LICENSE)
[![eslint code style](https://img.shields.io/badge/code_style-eslint-5ed9c7.svg?style=for-the-badge)](https://github.com/th-ch/youtube-music/blob/master/.eslintrc.js)
[![Build status](https://img.shields.io/github/actions/workflow/status/th-ch/youtube-music/build.yml?branch=master&style=for-the-badge&logo=youtube-music)](https://GitHub.com/th-ch/youtube-music/releases/)
[![GitHub All Releases](https://img.shields.io/github/downloads/th-ch/youtube-music/total?style=for-the-badge&logo=youtube-music)](https://GitHub.com/th-ch/youtube-music/releases/)
[![AUR](https://img.shields.io/aur/version/youtube-music-bin?color=blueviolet&style=for-the-badge&logo=youtube-music)](https://aur.archlinux.org/packages/youtube-music-bin)
[![Known Vulnerabilities](https://snyk.io/test/github/th-ch/youtube-music/badge.svg)](https://snyk.io/test/github/th-ch/youtube-music)
</div>
![Screenshot](web/screenshot.jpg "Screenshot")
<div align="center">
<a href="https://github.com/th-ch/youtube-music/releases/latest">
<img src="web/youtube-music.svg" width="400" height="100" alt="YouTube Music SVG">
</a>
</div>
**Клиент для YouTube Music основанный на Electron с поддержкой:**
- Нативный вид приложения, нацелен на сохранение оригинального интерфейса
- Фреймворк для пользовательских плагинов: изменяйте YouTube Music под ваши нужды (внешний вид, контент, возможности), включайте/выключайте плагины в один клик
## Демо-изображение
| Экран плеера (цветовая тема альбома & режим Ambient) |
|:---------------------------------------------------------------------------------------------------------:|
|![Screenshot1](https://github.com/th-ch/youtube-music/assets/16558115/53efdf73-b8fa-4d7b-a235-b96b91ea77fc)|
## Содержание
- [Возможности](#features)
- [Доступные плагины](#available-plugins)
- [Перевод](#translation)
- [Скачать](#download)
- [Arch Linux](#arch-linux)
- [MacOS](#macos)
- [Windows](#windows)
- [Как установить без подключения к интернету? (в Windows)](#how-to-install-without-a-network-connection-in-windows)
- [Темы](#themes)
- [Для разработчиков](#dev)
- [Создайте свои собственные плагины](#build-your-own-plugins)
- [Создание плагина](#creating-a-plugin)
- [Примеры использования](#common-use-cases)
- [Сборка](#build)
- [Предварительный просмотр](#production-preview)
- [Тестирование](#tests)
- [Лицензия](#license)
- [Часто задаваемые вопросы](#faq)
## Возможности:
- **Авто-подтверждение при паузе** (Всегда включено): отключает всплывающие уведомление ["Продолжить просмотр?"](https://user-images.githubusercontent.com/61631665/129977894-01c60740-7ec6-4bf0-9a2c-25da24491b0e.png),
которое приостанавливает воспроизведение через определённое время
- И больше ...
## Доступные плагины:
- **Блокировщик рекламы**: Блокирует всю рекламу и трекеры
- **Действия с альбомом**: Добавляет кнопки "Убрать дизлайк", "Дизлайк", "Лайк", "Убрать лайк" и применяет их действия ко всем трекам в плейлисте или альбоме
- **Цветовая тема альбома**: Применяет динамическую тему и эффекты, основываясь на цветовой палитре альбома
- **Режим Ambient**: Применяет световой эффект, проецируя нежные цвета из видео на задний фон вашего экрана
- **Нормализация аудио**: Применяет нормализацию к аудио (уменьшает громкость громких частей трека и повышает громкость тихих частей трека)
- **Размытие панели навигации**: Делает панель навигации прозрачной и размытой
- **Обход возрастных ограничений**: Обходит проверку возраста YouTube
- **Выбор субтитров**: Включить субтитры
- **Компактная боковая панель**: Всегда показывать боковую панель компактно
- **Плавный переход**: Плавный переход между треками
- **Отключить автопроигрыш**: Каждый трек начинается в режиме паузы
- **[Discord](https://discord.com/) Rich Presence**: Показывает вашим друзьям, что вы слушаете с помощью [Rich Presence](https://user-images.githubusercontent.com/28219076/104362104-a7a0b980-5513-11eb-9744-bb89eabe0016.png)
- **Загрузчик**: Загрузка MP3 [напрямую из интерфейса](https://user-images.githubusercontent.com/61631665/129977677-83a7d067-c192-45e1-98ae-b5a4927393be.png) [(youtube-dl)](https://github.com/ytdl-org/youtube-dl)
- **Расширенная громкость**: Делает слайдер громкости [расширенным](https://greasyfork.org/en/scripts/397686-youtube-music-fix-volume-ratio/) облегчая выбор громкости
- **Меню в приложении**: [Придаёт панели меню красивый тёмный вид](https://user-images.githubusercontent.com/78568641/112215894-923dbf00-8c29-11eb-95c3-3ce15db27eca.png)
> (посмотрите [этот пост,](https://github.com/th-ch/youtube-music/issues/410#issuecomment-952060709) если у вас есть проблемы с доступом к меню после включения этого плагина и опции "Скрыть меню")
- **Скробблер**: Добавляет поддержку скробблинга [Last.fm](https://www.last.fm/) и [ListenBrainz](https://listenbrainz.org/)
- **Lumia Stream**: Добавляет поддержку [Lumia Stream](https://lumiastream.com/)
- **Тесты песен Genius**: Добавляет поддержку текстов для большинства песен
- **Music Together**: Делитесь плейлистом с другими. Когда ведущий воспроизводит трек, все остальные будут слушать этот же трек.
- **Навигация**: Кнопки Назад/Вперед интегрированы в интерфейс, как в вашем любимом браузере
- **Без входа в систему Google**: Убирает из интерфейса кнопки и ссылки для входа через Google
- **Уведомления**: Показывает уведомление, когда трек начинает играть ([интерактивные уведомления](https://user-images.githubusercontent.com/78568641/114102651-63ce0e00-98d0-11eb-9dfe-c5a02bb54f9c.png) доступны только для Windows)
- **Картинка в картинке**: Позволяет переключить приложение в режим "картинка в картинке"
- **Скорость воспроизведения**: Слушайте быстрее, слушайте медленнее! [Добавляет слайдер для контроля скорости трека](https://user-images.githubusercontent.com/61631665/129976003-e55db5ba-bf42-448c-a059-26a009775e68.png)
- **Точная громкость**: Точечно управляйте громкостью с помощью колеса мыши/горячих клавиш, с кастомным интерфейсом и настраиваемыми шагами громкости
- **Ярлыки (и MPRIS)**: Позволяет настроить глобальные горячие клавиши управления воспроизведением (плей/пауза/следующий/предыдущий) + отключает [отображение медиа на экране,](https://user-images.githubusercontent.com/84923831/128601225-afa38c1f-dea8-4209-9f72-0f84c1dd8b54.png) переопределяя клавиши управления + включает Ctrl/CMD + F для поиска + включает поддержку linux mpris для клавиш управления медиа + [настраиваемые сочетания клавиш](https://github.com/Araxeus/youtube-music/blob/1e591d6a3df98449bcda6e63baab249b28026148/providers/song-controls.js#L13-L50) для [продвинутых пользователей](https://github.com/th-ch/youtube-music/issues/106#issuecomment-952156902)
- **Пропускать непонравившиеся треки**: Пропускает непонравившиеся треки
- **Пропуск тишины**: Автоматически пропускает тихие моменты в песнях
- [**SponsorBlock**](https://github.com/ajayyy/SponsorBlock): Автоматически пропускает немузыкальные части, такие как интро/аутро музыкальных видео, где трек не играет
- **Управление воспроизведением из панели задач**: Управляйте воспроизведением из [панели задач Windows](https://user-images.githubusercontent.com/78568641/111916130-24a35e80-8a82-11eb-80c8-5021c1aa27f4.png)
- **TouchBar**: Кастомная раскладка TouchBar для MacOS
- **Tuna OBS**: Интеграция с [OBS](https://obsproject.com/) плагином [Tuna](https://obsproject.com/forum/resources/tuna.843/)
- **Изменение качества видео**: Позволяет менять качество видео [кнопкой](https://user-images.githubusercontent.com/78568641/138574366-70324a5e-2d64-4f6a-acdd-dc2a2b9cecc5.png) на медиаплеере видео
- **Переключатель видео**: Добавляет
[кнопку](https://user-images.githubusercontent.com/28893833/173663950-63e6610e-a532-49b7-9afa-54cb57ddfc15.png) переключения режимов Трек/Видео. Также может удалять вкладку "Видео" полностью
- **Визуализатор**: Различные визуализаторы музыки
- **Synced Lyrics**:
Предоставляет синхронизированные слова для песен из таких источников, как [LRClib](https://lrclib.net).
## Перевод
Вы можете помочь с переводом на ваш язык на [Hosted Weblate](https://hosted.weblate.org/projects/youtube-music/).
<a href="https://hosted.weblate.org/engage/youtube-music/">
<img src="https://hosted.weblate.org/widget/youtube-music/i18n/multi-auto.svg" alt="translation status" />
<img src="https://hosted.weblate.org/widget/youtube-music/i18n/287x66-black.png" alt="translation status 2" />
</a>
## Скачать
Вы можете посмотреть [latest release,](https://github.com/th-ch/youtube-music/releases/latest) чтобы быстро найти новую версию.
### Arch Linux
Установите пакет [`youtube-music-bin`](https://aur.archlinux.org/packages/youtube-music-bin) из AUR. Инструкции по установке из AUR можете найти на этой [вики-странице](https://wiki.archlinux.org/index.php/Arch_User_Repository#Installing_packages).
### macOS
Вы можете установить приложение с помощью Homebrew (сморите [cask definition](https://github.com/th-ch/homebrew-youtube-music)):
```bash
brew install th-ch/youtube-music/youtube-music
```
Если вы устанавливаете приложение вручную и получаете ошибку "is damaged and cant be opened.", запустите в терминале следующую команду:
```bash
xattr -cr /Applications/YouTube\ Music.app
```
### Windows
Вы можете использовать [пакетный менеджер Scoop](https://scoop.sh) для установки пакета `youtube-music` из [`extras` bucket](https://github.com/ScoopInstaller/Extras).
```bash
scoop bucket add extras
scoop install extras/youtube-music
```
Также для установки вы можете использовать [Winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/), официальный пакетный менеджер командной строки Windows 11, для установки пакета `th-ch.YouTubeMusic`.
*К сведению: SmartScreen защитника Windows может блокировать установку, так как она от "неизвестного издателя". Это также применимо к методу ручной установки, когда вы пытаетесь запустить исполняемый файл(.exe) после загрузки здесь, на GitHub (тот же файл).*
```bash
winget install th-ch.YouTubeMusic
```
#### Установка без подключения к Интернету? (в Windows)
- Скачайте файл `*.nsis.7z` из _архетиктура вашего устройства_ на [release page](https://github.com/th-ch/youtube-music/releases/latest).
- `x64` для 64-bit Windows
- `ia32` для 32-bit Windows
- `arm64` для ARM64 Windows
- Скачайте установщик в release page. (`*-Setup.exe`)
- Поместите их в **одной директории**.
- Запустите установщик.
## Темы
Вы можете загрузить файл CSS для смены внешнего вида приложения (Настройки > Визуальные настройки > Тема).
Некоторые предустановленные темы доступны здесь: https://github.com/kerichdev/themes-for-ytmdesktop-player.
## Для разработчиков
```bash
git clone https://github.com/th-ch/youtube-music
cd youtube-music
pnpm install --frozen-lockfile
pnpm dev
```
## Создайте свои собственные плагины
Используя плагины вы можете:
- Манипулировать приложением - `BrowserWindow` из electron проброшен обработчику плагинов
- Изменять внешний вид, манипулируя HTML/CSS
### Создание плагина
Создайте директорию в `src/plugins/YOUR-PLUGIN-NAME`:
- `index.ts`: основной файл плагина
```typescript
import style from './style.css?inline'; // import style as inline
import { createPlugin } from '@/utils';
export default createPlugin({
name: 'Plugin Label',
restartNeeded: true, // if value is true, ytmusic show restart dialog
config: {
enabled: false,
}, // your custom config
stylesheets: [style], // your custom style,
menu: async ({ getConfig, setConfig }) => {
// All *Config methods are wrapped Promise<T>
const config = await getConfig();
return [
{
label: 'menu',
submenu: [1, 2, 3].map((value) => ({
label: `value ${value}`,
type: 'radio',
checked: config.value === value,
click() {
setConfig({ value });
},
})),
},
];
},
backend: {
start({ window, ipc }) {
window.maximize();
// you can communicate with renderer plugin
ipc.handle('some-event', () => {
return 'hello';
});
},
// it fired when config changed
onConfigChange(newConfig) { /* ... */ },
// it fired when plugin disabled
stop(context) { /* ... */ },
},
renderer: {
async start(context) {
console.log(await context.ipc.invoke('some-event'));
},
// Only renderer available hook
onPlayerApiReady(api: YoutubePlayer, context: RendererContext) {
// set plugin config easily
context.setConfig({ myConfig: api.getVolume() });
},
onConfigChange(newConfig) { /* ... */ },
stop(_context) { /* ... */ },
},
preload: {
async start({ getConfig }) {
const config = await getConfig();
},
onConfigChange(newConfig) {},
stop(_context) {},
},
});
```
### Примеры использования
- Кастомный CSS: создайте файл `style.css` в той же директории, затем:
```typescript
// index.ts
import style from './style.css?inline'; // import style as inline
import { createPlugin } from '@/utils';
export default createPlugin({
name: 'Plugin Label',
restartNeeded: true, // if value is true, ytmusic will show a restart dialog
config: {
enabled: false,
}, // your custom config
stylesheets: [style], // your custom style
renderer() {} // define renderer hook
});
```
- Если вы хотите изменить HTML:
```typescript
import { createPlugin } from '@/utils';
export default createPlugin({
name: 'Plugin Label',
restartNeeded: true, // if value is true, ytmusic will show the restart dialog
config: {
enabled: false,
}, // your custom config
renderer() {
// Remove the login button
document.querySelector(".sign-in-link.ytmusic-nav-bar").remove();
} // define renderer hook
});
```
- обмен между фронтом и бэком может быть выполнен с помощью модуля ipcMain из electron. Смотрите файл `index.ts` и
пример в плагине `sponsorblock`.
## Сборка
1. Склонируйте репозиторий
2. Следуйте [этой инструкции,](https://pnpm.io/installation) чтобы установить `pnpm`
3. Запустите `pnpm install --frozen-lockfile` для установки зависимостей
4. Запустите `pnpm build:OS`
- `pnpm dist:win` - Windows
- `pnpm dist:linux` - Linux (amd64)
- `pnpm dist:linux:deb-arm64` - Linux (arm64 for Debian)
- `pnpm dist:linux:rpm-arm64` - Linux (arm64 for Fedora)
- `pnpm dist:mac` - macOS (amd64)
- `pnpm dist:mac:arm64` - macOS (arm64)
Сборка приложения для macOS, Linux, и Windows,
используя [electron-builder](https://github.com/electron-userland/electron-builder).
## Предварительный просмотр
```bash
pnpm start
```
## Тестирование
```bash
pnpm test
```
Использует [Playwright](https://playwright.dev/) для тестирования приложения.
## Лицензия
MIT © [th-ch](https://github.com/th-ch/youtube-music)
## Часто задаваемые вопросы
### Почему меня приложения не отображается?
Если опция `Скрыть меню` включена - вы можете отобразить меню с помощью клавиши <kbd>alt</kbd> (или <kbd>\`</kbd> [обратный апостроф], если используете плагин "Меню в приложении")

View File

@ -51,6 +51,7 @@ export default defineConfig({
}; };
if (mode === 'development') { if (mode === 'development') {
commonConfig.build!.sourcemap = 'inline';
commonConfig.plugins?.push( commonConfig.plugins?.push(
Inspect({ Inspect({
build: true, build: true,
@ -98,6 +99,7 @@ export default defineConfig({
}; };
if (mode === 'development') { if (mode === 'development') {
commonConfig.build!.sourcemap = 'inline';
commonConfig.plugins?.push( commonConfig.plugins?.push(
Inspect({ Inspect({
build: true, build: true,
@ -148,6 +150,7 @@ export default defineConfig({
}; };
if (mode === 'development') { if (mode === 'development') {
commonConfig.build!.sourcemap = 'inline';
commonConfig.plugins?.push( commonConfig.plugins?.push(
Inspect({ Inspect({
build: true, build: true,

View File

@ -1,7 +1,7 @@
{ {
"name": "youtube-music", "name": "youtube-music",
"productName": "YouTube Music", "productName": "YouTube Music",
"version": "3.5.0", "version": "3.5.2",
"description": "YouTube Music Desktop App - including custom plugins", "description": "YouTube Music Desktop App - including custom plugins",
"main": "./dist/main/index.js", "main": "./dist/main/index.js",
"license": "MIT", "license": "MIT",
@ -94,6 +94,10 @@
"rpm": { "rpm": {
"depends": [ "depends": [
"/usr/lib64/libuuid.so.1" "/usr/lib64/libuuid.so.1"
],
"fpm": [
"--rpm-rpmbuild-define",
"_build_id_links none"
] ]
}, },
"snap": { "snap": {
@ -116,7 +120,7 @@
"vite:inspect": "pnpm clean && electron-vite build --mode development && pnpm exec serve .vite-inspect", "vite:inspect": "pnpm clean && electron-vite build --mode development && pnpm exec serve .vite-inspect",
"start": "electron-vite preview", "start": "electron-vite preview",
"start:debug": "cross-env ELECTRON_ENABLE_LOGGING=1 pnpm start", "start:debug": "cross-env ELECTRON_ENABLE_LOGGING=1 pnpm start",
"dev": "electron-vite dev --watch", "dev": "cross-env NODE_OPTIONS=--enable-source-maps electron-vite dev --watch",
"dev:debug": "cross-env ELECTRON_ENABLE_LOGGING=1 pnpm dev", "dev:debug": "cross-env ELECTRON_ENABLE_LOGGING=1 pnpm dev",
"clean": "del-cli dist && del-cli pack && del-cli .vite-inspect", "clean": "del-cli dist && del-cli pack && del-cli .vite-inspect",
"dist": "pnpm clean && pnpm build && pnpm electron-builder --win --mac --linux -p never", "dist": "pnpm clean && pnpm build && pnpm electron-builder --win --mac --linux -p never",
@ -145,11 +149,10 @@
"xml2js": "0.6.2", "xml2js": "0.6.2",
"node-fetch": "3.3.2", "node-fetch": "3.3.2",
"@electron/universal": "2.0.1", "@electron/universal": "2.0.1",
"@babel/runtime": "7.25.0" "@babel/runtime": "7.25.6"
}, },
"patchedDependencies": { "patchedDependencies": {
"vudio@2.1.1": "patches/vudio@2.1.1.patch", "vudio@2.1.1": "patches/vudio@2.1.1.patch",
"@xhayper/discord-rpc@1.1.4": "patches/@xhayper__discord-rpc@1.1.4.patch",
"app-builder-lib@24.13.3": "patches/app-builder-lib@24.13.3.patch" "app-builder-lib@24.13.3": "patches/app-builder-lib@24.13.3.patch"
} }
}, },
@ -160,12 +163,12 @@
"@electron/remote": "2.1.2", "@electron/remote": "2.1.2",
"@ffmpeg.wasm/core-mt": "0.12.0", "@ffmpeg.wasm/core-mt": "0.12.0",
"@ffmpeg.wasm/main": "0.12.0", "@ffmpeg.wasm/main": "0.12.0",
"@floating-ui/dom": "1.6.8", "@floating-ui/dom": "1.6.10",
"@foobar404/wave": "2.0.5", "@foobar404/wave": "2.0.5",
"@jellybrick/electron-better-web-request": "1.0.4", "@jellybrick/electron-better-web-request": "1.0.4",
"@jellybrick/mpris-service": "2.1.4", "@jellybrick/mpris-service": "2.1.4",
"@skyra/jaro-winkler": "^1.1.1", "@skyra/jaro-winkler": "^1.1.1",
"@xhayper/discord-rpc": "1.1.4", "@xhayper/discord-rpc": "1.2.0",
"async-mutex": "0.5.0", "async-mutex": "0.5.0",
"butterchurn": "3.0.0-beta.4", "butterchurn": "3.0.0-beta.4",
"butterchurn-presets": "3.0.0-beta.4", "butterchurn-presets": "3.0.0-beta.4",
@ -174,18 +177,18 @@
"custom-electron-prompt": "1.5.8", "custom-electron-prompt": "1.5.8",
"dbus-next": "0.10.2", "dbus-next": "0.10.2",
"deepmerge-ts": "7.1.0", "deepmerge-ts": "7.1.0",
"electron-debug": "4.0.0", "electron-debug": "4.0.1",
"electron-is": "3.0.0", "electron-is": "3.0.0",
"electron-localshortcut": "3.2.1", "electron-localshortcut": "3.2.1",
"electron-store": "10.0.0", "electron-store": "10.0.0",
"electron-unhandled": "4.0.1", "electron-unhandled": "4.0.1",
"electron-updater": "6.3.2", "electron-updater": "6.3.4",
"fast-average-color": "9.4.0", "fast-average-color": "9.4.0",
"fast-equals": "5.0.1", "fast-equals": "5.0.1",
"filenamify": "6.0.0", "filenamify": "6.0.0",
"howler": "2.2.4", "howler": "2.2.4",
"html-to-text": "9.0.5", "html-to-text": "9.0.5",
"i18next": "23.12.2", "i18next": "23.14.0",
"keyboardevent-from-electron-accelerator": "2.0.0", "keyboardevent-from-electron-accelerator": "2.0.0",
"keyboardevents-areequal": "0.2.2", "keyboardevents-areequal": "0.2.2",
"node-html-parser": "6.1.13", "node-html-parser": "6.1.13",
@ -195,47 +198,47 @@
"serve": "14.2.3", "serve": "14.2.3",
"simple-youtube-age-restriction-bypass": "github:organization/Simple-YouTube-Age-Restriction-Bypass#v2.5.9", "simple-youtube-age-restriction-bypass": "github:organization/Simple-YouTube-Age-Restriction-Bypass#v2.5.9",
"solid-floating-ui": "0.3.1", "solid-floating-ui": "0.3.1",
"solid-js": "1.8.19", "solid-js": "1.8.22",
"solid-styled-components": "0.28.5", "solid-styled-components": "0.28.5",
"solid-transition-group": "0.2.3", "solid-transition-group": "0.2.3",
"ts-morph": "23.0.0", "ts-morph": "23.0.0",
"vudio": "2.1.1", "vudio": "2.1.1",
"x11": "2.3.0", "x11": "2.3.0",
"youtubei.js": "10.2.0" "youtubei.js": "10.4.0"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "1.45.3", "@playwright/test": "1.47.0",
"@total-typescript/ts-reset": "0.5.1", "@total-typescript/ts-reset": "0.6.1",
"@types/color": "3.0.6", "@types/color": "3.0.6",
"@types/electron-localshortcut": "3.1.3", "@types/electron-localshortcut": "3.1.3",
"@types/howler": "2.2.11", "@types/howler": "2.2.11",
"@types/html-to-text": "9.0.4", "@types/html-to-text": "9.0.4",
"@types/semver": "7.5.8", "@types/semver": "7.5.8",
"@typescript-eslint/eslint-plugin": "7.18.0", "@typescript-eslint/eslint-plugin": "8.4.0",
"@typescript-eslint/parser": "7.18.0", "@typescript-eslint/parser": "8.4.0",
"bufferutil": "4.0.8", "bufferutil": "4.0.8",
"builtin-modules": "4.0.0", "builtin-modules": "4.0.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"del-cli": "5.1.0", "del-cli": "5.1.0",
"discord-api-types": "0.37.93", "discord-api-types": "0.37.100",
"electron": "31.3.1", "electron": "32.0.2",
"electron-builder": "24.13.3", "electron-builder": "24.13.3",
"electron-devtools-installer": "3.2.0", "electron-devtools-installer": "3.2.0",
"electron-vite": "2.3.0", "electron-vite": "2.3.0",
"esbuild": "0.23.0", "esbuild": "0.23.1",
"eslint": "8.57.0", "eslint": "8.57.0",
"eslint-import-resolver-exports": "1.0.0-beta.5", "eslint-import-resolver-exports": "1.0.0-beta.5",
"eslint-import-resolver-typescript": "3.6.1", "eslint-import-resolver-typescript": "3.6.3",
"eslint-plugin-import": "2.29.1", "eslint-plugin-import": "2.30.0",
"eslint-plugin-prettier": "5.2.1", "eslint-plugin-prettier": "5.2.1",
"glob": "11.0.0", "glob": "11.0.0",
"node-gyp": "10.2.0", "node-gyp": "10.2.0",
"playwright": "1.45.3", "playwright": "1.47.0",
"rollup": "4.19.1", "rollup": "4.21.2",
"typescript": "5.5.4", "typescript": "5.5.4",
"utf-8-validate": "6.0.4", "utf-8-validate": "6.0.4",
"vite": "5.3.5", "vite": "5.4.3",
"vite-plugin-inspect": "0.8.5", "vite-plugin-inspect": "0.8.7",
"vite-plugin-resolve": "2.5.2", "vite-plugin-resolve": "2.5.2",
"vite-plugin-solid": "2.10.2", "vite-plugin-solid": "2.10.2",
"ws": "8.18.0" "ws": "8.18.0"

View File

@ -1,17 +0,0 @@
diff --git a/package.json b/package.json
index 40db5dfbd8a4455ce2987d8115eca9882e1f9f14..414fc6986b9c0cc288908eb0107b90c4bfd916b2 100644
--- a/package.json
+++ b/package.json
@@ -25,11 +25,7 @@
},
"dependencies": {
"axios": "^1.7.2",
- "ws": "^8.18.0"
- },
- "optionalDependencies": {
- "bufferutil": "^4.0.8",
- "utf-8-validate": "^6.0.4"
+ "ws": "^8.18.0"
},
"devDependencies": {
"@types/node": "^14.*",

1653
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,8 @@
import Store from 'electron-store';
import { deepmergeCustom } from 'deepmerge-ts'; import { deepmergeCustom } from 'deepmerge-ts';
import defaultConfig from './defaults'; import defaultConfig from './defaults';
import store from './store'; import store, { IStore } from './store';
import plugins from './plugins'; import plugins from './plugins';
import { restart } from '@/providers/app-controls'; import { restart } from '@/providers/app-controls';
@ -62,20 +61,19 @@ type Join<K, P> = K extends string | number
type Paths<T, D extends number = 10> = [D] extends [never] type Paths<T, D extends number = 10> = [D] extends [never]
? never ? never
: T extends object : T extends object
? { ? {
[K in keyof T]-?: K extends string | number [K in keyof T]-?: K extends string | number
? `${K}` | Join<K, Paths<T[K], Prev[D]>> ? `${K}` | Join<K, Paths<T[K], Prev[D]>>
: never; : never;
}[keyof T] }[keyof T]
: ''; : '';
type SplitKey<K> = K extends `${infer A}.${infer B}` ? [A, B] : [K, string]; type SplitKey<K> = K extends `${infer A}.${infer B}` ? [A, B] : [K, string];
type PathValue<T, K extends string> = SplitKey<K> extends [ type PathValue<T, K extends string> =
infer A extends keyof T, SplitKey<K> extends [infer A extends keyof T, infer B extends string]
infer B extends string, ? PathValue<T[A], B>
] : T;
? PathValue<T[A], B>
: T;
const get = <Key extends Paths<typeof defaultConfig>>(key: Key) => const get = <Key extends Paths<typeof defaultConfig>>(key: Key) =>
store.get(key) as PathValue<typeof defaultConfig, typeof key>; store.get(key) as PathValue<typeof defaultConfig, typeof key>;
@ -86,7 +84,7 @@ export default {
setPartial, setPartial,
setMenuOption, setMenuOption,
edit: () => store.openInEditor(), edit: () => store.openInEditor(),
watch(cb: Parameters<Store['onDidAnyChange']>[0]) { watch(cb: Parameters<IStore['onDidAnyChange']>[0]) {
store.onDidAnyChange(cb); store.onDidAnyChange(cb);
}, },
plugins, plugins,

View File

@ -1,12 +1,14 @@
import Store from 'electron-store'; import Store from 'electron-store';
import Conf from 'conf';
import defaults from './defaults'; import defaults from './defaults';
import { DefaultPresetList, type Preset } from '@/plugins/downloader/types'; import { DefaultPresetList, type Preset } from '@/plugins/downloader/types';
// prettier-ignore
export type IStore = InstanceType<typeof import('conf/dist/source/index').default<Record<string, unknown>>>;
const migrations = { const migrations = {
'>=3.3.0'(store: Conf<Record<string, unknown>>) { '>=3.3.0'(store: IStore) {
const lastfmConfig = store.get('plugins.lastfm') as { const lastfmConfig = store.get('plugins.lastfm') as {
enabled?: boolean; enabled?: boolean;
token?: string; token?: string;
@ -16,21 +18,21 @@ const migrations = {
secret?: string; secret?: string;
}; };
if (lastfmConfig) { if (lastfmConfig) {
let scrobblerConfig = store.get( let scrobblerConfig = store.get('plugins.scrobbler') as
'plugins.scrobbler', | {
) as {
enabled?: boolean;
scrobblers?: {
lastfm?: {
enabled?: boolean; enabled?: boolean;
token?: string; scrobblers?: {
sessionKey?: string; lastfm?: {
apiRoot?: string; enabled?: boolean;
apiKey?: string; token?: string;
secret?: string; sessionKey?: string;
}; apiRoot?: string;
}; apiKey?: string;
} | undefined; secret?: string;
};
};
}
| undefined;
if (!scrobblerConfig) { if (!scrobblerConfig) {
scrobblerConfig = { scrobblerConfig = {
@ -56,7 +58,7 @@ const migrations = {
store.delete('plugins.lastfm'); store.delete('plugins.lastfm');
} }
}, },
'>=3.0.0'(store: Conf<Record<string, unknown>>) { '>=3.0.0'(store: IStore) {
const discordConfig = store.get('plugins.discord') as Record< const discordConfig = store.get('plugins.discord') as Record<
string, string,
unknown unknown
@ -78,14 +80,14 @@ const migrations = {
} }
} }
}, },
'>=2.1.3'(store: Conf<Record<string, unknown>>) { '>=2.1.3'(store: IStore) {
const listenAlong = store.get('plugins.discord.listenAlong'); const listenAlong = store.get('plugins.discord.listenAlong');
if (listenAlong !== undefined) { if (listenAlong !== undefined) {
store.set('plugins.discord.playOnYouTubeMusic', listenAlong); store.set('plugins.discord.playOnYouTubeMusic', listenAlong);
store.delete('plugins.discord.listenAlong'); store.delete('plugins.discord.listenAlong');
} }
}, },
'>=2.1.0'(store: Conf<Record<string, unknown>>) { '>=2.1.0'(store: IStore) {
const originalPreset = store.get('plugins.downloader.preset') as const originalPreset = store.get('plugins.downloader.preset') as
| string | string
| undefined; | undefined;
@ -110,7 +112,7 @@ const migrations = {
store.delete('plugins.downloader.ffmpegArgs'); store.delete('plugins.downloader.ffmpegArgs');
} }
}, },
'>=1.20.0'(store: Conf<Record<string, unknown>>) { '>=1.20.0'(store: IStore) {
store.delete('plugins.visualizer'); // default value is now in the plugin store.delete('plugins.visualizer'); // default value is now in the plugin
if (store.get('plugins.notifications.toastStyle') === undefined) { if (store.get('plugins.notifications.toastStyle') === undefined) {
@ -125,14 +127,14 @@ const migrations = {
store.set('options.likeButtons', 'force'); store.set('options.likeButtons', 'force');
} }
}, },
'>=1.17.0'(store: Conf<Record<string, unknown>>) { '>=1.17.0'(store: IStore) {
store.delete('plugins.picture-in-picture'); // default value is now in the plugin store.delete('plugins.picture-in-picture'); // default value is now in the plugin
if (store.get('plugins.video-toggle.mode') === undefined) { if (store.get('plugins.video-toggle.mode') === undefined) {
store.set('plugins.video-toggle.mode', 'custom'); store.set('plugins.video-toggle.mode', 'custom');
} }
}, },
'>=1.14.0'(store: Conf<Record<string, unknown>>) { '>=1.14.0'(store: IStore) {
if ( if (
typeof store.get('plugins.precise-volume.globalShortcuts') !== 'object' typeof store.get('plugins.precise-volume.globalShortcuts') !== 'object'
) { ) {
@ -144,12 +146,12 @@ const migrations = {
store.set('plugins.video-toggle.enabled', true); store.set('plugins.video-toggle.enabled', true);
} }
}, },
'>=1.13.0'(store: Conf<Record<string, unknown>>) { '>=1.13.0'(store: IStore) {
if (store.get('plugins.discord.listenAlong') === undefined) { if (store.get('plugins.discord.listenAlong') === undefined) {
store.set('plugins.discord.listenAlong', true); store.set('plugins.discord.listenAlong', true);
} }
}, },
'>=1.12.0'(store: Conf<Record<string, unknown>>) { '>=1.12.0'(store: IStore) {
const options = store.get('plugins.shortcuts') as const options = store.get('plugins.shortcuts') as
| Record< | Record<
string, string,
@ -187,12 +189,12 @@ const migrations = {
} }
} }
}, },
'>=1.11.0'(store: Conf<Record<string, unknown>>) { '>=1.11.0'(store: IStore) {
if (store.get('options.resumeOnStart') === undefined) { if (store.get('options.resumeOnStart') === undefined) {
store.set('options.resumeOnStart', true); store.set('options.resumeOnStart', true);
} }
}, },
'>=1.7.0'(store: Conf<Record<string, unknown>>) { '>=1.7.0'(store: IStore) {
const enabledPlugins = store.get('plugins') as string[]; const enabledPlugins = store.get('plugins') as string[];
if (!Array.isArray(enabledPlugins)) { if (!Array.isArray(enabledPlugins)) {
console.warn('Plugins are not in array format, cannot migrate'); console.warn('Plugins are not in array format, cannot migrate');
@ -233,4 +235,4 @@ export default new Store({
}, },
clearInvalidConfig: false, clearInvalidConfig: false,
migrations, migrations,
}); }) as Store & IStore;

View File

@ -15,7 +15,7 @@
}, },
"language": { "language": {
"code": "ca", "code": "ca",
"local-name": "català", "local-name": "Català",
"name": "Catalan" "name": "Catalan"
}, },
"main": { "main": {
@ -84,9 +84,9 @@
"label": "Navegació", "label": "Navegació",
"submenu": { "submenu": {
"copy-current-url": "Copia l'URL actual", "copy-current-url": "Copia l'URL actual",
"go-back": "Anar enrere", "go-back": "Ves enrere",
"go-forward": "Anar endavant", "go-forward": "Ves endavant",
"quit": "Sortir", "quit": "Surt",
"restart": "Reinicia l'aplicació" "restart": "Reinicia l'aplicació"
} }
}, },
@ -102,11 +102,11 @@
"override-user-agent": "Sobreescriu l'agent d'usuari (User-Agent)", "override-user-agent": "Sobreescriu l'agent d'usuari (User-Agent)",
"restart-on-config-changes": "Reinicia quan es canviï la configuració", "restart-on-config-changes": "Reinicia quan es canviï la configuració",
"set-proxy": { "set-proxy": {
"label": "Definir proxy", "label": "Definir servidor intermediari (proxy)",
"prompt": { "prompt": {
"label": "Introduir l'adreça del proxy: (deixar en blanc per deshabilitar)", "label": "Introduir l'adreça del servidor intermediari: (deixar en blanc per deshabilitar)",
"placeholder": "Exemple: SOCKS5://127.0.0.1:9999", "placeholder": "Exemple: SOCKS5://127.0.0.1:9999",
"title": "Definir proxy" "title": "Definir servidor intermediari (proxy)"
} }
}, },
"toggle-dev-tools": "Commuta les DevTools" "toggle-dev-tools": "Commuta les DevTools"
@ -144,11 +144,11 @@
"disabled": "Deshabilitat", "disabled": "Deshabilitat",
"enabled-and-hide-app": "Mostra la icona i amaga l'aplicació", "enabled-and-hide-app": "Mostra la icona i amaga l'aplicació",
"enabled-and-show-app": "Mostra la icona i mostra l'aplicació", "enabled-and-show-app": "Mostra la icona i mostra l'aplicació",
"play-pause-on-click": "Reproduir/Pausar en clicar" "play-pause-on-click": "Reprodueix / pausa en clicar"
} }
}, },
"visual-tweaks": { "visual-tweaks": {
"label": "Configuració visual", "label": "Opcions visuals",
"submenu": { "submenu": {
"like-buttons": { "like-buttons": {
"default": "Per defecte", "default": "Per defecte",
@ -182,9 +182,9 @@
"new": "NOU" "new": "NOU"
}, },
"view": { "view": {
"label": "Mostra", "label": "Veure",
"submenu": { "submenu": {
"force-reload": "Força recàrrega", "force-reload": "Força la recàrrega",
"reload": "Recarrega", "reload": "Recarrega",
"reset-zoom": "Mida real", "reset-zoom": "Mida real",
"toggle-fullscreen": "Commuta la pantalla completa", "toggle-fullscreen": "Commuta la pantalla completa",
@ -220,7 +220,7 @@
}, },
"album-actions": { "album-actions": {
"description": "Afegeix botons de «no m'agrada / retirar el no m'agrada» i «m'agrada / retirar el m'agrada» per aplicar-ho a totes les cançons en una llista de reproducció o àlbum", "description": "Afegeix botons de «no m'agrada / retirar el no m'agrada» i «m'agrada / retirar el m'agrada» per aplicar-ho a totes les cançons en una llista de reproducció o àlbum",
"name": "Accions de l'àlbum" "name": "Accions a l'àlbum"
}, },
"album-color-theme": { "album-color-theme": {
"description": "Aplica un tema dinàmic i efectes visuals basats en la paleta de colors de l'àlbum", "description": "Aplica un tema dinàmic i efectes visuals basats en la paleta de colors de l'àlbum",
@ -281,7 +281,7 @@
}, },
"audio-compressor": { "audio-compressor": {
"description": "Aplica compressió a l'àudio (baixa el volum de les parts més sorolloses de la senyal d'àudio i puja el volum de les parts més fluixes)", "description": "Aplica compressió a l'àudio (baixa el volum de les parts més sorolloses de la senyal d'àudio i puja el volum de les parts més fluixes)",
"name": "Compressor d'àudio" "name": "Compress d'àudio"
}, },
"blur-nav-bar": { "blur-nav-bar": {
"description": "Desenfoca i aplica transparència a la barra de navegació", "description": "Desenfoca i aplica transparència a la barra de navegació",
@ -518,7 +518,7 @@
}, },
"no-google-login": { "no-google-login": {
"description": "Elimina els botons d'inici de sessió de Google de la interfície", "description": "Elimina els botons d'inici de sessió de Google de la interfície",
"name": "Sense inici de sessió de Google" "name": "Amaga l'inici de sessió de Google"
}, },
"notifications": { "notifications": {
"description": "Mostra una notificació quan una cançó es comença a reproduir (les notificacions interactives estan disponibles a Windows)", "description": "Mostra una notificació quan una cançó es comença a reproduir (les notificacions interactives estan disponibles a Windows)",
@ -602,7 +602,7 @@
} }
}, },
"description": "Permet canviar la qualitat del vídeo amb un botó que s'hi mostra a sobre", "description": "Permet canviar la qualitat del vídeo amb un botó que s'hi mostra a sobre",
"name": "Canvia la qualitat del vídeo" "name": "Botó de qualitat del vídeo"
}, },
"scrobbler": { "scrobbler": {
"description": "Afegeix suport per scrobbling (Last.fm, ListenBrainz, etc.)", "description": "Afegeix suport per scrobbling (Last.fm, ListenBrainz, etc.)",
@ -657,8 +657,8 @@
} }
}, },
"skip-disliked-songs": { "skip-disliked-songs": {
"description": "Omet cançons amb «No m'agrada»", "description": "Salta les cançons amb «no m'agrada»",
"name": "Omet cançons amb «No m'agrad" "name": "Salta les cançons que no t'agraden"
}, },
"skip-silences": { "skip-silences": {
"description": "Omet automàticament les seccions amb silenci a les cançons", "description": "Omet automàticament les seccions amb silenci a les cançons",
@ -668,6 +668,59 @@
"description": "Omet automàticament els segments dels vídeos que no son música, com la intro o el final", "description": "Omet automàticament els segments dels vídeos que no son música, com la intro o el final",
"name": "SponsorBlock" "name": "SponsorBlock"
}, },
"synced-lyrics": {
"description": "Proporciona lletres sincronitzades amb les cançons, a través de proveïdors com LRClib.",
"errors": {
"fetch": "⚠️ - Se ha produït un error en descarregar la lletra. Si us plau, intenta-ho més tard.",
"not-found": "⚠️ - No s'ha trobat la lletra per aquesta cançó."
},
"menu": {
"default-text-string": {
"label": "Caràcter per defecte entre lletres",
"tooltip": "Tria el caràcter per defecte que es mostrarà a l'espai entre les lletres"
},
"line-effect": {
"label": "Efecte de la línia",
"submenu": {
"focus": {
"label": "Focus",
"tooltip": "Mostra tan sols la línia actual en blanc"
},
"offset": {
"label": "Desplaçament",
"tooltip": "Desplaçament a la dreta de la línia actual"
},
"scale": {
"label": "Escala",
"tooltip": "Redimensiona la línia actual"
}
},
"tooltip": "Tria l'efecte a aplicar a la línia actual"
},
"precise-timing": {
"label": "Fes que les lletres es sincronitzin a la perfecció",
"tooltip": "Calcula al mil·lisegon l'aparició de la següent línia (pot tenir un petit impacte en el rendiment)"
},
"show-lyrics-even-if-inexact": {
"label": "Mostra la lletra tot i que sigui inexacta",
"tooltip": "Si no es troba la cançó, el plugin torna a intentar obtenir la lletra amb una cerca diferent.\nEl resultat d'aquesta segona cerca podria no ser exacte."
},
"show-time-codes": {
"label": "Mostra els codis de temps",
"tooltip": "Mostra els codis de temps al costat de la lletra"
}
},
"name": "Lletres sincronitzades",
"refetch-btn": {
"fetching": "Obtenint...",
"normal": "Tornar a obtenir la lletra"
},
"warnings": {
"duration-mismatch": "⚠️ - La lletra podria no estar ben sincronitzada, la durada no és coincident.",
"inexact": "⚠️ - La lletra d'aquesta cançó podria no ser exacta",
"instrumental": "⚠️ - Aquesta cançó és instrumental"
}
},
"taskbar-mediacontrol": { "taskbar-mediacontrol": {
"description": "Controla la reproducció des de la barra de tasques del Windows", "description": "Controla la reproducció des de la barra de tasques del Windows",
"name": "Control multimèdia a la barra de tasques" "name": "Control multimèdia a la barra de tasques"
@ -701,7 +754,7 @@
} }
} }
}, },
"name": "Commutador de vídeo", "name": "Botó de vídeo",
"templates": { "templates": {
"button": "Cançó" "button": "Cançó"
} }

View File

@ -668,6 +668,54 @@
"description": "Überspringt automatisch nicht-musikalische Teile wie Intro/Outro oder Teile von Musikvideos, in denen der Song nicht gespielt wird", "description": "Überspringt automatisch nicht-musikalische Teile wie Intro/Outro oder Teile von Musikvideos, in denen der Song nicht gespielt wird",
"name": "SponsorBlock" "name": "SponsorBlock"
}, },
"synced-lyrics": {
"description": "Bietet synchronisierte Liedtexte zu Songs, verwendet Anbieter wie LRClib.",
"errors": {
"fetch": "⚠️ - Beim Abrufen des Liedtexts ist ein Fehler aufgetreten. Bitte versuchen Sie es später nochmal.",
"not-found": "⚠️ - Kein Text für diesen Song gefunden."
},
"menu": {
"default-text-string": {
"label": "Standardzeichen zwischen Texten",
"tooltip": "Standardzeichen für die Lücke zwischen Songtexten auswählen"
},
"line-effect": {
"label": "Zeileneffekt",
"submenu": {
"focus": {
"label": "Fokussieren",
"tooltip": "Nur aktive Zeile weiß darstellen"
},
"offset": {
"label": "Versatz"
},
"scale": {
"label": "Skalieren",
"tooltip": "Aktuelle Zeile skalieren"
}
},
"tooltip": "Effekt für aktive Zeile auswählen"
},
"precise-timing": {
"label": "Den Songtext perfekt synchronisieren",
"tooltip": "Auf die Millisekunde genau berechnen, wann die nächste Zeile angezeigt werden soll (Kann Einfluss auf die Leistung haben)"
},
"show-time-codes": {
"label": "Zeitkodierungen anzeigen",
"tooltip": "Zeitkodierungen neben Songtext anzeigen"
}
},
"name": "Synchronisierte Texte",
"refetch-btn": {
"fetching": "Hole Songtext...",
"normal": "Songtext neu holen"
},
"warnings": {
"duration-mismatch": "⚠️ - Es kann sein, dass die Synchronization nicht stimmt, da die Songdauer nicht übereinstimmt.",
"inexact": "⚠️ - Es ist Möglich, dass der Songtext für diesen Song nicht übereinstimmt.",
"instrumental": "⚠️ - Das ist ein instrumentales Lied"
}
},
"taskbar-mediacontrol": { "taskbar-mediacontrol": {
"description": "Wiedergabe aus der Windows Taskleiste kontrollieren", "description": "Wiedergabe aus der Windows Taskleiste kontrollieren",
"name": "Mediensteuerung in der Taskleiste" "name": "Mediensteuerung in der Taskleiste"

View File

@ -2,7 +2,7 @@
"common": { "common": {
"console": { "console": {
"plugins": { "plugins": {
"execute-failed": "Αποτυχία εκτέλεσης προσθέτου {{pluginName}}::{{contextName}}", "execute-failed": "Απέτυχε η εκτέλεση του πρόσθετου {{pluginName}}::{{contextName}}",
"executed-at-ms": "Το πρόσθετο {{pluginName}}::{{contextName}} εκτελέστηκε σε {{ms}}ms", "executed-at-ms": "Το πρόσθετο {{pluginName}}::{{contextName}} εκτελέστηκε σε {{ms}}ms",
"initialize-failed": "Απέτυχε η αρχικοποίηση του πρόσθετου \"{{pluginName}}\"", "initialize-failed": "Απέτυχε η αρχικοποίηση του πρόσθετου \"{{pluginName}}\"",
"load-all": "Φόρτωση όλων των πρόσθετων", "load-all": "Φόρτωση όλων των πρόσθετων",
@ -36,7 +36,7 @@
"details": "Σφάλμα ανταπόκρισης!\n{{error}}" "details": "Σφάλμα ανταπόκρισης!\n{{error}}"
}, },
"when-ready": { "when-ready": {
"clearing-cache-after-20s": "Εκκαθάριση της cache της εφαρμογής" "clearing-cache-after-20s": "Εκκαθάριση μνήμης cache εφαρμογής"
}, },
"window": { "window": {
"tried-to-render-offscreen": "Το παράθυρο προσπάθησε να απεικονίσει εκτός οθόνης, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}" "tried-to-render-offscreen": "Το παράθυρο προσπάθησε να απεικονίσει εκτός οθόνης, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
@ -45,23 +45,23 @@
"dialog": { "dialog": {
"hide-menu-enabled": { "hide-menu-enabled": {
"detail": "Το μενού είναι κρυμμένο, χρησιμοποιήστε το 'Alt' για να το εμφανίσετε (ή το 'Escape' αν χρησιμοποιείτε το μενού εφαρμογής)", "detail": "Το μενού είναι κρυμμένο, χρησιμοποιήστε το 'Alt' για να το εμφανίσετε (ή το 'Escape' αν χρησιμοποιείτε το μενού εφαρμογής)",
"message": "Απόκρυψη μενού είναι ενεργοποιημένο", "message": "Η απόκρυψη μενού είναι ενεργοποιημένη",
"title": "Ενεργοποιήθηκε η Απόκρυψη του Μενού" "title": "Η απόκρυψη μενού ενεργοποιήθηκε"
}, },
"need-to-restart": { "need-to-restart": {
"buttons": { "buttons": {
"later": "Αργότερα", "later": "Αργότερα",
"restart-now": "Επανεκκίνηση Τώρα" "restart-now": "Επανεκκίνηση τώρα"
}, },
"detail": "Το πρόσθετο \"{{pluginName}}\" απαιτεί επανεκκίνηση για να ισχύσει", "detail": "Το πρόσθετο \"{{pluginName}}\" απαιτεί επανεκκίνηση για να ισχύσει",
"message": "Το \"{{pluginName}}\" χρειάζεται επανεκκίνηση", "message": "Το \"{{pluginName}}\" χρειάζεται επανεκκίνηση",
"title": "Απαιτείται Επανεκκίνηση" "title": "Απαιτείται επανεκκίνηση"
}, },
"unresponsive": { "unresponsive": {
"buttons": { "buttons": {
"quit": "Έξοδος", "quit": "Έξοδος",
"relaunch": "Επανεκκίνηση", "relaunch": "Επανεκκίνηση",
"wait": "Περίμενε" "wait": "Περιμένετε"
}, },
"detail": "Λυπούμαστε για την ταλαιπωρία! Παρακαλούμε επιλέξτε τι να κάνετε:", "detail": "Λυπούμαστε για την ταλαιπωρία! Παρακαλούμε επιλέξτε τι να κάνετε:",
"message": "Η εφαρμογή δεν ανταποκρίνεται", "message": "Η εφαρμογή δεν ανταποκρίνεται",
@ -69,13 +69,13 @@
}, },
"update-available": { "update-available": {
"buttons": { "buttons": {
"disable": "Απενεργοποίηση Ενημερώσεων", "disable": "Απενεργοποίηση ενημερώσεων",
"download": "Λήψη", "download": "Λήψη",
"ok": "Εντάξει" "ok": "OK"
}, },
"detail": "Μια νέα έκδοση είναι διαθέσιμη και μπορεί να ληφθεί από τον σύνδεσμο {{downloadLink}}", "detail": "Μια νέα έκδοση είναι διαθέσιμη και μπορεί να ληφθεί από τον σύνδεσμο {{downloadLink}}",
"message": "Μια νέα έκδοση είναι διαθέσιμη", "message": "Μια νέα έκδοση είναι διαθέσιμη",
"title": "Υπάρχει Διαθέσιμη Ενημέρωση" "title": "Υπάρχει διαθέσιμη ενημέρωση"
} }
}, },
"menu": { "menu": {
@ -87,7 +87,7 @@
"go-back": "Πήγαινε πίσω", "go-back": "Πήγαινε πίσω",
"go-forward": "Πήγαινε μπροστά", "go-forward": "Πήγαινε μπροστά",
"quit": "Έξοδος", "quit": "Έξοδος",
"restart": "Επανεκκίνηση Εφαρμογής" "restart": "Επανεκκίνηση εφαρμογής"
} }
}, },
"options": { "options": {
@ -106,7 +106,7 @@
"prompt": { "prompt": {
"label": "Εισαγωγή διεύθυνσης διακομιστή μεσολάβησης (proxy): (αφήστε κενό για απενεργοποίηση)", "label": "Εισαγωγή διεύθυνσης διακομιστή μεσολάβησης (proxy): (αφήστε κενό για απενεργοποίηση)",
"placeholder": "Παράδειγμα: SOCKS5://127.0.0.1:9999", "placeholder": "Παράδειγμα: SOCKS5://127.0.0.1:9999",
"title": "Ορισμός διακομιστή μεσολάβησης (proxy)" "title": "Ορισμός μεσολάβησης"
} }
}, },
"toggle-dev-tools": "Εναλλαγή DevTools" "toggle-dev-tools": "Εναλλαγή DevTools"
@ -135,8 +135,8 @@
"single-instance-lock": "Κλείδωμα Μοναδικής Εκδοχής", "single-instance-lock": "Κλείδωμα Μοναδικής Εκδοχής",
"start-at-login": "Έναρξη κατά την σύνδεση", "start-at-login": "Έναρξη κατά την σύνδεση",
"starting-page": { "starting-page": {
"label": "Σελίδα Έναρξης", "label": "Σελίδα έναρξης",
"unset": "Κατάργηση Ορισμού" "unset": "Κατάργηση ορισμού"
}, },
"tray": { "tray": {
"label": "Δίσκος", "label": "Δίσκος",
@ -148,13 +148,27 @@
} }
}, },
"visual-tweaks": { "visual-tweaks": {
"label": "Τροποποιήσεις Εμφάνισης",
"submenu": { "submenu": {
"like-buttons": { "like-buttons": {
"default": "Default" "default": "Default",
"force-show": "Αναγκαστική Εμφάνιση",
"hide": "Απόκρυψη",
"label": "Μου αρέσει"
}, },
"remove-upgrade-button": "Αφαίρεση κουμπιού αναβάθμισης",
"theme": { "theme": {
"label": "Theme", "dialog": {
"button": {
"cancel": "Άκυρο",
"remove": "Αφαίρεση"
},
"remove-theme": "Είστε βέβαιοι ότι θέλετε να αφαιρέσετε το προσαρμοσμένο θέμα;",
"remove-theme-message": "Αυτό θα αφαιρέσει το προσαρμοσμένο θέμα"
},
"label": "Θέμα",
"submenu": { "submenu": {
"import-css-file": "Εισαγωγή προσαρμοσμένου αρχείου CSS",
"no-theme": "No theme" "no-theme": "No theme"
} }
} }
@ -163,29 +177,68 @@
} }
}, },
"plugins": { "plugins": {
"label": "Plugins" "enabled": "Ενεργοποιημένο",
"label": "Πρόσθετα",
"new": "ΝΕΟ"
}, },
"view": { "view": {
"label": "View" "label": "Προβολή",
"submenu": {
"force-reload": "Αναγκαστική Eπαναφόρτωση",
"reload": "Επαναφόρτωση",
"reset-zoom": "Πραγματικό μέγεθος",
"toggle-fullscreen": "Εναλλαγή Πλήρους Οθόνης",
"zoom-in": "Μεγέθυνση",
"zoom-out": "Σμίκρυνση"
}
}
},
"tray": {
"next": "Επόμενο",
"play-pause": "Αναπαραγωγή/Παύση",
"previous": "Προηγούμενο",
"quit": "Έξοδος",
"restart": "Επανεκκίνηση εφαρμογής",
"show": "Εμφάνιση παραθύρου",
"tooltip": {
"default": "YouTube Music",
"with-song-info": "YouTube Music: {{artist}} - {{title}}"
} }
} }
}, },
"plugins": { "plugins": {
"ad-speedup": {
"description": "Εαν παίξει διαφήμιση κάνει σίγαση του ήχου και θέτει την ταχύτητα αναπαραγωγής στο 16x",
"name": "Γρήγορη Προώθηση Διαφημίσεων"
},
"adblocker": { "adblocker": {
"description": "Αποκλεισμός όλων των διαφημίσεων και tracker", "description": "Αποκλεισμός όλων των διαφημίσεων και tracker",
"menu": { "menu": {
"blocker": "Μέθοδος αποκλεισμού" "blocker": "Μέθοδος αποκλεισμού"
}, },
"name": "Adblocker" "name": "Μπλοκάρισμα Διαφημίσεων"
},
"album-actions": {
"description": "Προσθέτει κουμπιά Like/Unlike και Dislike/Undislike που δρουν συνολικά σε όλα τα κομμάτια μιας playlist ή ενός άλμπουμ",
"name": "Ενέργειες σε Άλμπουμ"
}, },
"album-color-theme": { "album-color-theme": {
"description": "Εφαρμόζει ένα δυναμικό θέμα και εφέ με βάση τη χρωματική παλέτα του άλμπουμ", "description": "Εφαρμόζει ένα δυναμικό θέμα και εφέ με βάση τη χρωματική παλέτα του άλμπουμ",
"menu": {
"color-mix-ratio": {
"label": "Αναλογία μίξης χρώματος",
"submenu": {
"percent": "{{ratio}}%"
}
}
},
"name": "Album Color Theme" "name": "Album Color Theme"
}, },
"ambient-mode": { "ambient-mode": {
"description": "Εφαρμόζει ένα εφέ φωτισμού ρίχνοντας απαλά χρώματα από το βίντεο, στο φόντο της οθόνης σας.", "description": "Εφαρμόζει ένα εφέ φωτισμού ρίχνοντας απαλά χρώματα από το βίντεο, στο φόντο της οθόνης σας.",
"menu": { "menu": {
"blur-amount": { "blur-amount": {
"label": "Ένταση θαμπώματος",
"submenu": { "submenu": {
"pixels": "{{blurAmount}} pixels" "pixels": "{{blurAmount}} pixels"
} }
@ -197,25 +250,30 @@
} }
}, },
"opacity": { "opacity": {
"label": "Ποσότητα αδιαφάνειας", "label": "Αδιαφάνεια",
"submenu": { "submenu": {
"percent": "{{opacity}}%" "percent": "{{opacity}}%"
} }
}, },
"quality": { "quality": {
"label": "Ποιότητα",
"submenu": { "submenu": {
"pixels": "{{quality}} pixels" "pixels": "{{quality}} pixels"
} }
}, },
"size": { "size": {
"label": "Μέγεθος",
"submenu": { "submenu": {
"percent": "{{size}}%" "percent": "{{size}}%"
} }
}, },
"smoothness-transition": { "smoothness-transition": {
"submenu": { "submenu": {
"during": "Σε {{interpolationTime}} δεύτερα" "during": "Σε {{interpolationTime}} δευτερόλεπτα"
} }
},
"use-fullscreen": {
"label": "Χρήση πλήρους οθόνης"
} }
} }
}, },
@ -257,11 +315,14 @@
"description": "Κάνει τα τραγούδια να είναι αυτόματα σε παύση", "description": "Κάνει τα τραγούδια να είναι αυτόματα σε παύση",
"menu": { "menu": {
"apply-once": "Εφαρμόζεται μόνο στο πρώτο τραγούδι" "apply-once": "Εφαρμόζεται μόνο στο πρώτο τραγούδι"
} },
"name": "Απενεργοποίηση αυτόματης αναπαραγωγής"
}, },
"discord": { "discord": {
"description": "Δείξτε στους φίλους σας τι ακούτε με το Rich Presence", "description": "Δείξτε στους φίλους σας τι ακούτε με το Rich Presence",
"menu": { "menu": {
"auto-reconnect": "Αυτόματη επανασύνδεση",
"clear-activity": "Εκκαθάριση δραστηριότητας",
"hide-duration-left": "Απόκρυψη της διάρκειας που απομένει", "hide-duration-left": "Απόκρυψη της διάρκειας που απομένει",
"hide-github-button": "Απόκρυψη του συνδέσμου προς GitHub", "hide-github-button": "Απόκρυψη του συνδέσμου προς GitHub",
"set-inactivity-timeout": "Ορισμός χρονικού ορίου αδράνειας" "set-inactivity-timeout": "Ορισμός χρονικού ορίου αδράνειας"
@ -280,34 +341,113 @@
"buttons": { "buttons": {
"ok": "OK" "ok": "OK"
}, },
"message": "Λήψη λίστας αναπαραγωγής {{playlistTitle}}", "detail": "{{playlistSize}} τραγούδια)",
"title": "Λήψη ξεκίνησε" "message": "Λήψη της λίστας αναπαραγωγής {{playlistTitle}}",
"title": "Η λήψη ξεκίνησε"
} }
}, },
"feedback": { "feedback": {
"conversion-progress": "Μετατροπή: {{percent}}%", "conversion-progress": "Μετατροπή: {{percent}}%",
"download-progress": "Download: {{percent}}%", "converting": "Μετατροπή…",
"preparing-file": "Προετοιμασία αρχείου…" "download-info": "Λήψη του {{artist}} - {{title}} [{{videoId}}",
"download-progress": "Λήψη: {{percent}}%",
"downloading": "Λήψη…",
"downloading-counter": "Λήψη {{current}}/{{total}}…",
"downloading-playlist": "Λήψη της λίστας αναπαραγωγής \"{{playlistTitle}}\" - {{playlistSize}} τραγούδια ({{playlistId}})",
"folder-already-exists": "Ο φάκελος {{playlistFolder}} υπάρχει ήδη",
"loading": "Φόρτωση…",
"playlist-is-empty": "Η λίστα αναπραγωγής είναι άδεια",
"preparing-file": "Προετοιμασία αρχείου…",
"saving": "Αποθήκευση…",
"video-id-not-found": "Το βίντεο δεν βρέθηκε"
} }
}, },
"menu": {
"download-finish-settings": {
"prompt": {
"last-seconds": "Τελευταία x δευτερόλεπτα"
},
"submenu": {
"percent": "Ποσοστό",
"seconds": "Δευτερόλεπτα"
}
},
"download-playlist": "Λήψη λίστας αναπαραγωγής",
"skip-existing": "Παράλειψη υπάρχοντων αρχείων"
},
"templates": { "templates": {
"button": "Download" "button": "Λήψη"
}
},
"music-together": {
"internal": {
"save": "Αποθήκευση",
"unknown-user": "Άγνωστος χρήστης"
},
"menu": {
"connected-users": "Συνδεδεμένοι χρήστες"
},
"toast": {
"add-song-failed": "Απέτυχε η προσθήκη τραγουδιού",
"remove-song-failed": "Απέτυχε η αφαίρεση τραγουδιού"
} }
}, },
"navigation": { "navigation": {
"name": "Navigation" "name": "Πλοήγηση"
}, },
"no-google-login": { "no-google-login": {
"name": "No Google Login" "name": "No Google Login"
}, },
"notifications": { "notifications": {
"name": "Notifications" "name": "Ειδοποιήσεις"
},
"picture-in-picture": {
"menu": {
"always-on-top": "Πάντα σε πρώτο πλάνο",
"hotkey": {
"label": "Πλήκτρο πρόσβασης",
"prompt": {
"keybind-options": {
"hotkey": "Πλήκτρο πρόσβασης"
}
}
},
"save-window-position": "Αποθήκευση θέσης παραθύρου",
"save-window-size": "Αποθήκευση μεγέθους παραθύρου"
}
},
"playback-speed": {
"name": "Ταχύτητα αναπαραγωγής",
"templates": {
"button": "Ταχύτητα"
}
},
"precise-volume": {
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "Μείωση έντασης",
"increase": "Αύξηση έντασης"
}
}
}
},
"quality-changer": {
"backend": {
"dialog": {
"quality-changer": {
"detail": "Τρέχουσα ποιότητα: {{quality}}"
}
}
}
}, },
"shortcuts": { "shortcuts": {
"prompt": { "prompt": {
"keybind": { "keybind": {
"keybind-options": { "keybind-options": {
"next": "Next" "next": "Επόμενο",
"play-pause": "Αναπαραγωγή / Παύση",
"previous": "Προηγούμενο"
} }
} }
} }

View File

@ -683,6 +683,42 @@
"refetch-btn": { "refetch-btn": {
"normal": "Refetch lyrics", "normal": "Refetch lyrics",
"fetching": "Fetching..." "fetching": "Fetching..."
},
"menu": {
"precise-timing": {
"label": "Make the lyrics perfectly synced",
"tooltip": "Calculate to the milisecond the display of the next line (can have a small impact on performance)"
},
"line-effect": {
"label": "Line effect",
"tooltip": "Choose the effect to apply to the current line",
"submenu": {
"scale": {
"label": "Scale",
"tooltip": "Scale the current line"
},
"offset": {
"label": "Offset",
"tooltip": "Offset on the right the current line"
},
"focus": {
"label": "Focus",
"tooltip": "Make only the current line white"
}
}
},
"default-text-string": {
"label": "Default character between lyrics",
"tooltip": "Choose the default character to use for the gap between lyrics"
},
"show-time-codes": {
"label": "Show time codes",
"tooltip": "Show the time codes next to the lyrics"
},
"show-lyrics-even-if-inexact": {
"label": "Show lyrics even if inexact",
"tooltip": "If the song is not found, the plugin tries again with a different search query.\nThe result from the second attempt may not be exact."
}
} }
}, },
"taskbar-mediacontrol": { "taskbar-mediacontrol": {

View File

@ -668,6 +668,59 @@
"description": "Salta automáticamente las partes no musicales como la introducción/final o secciones de videos musicales donde la canción no está sonando", "description": "Salta automáticamente las partes no musicales como la introducción/final o secciones de videos musicales donde la canción no está sonando",
"name": "SponsorBlock" "name": "SponsorBlock"
}, },
"synced-lyrics": {
"description": "Proporciona letras de canciones sincronizadas, utilizando proveedores como LRClib.",
"errors": {
"fetch": "⚠️ - Se ha producido un error al descargar la letra. Por favor, vuelve a intentarlo más tarde.",
"not-found": "⚠️ - No se ha encontrado ninguna letra para esta canción."
},
"menu": {
"default-text-string": {
"label": "Carácter predeterminado entre letras",
"tooltip": "Elige el carácter predeterminado que se utilizará para el espacio entre letras"
},
"line-effect": {
"label": "Efecto de la línea",
"submenu": {
"focus": {
"label": "Enfoque",
"tooltip": "Mostrar solo la línea actual en blanco"
},
"offset": {
"label": "Desplazamiento",
"tooltip": "Desplazamiento a la derecha de la línea actual"
},
"scale": {
"label": "Escala",
"tooltip": "Escala de la línea actual"
}
},
"tooltip": "Elige el efecto que deseas aplicar a la línea actual"
},
"precise-timing": {
"label": "Haz que la letra esté perfectamente sincronizada",
"tooltip": "Calcular al milisegundo la visualización de la línea siguiente (puede tener un pequeño impacto en el rendimiento)"
},
"show-lyrics-even-if-inexact": {
"label": "Mostrar la letra aunque sea inexacta",
"tooltip": "Si no se encuentra la canción, el complemento vuelve a intentarlo con una búsqueda diferente.\nEl resultado del segundo intento puede no ser exacto."
},
"show-time-codes": {
"label": "Visualización del código de tiempo",
"tooltip": "Mostrar los códigos de tiempo junto a la letra"
}
},
"name": "Letras sincronizadas",
"refetch-btn": {
"fetching": "Recuperando...",
"normal": "Volver a buscar letras"
},
"warnings": {
"duration-mismatch": "⚠️ - La letra puede estar desincronizada debido a un desajuste en la duración.",
"inexact": "⚠️ - La letra de esta canción puede no ser exacta",
"instrumental": "⚠️ - Se trata de una canción instrumental"
}
},
"taskbar-mediacontrol": { "taskbar-mediacontrol": {
"description": "Controla la reproducción desde la barra de tareas de Windows", "description": "Controla la reproducción desde la barra de tareas de Windows",
"name": "Control de medios de la barra de tareas" "name": "Control de medios de la barra de tareas"

182
src/i18n/resources/et.json Normal file
View File

@ -0,0 +1,182 @@
{
"common": {
"console": {
"plugins": {
"execute-failed": "{{pluginName}}::{{contextName}} lisamooduli käivitamine ei õnnestunud",
"executed-at-ms": "{{pluginName}}::{{contextName}} lisamoodul käivitus {{ms}} millisekundiga",
"initialize-failed": "„{{pluginName}}“ lisamooduli töö alustamine ei õnnestunud",
"load-all": "Laadime kõiki lisamooduleid",
"load-failed": "„{{pluginName}}“ lisamooduli laadimine ei õnnestunud",
"loaded": "„{{pluginName}}“ lisamoodul on laaditud",
"unload-failed": "„{{pluginName}}“ lisamooduli mälust eemaldamine ei õnnestunud",
"unloaded": "„{{pluginName}}“ lisamoodul on mälust eemaldatud"
}
}
},
"language": {
"code": "et",
"local-name": "Eesti",
"name": "Estonian"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Laadimine lõppes, arendaja tarvikud on avatud"
},
"i18n": {
"loaded": "i18n on laaditud"
},
"second-instance": {
"receive-command": "„{{command}}“ käsk on vastu võetud"
},
"theme": {
"css-file-not-found": "CSS faili „{{cssFile}}“ pole olemas, seega eirame eelistust"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "Menüü on peidetud ja „Alt“ klahviga saad ta nähtavaks (rakenduse-siseses menüüs „Esc“ klahviga)",
"message": "Menüü peitmine on sisselülitatud",
"title": "Menüü peitmine on sisselülitatud"
},
"need-to-restart": {
"buttons": {
"later": "Hiljem",
"restart-now": "Taaskäivita kohe"
},
"detail": "„{{pluginName}}“ lisamooduli sisselülitamine eeldab rakenduse taaskäivitamist",
"message": "„{{pluginName}}“ lisamoodul eeldab rakenduse taaskäivitamist",
"title": "Palun käivita rakendus uuesti"
},
"unresponsive": {
"buttons": {
"quit": "Välju",
"relaunch": "Käivita uuesti",
"wait": "Oota"
}
}
},
"menu": {
"navigation": {
"label": "Liikumine",
"submenu": {
"copy-current-url": "Kopeeri esitamisel oleva pala URL",
"go-back": "Mine tagasi",
"go-forward": "Mine edasi",
"quit": "Välju",
"restart": "Käivita rakendus uuesti"
}
},
"plugins": {
"label": "Lisamoodulid",
"new": "UUS"
},
"view": {
"submenu": {
"zoom-in": "Suumi sisse",
"zoom-out": "Suumi välja"
}
}
},
"tray": {
"next": "Edasi",
"play-pause": "Esita/Peata esitus",
"previous": "Eelmine",
"quit": "Välju",
"restart": "Käivita rakendus uuesti",
"show": "Näita akent",
"tooltip": {
"default": "YouTube Music",
"with-song-info": "YouTube Music: {{artist}} - {{title}}"
}
}
},
"plugins": {
"ad-speedup": {
"description": "Reklaami esitamisel summutatakse heli ja keritakse edasi 16-kordse kiirusega",
"name": "Reklaamikiirendaja"
},
"adblocker": {
"description": "Blokeeri kõik reklaamid ja jälitajad",
"menu": {
"blocker": "Blokeerijad"
},
"name": "Reklaamiblokeerija"
},
"ambient-mode": {
"menu": {
"opacity": {
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "Kvaliteet",
"submenu": {
"pixels": "{{quality}} pikslit"
}
}
}
},
"blur-nav-bar": {
"description": "Muudab navigatsiooniriba läbipaistavaks ja hägusaks",
"name": "Hägus navigatsiooniriba"
},
"lyrics-genius": {
"description": "Lisa enamustele lugudele laulusõnad",
"menu": {
"romanized-lyrics": "Latiniseeritud laulusõnad"
},
"name": "Lyrics Genius",
"renderer": {
"fetched-lyrics": "Leidsime Geeniuse jaoks ühed laulusõnad"
}
},
"navigation": {
"name": "Liikumine"
},
"scrobbler": {
"description": "Lisa kraasimise tugi (last.fm, Listenbrainz, jne)",
"dialog": {
"lastfm": {
"auth-failed": {
"message": "Last.fm'i autentimine ei õnnestunud\nPeida hüpikaken järgmise taaskäivituseni.",
"title": "Autentimine ei õnnestunud"
}
}
},
"menu": {
"lastfm": {
"api-settings": "Last.fm API seadistused"
},
"listenbrainz": {
"token": "Sisesta ListenBrainz'i kasutaja tunnusluba"
},
"scrobble-other-media": "Kraasi muud meediat"
},
"name": "Kraasija",
"prompt": {
"lastfm": {
"api-key": "Last.fm API võti",
"api-secret": "Last.fm API saladus"
},
"listenbrainz": {
"token": {
"label": "Sisesta oma ListenBrainz'i tunnusluba:",
"title": "ListenBrainz'i tunnusluba"
}
}
}
},
"synced-lyrics": {
"menu": {
"show-lyrics-even-if-inexact": {
"tooltip": "Kui lugu ei leidu, siis lisamoodul üritab uut otsingut teistsuguse päringuga.\nTeise katse puhul tulemused ei pruugi olla väga täpsed."
}
}
},
"tuna-obs": {
"description": "Lõimimine OBSi Tuna lisamooduliga"
}
}
}

View File

@ -207,10 +207,12 @@
}, },
"plugins": { "plugins": {
"ad-speedup": { "ad-speedup": {
"description": "Pag mag-play ng ad, I-mute ang audio at i-set ang bilis ng playback ng 16x" "description": "Pag mag-play ng ad, I-mute ang audio at i-set ang bilis ng playback ng 16x",
"name": "Pagbilis ng Ad"
}, },
"adblocker": { "adblocker": {
"description": "I-block ang lahat ng ad at tracking" "description": "I-block ang lahat ng ad at tracking",
"name": "Pag-block ng Ad"
}, },
"album-actions": { "album-actions": {
"description": "Idadagdag ang Undislike, Dislike, Like, at Unlike na button para ilapat ito sa lahat ng kanta sa isang playlist o album", "description": "Idadagdag ang Undislike, Dislike, Like, at Unlike na button para ilapat ito sa lahat ng kanta sa isang playlist o album",
@ -392,6 +394,8 @@
"download-finish-settings": { "download-finish-settings": {
"label": "Kung natapos ang download", "label": "Kung natapos ang download",
"prompt": { "prompt": {
"last-percent": "Tapos ng x na porsyento",
"last-seconds": "Huling x na segundo",
"title": "I-configure kung kailan magda-download" "title": "I-configure kung kailan magda-download"
}, },
"submenu": { "submenu": {
@ -406,6 +410,9 @@
}, },
"renderer": { "renderer": {
"can-not-update-progress": "Hindi ma-update ang progress" "can-not-update-progress": "Hindi ma-update ang progress"
},
"templates": {
"button": "Mag download"
} }
}, },
"exponential-volume": { "exponential-volume": {
@ -475,7 +482,8 @@
"name": "Nabigasyon" "name": "Nabigasyon"
}, },
"no-google-login": { "no-google-login": {
"description": "Tanggalin ang mga Google login na button at mga link mula sa interface" "description": "Tanggalin ang mga Google login na button at mga link mula sa interface",
"name": "Walang Google na Login"
}, },
"notifications": { "notifications": {
"description": "Magpakita ng notification kapag nagsimulang tumugtog ang kanta (magagamit ang mga interactive na notification sa Windows)", "description": "Magpakita ng notification kapag nagsimulang tumugtog ang kanta (magagamit ang mga interactive na notification sa Windows)",
@ -491,7 +499,8 @@
}, },
"priority": "Prioridad ng Notification", "priority": "Prioridad ng Notification",
"unpause-notification": "Ipakita ang notification sa pag-unpause" "unpause-notification": "Ipakita ang notification sa pag-unpause"
} },
"name": "Mga Abiso"
}, },
"picture-in-picture": { "picture-in-picture": {
"description": "Payagan ang pag-palit ng app sa picture-in-picture mode", "description": "Payagan ang pag-palit ng app sa picture-in-picture mode",
@ -580,6 +589,7 @@
"shortcuts": { "shortcuts": {
"description": "Nagbibigay-daan sa pagtatakda ng mga global hotkey para sa playback (play/pause/susunod/nakaraan) at pag-off ng media OSD sa pamamagitan ng pag-override sa mga media key, pag-on sa Ctrl/CMD + F para maghanap, pag-on sa suporta ng Linux MPRIS para sa mga media key, at mga custom na hotkey para sa mga advanced na user", "description": "Nagbibigay-daan sa pagtatakda ng mga global hotkey para sa playback (play/pause/susunod/nakaraan) at pag-off ng media OSD sa pamamagitan ng pag-override sa mga media key, pag-on sa Ctrl/CMD + F para maghanap, pag-on sa suporta ng Linux MPRIS para sa mga media key, at mga custom na hotkey para sa mga advanced na user",
"menu": { "menu": {
"override-media-keys": "I-override ang mga Media Key",
"set-keybinds": "I-set ang Global Song Control" "set-keybinds": "I-set ang Global Song Control"
}, },
"name": "Mga shortcut (at MPRIS)", "name": "Mga shortcut (at MPRIS)",
@ -587,6 +597,7 @@
"keybind": { "keybind": {
"keybind-options": { "keybind-options": {
"next": "Susunod", "next": "Susunod",
"play-pause": "Mag-play / Mag-pause",
"previous": "Nakaraan" "previous": "Nakaraan"
}, },
"label": "Pumili ng Global na Keybind para sa Songs Control:" "label": "Pumili ng Global na Keybind para sa Songs Control:"
@ -594,14 +605,65 @@
} }
}, },
"skip-disliked-songs": { "skip-disliked-songs": {
"description": "Laktawan ang na-dislike na kanta" "description": "Laktawan ang na-dislike na kanta",
"name": "I-skip ang mga Na-dislike na Kanta"
}, },
"skip-silences": { "skip-silences": {
"description": "Automatikong laktawan ang mga tahimik na mga seksyon sa kanta" "description": "Automatikong laktawan ang mga tahimik na mga seksyon sa kanta",
"name": "I-skip ang mga Katahimikan"
}, },
"sponsorblock": { "sponsorblock": {
"description": "Automatikong Laktawan ang di part ng kanta tulad ng intro/outro o part ng mga music video na ang kanta ay di nagple-play" "description": "Automatikong Laktawan ang di part ng kanta tulad ng intro/outro o part ng mga music video na ang kanta ay di nagple-play"
}, },
"synced-lyrics": {
"description": "Nagbibigay ng naka-sync na lyrics sa mga kanta, gamit ang mga provider tulad ng LRClib.",
"errors": {
"fetch": "⚠️ - Nagkaroon ng error habang kinukuha ang lyrics. Subukang muli mamaya.",
"not-found": "⚠️ - Walang nakitang lyrics para sa kantang ito."
},
"menu": {
"default-text-string": {
"label": "Default na character sa pagitan ng lyrics",
"tooltip": "Pumili ng default na character na gagamitin sa pagitan ng lyrics"
},
"line-effect": {
"label": "Effect ng Linya",
"submenu": {
"focus": {
"tooltip": "Gawing puti lamang ang kasalukuyang linya"
},
"offset": {
"tooltip": "I-offset sa kanan ang kasalukuyang linya"
},
"scale": {
"tooltip": "I-scale ang kasalukuyang linya"
}
},
"tooltip": "Pumili ng effect na ilalapat sa kasalukuyang linya"
},
"precise-timing": {
"label": "Gawing perpektong naka-sync ang lyrics",
"tooltip": "Kalkulahin sa millisecond ang pagpapakita ng susunod na linya (maaaring magkaroon ng maliit na epekto sa performance)"
},
"show-lyrics-even-if-inexact": {
"label": "Ipakita ang lyrics kahit di-eksakto",
"tooltip": "Kung hindi matagpuan ang kanta, susubukan muli ng plugin gamit ang ibang query sa paghahanap.\nAng resulta mula sa pangalawang pagsubok ay maaaring hindi eksakto."
},
"show-time-codes": {
"label": "Ipakita ang mga time code",
"tooltip": "Ipakita ang mga time code kasunod sa lyrics"
}
},
"refetch-btn": {
"fetching": "Nag-fe-fetch...",
"normal": "I-fetch muli ang lyrics"
},
"warnings": {
"duration-mismatch": "⚠️ - Maaaring hindi naka-sync ang lyrics dahil sa hindi pagkakatugma ng duration.",
"inexact": "⚠️ - Maaaring hindi eksakto ang lyrics para sa kantang ito",
"instrumental": "⚠️ - Ito ay isang instrumental na kanta"
}
},
"taskbar-mediacontrol": { "taskbar-mediacontrol": {
"description": "Kontrolin ang pag-play mula sa iyong taskbar ng Windows" "description": "Kontrolin ang pag-play mula sa iyong taskbar ng Windows"
}, },

View File

@ -668,6 +668,59 @@
"description": "Saute automatiquement les parties non musicales comme l'intro/outro ou les parties de clips vidéo où la chanson n'est pas lue", "description": "Saute automatiquement les parties non musicales comme l'intro/outro ou les parties de clips vidéo où la chanson n'est pas lue",
"name": "SponsorBlock" "name": "SponsorBlock"
}, },
"synced-lyrics": {
"description": "Ajoute des paroles synchronisées aux chansons, grâce à LRClib par exemple.",
"errors": {
"fetch": "⚠️ - Une erreur s'est produite en allant chercher les paroles. Merci de réessayer plus tard.",
"not-found": "⚠️ - Aucune paroles trouvées pour cette musique."
},
"menu": {
"default-text-string": {
"label": "Caractère par défaut entre les paroles",
"tooltip": "Choisi le caractère par défaut à utiliser pour l'espace entre les paroles"
},
"line-effect": {
"label": "Effet de ligne",
"submenu": {
"focus": {
"label": "Focus",
"tooltip": "Rend seulement la ligne actuelle blanche"
},
"offset": {
"label": "Décalage",
"tooltip": "Décale sur la droite la ligne actuelle"
},
"scale": {
"label": "Grossissement",
"tooltip": "Agrandis la ligne actuelle"
}
},
"tooltip": "Choisi l'effet à appliquer sur la ligne actuelle"
},
"precise-timing": {
"label": "Rend les paroles parfaitement synchronisées",
"tooltip": "Calcul à la milliseconde près l'affichage de la ligne suivante (peut avoir un faible impact sur les performances)"
},
"show-lyrics-even-if-inexact": {
"label": "Afficher les paroles même si inexactes",
"tooltip": "Si la musique n'est pas trouvé, le plugin essaye à nouveau avec une différence requête.\nLe résultat du deuxième essais peut ne pas être exacte."
},
"show-time-codes": {
"label": "Afficher les timecodes",
"tooltip": "Affiche à côté de chaque paroles son timecode"
}
},
"name": "Paroles Synchronisées",
"refetch-btn": {
"fetching": "Chargement...",
"normal": "Rafraîchir les paroles"
},
"warnings": {
"duration-mismatch": "⚠️ - Les paroles peuvent ne pas être synchronisées à cause d'une différence de durée.",
"inexact": "⚠️ - Les paroles de cette chanson peuvent ne pas être exactes",
"instrumental": "⚠️ - Cette musique n'a pas de paroles"
}
},
"taskbar-mediacontrol": { "taskbar-mediacontrol": {
"description": "Contrôlez la lecture depuis votre barre des tâches Windows", "description": "Contrôlez la lecture depuis votre barre des tâches Windows",
"name": "Contrôle multimédia de la barre des tâches" "name": "Contrôle multimédia de la barre des tâches"

View File

@ -7,8 +7,8 @@
"initialize-failed": "Nem sikerült inicializálni a \"{{pluginName}}\" plugint", "initialize-failed": "Nem sikerült inicializálni a \"{{pluginName}}\" plugint",
"load-all": "Összes bővítmény betöltése", "load-all": "Összes bővítmény betöltése",
"load-failed": "Nem sikerült betölteni a \"{{pluginName}}\" plugint", "load-failed": "Nem sikerült betölteni a \"{{pluginName}}\" plugint",
"loaded": "\"{{pluginName}}\" nevű plugin betöltve", "loaded": "\"{{pluginName}}\" plugin betöltve",
"unload-failed": "Nem sikerült a \"{{pluginName}}\" bővítményt letölteni", "unload-failed": "Nem sikerült a \"{{pluginName}}\" bővítményt kikapcsolni",
"unloaded": "A \"{{pluginName}}\" bővítmény kikapcsolva" "unloaded": "A \"{{pluginName}}\" bővítmény kikapcsolva"
} }
} }
@ -207,6 +207,10 @@
} }
}, },
"plugins": { "plugins": {
"ad-speedup": {
"description": "Ha reklám szól, elnémítja a hangot és a lejátszási sebességet 16x-ra állítja",
"name": "Gyorsítás hozzáadása"
},
"adblocker": { "adblocker": {
"description": "Alapértelmezés szerint blokkolja az összes hirdetést és nyomkövetést", "description": "Alapértelmezés szerint blokkolja az összes hirdetést és nyomkövetést",
"menu": { "menu": {
@ -407,7 +411,21 @@
"description": "MP3 / forrás hanganyag letöltése közvetlenül az interfészről", "description": "MP3 / forrás hanganyag letöltése közvetlenül az interfészről",
"menu": { "menu": {
"choose-download-folder": "Letöltési mappa kiválasztása", "choose-download-folder": "Letöltési mappa kiválasztása",
"download-finish-settings": {
"prompt": {
"last-percent": "x százalék után",
"last-seconds": "Utolsó x másodperc"
},
"submenu": {
"advanced": "Speciális",
"enabled": "Engedélyezve",
"mode": "Időmód",
"percent": "Százalék",
"seconds": "Másodpercek"
}
},
"download-playlist": "Lejátszási lista letöltése", "download-playlist": "Lejátszási lista letöltése",
"presets": "Előbeállítások",
"skip-existing": "Meglévő fájlok kihagyása" "skip-existing": "Meglévő fájlok kihagyása"
}, },
"name": "Letöltő", "name": "Letöltő",
@ -489,6 +507,7 @@
} }
}, },
"navigation": { "navigation": {
"description": "Következő/Vissza navigációs nyilak közvetlenül az interfészbe integrálva, mint a kedvenc böngésződben",
"name": "Navigáció" "name": "Navigáció"
}, },
"no-google-login": { "no-google-login": {
@ -502,10 +521,14 @@
"interactive-settings": { "interactive-settings": {
"label": "Interaktív beállítások", "label": "Interaktív beállítások",
"submenu": { "submenu": {
"hide-button-text": "Gombok szövegének elrejtése" "hide-button-text": "Gombok szövegének elrejtése",
"refresh-on-play-pause": "Frissítés lejátszás/szünet megnyomásakor",
"tray-controls": "Megnyitás/Bezárás tálca ikonra kattintva"
} }
}, },
"priority": "Értesítési prioritás" "priority": "Értesítési prioritás",
"toast-style": "Értesítés stílusa",
"unpause-notification": "Értesítés megjelenítése a lejátszás folytatásakor"
}, },
"name": "Értesítések" "name": "Értesítések"
}, },
@ -542,7 +565,9 @@
"precise-volume": { "precise-volume": {
"description": "A hangerő precíz szabályozása egérgörgővel/gyorsbillentyűkkel, egy egyedi HUD és testreszabható hangerő csuszka segítségével", "description": "A hangerő precíz szabályozása egérgörgővel/gyorsbillentyűkkel, egy egyedi HUD és testreszabható hangerő csuszka segítségével",
"menu": { "menu": {
"global-shortcuts": "Globális gyorsbillentyűk" "arrows-shortcuts": "Helyi nyíl-billentyűkkel való vezérlés",
"custom-volume-steps": "Egyedi hangerőléptetés beállítása",
"global-shortcuts": "Globális Gyorsbillentyűk"
}, },
"name": "Precíz hangerő", "name": "Precíz hangerő",
"prompt": { "prompt": {
@ -553,6 +578,10 @@
}, },
"label": "Válaszd ki a globális hangerő gyorsbillentyűket:", "label": "Válaszd ki a globális hangerő gyorsbillentyűket:",
"title": "Globális hangerő gyorsbillentyűk" "title": "Globális hangerő gyorsbillentyűk"
},
"volume-steps": {
"label": "Hangerő növelés/csökkentés léptékének kiválasztása",
"title": "Hangerő lépték"
} }
} }
}, },
@ -566,7 +595,8 @@
} }
} }
}, },
"description": "Lehetővé teszi a videó minőségének megváltoztatását egy gombbal a videó fedvényen" "description": "Lehetővé teszi a videó minőségének megváltoztatását egy gombbal a videó fedvényen",
"name": "Videóminőség modosító"
}, },
"scrobbler": { "scrobbler": {
"description": "Scrobbling támogatás hozzáadása (pl. last.fm, ListenBrainz)", "description": "Scrobbling támogatás hozzáadása (pl. last.fm, ListenBrainz)",
@ -613,6 +643,7 @@
"play-pause": "Lejátszás / Szünet", "play-pause": "Lejátszás / Szünet",
"previous": "Előző" "previous": "Előző"
}, },
"label": "Globális billentyűparancsok választása a dalok vezérléséhez:",
"title": "Globális gyorsbillentyűk" "title": "Globális gyorsbillentyűk"
} }
} }
@ -629,8 +660,26 @@
"description": "Automatikusan kihagyja a nem zenés részeket, mint például az intro/outro vagy a zenei videók olyan részeit, ahol a zene nem szól", "description": "Automatikusan kihagyja a nem zenés részeket, mint például az intro/outro vagy a zenei videók olyan részeit, ahol a zene nem szól",
"name": "SzponzorBlokk" "name": "SzponzorBlokk"
}, },
"synced-lyrics": {
"description": "Szinkronizált dalszövegeket biztosít dalokhoz, LRClib-hez hasonló szolgáltatókat használva.",
"errors": {
"fetch": "⚠️ - Hiba történt a dalszövegek lekérése közben. Kérlek, próbáld újra később.",
"not-found": "⚠️ - Nem található dalszöveg ehhez a zenéhez."
},
"name": "Szinkronizált dalszövegek",
"refetch-btn": {
"fetching": "Lekérés folyamatban...",
"normal": "Dalszöveg újra lekérése"
},
"warnings": {
"duration-mismatch": "⚠️ - A dalszövegek időzítése eltérhet a zene hossza miatt.",
"inexact": "⚠️ - Ennek a zenének a dalszövege pontatlan lehet",
"instrumental": "⚠️ - Ez egy hangszerekkel játszott zene"
}
},
"taskbar-mediacontrol": { "taskbar-mediacontrol": {
"description": "Lejátszás vezérlése a Windows tálcáról" "description": "Lejátszás vezérlése a Windows tálcáról",
"name": "Médiavezérlés a tálcán"
}, },
"touchbar": { "touchbar": {
"description": "macOS felhasználók számára hozzáad egy widgetet a TouchBar-hoz", "description": "macOS felhasználók számára hozzáad egy widgetet a TouchBar-hoz",
@ -667,6 +716,7 @@
} }
}, },
"visualizer": { "visualizer": {
"description": "Vizualizációt ad a lejátszóhoz",
"menu": { "menu": {
"visualizer-type": "Vizualizáció típus" "visualizer-type": "Vizualizáció típus"
}, },

View File

@ -207,6 +207,10 @@
} }
}, },
"plugins": { "plugins": {
"ad-speedup": {
"description": "Jika iklan diputar, audio akan dimatikan dan kecepatan pemutaran akan diatur ke 16x",
"name": "Percepatan Iklan"
},
"adblocker": { "adblocker": {
"description": "Blokir semua iklan dan pelacakan di luar kotak", "description": "Blokir semua iklan dan pelacakan di luar kotak",
"menu": { "menu": {
@ -410,6 +414,21 @@
"description": "Unduh MP3 / sumber suara secara langsung via antarmuka", "description": "Unduh MP3 / sumber suara secara langsung via antarmuka",
"menu": { "menu": {
"choose-download-folder": "Pilih folder unduhan", "choose-download-folder": "Pilih folder unduhan",
"download-finish-settings": {
"label": "Unduh setelah selesai",
"prompt": {
"last-percent": "x persen terakhir",
"last-seconds": "x detik terakhir",
"title": "Konfigurasikan kapan akan mengunduh"
},
"submenu": {
"advanced": "Lanjutan",
"enabled": "Diaktifkan",
"mode": "Mode waktu",
"percent": "Persen",
"seconds": "Detik"
}
},
"download-playlist": "Unduh daftar putar", "download-playlist": "Unduh daftar putar",
"presets": "Prasetel", "presets": "Prasetel",
"skip-existing": "Lewati berkas yang sudah ada" "skip-existing": "Lewati berkas yang sudah ada"
@ -649,6 +668,59 @@
"description": "Otomatis Melewati bagian yang bukan musik seperti intro/outro atau bagian dari video musik di mana lagu tidak dimainkan", "description": "Otomatis Melewati bagian yang bukan musik seperti intro/outro atau bagian dari video musik di mana lagu tidak dimainkan",
"name": "SponsorBlock" "name": "SponsorBlock"
}, },
"synced-lyrics": {
"description": "Menyediakan lirik lagu yang disinkronkan, menggunakan penyedia seperti LRClib.",
"errors": {
"fetch": "⚠️ - Terjadi kesalahan saat mengambil lirik. Coba lagi nanti.",
"not-found": "⚠️ - Tidak ada lirik yang ditemukan untuk lagu ini."
},
"menu": {
"default-text-string": {
"label": "Karakter default antara lirik",
"tooltip": "Pilih karakter default yang akan digunakan untuk celah antar lirik"
},
"line-effect": {
"label": "Efek garis",
"submenu": {
"focus": {
"label": "Fokus",
"tooltip": "Jadikan hanya baris saat ini berwarna putih"
},
"offset": {
"label": "Offset",
"tooltip": "Mengimbangi garis saat ini di sebelah kanan"
},
"scale": {
"label": "Skala",
"tooltip": "Skala garis saat ini"
}
},
"tooltip": "Pilih efek yang akan diterapkan ke baris saat ini"
},
"precise-timing": {
"label": "Buat liriknya tersinkronisasi dengan sempurna",
"tooltip": "Hitung hingga milidetik tampilan baris berikutnya (dapat berdampak kecil pada kinerja)"
},
"show-lyrics-even-if-inexact": {
"label": "Tampilkan lirik meskipun tidak tepat",
"tooltip": "Jika lagu tidak ditemukan, plugin akan mencoba lagi dengan kueri pencarian yang berbeda.\nHasil dari percobaan kedua mungkin tidak tepat."
},
"show-time-codes": {
"label": "Tampilkan kode waktu",
"tooltip": "Tampilkan kode waktu di samping lirik"
}
},
"name": "Lirik yang Disinkronkan",
"refetch-btn": {
"fetching": "Mengambil...",
"normal": "Ambil ulang lirik"
},
"warnings": {
"duration-mismatch": "⚠️ - Liriknya mungkin tidak sinkron karena ketidakcocokan durasi.",
"inexact": "⚠️ - Lirik lagu ini mungkin tidak tepat",
"instrumental": "⚠️ - Ini adalah lagu instrumental"
}
},
"taskbar-mediacontrol": { "taskbar-mediacontrol": {
"description": "Kendalikan pemutaran dari bilah alat Windows", "description": "Kendalikan pemutaran dari bilah alat Windows",
"name": "Pengendali Media di Bilah Alat" "name": "Pengendali Media di Bilah Alat"

View File

@ -207,6 +207,10 @@
} }
}, },
"plugins": { "plugins": {
"ad-speedup": {
"description": "Se viene riprodotto un annuncio, l'audio viene disattivato e viene impostata la velocità di riproduzione su 16x",
"name": "Accelerazione ad"
},
"adblocker": { "adblocker": {
"description": "Blocca tutti gli annunci e i tracker", "description": "Blocca tutti gli annunci e i tracker",
"menu": { "menu": {
@ -410,6 +414,21 @@
"description": "Download MP3 / sorgenti audio direttamente dall'interfaccia", "description": "Download MP3 / sorgenti audio direttamente dall'interfaccia",
"menu": { "menu": {
"choose-download-folder": "Scegli cartella download", "choose-download-folder": "Scegli cartella download",
"download-finish-settings": {
"label": "Scarica al termine",
"prompt": {
"last-percent": "Dopo x percento",
"last-seconds": "Ultimi x secondi",
"title": "Configura quando scaricare"
},
"submenu": {
"advanced": "Avanzato",
"enabled": "Abilitato",
"mode": "Modalità tempo",
"percent": "Percentuale",
"seconds": "Secondi"
}
},
"download-playlist": "Scarica la playlist", "download-playlist": "Scarica la playlist",
"presets": "Preimpostazioni", "presets": "Preimpostazioni",
"skip-existing": "Salta i file esistenti" "skip-existing": "Salta i file esistenti"
@ -649,6 +668,23 @@
"description": "Salta automaticamente le parti non musicali, come l'intro/outro delle canzoni o le parti dei video musicali in cui non viene riprodotto il brano", "description": "Salta automaticamente le parti non musicali, come l'intro/outro delle canzoni o le parti dei video musicali in cui non viene riprodotto il brano",
"name": "Blocco sponsor" "name": "Blocco sponsor"
}, },
"synced-lyrics": {
"description": "Fornisce testi sincronizzati alle canzoni, utilizzando provider come LRClib.",
"errors": {
"fetch": "⚠️ - Si è verificato un errore nel recuperare il testo. Per favore riprova più tardi.",
"not-found": "⚠️ - Nessun testo trovato per questa canzone."
},
"name": "Testi sincronizzati",
"refetch-btn": {
"fetching": "Sto recuperando...",
"normal": "Recupera i testi"
},
"warnings": {
"duration-mismatch": "⚠️ - I testi potrebbero non essere sincronizzati a causa di una mancata corrispondenza della durata.",
"inexact": "⚠️ - Il testo di questa canzone potrebbe essere inesatto",
"instrumental": "⚠️ - Questo è un brano strumentale"
}
},
"taskbar-mediacontrol": { "taskbar-mediacontrol": {
"description": "Controlla riproduzione dalla taskbar di Windows", "description": "Controlla riproduzione dalla taskbar di Windows",
"name": "Controlli multimediali sulla taskbar" "name": "Controlli multimediali sulla taskbar"

View File

@ -65,7 +65,7 @@
}, },
"detail": "ご不便をおかけして申し訳ございません! 何をするか選んでください:", "detail": "ご不便をおかけして申し訳ございません! 何をするか選んでください:",
"message": "アプリケーションは応答していません", "message": "アプリケーションは応答していません",
"title": "ウィンドウが応答しません" "title": "ウィンドウが応答していません"
}, },
"update-available": { "update-available": {
"buttons": { "buttons": {
@ -207,6 +207,10 @@
} }
}, },
"plugins": { "plugins": {
"ad-speedup": {
"description": "広告が再生されると、自動的にミュートされ、再生速度が16倍に設定されます",
"name": "広告のスピードを上げる"
},
"adblocker": { "adblocker": {
"description": "すべての広告とトラッカーをブロックj", "description": "すべての広告とトラッカーをブロックj",
"menu": { "menu": {
@ -410,6 +414,21 @@
"description": "UIから直にMP3・ソースオーディオをダウンロードします", "description": "UIから直にMP3・ソースオーディオをダウンロードします",
"menu": { "menu": {
"choose-download-folder": "ダウンロードフォルダ", "choose-download-folder": "ダウンロードフォルダ",
"download-finish-settings": {
"label": "完了時にダウンロード",
"prompt": {
"last-percent": "x パーセント後",
"last-seconds": "最後の x 秒",
"title": "保存するタイミング"
},
"submenu": {
"advanced": "高度な設定",
"enabled": "有効",
"mode": "時間モード",
"percent": "パーセント",
"seconds": "秒"
}
},
"download-playlist": "プレイリストをダウンロード", "download-playlist": "プレイリストをダウンロード",
"presets": "プリセット", "presets": "プリセット",
"skip-existing": "存在するファイルをスキップ" "skip-existing": "存在するファイルをスキップ"
@ -649,6 +668,59 @@
"description": "イントロ/アウトロなどの音楽以外の部分や、曲が再生されていないミュージック ビデオの部分を自動的にスキップします", "description": "イントロ/アウトロなどの音楽以外の部分や、曲が再生されていないミュージック ビデオの部分を自動的にスキップします",
"name": "SponsorBlock" "name": "SponsorBlock"
}, },
"synced-lyrics": {
"description": "LRClibのようなプロバイダを使って、楽曲に同期した歌詞を使用する。",
"errors": {
"fetch": "⚠️ - 歌詞の取得中にエラーが発生しました。 後でもう一度お試しください。",
"not-found": "⚠️ - この曲の歌詞は見つかりませんでした。"
},
"menu": {
"default-text-string": {
"label": "デフォルトの歌詞間の文字",
"tooltip": "歌詞と歌詞の間に使用するデフォルトの文字を選択してください"
},
"line-effect": {
"label": "歌詞表示のエフェクト",
"submenu": {
"focus": {
"label": "フォーカス",
"tooltip": "現在の行だけを白くする"
},
"offset": {
"label": "オフセット",
"tooltip": "オフセットを現在の行の右側にする"
},
"scale": {
"label": "サイズ",
"tooltip": "現在の行のサイズ変更をする"
}
},
"tooltip": "現在の行に適用するエフェクトを選択"
},
"precise-timing": {
"label": "歌詞を完璧に同期させる",
"tooltip": "次の行の表示をミリ秒単位で計算する(パフォーマンスに若干の影響を与える可能性があります)"
},
"show-lyrics-even-if-inexact": {
"label": "歌詞が不正確でも表示する",
"tooltip": "曲が見つからなかった場合、プラグインは別の検索クエリで再試行します。\nただし、再試行の結果は正確でない可能性があります。"
},
"show-time-codes": {
"label": "タイムコードを表示",
"tooltip": "歌詞の横にタイムコードを表示"
}
},
"name": "歌詞を同期",
"refetch-btn": {
"fetching": "取得中...",
"normal": "歌詞を再取得"
},
"warnings": {
"duration-mismatch": "⚠️ - タイミングが合わないため、歌詞が同期されていない可能性があります。",
"inexact": "⚠️ - この曲の歌詞は正確ではないかもしれません",
"instrumental": "⚠️ - これは演奏のみの曲です"
}
},
"taskbar-mediacontrol": { "taskbar-mediacontrol": {
"description": "Windowsタスクバーから再生をコントロール", "description": "Windowsタスクバーから再生をコントロール",
"name": "タスクバーメディアコントロール" "name": "タスクバーメディアコントロール"

View File

@ -674,6 +674,42 @@
"fetch": "⚠️ - 가사를 불러오는 동안 오류가 발생했습니다. 나중에 다시 시도해 주세요.", "fetch": "⚠️ - 가사를 불러오는 동안 오류가 발생했습니다. 나중에 다시 시도해 주세요.",
"not-found": "⚠️ - 이 노래의 가사를 찾을 수 없습니다." "not-found": "⚠️ - 이 노래의 가사를 찾을 수 없습니다."
}, },
"menu": {
"default-text-string": {
"label": "가사 사이에 표시할 문자",
"tooltip": "가사 사이의 빈 공간에 사용할 문자를 선택합니다"
},
"line-effect": {
"label": "줄 표시 효과",
"submenu": {
"focus": {
"label": "포커스",
"tooltip": "현재 줄만 하얀색으로 표시"
},
"offset": {
"label": "오프셋",
"tooltip": "현재 줄의 오른쪽에 오프셋 적용"
},
"scale": {
"label": "스케일",
"tooltip": "현재 줄에 스케일 적용"
}
},
"tooltip": "현재 줄에 적용할 효과를 선택합니다"
},
"precise-timing": {
"label": "가사를 최대한 정교하게 동기화",
"tooltip": "다음 줄의 표시를 밀리초 단위로 계산합니다 (성능에 약간의 영향을 미칠 수 있음)"
},
"show-lyrics-even-if-inexact": {
"label": "가사가 정확하지 않더라도 표시",
"tooltip": "노래를 찾을 수 없는 경우, 플러그인이 다른 검색어로 다시 검색합니다.\n두번째 검색 결과는 정확하지 않을 수 있습니다."
},
"show-time-codes": {
"label": "시간 코드 표시",
"tooltip": "가사 옆에 시간 코드 표시"
}
},
"name": "싱크 가사", "name": "싱크 가사",
"refetch-btn": { "refetch-btn": {
"fetching": "가져오는 중...", "fetching": "가져오는 중...",

View File

@ -5,6 +5,7 @@
"execute-failed": "Pelaksaan plugin gagal {{pluginName}}::{{contextName}}", "execute-failed": "Pelaksaan plugin gagal {{pluginName}}::{{contextName}}",
"executed-at-ms": "Plugin {{pluginName}}::{{contextName}} dilaksanakan pada {{ms}}ms", "executed-at-ms": "Plugin {{pluginName}}::{{contextName}} dilaksanakan pada {{ms}}ms",
"initialize-failed": "Gagal untuk memulakan plugin \"{{pluginName}}\"", "initialize-failed": "Gagal untuk memulakan plugin \"{{pluginName}}\"",
"load-all": "Memuatkan semua plugin",
"loaded": "Plugin \"{{pluginName}}\" dimuatkan", "loaded": "Plugin \"{{pluginName}}\" dimuatkan",
"unload-failed": "Gagal untuk memunggah plugin \"{{pluginName}}\"", "unload-failed": "Gagal untuk memunggah plugin \"{{pluginName}}\"",
"unloaded": "Plugin \"{{pluginName}}\" dipunggahkan" "unloaded": "Plugin \"{{pluginName}}\" dipunggahkan"
@ -39,15 +40,22 @@
"detail": "Menu telah disembunyikan, guna 'Alt' untuk menunjukkannya (atau 'Escape' jika menggunakan In-App Menu)" "detail": "Menu telah disembunyikan, guna 'Alt' untuk menunjukkannya (atau 'Escape' jika menggunakan In-App Menu)"
}, },
"need-to-restart": { "need-to-restart": {
"buttons": {
"later": "Nanti",
"restart-now": "Restart Sekarang"
},
"message": "\"{{pluginName}}\" perlu dimulakan semula", "message": "\"{{pluginName}}\" perlu dimulakan semula",
"title": "Mulakan Semula Diperlukan" "title": "Mulakan Semula Diperlukan"
}, },
"unresponsive": { "unresponsive": {
"buttons": { "buttons": {
"quit": "Berhenti",
"relaunch": "Lancar Semula", "relaunch": "Lancar Semula",
"wait": "Tunggu" "wait": "Tunggu"
}, },
"detail": "Kami memohon maaf atas kesulitan! sila pilih apa yang perlu dilakukan:" "detail": "Kami memohon maaf atas kesulitan! sila pilih apa yang perlu dilakukan:",
"message": "Aplikasi Tidak Responsif",
"title": "Window Tidak Responsif"
}, },
"update-available": { "update-available": {
"buttons": { "buttons": {
@ -58,16 +66,137 @@
} }
}, },
"menu": { "menu": {
"about": "Mengenai",
"navigation": { "navigation": {
"label": "Navigasi", "label": "Navigasi",
"submenu": { "submenu": {
"copy-current-url": "Salin URL semasa", "copy-current-url": "Salin URL semasa",
"go-back": "Pulang", "go-back": "Belakang",
"go-forward": "Depan",
"quit": "Keluar" "quit": "Keluar"
} }
}, },
"options": { "options": {
"label": "Pilihan" "label": "Tetapan",
"submenu": {
"advanced-options": {
"label": "Tetapan Lanjutan",
"submenu": {
"set-proxy": {
"prompt": {
"placeholder": "Contoh: SOCKS5://127.0.0.1:9999",
"title": "Set proksi"
}
}
}
},
"always-on-top": "Sentiasa di atas",
"auto-update": "Kemas Kini Automatik",
"hide-menu": {
"dialog": {
"message": "Menu akan disembunyikan pada pelancaran seterusnya, gunakan [Alt] untuk menunjukkannya (atau backtick [`] jika menggunakan dalam aplikasi-menu)"
}
},
"language": {
"dialog": {
"message": "Bahasa akan ditukar selepas dimulakan semula",
"title": "Bahasa Berubah"
},
"label": "Bahasa",
"submenu": {
"to-help-translate": "Ingin membantu menterjemah? Klik di sini"
}
},
"resume-on-start": "Mulakan semula lagu terakhir apabila aplikasi dimulakan",
"start-at-login": "Mulakan semasa log masuk",
"starting-page": {
"label": "Halaman Permulaan"
},
"tray": {
"submenu": {
"play-pause-on-click": "Main / Hentikan pada klik"
}
},
"visual-tweaks": {
"label": "Pembaikan Visual",
"submenu": {
"like-buttons": {
"default": "Lalai",
"hide": "Sembunyi"
},
"theme": {
"dialog": {
"button": {
"cancel": "Batalkan",
"remove": "Padam"
}
},
"label": "Tema"
}
}
}
}
}
},
"tray": {
"next": "Seterusnya",
"play-pause": "Main / Jeda",
"previous": "Sebelumnya",
"quit": "Keluar",
"restart": "Mulakan Semula Aplikasi"
}
},
"plugins": {
"ambient-mode": {
"menu": {
"quality": {
"label": "Kualiti"
},
"size": {
"label": "Saiz"
}
}
},
"captions-selector": {
"prompt": {
"selector": {
"title": "Pilih bahasa kapsyen"
}
}
},
"synced-lyrics": {
"menu": {
"show-lyrics-even-if-inexact": {
"label": "Tunjukkan lirik walaupun tidak tepat",
"tooltip": "Jika lagu tidak ditemui, plugin cuba lagi dengan pertanyaan carian yang berbeza. \nHasil dari percubaan kedua mungkin tidak tepat."
},
"show-time-codes": {
"tooltip": "Tunjukkan kod masa di sebelah lirik"
}
}
},
"taskbar-mediacontrol": {
"description": "Kawalan main balik dari bar tugas Windows anda",
"name": "Kawalan Media Bar Tugas"
},
"video-toggle": {
"menu": {
"align": {
"submenu": {
"left": "Kiri",
"middle": "Tengah",
"right": "Kanan"
}
},
"force-hide": "Alih Keluar Tab Video",
"mode": {
"submenu": {
"disabled": "Tidak Aktif"
}
}
},
"templates": {
"button": "Lagu"
} }
} }
} }

View File

@ -158,6 +158,14 @@
}, },
"remove-upgrade-button": "Upgrade-knop verwijderen", "remove-upgrade-button": "Upgrade-knop verwijderen",
"theme": { "theme": {
"dialog": {
"button": {
"cancel": "Annuleren",
"remove": "Verwijderen"
},
"remove-theme": "Weet je zeker dat je het aangepaste thema wilt verwijderen?",
"remove-theme-message": "Dit verwijderd het aangepaste thema"
},
"label": "Thema", "label": "Thema",
"submenu": { "submenu": {
"import-css-file": "Aangepast CSS-bestand importeren", "import-css-file": "Aangepast CSS-bestand importeren",
@ -199,6 +207,10 @@
} }
}, },
"plugins": { "plugins": {
"ad-speedup": {
"description": "Wanneer een advertentie afspeelt, dempt het geluid en versnelt de playback naar 16x",
"name": "Snellere advertenties"
},
"adblocker": { "adblocker": {
"description": "Blokkeer alle advertenties en tracking vanuit de doos", "description": "Blokkeer alle advertenties en tracking vanuit de doos",
"menu": { "menu": {
@ -402,6 +414,21 @@
"description": "Download MP3 / bron-audio rechtstreeks vanuit de interface", "description": "Download MP3 / bron-audio rechtstreeks vanuit de interface",
"menu": { "menu": {
"choose-download-folder": "Kies de downloadmap", "choose-download-folder": "Kies de downloadmap",
"download-finish-settings": {
"label": "Downloaden bij voltooiing",
"prompt": {
"last-percent": "Na x procent",
"last-seconds": "Laatste x seconden",
"title": "Configureren wanneer te downloaden"
},
"submenu": {
"advanced": "Geavanceerd",
"enabled": "Ingeschakeld",
"mode": "Tijd-modus",
"percent": "Procent",
"seconds": "Seconden"
}
},
"download-playlist": "Afspeellijst downloaden", "download-playlist": "Afspeellijst downloaden",
"presets": "Voorinstellingen", "presets": "Voorinstellingen",
"skip-existing": "Bestaande bestanden overslaan" "skip-existing": "Bestaande bestanden overslaan"
@ -523,8 +550,20 @@
"save-window-size": "Sla schermgrootte op" "save-window-size": "Sla schermgrootte op"
} }
}, },
"video-toggle": {
"menu": {
"mode": {
"submenu": {
"disabled": "Uitgeschakeld"
}
}
}
},
"visualizer": { "visualizer": {
"description": "Voeg een visuele equalizer toe", "description": "Voeg een visuele equalizer toe",
"menu": {
"visualizer-type": "Type visualisator"
},
"name": "Visualisator" "name": "Visualisator"
} }
} }

View File

@ -668,6 +668,59 @@
"description": "Ignora automaticamente partes não musicais, como introdução/final ou partes de videoclipes onde a música não está tocando", "description": "Ignora automaticamente partes não musicais, como introdução/final ou partes de videoclipes onde a música não está tocando",
"name": "SponsorBlock (bloqueador de patrocínios)" "name": "SponsorBlock (bloqueador de patrocínios)"
}, },
"synced-lyrics": {
"description": "Fornece letras sincronizadas de músicas, utilizando fornecedores como o LRClib.",
"errors": {
"fetch": "⚠️ - Ocorreu um erro ao obter as letras da música. Por favor, tenta novamente mais tarde.",
"not-found": "⚠️ - Não foram encontradas letras para esta música."
},
"menu": {
"default-text-string": {
"label": "Caractere padrão entre as letras",
"tooltip": "Escolha o caractere padrão para usar no espaço entre as letras"
},
"line-effect": {
"label": "Efeito de linha",
"submenu": {
"focus": {
"label": "Foco",
"tooltip": "Deixe apenas a linha atual branca"
},
"offset": {
"label": "Deslocamento",
"tooltip": "Desloque a linha atual para a direita"
},
"scale": {
"label": "Escala",
"tooltip": "Escalar a linha atual"
}
},
"tooltip": "Escolha o efeito a ser aplicado à linha atual"
},
"precise-timing": {
"label": "Sincronize perfeitamente as letras",
"tooltip": "Calcule até o milissegundo a exibição da próxima linha (pode ter um pequeno impacto no desempenho)"
},
"show-lyrics-even-if-inexact": {
"label": "Mostrar letras mesmo que imprecisas",
"tooltip": "Se a música não for encontrada, o plugin tenta novamente com uma consulta de pesquisa diferente.\nO resultado da segunda tentativa pode não ser exato."
},
"show-time-codes": {
"label": "Mostrar códigos de tempo",
"tooltip": "Mostrar os códigos de tempo ao lado das letras"
}
},
"name": "Letras Sincronizadas",
"refetch-btn": {
"fetching": "Buscando...",
"normal": "Buscar as letras novamente"
},
"warnings": {
"duration-mismatch": "⚠️ - As letras da música pode estar dessincronizada devido a um erro de duração.",
"inexact": "⚠️ - As letras desta música podem não ser exactas.",
"instrumental": "⚠️ - Esta é uma música instrumental."
}
},
"taskbar-mediacontrol": { "taskbar-mediacontrol": {
"description": "Controle a reprodução na barra de tarefas do Windows", "description": "Controle a reprodução na barra de tarefas do Windows",
"name": "Controle de mídia da barra de tarefas" "name": "Controle de mídia da barra de tarefas"

View File

@ -207,6 +207,10 @@
} }
}, },
"plugins": { "plugins": {
"ad-speedup": {
"description": "Если воспроизводится реклама, аудио заглушается и скорость воспроизведения устанавливается на 16х",
"name": "Ускоренная перемотка"
},
"adblocker": { "adblocker": {
"description": "Блокируйте всю рекламу и трекинг сразу после установки", "description": "Блокируйте всю рекламу и трекинг сразу после установки",
"menu": { "menu": {
@ -277,7 +281,7 @@
}, },
"audio-compressor": { "audio-compressor": {
"description": "Применяет компрессию к аудио (уменьшает громкость самых громких частей сигнала и повышает громкость самых тихих частей)", "description": "Применяет компрессию к аудио (уменьшает громкость самых громких частей сигнала и повышает громкость самых тихих частей)",
"name": "Аудио компрессор" "name": "Нормализация аудио"
}, },
"blur-nav-bar": { "blur-nav-bar": {
"description": "Делает панель навигации прозрачной и размытой", "description": "Делает панель навигации прозрачной и размытой",
@ -649,6 +653,58 @@
"description": "Автоматически пропускает не музыкальные фрагменты, например интро/аутро или фрагменты музыкальных клипов, в которых песня не звучит (тишина)", "description": "Автоматически пропускает не музыкальные фрагменты, например интро/аутро или фрагменты музыкальных клипов, в которых песня не звучит (тишина)",
"name": "SponsorBlock" "name": "SponsorBlock"
}, },
"synced-lyrics": {
"description": "Предоставляет синхронизированные слова для песен из таких источников, как LRClib.",
"errors": {
"fetch": "⚠️ - Возникла ошибка во время получения слов. Повторите попытку позже.",
"not-found": "⚠️ - Для этой песни не найдено слов."
},
"menu": {
"default-text-string": {
"label": "Стандартный символ между словами",
"tooltip": "Выберите стандартный символ для заполнения пространства между словами"
},
"line-effect": {
"label": "Эффект строки",
"submenu": {
"focus": {
"label": "Фокусировка",
"tooltip": "Делает только текущую строку белой"
},
"offset": {
"label": "Сдвиг",
"tooltip": "Сдвигает текущую строку вправо"
},
"scale": {
"label": "Увеличение",
"tooltip": "Увеличивает текущую строку"
}
},
"tooltip": "Выберите эффект применяемый к текущей строке"
},
"precise-timing": {
"label": "Идеально синхронизировать слова",
"tooltip": "До миллисекунды рассчитывает отображение следующей строки(может оказать небольшое влияние на производительность)"
},
"show-lyrics-even-if-inexact": {
"label": "Показывать слова, даже если неточные",
"tooltip": "Если песня не найдена, плагин попытается снова с другим поисковым запросом.\nСо второй попытки результат может быть неточным."
},
"show-time-codes": {
"label": "Показывать временные метки",
"tooltip": "Показывает временные метки рядом со словами"
}
},
"refetch-btn": {
"fetching": "Сбор данных...",
"normal": "Обновить слова"
},
"warnings": {
"duration-mismatch": "⚠️ - Слова могут быть неточно синхронизированы из-за несовпадения длины трека.",
"inexact": "⚠️ - Слова для этой песни могут быть неточными",
"instrumental": "⚠️ - Это инструментальная музыка"
}
},
"taskbar-mediacontrol": { "taskbar-mediacontrol": {
"description": "Управляйте воспроизведением с панели задач Windows", "description": "Управляйте воспроизведением с панели задач Windows",
"name": "Управление мультимедиа на панели задач" "name": "Управление мультимедиа на панели задач"
@ -692,7 +748,7 @@
"menu": { "menu": {
"visualizer-type": "Вид визуализации" "visualizer-type": "Вид визуализации"
}, },
"name": "Визуалайзер" "name": "Визуализатор"
} }
} }
} }

View File

@ -207,6 +207,10 @@
} }
}, },
"plugins": { "plugins": {
"ad-speedup": {
"description": "Bir reklam oynatılırsa sesi kapatır ve oynatma hızını 16x olarak ayarlar",
"name": "Hızlandırma"
},
"adblocker": { "adblocker": {
"description": "Tüm reklamları ve izleyicileri engelle", "description": "Tüm reklamları ve izleyicileri engelle",
"menu": { "menu": {
@ -664,6 +668,59 @@
"description": "Giriş/Çıkış gibi müzik olmayan kısımları veya müzik videolarında şarkının çalmadığı kısımları otomatik olarak atlar", "description": "Giriş/Çıkış gibi müzik olmayan kısımları veya müzik videolarında şarkının çalmadığı kısımları otomatik olarak atlar",
"name": "SponsorBlock" "name": "SponsorBlock"
}, },
"synced-lyrics": {
"description": "LRClib gibi sağlayıcıları kullanarak şarkılara senkronize şarkı sözleri sağlar.",
"errors": {
"fetch": "⚠️ - Şarkı sözleri getirilirken bir hata oluştu. Lütfen daha sonra tekrar deneyin.",
"not-found": "⚠️ - Bu şarkı için şarkı sözü bulunamadı."
},
"menu": {
"default-text-string": {
"label": "Şarkı sözleri arasında varsayılan karakter",
"tooltip": "Şarkı sözleri arasındaki boşluk için kullanılacak varsayılan karakteri seçin"
},
"line-effect": {
"label": "Çizgi etkisi",
"submenu": {
"focus": {
"label": "odak",
"tooltip": "Yalnızca geçerli satırı beyaz yapın"
},
"offset": {
"label": "telafi etmek,ofset",
"tooltip": "Geçerli satırın sağındaki ofset"
},
"scale": {
"label": "ölçek",
"tooltip": "Geçerli satırı ölçeklendirir"
}
},
"tooltip": "Geçerli satıra uygulanacak efekti seçin"
},
"precise-timing": {
"label": "Şarkı sözlerini mükemmel şekilde senkronize edin",
"tooltip": "Bir sonraki satırın görüntülenmesini milisaniyesine kadar hesaplayın (performans üzerinde küçük bir etkisi olabilir)"
},
"show-lyrics-even-if-inexact": {
"label": "Kesin olmasa bile şarkı sözlerini gösterin",
"tooltip": "Şarkı bulunamazsa, eklenti farklı bir arama sorgusuyla tekrar dener. \nİkinci denemenin sonucu tam olmayabilir."
},
"show-time-codes": {
"label": "Zaman kodlarını göster",
"tooltip": "Şarkı sözlerinin yanında zaman kodlarını gösterin"
}
},
"name": "Senkronize Şarkı Sözleri",
"refetch-btn": {
"fetching": "Getiriliyor...",
"normal": "Refetch şarkı sözleri"
},
"warnings": {
"duration-mismatch": "⚠️ - Süre uyuşmazlığı nedeniyle şarkı sözleri senkronize olmayabilir.",
"inexact": "⚠️ - Bu şarkının sözleri tam olmayabilir",
"instrumental": "⚠️ - Bu enstrümantal bir şarkıdır"
}
},
"taskbar-mediacontrol": { "taskbar-mediacontrol": {
"description": "Windows görev çubuğu üzerinden oynatmayı kontrol edebilmenize olanak sağlar", "description": "Windows görev çubuğu üzerinden oynatmayı kontrol edebilmenize olanak sağlar",
"name": "Görev Çubuğu Medya Kontrolü" "name": "Görev Çubuğu Medya Kontrolü"

View File

@ -668,6 +668,55 @@
"description": "Tự động bỏ qua các phần không phải âm nhạc như phần giới thiệu/kết thúc hoặc các phần của video nhạc mà bài hát không được phát", "description": "Tự động bỏ qua các phần không phải âm nhạc như phần giới thiệu/kết thúc hoặc các phần của video nhạc mà bài hát không được phát",
"name": "SponsorBlock" "name": "SponsorBlock"
}, },
"synced-lyrics": {
"description": "Cung cấp lời bài hát được đồng bộ hoá với các bài hát, sử dụng những nhà cung cấp như LRClib.",
"errors": {
"fetch": "⚠️ - Đã xảy ra lỗi khi tìm nạp lời bài hát, Vui lòng thử lại sau.",
"not-found": "⚠️ - Không tìm thấy lời cho bài hát này."
},
"menu": {
"default-text-string": {
"label": "Kí tự mặc định giữa các lời bài hát",
"tooltip": "Chọn kí tự mặc định cho khoảng trống giữa các lời bài hát"
},
"line-effect": {
"label": "Kiểu đường thẳng",
"submenu": {
"focus": {
"label": "Tập trung",
"tooltip": "Chỉ làm cho dòng hiện tại có màu trắng"
},
"scale": {
"label": "Tỉ lệ",
"tooltip": "Áp dụng tỉ lệ cho dòng hiện tại"
}
},
"tooltip": "Chọn kiểu để áp dụng cho dòng hiện tại"
},
"precise-timing": {
"label": "Làm cho lời bài hát được đồng bộ hoàn hảo",
"tooltip": "Tính toán chính xác đến mili giây thời gian hiển thị dòng tiếp theo (có thể có tác động nhỏ đến hiệu suất)"
},
"show-lyrics-even-if-inexact": {
"label": "Hiển thị lời bài hát ngay cả khi không chính xác",
"tooltip": "Nếu không tìm thấy bài hát, plugin sẽ thử lại bằng truy vấn tìm kiếm khác.\nKết quả từ lần thử thứ hai có thể không chính xác."
},
"show-time-codes": {
"label": "Hiện mốc thời gian",
"tooltip": "Hiện mốc thời gian bên cạnh lời bài hát"
}
},
"name": "Lời bài hát được đồng bộ hoá",
"refetch-btn": {
"fetching": "Đang tìm nạp...",
"normal": "Tải lại lời bài hát"
},
"warnings": {
"duration-mismatch": "⚠️ - Lời bài hát có thể không đồng bộ do thời lượng không khớp.",
"inexact": "⚠️ - Lời bài hát này có thể không chính xác",
"instrumental": "⚠️ - Đây là một bài hát trình diễn bằng nhạc khí"
}
},
"taskbar-mediacontrol": { "taskbar-mediacontrol": {
"description": "Kiểm soát phát lại từ thanh tác vụ Windows của bạn", "description": "Kiểm soát phát lại từ thanh tác vụ Windows của bạn",
"name": "Kiểm soát phương tiện trên thanh tác vụ" "name": "Kiểm soát phương tiện trên thanh tác vụ"

View File

@ -207,6 +207,10 @@
} }
}, },
"plugins": { "plugins": {
"ad-speedup": {
"description": "使用静音以及 16 倍速播放跳过广告片段",
"name": "广告加速跳过"
},
"adblocker": { "adblocker": {
"description": "屏蔽所有广告与跟踪器", "description": "屏蔽所有广告与跟踪器",
"menu": { "menu": {
@ -664,6 +668,59 @@
"description": "自动跳过非音乐部分,如 MV 的介绍/结语以及歌曲未开始的部分", "description": "自动跳过非音乐部分,如 MV 的介绍/结语以及歌曲未开始的部分",
"name": "SponsorBlock" "name": "SponsorBlock"
}, },
"synced-lyrics": {
"description": "透过 LRClib 等服务提供滚动歌词显示。",
"errors": {
"fetch": "⚠️ - 获取歌词时发生错误。请稍后再试。",
"not-found": "⚠️ - 未找到此歌曲的歌词。"
},
"menu": {
"default-text-string": {
"label": "默认的歌词行间字符",
"tooltip": "选择在歌词间隙期间默认显示的字符"
},
"line-effect": {
"label": "歌词行特效",
"submenu": {
"focus": {
"label": "高亮",
"tooltip": "仅将当前歌词行显示为白色"
},
"offset": {
"label": "偏移",
"tooltip": "将当前歌词行向右偏移"
},
"scale": {
"label": "放大",
"tooltip": "放大当前歌词行"
}
},
"tooltip": "选择当前歌词行应用的特效"
},
"precise-timing": {
"label": "让滚动歌词完全同步",
"tooltip": "以毫秒精度估算下句歌词的显示时间(可能对性能有小幅影响)"
},
"show-lyrics-even-if-inexact": {
"label": "即使时值不精确依然显示歌词",
"tooltip": "若首次搜索未找到该歌曲的歌词,插件将尝试用不同的查询方式重新获取。\n重试查询的结果可能不精确。"
},
"show-time-codes": {
"label": "显示时值",
"tooltip": "在歌词旁显示时值"
}
},
"name": "滚动歌词",
"refetch-btn": {
"fetching": "正在获取…",
"normal": "重新获取歌词"
},
"warnings": {
"duration-mismatch": "⚠️ - 由于持续时间不对应,滚动歌词可能不同步。",
"inexact": "⚠️ - 此曲目的歌词可能不准确",
"instrumental": "⚠️ - 此曲目为纯音乐"
}
},
"taskbar-mediacontrol": { "taskbar-mediacontrol": {
"description": "从 Windows 任务栏控制音乐回放", "description": "从 Windows 任务栏控制音乐回放",
"name": "任务栏媒体控件" "name": "任务栏媒体控件"

View File

@ -207,6 +207,10 @@
} }
}, },
"plugins": { "plugins": {
"ad-speedup": {
"description": "使用 16 倍速播放及靜音來跳過廣告片段",
"name": "加速略過"
},
"adblocker": { "adblocker": {
"description": "阻擋所有廣告", "description": "阻擋所有廣告",
"menu": { "menu": {
@ -341,10 +345,10 @@
"discord": { "discord": {
"backend": { "backend": {
"already-connected": "已嘗試可用連接", "already-connected": "已嘗試可用連接",
"connected": "已連接至Discord", "connected": "已連接至 Discord",
"disconnected": "與Discord斷開連接" "disconnected": "與 Discord 斷開連接"
}, },
"description": "使用Discord狀態與你的好友分享你正在收聽的音樂", "description": "使用 Discord 狀態與你的好友分享你正在收聽的音樂",
"menu": { "menu": {
"auto-reconnect": "自動重新連接", "auto-reconnect": "自動重新連接",
"clear-activity": "清除狀態", "clear-activity": "清除狀態",
@ -352,11 +356,11 @@
"connected": "已連接", "connected": "已連接",
"disconnected": "已斷開連接", "disconnected": "已斷開連接",
"hide-duration-left": "隱藏音樂剩餘時間狀態", "hide-duration-left": "隱藏音樂剩餘時間狀態",
"hide-github-button": "隱藏Github頁面按鈕", "hide-github-button": "隱藏 Github 頁面按鈕",
"play-on-youtube-music": "顯示Play on YouTube Music按鈕", "play-on-youtube-music": "顯示 Play on YouTube Music 按鈕",
"set-inactivity-timeout": "設定閒置狀態時長" "set-inactivity-timeout": "設定閒置狀態時長"
}, },
"name": "Discord狀態", "name": "Discord 狀態",
"prompt": { "prompt": {
"set-inactivity-timeout": { "set-inactivity-timeout": {
"label": "設定多少秒後清除狀態:", "label": "設定多少秒後清除狀態:",
@ -513,8 +517,8 @@
"name": "導覽列" "name": "導覽列"
}, },
"no-google-login": { "no-google-login": {
"description": "移除Google登入按鈕及連結", "description": "移除 Google 登入按鈕及連結",
"name": "停用Google登入" "name": "停用 Google 登入"
}, },
"notifications": { "notifications": {
"description": "在歌曲播放時發送一個系統通知 (可互動通知僅限Windows)", "description": "在歌曲播放時發送一個系統通知 (可互動通知僅限Windows)",
@ -664,6 +668,59 @@
"description": "自動跳過贊助片段", "description": "自動跳過贊助片段",
"name": "贊助阻擋" "name": "贊助阻擋"
}, },
"synced-lyrics": {
"description": "使用 LRClib 等管道提供歌詞同步顯示。",
"errors": {
"fetch": "⚠️擷取歌詞時發生錯誤。請稍後再試。",
"not-found": "⚠️未找到該首歌曲的歌詞。"
},
"menu": {
"default-text-string": {
"label": "預設歌詞中間隔的符號",
"tooltip": "選擇歌詞中間隔要使用的符號"
},
"line-effect": {
"label": "歌詞顯示效果",
"submenu": {
"focus": {
"label": "高亮",
"tooltip": "高亮當前的歌詞"
},
"offset": {
"label": "凸行",
"tooltip": "凸行當前的歌詞"
},
"scale": {
"label": "放大",
"tooltip": "放大當前的歌詞"
}
},
"tooltip": "選擇要使用的歌詞顯示效果"
},
"precise-timing": {
"label": "使歌詞完美同步",
"tooltip": "更精確的計算下一行歌詞的顯示(將會降低些許效能)"
},
"show-lyrics-even-if-inexact": {
"label": "即使不精確依然強制顯示歌詞",
"tooltip": "當找不到符合該歌曲的歌詞時,該功能會嘗試不同的搜尋方式\n使用不同的搜尋方式會導致不精確的結果。"
},
"show-time-codes": {
"label": "顯示時間線",
"tooltip": "在歌詞旁顯示時間線"
}
},
"name": "歌詞同步",
"refetch-btn": {
"fetching": "擷取中...",
"normal": "重新擷取歌詞"
},
"warnings": {
"duration-mismatch": "⚠️歌詞可能會出現不同步的情況。",
"inexact": "⚠️該歌曲的歌詞可能並不精確",
"instrumental": "⚠️該首歌曲並無人聲"
}
},
"taskbar-mediacontrol": { "taskbar-mediacontrol": {
"description": "允許工作列應用程式預覽介面顯示媒體控制相關按鈕", "description": "允許工作列應用程式預覽介面顯示媒體控制相關按鈕",
"name": "工作列媒體控制" "name": "工作列媒體控制"

View File

@ -56,6 +56,7 @@ import { loadI18n, setLanguage, t } from '@/i18n';
import ErrorHtmlAsset from '@assets/error.html?asset'; import ErrorHtmlAsset from '@assets/error.html?asset';
import type { PluginConfig } from '@/types/plugins'; import type { PluginConfig } from '@/types/plugins';
import BrowserWindowConstructorOptions = Electron.BrowserWindowConstructorOptions;
if (!is.macOS()) { if (!is.macOS()) {
delete allPlugins['touchbar']; delete allPlugins['touchbar'];
@ -286,6 +287,23 @@ async function createMainWindow() {
height: 32, height: 32,
}; };
const decorations: Partial<BrowserWindowConstructorOptions> = {
frame: !is.macOS() && !useInlineMenu,
titleBarOverlay: defaultTitleBarOverlayOptions,
titleBarStyle: useInlineMenu
? 'hidden'
: is.macOS()
? 'hiddenInset'
: 'default',
autoHideMenuBar: config.get('options.hideMenu'),
};
// Note: on linux, for some weird reason, having these extra properties with 'frame: false' does not work
if (is.linux() && useInlineMenu) {
delete decorations.titleBarOverlay;
delete decorations.titleBarStyle;
}
const win = new BrowserWindow({ const win = new BrowserWindow({
icon, icon,
width: windowSize.width, width: windowSize.width,
@ -303,14 +321,7 @@ async function createMainWindow() {
sandbox: false, sandbox: false,
}), }),
}, },
frame: !is.macOS() && !useInlineMenu, ...decorations,
titleBarOverlay: defaultTitleBarOverlayOptions,
titleBarStyle: useInlineMenu
? 'hidden'
: is.macOS()
? 'hiddenInset'
: 'default',
autoHideMenuBar: config.get('options.hideMenu'),
}); });
initHook(win); initHook(win);
initTheme(win); initTheme(win);
@ -321,27 +332,29 @@ async function createMainWindow() {
const { x: windowX, y: windowY } = windowPosition; const { x: windowX, y: windowY } = windowPosition;
const winSize = win.getSize(); const winSize = win.getSize();
const display = screen.getDisplayNearestPoint(windowPosition); const display = screen.getDisplayNearestPoint(windowPosition);
const primaryDisplay = screen.getPrimaryDisplay();
const scaledWidth = windowSize.width; const scaleFactor = is.windows() ? primaryDisplay.scaleFactor / display.scaleFactor : 1;
const scaledHeight = windowSize.height; const scaledWidth = Math.floor(windowSize.width * scaleFactor);
const scaledHeight = Math.floor(windowSize.height * scaleFactor);
const scaledX = windowX; const scaledX = windowX;
const scaledY = windowY; const scaledY = windowY;
if ( if (
scaledX + scaledWidth < display.bounds.x - 8 || scaledX + (scaledWidth / 2) < display.bounds.x - 8 || // Left
scaledX - scaledWidth > display.bounds.x + display.bounds.width || scaledX + (scaledWidth / 2) > display.bounds.x + display.bounds.width || // Right
scaledY < display.bounds.y - 8 || scaledY < display.bounds.y - 8 || // Top
scaledY > display.bounds.y + display.bounds.height scaledY + (scaledHeight / 2) > display.bounds.y + display.bounds.height // Bottom
) { ) {
// Window is offscreen // Window is offscreen
if (is.dev()) { if (is.dev()) {
console.warn( console.warn(
LoggerPrefix, LoggerPrefix,
t('main.console.window.tried-to-render-offscreen', { t('main.console.window.tried-to-render-offscreen', {
winSize: String(winSize), windowSize: String(winSize),
displaySize: String(display.bounds), displaySize: JSON.stringify(display.bounds),
windowPosition: String(windowPosition), position: JSON.stringify(windowPosition),
}), }),
); );
} }

View File

@ -10,11 +10,11 @@ import {
import injectCliqzPreload from './injectors/inject-cliqz-preload'; import injectCliqzPreload from './injectors/inject-cliqz-preload';
import { inject, isInjected } from './injectors/inject'; import { inject, isInjected } from './injectors/inject';
import { loadAdSpeedup } from './adSpeedup';
import { t } from '@/i18n'; import { t } from '@/i18n';
import type { BrowserWindow } from 'electron'; import type { BrowserWindow } from 'electron';
import { loadAdSpeedup } from './adSpeedup';
interface AdblockerConfig { interface AdblockerConfig {
/** /**
@ -74,7 +74,7 @@ export default createPlugin({
]; ];
}, },
renderer: { renderer: {
async onPlayerApiReady(_, {getConfig}) { async onPlayerApiReady(_, { getConfig }) {
const config = await getConfig(); const config = await getConfig();
if (config.blocker === blockers.AdSpeedup) { if (config.blocker === blockers.AdSpeedup) {
await loadAdSpeedup(); await loadAdSpeedup();
@ -118,7 +118,19 @@ export default createPlugin({
}, },
}, },
preload: { preload: {
script: 'window.JSON.parse = window._proxyJsonParse; window._proxyJsonParse = undefined; window.Response.prototype.json = window._proxyResponseJson; window._proxyResponseJson = undefined; 0', // see #1478
script: `const _prunerFn = window._pruner;
window._pruner = undefined;
JSON.parse = new Proxy(JSON.parse, {
apply() {
return _prunerFn(Reflect.apply(...arguments));
},
});
Response.prototype.json = new Proxy(Response.prototype.json, {
apply() {
return Reflect.apply(...arguments).then((o) => _prunerFn(o));
},
}); 0`,
async start({ getConfig }) { async start({ getConfig }) {
const config = await getConfig(); const config = await getConfig();

View File

@ -37,19 +37,9 @@ export const inject = (contextBridge) => {
// //
return o; return o;
}; }
contextBridge.exposeInMainWorld('_proxyJsonParse', new Proxy(JSON.parse, { contextBridge.exposeInMainWorld('_pruner', pruner);
apply() {
return pruner(Reflect.apply(...arguments));
},
}));
contextBridge.exposeInMainWorld('_proxyResponseJson', new Proxy(Response.prototype.json, {
apply() {
return Reflect.apply(...arguments).then((o) => pruner(o));
},
}));
} }
const chains = [ const chains = [

View File

@ -1,6 +1,7 @@
import { t } from '@/i18n'; import { t } from '@/i18n';
import { createPlugin } from '@/utils'; import { createPlugin } from '@/utils';
import { ElementFromHtml } from '@/plugins/utils/renderer'; import { ElementFromHtml } from '@/plugins/utils/renderer';
import { waitForElement } from '@/utils/wait-for-element';
import undislikeHTML from './templates/undislike.html?raw'; import undislikeHTML from './templates/undislike.html?raw';
import dislikeHTML from './templates/dislike.html?raw'; import dislikeHTML from './templates/dislike.html?raw';
@ -16,7 +17,6 @@ export default createPlugin<
changeObserver?: MutationObserver; changeObserver?: MutationObserver;
waiting: boolean; waiting: boolean;
onPageChange(): void; onPageChange(): void;
waitForElem(selector: string): Promise<HTMLElement>;
loadFullList: (event: MouseEvent) => void; loadFullList: (event: MouseEvent) => void;
applyToList(id: string, loader: HTMLElement): void; applyToList(id: string, loader: HTMLElement): void;
start(): void; start(): void;
@ -50,7 +50,7 @@ export default createPlugin<
} else { } else {
this.waiting = true; this.waiting = true;
} }
const continuations = await this.waitForElem('#continuations'); const continuations = await waitForElement<HTMLElement>('#continuations');
this.waiting = false; this.waiting = false;
//Gets the for buttons //Gets the for buttons
const buttons: Array<HTMLElement> = [ const buttons: Array<HTMLElement> = [
@ -183,16 +183,5 @@ export default createPlugin<
button.remove(); button.remove();
} }
}, },
waitForElem(selector: string) {
return new Promise((resolve) => {
const interval = setInterval(() => {
const elem = document.querySelector<HTMLElement>(selector);
if (!elem) return;
clearInterval(interval);
resolve(elem);
});
});
},
}, },
}); });

View File

@ -4,6 +4,7 @@ import { t } from '@/i18n';
import { createPlugin } from '@/utils'; import { createPlugin } from '@/utils';
import { menu } from './menu'; import { menu } from './menu';
import { AmbientModePluginConfig } from './types'; import { AmbientModePluginConfig } from './types';
import { waitForElement } from '@/utils/wait-for-element';
const defaultConfig: AmbientModePluginConfig = { const defaultConfig: AmbientModePluginConfig = {
enabled: false, enabled: false,
@ -36,7 +37,7 @@ export default createPlugin({
unregister: null as (() => void) | null, unregister: null as (() => void) | null,
update: null as (() => void) | null, update: null as (() => void) | null,
interval: null as NodeJS.Timeout | null, interval: null as NodeJS.Timeout | null,
lastMediaType: null as "video" | "image" | null, lastMediaType: null as 'video' | 'image' | null,
lastVideoSource: null as string | null, lastVideoSource: null as string | null,
lastImageSource: null as string | null, lastImageSource: null as string | null,
@ -53,7 +54,8 @@ export default createPlugin({
const songImage = document.querySelector<HTMLImageElement>('#song-image'); const songImage = document.querySelector<HTMLImageElement>('#song-image');
const songVideo = document.querySelector<HTMLDivElement>('#song-video'); const songVideo = document.querySelector<HTMLDivElement>('#song-video');
const image = songImage?.querySelector<HTMLImageElement>('yt-img-shadow > img'); const image = songImage?.querySelector<HTMLImageElement>('yt-img-shadow > img');
const video = songVideo?.querySelector<HTMLVideoElement>('.html5-video-container > video'); const video = await waitForElement<HTMLVideoElement>('.html5-video-container > video');
const videoWrapper = document.querySelector('#song-video > .player-wrapper'); const videoWrapper = document.querySelector('#song-video > .player-wrapper');
const injectBlurImage = () => { const injectBlurImage = () => {
@ -179,12 +181,12 @@ export default createPlugin({
const isVideoMode = () => { const isVideoMode = () => {
const songVideo = document.querySelector<HTMLDivElement>('#song-video'); const songVideo = document.querySelector<HTMLDivElement>('#song-video');
if (!songVideo) { if (!songVideo) {
this.lastMediaType = "image"; this.lastMediaType = 'image';
return false; return false;
} }
const isVideo = getComputedStyle(songVideo).display !== 'none'; const isVideo = getComputedStyle(songVideo).display !== 'none';
this.lastMediaType = isVideo ? "video" : "image"; this.lastMediaType = isVideo ? 'video' : 'image';
return isVideo; return isVideo;
}; };
@ -196,8 +198,8 @@ export default createPlugin({
if (isPageOpen) { if (isPageOpen) {
const isVideo = isVideoMode(); const isVideo = isVideoMode();
if (!force) { if (!force) {
if (this.lastMediaType === "video" && this.lastVideoSource === video?.src) return false; if (this.lastMediaType === 'video' && this.lastVideoSource === video?.src) return false;
if (this.lastMediaType === "image" && this.lastImageSource === image?.src) return false; if (this.lastMediaType === 'image' && this.lastImageSource === image?.src) return false;
} }
this.unregister?.(); this.unregister?.();
this.unregister = (isVideo ? injectBlurVideo() : injectBlurImage()) ?? null; this.unregister = (isVideo ? injectBlurVideo() : injectBlurImage()) ?? null;
@ -205,7 +207,7 @@ export default createPlugin({
this.unregister?.(); this.unregister?.();
this.unregister = null; this.unregister = null;
} }
} };
/* needed for switching between different views (e.g. miniplayer) */ /* needed for switching between different views (e.g. miniplayer) */
const observer = new MutationObserver((mutationsList) => { const observer = new MutationObserver((mutationsList) => {

View File

@ -209,8 +209,8 @@ export const backend = createBackend<
info.rpc.user?.setActivity(activityInfo).catch(console.error); info.rpc.user?.setActivity(activityInfo).catch(console.error);
}, },
async start({ window: win, getConfig }) { async start(ctx) {
this.config = await getConfig(); this.config = await ctx.getConfig();
info.rpc.on('connected', () => { info.rpc.on('connected', () => {
if (dev()) { if (dev()) {
@ -239,10 +239,10 @@ export const backend = createBackend<
info.autoReconnect = this.config.autoReconnect; info.autoReconnect = this.config.autoReconnect;
window = win; window = ctx.window;
// If the page is ready, register the callback // If the page is ready, register the callback
win.once('ready-to-show', () => { ctx.window.once('ready-to-show', () => {
let lastSongInfo: SongInfo; let lastSongInfo: SongInfo;
registerCallback((songInfo) => { registerCallback((songInfo) => {
lastSongInfo = songInfo; lastSongInfo = songInfo;

View File

@ -261,12 +261,12 @@ async function downloadSongUnsafe(
let playabilityStatus = info.playability_status; let playabilityStatus = info.playability_status;
let bypassedResult = null; let bypassedResult = null;
if (playabilityStatus.status === 'LOGIN_REQUIRED') { if (playabilityStatus?.status === 'LOGIN_REQUIRED') {
// Try to bypass the age restriction // Try to bypass the age restriction
bypassedResult = await getAndroidTvInfo(id); bypassedResult = await getAndroidTvInfo(id);
playabilityStatus = bypassedResult.playability_status; playabilityStatus = bypassedResult.playability_status;
if (playabilityStatus.status === 'LOGIN_REQUIRED') { if (playabilityStatus?.status === 'LOGIN_REQUIRED') {
throw new Error( throw new Error(
`[${playabilityStatus.status}] ${playabilityStatus.reason}`, `[${playabilityStatus.status}] ${playabilityStatus.reason}`,
); );
@ -275,7 +275,7 @@ async function downloadSongUnsafe(
info = bypassedResult; info = bypassedResult;
} }
if (playabilityStatus.status === 'UNPLAYABLE') { if (playabilityStatus?.status === 'UNPLAYABLE') {
const errorScreen = const errorScreen =
playabilityStatus.error_screen as PlayerErrorMessage | null; playabilityStatus.error_screen as PlayerErrorMessage | null;
throw new Error( throw new Error(

View File

@ -1,9 +1,9 @@
import { JSX } from 'solid-js'; import { JSX } from 'solid-js';
import { css } from 'solid-styled-components'; import { css } from 'solid-styled-components';
import { cache } from '@/providers/decorators'; import { cacheNoArgs } from '@/providers/decorators';
const iconButton = cache(() => css` const iconButton = cacheNoArgs(() => css`
-webkit-app-region: none; -webkit-app-region: none;
background: transparent; background: transparent;

View File

@ -1,9 +1,9 @@
import { JSX, splitProps } from 'solid-js'; import { JSX, splitProps } from 'solid-js';
import { css } from 'solid-styled-components'; import { css } from 'solid-styled-components';
import { cache } from '@/providers/decorators'; import { cacheNoArgs } from '@/providers/decorators';
const menuStyle = cache(() => css` const menuStyle = cacheNoArgs(() => css`
-webkit-app-region: none; -webkit-app-region: none;
display: flex; display: flex;

View File

@ -5,9 +5,9 @@ import { Transition } from 'solid-transition-group';
import { autoUpdate, flip, offset, OffsetOptions, size } from '@floating-ui/dom'; import { autoUpdate, flip, offset, OffsetOptions, size } from '@floating-ui/dom';
import { useFloating } from 'solid-floating-ui'; import { useFloating } from 'solid-floating-ui';
import { cache } from '@/providers/decorators'; import { cacheNoArgs } from '@/providers/decorators';
const panelStyle = cache(() => css` const panelStyle = cacheNoArgs(() => css`
position: fixed; position: fixed;
top: var(--offset-y, 0); top: var(--offset-y, 0);
left: var(--offset-x, 0); left: var(--offset-x, 0);
@ -36,7 +36,7 @@ const panelStyle = cache(() => css`
transform-origin: var(--origin-x, 50%) var(--origin-y, 50%); transform-origin: var(--origin-x, 50%) var(--origin-y, 50%);
`); `);
const animationStyle = cache(() => ({ const animationStyle = cacheNoArgs(() => ({
enter: css` enter: css`
opacity: 0; opacity: 0;
transform: scale(0.9); transform: scale(0.9);

View File

@ -8,9 +8,9 @@ import { useFloating } from 'solid-floating-ui';
import { autoUpdate, offset, size } from '@floating-ui/dom'; import { autoUpdate, offset, size } from '@floating-ui/dom';
import { Panel } from './Panel'; import { Panel } from './Panel';
import { cache } from '@/providers/decorators'; import { cacheNoArgs } from '@/providers/decorators';
const itemStyle = cache(() => css` const itemStyle = cacheNoArgs(() => css`
position: relative; position: relative;
-webkit-app-region: none; -webkit-app-region: none;
@ -47,18 +47,18 @@ const itemStyle = cache(() => css`
} }
`); `);
const itemIconStyle = cache(() => css` const itemIconStyle = cacheNoArgs(() => css`
height: 32px; height: 32px;
padding: 4px; padding: 4px;
color: white; color: white;
`); `);
const itemLabelStyle = cache(() => css` const itemLabelStyle = cacheNoArgs(() => css`
font-size: 12px; font-size: 12px;
color: white; color: white;
`); `);
const itemChipStyle = cache(() => css` const itemChipStyle = cacheNoArgs(() => css`
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@ -76,7 +76,7 @@ const itemChipStyle = cache(() => css`
line-height: 1; line-height: 1;
`); `);
const toolTipStyle = cache(() => css` const toolTipStyle = cacheNoArgs(() => css`
min-width: 32px; min-width: 32px;
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -92,7 +92,7 @@ const toolTipStyle = cache(() => css`
font-size: 10px; font-size: 10px;
`); `);
const popupStyle = cache(() => css` const popupStyle = cacheNoArgs(() => css`
position: fixed; position: fixed;
top: var(--offset-y, 0); top: var(--offset-y, 0);
left: var(--offset-x, 0); left: var(--offset-x, 0);
@ -105,7 +105,7 @@ const popupStyle = cache(() => css`
`); `);
const animationStyle = cache(() => ({ const animationStyle = cacheNoArgs(() => ({
enter: css` enter: css`
opacity: 0; opacity: 0;
transform: scale(0.9); transform: scale(0.9);

View File

@ -9,12 +9,12 @@ import { PanelItem } from './PanelItem';
import { IconButton } from './IconButton'; import { IconButton } from './IconButton';
import { WindowController } from './WindowController'; import { WindowController } from './WindowController';
import { cache } from '@/providers/decorators'; import { cacheNoArgs } from '@/providers/decorators';
import type { RendererContext } from '@/types/contexts'; import type { RendererContext } from '@/types/contexts';
import type { InAppMenuConfig } from '../constants'; import type { InAppMenuConfig } from '../constants';
const titleStyle = cache(() => css` const titleStyle = cacheNoArgs(() => css`
-webkit-app-region: drag; -webkit-app-region: drag;
box-sizing: border-box; box-sizing: border-box;
@ -50,7 +50,7 @@ const titleStyle = cache(() => css`
} }
`); `);
const separatorStyle = cache(() => css` const separatorStyle = cacheNoArgs(() => css`
min-height: 1px; min-height: 1px;
height: 1px; height: 1px;
margin: 4px 0; margin: 4px 0;
@ -58,7 +58,7 @@ const separatorStyle = cache(() => css`
background-color: rgba(255, 255, 255, 0.2); background-color: rgba(255, 255, 255, 0.2);
`); `);
const animationStyle = cache(() => ({ const animationStyle = cacheNoArgs(() => ({
enter: css` enter: css`
opacity: 0; opacity: 0;
transform: translateX(-50%) scale(0.8); transform: translateX(-50%) scale(0.8);
@ -271,16 +271,15 @@ export const TitleBar = (props: TitleBarProps) => {
// tracking mouse position // tracking mouse position
window.addEventListener('mousemove', listener); window.addEventListener('mousemove', listener);
const ytmusicAppLayout = document.querySelector<HTMLElement>('#layout'); const ytmusicAppLayout = document.querySelector<HTMLElement>('#layout');
ytmusicAppLayout?.addEventListener("scroll",()=>{ ytmusicAppLayout?.addEventListener('scroll', () => {
const scrollValue = ytmusicAppLayout.scrollTop; const scrollValue = ytmusicAppLayout.scrollTop;
if (scrollValue > 20){ if (scrollValue > 20){
ytmusicAppLayout.classList.add("content-scrolled"); ytmusicAppLayout.classList.add('content-scrolled');
} }
else{ else{
ytmusicAppLayout.classList.remove("content-scrolled"); ytmusicAppLayout.classList.remove('content-scrolled');
} }
}) });
}); });

View File

@ -2,9 +2,9 @@ import { css } from 'solid-styled-components';
import { Show } from 'solid-js'; import { Show } from 'solid-js';
import { IconButton } from './IconButton'; import { IconButton } from './IconButton';
import { cache } from '@/providers/decorators'; import { cacheNoArgs } from '@/providers/decorators';
const containerStyle = cache(() => css` const containerStyle = cacheNoArgs(() => css`
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
align-items: center; align-items: center;

View File

@ -1,4 +1,4 @@
import { BrowserWindow, ipcMain, globalShortcut } from 'electron'; import { BrowserWindow, globalShortcut } from 'electron';
import is from 'electron-is'; import is from 'electron-is';
import { register as registerElectronLocalShortcut } from 'electron-localshortcut'; import { register as registerElectronLocalShortcut } from 'electron-localshortcut';
@ -48,9 +48,7 @@ export const onMainLoad = async ({
_registerLocalShortcut(window, 'CommandOrControl+L', search); _registerLocalShortcut(window, 'CommandOrControl+L', search);
if (is.linux()) { if (is.linux()) {
ipcMain.once('ytmd:video-src-changed', (_) => { registerMPRIS(window);
registerMPRIS(window);
});
} }
const { global, local } = config; const { global, local } = config;

View File

@ -1,12 +1,12 @@
import { t } from '@/i18n'; import { t } from '@/i18n';
import { createPlugin } from '@/utils'; import { createPlugin } from '@/utils';
import { waitForElement } from '@/utils/wait-for-element';
export default createPlugin< export default createPlugin<
unknown, unknown,
unknown, unknown,
{ {
observer?: MutationObserver; observer?: MutationObserver;
waitForElem(selector: string): Promise<HTMLElement>;
start(): void; start(): void;
stop(): void; stop(): void;
} }
@ -15,19 +15,8 @@ export default createPlugin<
description: () => t('plugins.skip-disliked-songs.description'), description: () => t('plugins.skip-disliked-songs.description'),
restartNeeded: false, restartNeeded: false,
renderer: { renderer: {
waitForElem(selector: string) {
return new Promise<HTMLElement>((resolve) => {
const interval = setInterval(() => {
const elem = document.querySelector<HTMLElement>(selector);
if (!elem) return;
clearInterval(interval);
resolve(elem);
});
});
},
start() { start() {
this.waitForElem('#dislike-button-renderer').then((dislikeBtn) => { waitForElement<HTMLElement>('#dislike-button-renderer').then((dislikeBtn) => {
this.observer = new MutationObserver(() => { this.observer = new MutationObserver(() => {
if (dislikeBtn?.getAttribute('like-status') == 'DISLIKE') { if (dislikeBtn?.getAttribute('like-status') == 'DISLIKE') {
document document

View File

@ -1,12 +1,11 @@
import style from './style.css?inline'; import style from './style.css?inline';
import { createPlugin } from '@/utils'; import { createPlugin } from '@/utils';
import { t } from '@/i18n';
import { SyncedLyricsPluginConfig } from './types';
import { menu } from './menu'; import { menu } from './menu';
import { renderer } from './renderer'; import { renderer } from './renderer';
import { t } from '@/i18n'; import type { SyncedLyricsPluginConfig } from './types';
export default createPlugin({ export default createPlugin({
name: () => t('plugins.synced-lyrics.name'), name: () => t('plugins.synced-lyrics.name'),
@ -15,12 +14,13 @@ export default createPlugin({
restartNeeded: true, restartNeeded: true,
addedVersion: '3.5.X', addedVersion: '3.5.X',
config: { config: {
enabled: false,
preciseTiming: true, preciseTiming: true,
showLyricsEvenIfInexact: true, showLyricsEvenIfInexact: true,
showTimeCodes: false, showTimeCodes: false,
defaultTextString: '♪', defaultTextString: '♪',
lineEffect: 'scale', lineEffect: 'scale',
} as SyncedLyricsPluginConfig, } satisfies SyncedLyricsPluginConfig,
menu, menu,
renderer, renderer,

View File

@ -1,7 +1,9 @@
import { MenuItemConstructorOptions } from 'electron'; import { MenuItemConstructorOptions } from 'electron';
import { MenuContext } from '@/types/contexts'; import { t } from '@/i18n';
import { SyncedLyricsPluginConfig } from './types';
import type { MenuContext } from '@/types/contexts';
import type { SyncedLyricsPluginConfig } from './types';
export const menu = async ({ export const menu = async ({
getConfig, getConfig,
@ -13,9 +15,8 @@ export const menu = async ({
return [ return [
{ {
label: 'Make the lyrics perfectly synced', label: t('plugins.synced-lyrics.menu.precise-timing.label'),
toolTip: toolTip: t('plugins.synced-lyrics.menu.precise-timing.tooltip'),
'Calculate to the milisecond the display of the next line (can have a small impact on performance)',
type: 'checkbox', type: 'checkbox',
checked: config.preciseTiming, checked: config.preciseTiming,
click(item) { click(item) {
@ -25,13 +26,13 @@ export const menu = async ({
}, },
}, },
{ {
label: 'Line effect', label: t('plugins.synced-lyrics.menu.line-effect.label'),
toolTip: 'Choose the effect to apply to the current line', toolTip: t('plugins.synced-lyrics.menu.line-effect.tooltip'),
type: 'submenu', type: 'submenu',
submenu: [ submenu: [
{ {
label: 'Scale', label: t('plugins.synced-lyrics.menu.line-effect.submenu.scale.label'),
toolTip: 'Scale the current line', toolTip: t('plugins.synced-lyrics.menu.line-effect.submenu.scale.tooltip'),
type: 'radio', type: 'radio',
checked: config.lineEffect === 'scale', checked: config.lineEffect === 'scale',
click() { click() {
@ -41,8 +42,8 @@ export const menu = async ({
}, },
}, },
{ {
label: 'Offset', label: t('plugins.synced-lyrics.menu.line-effect.submenu.offset.label'),
toolTip: 'Offset on the right the current line', toolTip: t('plugins.synced-lyrics.menu.line-effect.submenu.offset.tooltip'),
type: 'radio', type: 'radio',
checked: config.lineEffect === 'offset', checked: config.lineEffect === 'offset',
click() { click() {
@ -52,8 +53,8 @@ export const menu = async ({
}, },
}, },
{ {
label: 'Focus', label: t('plugins.synced-lyrics.menu.line-effect.submenu.focus.label'),
toolTip: 'Make only the current line white', toolTip: t('plugins.synced-lyrics.menu.line-effect.submenu.focus.tooltip'),
type: 'radio', type: 'radio',
checked: config.lineEffect === 'focus', checked: config.lineEffect === 'focus',
click() { click() {
@ -65,8 +66,8 @@ export const menu = async ({
], ],
}, },
{ {
label: 'Default character between lyrics', label: t('plugins.synced-lyrics.menu.default-text-string.label'),
toolTip: 'Choose the default string to use for the gap between lyrics', toolTip: t('plugins.synced-lyrics.menu.default-text-string.tooltip'),
type: 'submenu', type: 'submenu',
submenu: [ submenu: [
{ {
@ -80,7 +81,7 @@ export const menu = async ({
}, },
}, },
{ {
label: '[SPACE]', label: '" "',
type: 'radio', type: 'radio',
checked: config.defaultTextString === ' ', checked: config.defaultTextString === ' ',
click() { click() {
@ -112,8 +113,8 @@ export const menu = async ({
], ],
}, },
{ {
label: 'Show time codes', label: t('plugins.synced-lyrics.menu.show-time-codes.label'),
toolTip: 'Show the time codes next to the lyrics', toolTip: t('plugins.synced-lyrics.menu.show-time-codes.tooltip'),
type: 'checkbox', type: 'checkbox',
checked: config.showTimeCodes, checked: config.showTimeCodes,
click(item) { click(item) {
@ -123,9 +124,8 @@ export const menu = async ({
}, },
}, },
{ {
label: 'Show lyrics even if inexact', label: t('plugins.synced-lyrics.menu.show-lyrics-even-if-inexact.label'),
toolTip: toolTip: t('plugins.synced-lyrics.menu.show-lyrics-even-if-inexact.tooltip'),
'If the song is not found, the plugin tries again with a different search query.\nThe result from the second attempt may not be exact.',
type: 'checkbox', type: 'checkbox',
checked: config.showLyricsEvenIfInexact, checked: config.showLyricsEvenIfInexact,
click(item) { click(item) {

View File

@ -5,7 +5,6 @@ import { SyncedLine } from './SyncedLine';
import { t } from '@/i18n'; import { t } from '@/i18n';
import { getSongInfo } from '@/providers/song-info-front'; import { getSongInfo } from '@/providers/song-info-front';
import { LineLyrics } from '../../types';
import { import {
differentDuration, differentDuration,
hadSecondAttempt, hadSecondAttempt,
@ -14,6 +13,8 @@ import {
makeLyricsRequest, makeLyricsRequest,
} from '../lyrics/fetch'; } from '../lyrics/fetch';
import type { LineLyrics } from '../../types';
export const [debugInfo, setDebugInfo] = createSignal<string>(); export const [debugInfo, setDebugInfo] = createSignal<string>();
export const [lineLyrics, setLineLyrics] = createSignal<LineLyrics[]>([]); export const [lineLyrics, setLineLyrics] = createSignal<LineLyrics[]>([]);
export const [currentTime, setCurrentTime] = createSignal<number>(-1); export const [currentTime, setCurrentTime] = createSignal<number>(-1);

View File

@ -1,4 +1,5 @@
import { createRenderer } from '@/utils'; import { createRenderer } from '@/utils';
import { waitForElement } from '@/utils/wait-for-element';
import { makeLyricsRequest } from './lyrics'; import { makeLyricsRequest } from './lyrics';
import { selectors, tabStates } from './utils'; import { selectors, tabStates } from './utils';
@ -15,11 +16,9 @@ export let _ytAPI: YoutubePlayer | null = null;
export const renderer = createRenderer<{ export const renderer = createRenderer<{
observerCallback: MutationCallback; observerCallback: MutationCallback;
onPlayerApiReady: (api: YoutubePlayer) => void;
hasAddedEvents: boolean;
observer?: MutationObserver; observer?: MutationObserver;
videoDataChange: () => void; videoDataChange: () => Promise<void>;
progressCallback: (evt: Event) => void; updateTimestampInterval?: NodeJS.Timeout | string | number;
}, SyncedLyricsPluginConfig>({ }, SyncedLyricsPluginConfig>({
onConfigChange(newConfig) { onConfigChange(newConfig) {
setConfig(newConfig); setConfig(newConfig);
@ -42,48 +41,34 @@ export const renderer = createRenderer<{
} }
}, },
onPlayerApiReady(api: YoutubePlayer) { async onPlayerApiReady(api: YoutubePlayer) {
_ytAPI = api; _ytAPI = api;
api.addEventListener('videodatachange', this.videoDataChange); api.addEventListener('videodatachange', this.videoDataChange);
this.videoDataChange(); await this.videoDataChange();
}, },
hasAddedEvents: false, async videoDataChange() {
if (!this.updateTimestampInterval) {
videoDataChange() { this.updateTimestampInterval = setInterval(
if (!this.hasAddedEvents) { () => setCurrentTime((_ytAPI?.getCurrentTime() ?? 0) * 1000),
const video = document.querySelector('video'); 100,
);
video?.addEventListener('timeupdate', this.progressCallback);
if (video) this.hasAddedEvents = true;
} }
const header = document.querySelector<HTMLElement>(selectors.head);
if (!header) return;
this.observer ??= new MutationObserver( this.observer ??= new MutationObserver(
this.observerCallback, this.observerCallback,
); );
// Force the lyrics tab to be enabled at all times. // Force the lyrics tab to be enabled at all times.
this.observer.disconnect(); this.observer.disconnect();
const header = await waitForElement<HTMLElement>(selectors.head);
this.observer.observe(header, { attributes: true }); this.observer.observe(header, { attributes: true });
header.removeAttribute('disabled'); header.removeAttribute('disabled');
}, },
progressCallback(evt: Event) {
switch (evt.type) {
case 'timeupdate': {
const video = evt.target as HTMLVideoElement;
setCurrentTime(video.currentTime * 1000);
break;
}
}
},
async start(ctx: RendererContext<SyncedLyricsPluginConfig>) { async start(ctx: RendererContext<SyncedLyricsPluginConfig>) {
setConfig(await ctx.getConfig()); setConfig(await ctx.getConfig());

View File

@ -1,12 +1,13 @@
import { createSignal } from 'solid-js'; import { createSignal } from 'solid-js';
import { jaroWinkler } from '@skyra/jaro-winkler'; import { jaroWinkler } from '@skyra/jaro-winkler';
import { SongInfo } from '@/providers/song-info';
import { LineLyrics, LRCLIBSearchResponse } from '../../types';
import { config } from '../renderer'; import { config } from '../renderer';
import { setDebugInfo, setLineLyrics } from '../components/LyricsContainer'; import { setDebugInfo, setLineLyrics } from '../components/LyricsContainer';
import type { SongInfo } from '@/providers/song-info';
import type { LineLyrics, LRCLIBSearchResponse } from '../../types';
// prettier-ignore // prettier-ignore
export const [isInstrumental, setIsInstrumental] = createSignal(false); export const [isInstrumental, setIsInstrumental] = createSignal(false);
// prettier-ignore // prettier-ignore
@ -16,14 +17,6 @@ export const [hadSecondAttempt, setHadSecondAttempt] = createSignal(false);
// prettier-ignore // prettier-ignore
export const [differentDuration, setDifferentDuration] = createSignal(false); export const [differentDuration, setDifferentDuration] = createSignal(false);
// eslint-disable-next-line prefer-const // eslint-disable-next-line prefer-const
export let foundPlainTextLyrics = false;
export type SongData = {
title: string;
artist: string;
album: string;
songDuration: number;
};
export const extractTimeAndText = ( export const extractTimeAndText = (
line: string, line: string,
@ -32,7 +25,7 @@ export const extractTimeAndText = (
const groups = /\[(\d+):(\d+)\.(\d+)\](.+)/.exec(line); const groups = /\[(\d+):(\d+)\.(\d+)\](.+)/.exec(line);
if (!groups) return null; if (!groups) return null;
const [_, rMinutes, rSeconds, rMillis, text] = groups; const [, rMinutes, rSeconds, rMillis, text] = groups;
const [minutes, seconds, millis] = [ const [minutes, seconds, millis] = [
parseInt(rMinutes), parseInt(rMinutes),
parseInt(rSeconds), parseInt(rSeconds),
@ -45,7 +38,7 @@ export const extractTimeAndText = (
return { return {
index, index,
timeInMs, timeInMs,
time: `${minutes}:${seconds}:${millis}`, time: `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}:${millis}`,
text: text?.trim() ?? config()!.defaultTextString, text: text?.trim() ?? config()!.defaultTextString,
status: 'upcoming', status: 'upcoming',
duration: 0, duration: 0,
@ -53,22 +46,31 @@ export const extractTimeAndText = (
}; };
export const makeLyricsRequest = async (extractedSongInfo: SongInfo) => { export const makeLyricsRequest = async (extractedSongInfo: SongInfo) => {
setIsFetching(true);
setLineLyrics([]); setLineLyrics([]);
const songData: SongData = {
const songData: Parameters<typeof getLyricsList>[0] = {
title: `${extractedSongInfo.title}`, title: `${extractedSongInfo.title}`,
artist: `${extractedSongInfo.artist}`, artist: `${extractedSongInfo.artist}`,
album: `${extractedSongInfo.album}`,
songDuration: extractedSongInfo.songDuration, songDuration: extractedSongInfo.songDuration,
}; };
const lyrics = await getLyricsList(songData); if (extractedSongInfo.album) {
songData.album = extractedSongInfo.album;
}
let lyrics;
try {
lyrics = await getLyricsList(songData);
} catch {}
setLineLyrics(lyrics ?? []); setLineLyrics(lyrics ?? []);
setIsFetching(false);
}; };
export const getLyricsList = async ( export const getLyricsList = async (
songData: SongData, songData: Pick<SongInfo, 'title' | 'artist' | 'album' | 'songDuration'>,
): Promise<LineLyrics[] | null> => { ): Promise<LineLyrics[] | null> => {
setIsFetching(true);
setIsInstrumental(false); setIsInstrumental(false);
setHadSecondAttempt(false); setHadSecondAttempt(false);
setDifferentDuration(false); setDifferentDuration(false);
@ -79,6 +81,7 @@ export const getLyricsList = async (
track_name: songData.title, track_name: songData.title,
}); });
if (songData.album) { if (songData.album) {
query.set('album_name', songData.album); query.set('album_name', songData.album);
} }
@ -87,18 +90,12 @@ export const getLyricsList = async (
let response = await fetch(url); let response = await fetch(url);
if (!response.ok) { if (!response.ok) {
setIsFetching(false);
setDebugInfo('Got non-OK response from server.'); setDebugInfo('Got non-OK response from server.');
return null; return null;
} }
let data = (await response.json().catch((e: Error) => { let data = await response.json() as LRCLIBSearchResponse;
setDebugInfo(`Error: ${e.message}\n\n${e.stack}`);
return null;
})) as LRCLIBSearchResponse | null;
if (!data || !Array.isArray(data)) { if (!data || !Array.isArray(data)) {
setIsFetching(false);
setDebugInfo('Unexpected server response.'); setDebugInfo('Unexpected server response.');
return null; return null;
} }
@ -114,14 +111,12 @@ export const getLyricsList = async (
response = await fetch(url); response = await fetch(url);
if (!response.ok) { if (!response.ok) {
setIsFetching(false);
setDebugInfo('Got non-OK response from server. (2)'); setDebugInfo('Got non-OK response from server. (2)');
return null; return null;
} }
data = (await response.json()) as LRCLIBSearchResponse; data = (await response.json()) as LRCLIBSearchResponse;
if (!Array.isArray(data)) { if (!Array.isArray(data)) {
setIsFetching(false);
setDebugInfo('Unexpected server response. (2)'); setDebugInfo('Unexpected server response. (2)');
return null; return null;
} }
@ -131,15 +126,18 @@ export const getLyricsList = async (
const filteredResults = []; const filteredResults = [];
for (const item of data) { for (const item of data) {
if (!item.syncedLyrics) continue;
const { artist } = songData; const { artist } = songData;
const { artistName } = item; const { artistName } = item;
const ratio = jaroWinkler(artist.toLowerCase(), artistName.toLowerCase()); const artists = artist.split(/[&,]/g).map((i) => i.trim());
const itemArtists = artistName.split(/[&,]/g).map((i) => i.trim());
if (ratio <= 0.9) continue; const permutations = artists.flatMap((artistA) =>
filteredResults.push(item); itemArtists.map((artistB) => [artistA.toLowerCase(), artistB.toLowerCase()])
);
const ratio = Math.max(...permutations.map(([x, y]) => jaroWinkler(x, y)));
if (ratio > 0.9) filteredResults.push(item);
} }
const duration = songData.songDuration; const duration = songData.songDuration;
@ -152,35 +150,43 @@ export const getLyricsList = async (
const closestResult = filteredResults[0]; const closestResult = filteredResults[0];
if (!closestResult) { if (!closestResult) {
setIsFetching(false);
setDebugInfo('No search result matched the criteria.'); setDebugInfo('No search result matched the criteria.');
return null; return null;
} }
// setDebugInfo(JSON.stringify(closestResult, null, 4)); setDebugInfo(JSON.stringify(closestResult, null, 4));
if (Math.abs(closestResult.duration - duration) > 15) {
return null;
}
if (Math.abs(closestResult.duration - duration) > 15) return null;
if (Math.abs(closestResult.duration - duration) > 5) { if (Math.abs(closestResult.duration - duration) > 5) {
// show message that the timings may be wrong // show message that the timings may be wrong
setDifferentDuration(true); setDifferentDuration(true);
} }
setIsInstrumental(closestResult.instrumental); setIsInstrumental(closestResult.instrumental);
if (closestResult.instrumental) {
return null;
}
// Separate the lyrics into lines // Separate the lyrics into lines
const raw = closestResult.syncedLyrics.split('\n'); const raw = closestResult.syncedLyrics?.split('\n') ?? [];
if (!raw.length) {
return null;
}
// Add a blank line at the beginning // Add a blank line at the beginning
raw.unshift('[0:0.0] '); raw.unshift('[0:0.0] ');
const syncedLyricList = []; const syncedLyricList = raw.reduce<LineLyrics[]>((acc, line, index) => {
const syncedLine = extractTimeAndText(line, index);
for (let idx = 0; idx < raw.length; idx++) {
const syncedLine = extractTimeAndText(raw[idx], idx);
if (syncedLine) { if (syncedLine) {
syncedLyricList.push(syncedLine); acc.push(syncedLine);
} }
}
return acc;
}, []);
for (const line of syncedLyricList) { for (const line of syncedLyricList) {
const next = syncedLyricList[line.index + 1]; const next = syncedLyricList[line.index + 1];
@ -192,6 +198,5 @@ export const getLyricsList = async (
line.duration = next.timeInMs - line.timeInMs; line.duration = next.timeInMs - line.timeInMs;
} }
setIsFetching(false);
return syncedLyricList; return syncedLyricList;
}; };

View File

@ -1,10 +1,9 @@
import { createSignal, Show } from 'solid-js'; import { createSignal, Show } from 'solid-js';
import { VideoDetails } from '@/types/video-details';
import { LyricsContainer } from './components/LyricsContainer'; import { LyricsContainer } from './components/LyricsContainer';
import { SyncedLyricsPluginConfig } from '../types'; import type { VideoDetails } from '@/types/video-details';
import type { SyncedLyricsPluginConfig } from '../types';
export const [isVisible, setIsVisible] = createSignal<boolean>(false); export const [isVisible, setIsVisible] = createSignal<boolean>(false);

View File

@ -1,7 +1,10 @@
import { render } from 'solid-js/web'; import { render } from 'solid-js/web';
import { waitForElement } from '@/utils/wait-for-element';
import { LyricsRenderer, setIsVisible, setPlayerState } from './renderer'; import { LyricsRenderer, setIsVisible, setPlayerState } from './renderer';
import { VideoDetails } from '@/types/video-details';
import type { VideoDetails } from '@/types/video-details';
export const selectors = { export const selectors = {
head: '#tabsContent > .tab-header:nth-of-type(2)', head: '#tabsContent > .tab-header:nth-of-type(2)',
@ -12,18 +15,17 @@ export const selectors = {
}; };
export const tabStates = { export const tabStates = {
true: (data?: VideoDetails) => { true: async (data?: VideoDetails) => {
setIsVisible(true); setIsVisible(true);
setPlayerState(data ?? null); setPlayerState(data ?? null);
const tabRenderer = document.querySelector<HTMLElement>(
selectors.body.tabRenderer,
);
if (!tabRenderer) return;
let container = document.querySelector('#synced-lyrics-container'); let container = document.querySelector('#synced-lyrics-container');
if (container) return; if (container) return;
const tabRenderer = await waitForElement<HTMLElement>(
selectors.body.tabRenderer,
);
container = Object.assign(document.createElement('div'), { container = Object.assign(document.createElement('div'), {
id: 'synced-lyrics-container', id: 'synced-lyrics-container',
}); });

View File

@ -40,6 +40,16 @@ export function cache<T extends (...params: P) => R, P extends never[], R>(
}) as T; }) as T;
} }
export function cacheNoArgs<R>(fn: () => R): () => R {
let cached: R;
return () => {
if (cached === undefined) {
cached = fn();
}
return cached;
};
}
/* /*
The following are currently unused, but potentially useful in the future The following are currently unused, but potentially useful in the future
*/ */

View File

@ -200,6 +200,26 @@ export default (api: YoutubePlayer) => {
for (const status of ['playing', 'pause'] as const) { for (const status of ['playing', 'pause'] as const) {
video.addEventListener(status, playPausedHandlers[status]); video.addEventListener(status, playPausedHandlers[status]);
} }
if (!isNaN(video.duration)) {
const {
title, author,
video_id: videoId,
list: playlistId
} = api.getVideoData();
const { playerOverlays } = api.getWatchNextResponse();
sendSongInfo(<VideoDataChangeValue>{
title, author, videoId, playlistId,
isUpcoming: false,
lengthSeconds: video.duration,
loading: true,
uhhh: { playerOverlays }
});
}
} }
function sendSongInfo(videoData: VideoDataChangeValue) { function sendSongInfo(videoData: VideoDataChangeValue) {

View File

@ -214,14 +214,14 @@ const suffixesToRemove = [
/\s*vevo$/i, /\s*vevo$/i,
// Video titles // Video titles
/\s*[(|\[]official(.*?)[)|\]]/i, // (Official Music Video), [Official Visualizer], etc... /\s*[(|[]official(.*?)[)|\]]/i, // (Official Music Video), [Official Visualizer], etc...
/\s*[(|\[]((lyrics?|visualizer|audio)\s*(video)?)[)|\]]/i, /\s*[(|[]((lyrics?|visualizer|audio)\s*(video)?)[)|\]]/i,
/\s*[(|\[](performance video)[)|\]]/i, /\s*[(|[](performance video)[)|\]]/i,
/\s*[(|\[](clip official)[)|\]]/i, /\s*[(|[](clip official)[)|\]]/i,
/\s*[(|\[](video version)[)|\]]/i, /\s*[(|[](video version)[)|\]]/i,
/\s*[(|\[](HD|HQ)\s*?(?:audio)?[)|\]]$/i, /\s*[(|[](HD|HQ)\s*?(?:audio)?[)|\]]$/i,
/\s*[(|\[](live)[)|\]]$/i, /\s*[(|[](live)[)|\]]$/i,
/\s*[(|\[]4K\s*?(?:upgrade)?[)|\]]$/i, /\s*[(|[]4K\s*?(?:upgrade)?[)|\]]$/i,
]; ];
export function cleanupName(name: string): string { export function cleanupName(name: string): string {

View File

@ -172,10 +172,19 @@ async function onApiLoaded() {
// Remove upgrade button // Remove upgrade button
if (window.mainConfig.get('options.removeUpgradeButton')) { if (window.mainConfig.get('options.removeUpgradeButton')) {
const itemsSelector = 'ytmusic-guide-section-renderer #items';
let selector = 'ytmusic-guide-entry-renderer:last-child';
const upgradeBtnIcon = document.querySelector<SVGGElement>('iron-iconset-svg[name="yt-sys-icons"] #youtube_music_monochrome');
if (upgradeBtnIcon) {
const path = upgradeBtnIcon.firstChild as SVGPathElement;
const data = path.getAttribute('d')!.substring(0, 15);
selector = `ytmusic-guide-entry-renderer:has(> tp-yt-paper-item > yt-icon path[d^="${data}"])`;
}
const styles = document.createElement('style'); const styles = document.createElement('style');
styles.innerHTML = `ytmusic-guide-section-renderer #items ytmusic-guide-entry-renderer:last-child { styles.textContent = `${itemsSelector} ${selector} { display: none; }`;
display: none;
}`;
document.head.appendChild(styles); document.head.appendChild(styles);
} }

View File

@ -241,7 +241,8 @@ export interface FlagEndpoint {
flagAction: string; flagAction: string;
} }
export type VideoDataChangeValue = { // see song-info-front.ts
export type VideoDataChangeValue = Record<string, unknown> & {
videoId: string; videoId: string;
title: string; title: string;
author: string; author: string;

View File

@ -5,3 +5,17 @@ export interface QueueResponse {
autoPlaying?: boolean; autoPlaying?: boolean;
continuation?: string; continuation?: string;
} }
export interface WatchNextResponse {
playerOverlays: {
playerOverlayRenderer: {
browserMediaSession: {
browserMediaSessionRenderer: {
album: {
runs: { text: string; }[]
}
}
}
}
};
}

View File

@ -3,6 +3,7 @@
import { VideoDetails } from './video-details'; import { VideoDetails } from './video-details';
import { GetPlayerResponse } from './get-player-response'; import { GetPlayerResponse } from './get-player-response';
import { PlayerAPIEvents } from './player-api-events'; import { PlayerAPIEvents } from './player-api-events';
import { WatchNextResponse } from '@/types/youtube-music-desktop-internal';
export interface YoutubePlayer { export interface YoutubePlayer {
getInternalApiInterface: <Parameters extends unknown[], Return>( getInternalApiInterface: <Parameters extends unknown[], Return>(
@ -427,4 +428,6 @@ export interface YoutubePlayer {
addEmbedsConversionTrackingParams: <Parameters extends unknown[], Return>( addEmbedsConversionTrackingParams: <Parameters extends unknown[], Return>(
...params: Parameters ...params: Parameters
) => Return; ) => Return;
getWatchNextResponse(): WatchNextResponse;
} }

View File

@ -0,0 +1,11 @@
export const waitForElement = <T extends Element>(selector: string): Promise<T> => {
return new Promise<T>((resolve) => {
const interval = setInterval(() => {
const elem = document.querySelector<T>(selector);
if (!elem) return;
clearInterval(interval);
resolve(elem);
}, 100 /* ms */);
});
};