mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-10 10:11:46 +00:00
Compare commits
109 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 089eff3152 | |||
| 306ee8dba5 | |||
| a4992bafb2 | |||
| 5b004eedff | |||
| 2a66076d31 | |||
| f48e46d29c | |||
| 20296f5463 | |||
| fe4c89c349 | |||
| 3640527c8c | |||
| 3326582a16 | |||
| 5dcb9fe9ba | |||
| 336fa1e6fd | |||
| 3679a109d6 | |||
| 5290ed3de2 | |||
| fe5195714f | |||
| 8eb846262d | |||
| e9a1c2a91f | |||
| 2d1f78b383 | |||
| 1899064fd3 | |||
| e0280e5fe2 | |||
| d577e0fba6 | |||
| 37577c2f7f | |||
| c880f0a4eb | |||
| 4875955914 | |||
| 5b28c780bd | |||
| 4db2674b15 | |||
| a454a0163f | |||
| 61eb28e780 | |||
| e165e64952 | |||
| f380822e11 | |||
| 6b1995145a | |||
| 9317e99f43 | |||
| 66d05d8683 | |||
| 545a3a4bb6 | |||
| 04a6d16dbe | |||
| 3c36477b1e | |||
| c5c191492e | |||
| b12e2f607c | |||
| 1d77ad6de4 | |||
| 25bd26d7f3 | |||
| d11d0abe73 | |||
| 8a643c465d | |||
| 233673b8d8 | |||
| 5a448fab31 | |||
| 42e8262cda | |||
| f64769b1d3 | |||
| e12998761b | |||
| 2e20fa83b8 | |||
| 5149757af3 | |||
| 655741f108 | |||
| 4e58571ad0 | |||
| 1e4a615b47 | |||
| dedcf0c9ff | |||
| a84a7d236a | |||
| e56b4b21f0 | |||
| 361f9e42bd | |||
| 918736d2ca | |||
| 8f3d5b08ac | |||
| 4ca327d801 | |||
| 8d0aa057ad | |||
| b7ffee089b | |||
| d72b994f66 | |||
| e6dafdb068 | |||
| 14886dd4bd | |||
| c9f2f88bac | |||
| 1eabbc0bbe | |||
| 32a572b35a | |||
| 59a5679cbb | |||
| ac51f798c3 | |||
| 7599cc694a | |||
| 53595654b1 | |||
| 7656c41dbc | |||
| ff0c5b87c9 | |||
| 506c95740a | |||
| 575a42e28a | |||
| dcdc6a825f | |||
| 0a41bb1cd6 | |||
| 72a4736dc9 | |||
| f2b1e6b6bf | |||
| bc0e28ad6d | |||
| 8ebae91c02 | |||
| 954ad90733 | |||
| 5af0643788 | |||
| be633ac1f2 | |||
| 0d047c1fd5 | |||
| e8b1aca629 | |||
| 5b9bacf390 | |||
| ccd16f4a5f | |||
| 02e519dab3 | |||
| 473ea78f12 | |||
| e7f366b770 | |||
| 66816ac42d | |||
| 08b985f2ab | |||
| 747bde2136 | |||
| eabc28b39f | |||
| 3537dc19ee | |||
| 6afeb60557 | |||
| 71115dedee | |||
| 8750b54f76 | |||
| 482a1c5073 | |||
| c8d516c40b | |||
| c1ad168c32 | |||
| 5f5be5d02f | |||
| 61ef56dccc | |||
| a73b5acc75 | |||
| 877573532c | |||
| 7b033b5caf | |||
| 8924ec29d3 | |||
| 23e688aaf8 |
@ -21,7 +21,7 @@
|
||||
</a>
|
||||
</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:**
|
||||
|
||||
@ -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
|
||||
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
|
||||
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
|
||||
|
||||
|
||||
## Translation
|
||||
|
||||
You can help with translation on [Hosted Weblate](https://hosted.weblate.org/projects/youtube-music/).
|
||||
|
||||
54
changelog.md
54
changelog.md
@ -2,8 +2,62 @@
|
||||
|
||||
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)
|
||||
|
||||
> 15 July 2024
|
||||
|
||||
- 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: fix touchbar icon [`#2183`](https://github.com/th-ch/youtube-music/issues/2183)
|
||||
|
||||
374
docs/readme/README-ru.md
Normal file
374
docs/readme/README-ru.md
Normal file
@ -0,0 +1,374 @@
|
||||
<div align="center">
|
||||
|
||||
# YouTube Music
|
||||
|
||||
[](https://github.com/th-ch/youtube-music/releases/)
|
||||
[](https://github.com/th-ch/youtube-music/blob/master/LICENSE)
|
||||
[](https://github.com/th-ch/youtube-music/blob/master/.eslintrc.js)
|
||||
[](https://GitHub.com/th-ch/youtube-music/releases/)
|
||||
[](https://GitHub.com/th-ch/youtube-music/releases/)
|
||||
[](https://aur.archlinux.org/packages/youtube-music-bin)
|
||||
[](https://snyk.io/test/github/th-ch/youtube-music)
|
||||
|
||||
</div>
|
||||
|
||||

|
||||
|
||||
|
||||
<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) |
|
||||
|:---------------------------------------------------------------------------------------------------------:|
|
||||
||
|
||||
|
||||
## Содержание
|
||||
|
||||
- [Возможности](#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 can’t 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> [обратный апостроф], если используете плагин "Меню в приложении")
|
||||
@ -51,6 +51,7 @@ export default defineConfig({
|
||||
};
|
||||
|
||||
if (mode === 'development') {
|
||||
commonConfig.build!.sourcemap = 'inline';
|
||||
commonConfig.plugins?.push(
|
||||
Inspect({
|
||||
build: true,
|
||||
@ -98,6 +99,7 @@ export default defineConfig({
|
||||
};
|
||||
|
||||
if (mode === 'development') {
|
||||
commonConfig.build!.sourcemap = 'inline';
|
||||
commonConfig.plugins?.push(
|
||||
Inspect({
|
||||
build: true,
|
||||
@ -148,6 +150,7 @@ export default defineConfig({
|
||||
};
|
||||
|
||||
if (mode === 'development') {
|
||||
commonConfig.build!.sourcemap = 'inline';
|
||||
commonConfig.plugins?.push(
|
||||
Inspect({
|
||||
build: true,
|
||||
|
||||
51
package.json
51
package.json
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "youtube-music",
|
||||
"productName": "YouTube Music",
|
||||
"version": "3.5.0",
|
||||
"version": "3.5.2",
|
||||
"description": "YouTube Music Desktop App - including custom plugins",
|
||||
"main": "./dist/main/index.js",
|
||||
"license": "MIT",
|
||||
@ -94,6 +94,10 @@
|
||||
"rpm": {
|
||||
"depends": [
|
||||
"/usr/lib64/libuuid.so.1"
|
||||
],
|
||||
"fpm": [
|
||||
"--rpm-rpmbuild-define",
|
||||
"_build_id_links none"
|
||||
]
|
||||
},
|
||||
"snap": {
|
||||
@ -116,7 +120,7 @@
|
||||
"vite:inspect": "pnpm clean && electron-vite build --mode development && pnpm exec serve .vite-inspect",
|
||||
"start": "electron-vite preview",
|
||||
"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",
|
||||
"clean": "del-cli dist && del-cli pack && del-cli .vite-inspect",
|
||||
"dist": "pnpm clean && pnpm build && pnpm electron-builder --win --mac --linux -p never",
|
||||
@ -145,11 +149,10 @@
|
||||
"xml2js": "0.6.2",
|
||||
"node-fetch": "3.3.2",
|
||||
"@electron/universal": "2.0.1",
|
||||
"@babel/runtime": "7.25.0"
|
||||
"@babel/runtime": "7.25.6"
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"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"
|
||||
}
|
||||
},
|
||||
@ -160,12 +163,12 @@
|
||||
"@electron/remote": "2.1.2",
|
||||
"@ffmpeg.wasm/core-mt": "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",
|
||||
"@jellybrick/electron-better-web-request": "1.0.4",
|
||||
"@jellybrick/mpris-service": "2.1.4",
|
||||
"@skyra/jaro-winkler": "^1.1.1",
|
||||
"@xhayper/discord-rpc": "1.1.4",
|
||||
"@xhayper/discord-rpc": "1.2.0",
|
||||
"async-mutex": "0.5.0",
|
||||
"butterchurn": "3.0.0-beta.4",
|
||||
"butterchurn-presets": "3.0.0-beta.4",
|
||||
@ -174,18 +177,18 @@
|
||||
"custom-electron-prompt": "1.5.8",
|
||||
"dbus-next": "0.10.2",
|
||||
"deepmerge-ts": "7.1.0",
|
||||
"electron-debug": "4.0.0",
|
||||
"electron-debug": "4.0.1",
|
||||
"electron-is": "3.0.0",
|
||||
"electron-localshortcut": "3.2.1",
|
||||
"electron-store": "10.0.0",
|
||||
"electron-unhandled": "4.0.1",
|
||||
"electron-updater": "6.3.2",
|
||||
"electron-updater": "6.3.4",
|
||||
"fast-average-color": "9.4.0",
|
||||
"fast-equals": "5.0.1",
|
||||
"filenamify": "6.0.0",
|
||||
"howler": "2.2.4",
|
||||
"html-to-text": "9.0.5",
|
||||
"i18next": "23.12.2",
|
||||
"i18next": "23.14.0",
|
||||
"keyboardevent-from-electron-accelerator": "2.0.0",
|
||||
"keyboardevents-areequal": "0.2.2",
|
||||
"node-html-parser": "6.1.13",
|
||||
@ -195,47 +198,47 @@
|
||||
"serve": "14.2.3",
|
||||
"simple-youtube-age-restriction-bypass": "github:organization/Simple-YouTube-Age-Restriction-Bypass#v2.5.9",
|
||||
"solid-floating-ui": "0.3.1",
|
||||
"solid-js": "1.8.19",
|
||||
"solid-js": "1.8.22",
|
||||
"solid-styled-components": "0.28.5",
|
||||
"solid-transition-group": "0.2.3",
|
||||
"ts-morph": "23.0.0",
|
||||
"vudio": "2.1.1",
|
||||
"x11": "2.3.0",
|
||||
"youtubei.js": "10.2.0"
|
||||
"youtubei.js": "10.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.45.3",
|
||||
"@total-typescript/ts-reset": "0.5.1",
|
||||
"@playwright/test": "1.47.0",
|
||||
"@total-typescript/ts-reset": "0.6.1",
|
||||
"@types/color": "3.0.6",
|
||||
"@types/electron-localshortcut": "3.1.3",
|
||||
"@types/howler": "2.2.11",
|
||||
"@types/html-to-text": "9.0.4",
|
||||
"@types/semver": "7.5.8",
|
||||
"@typescript-eslint/eslint-plugin": "7.18.0",
|
||||
"@typescript-eslint/parser": "7.18.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.4.0",
|
||||
"@typescript-eslint/parser": "8.4.0",
|
||||
"bufferutil": "4.0.8",
|
||||
"builtin-modules": "4.0.0",
|
||||
"cross-env": "7.0.3",
|
||||
"del-cli": "5.1.0",
|
||||
"discord-api-types": "0.37.93",
|
||||
"electron": "31.3.1",
|
||||
"discord-api-types": "0.37.100",
|
||||
"electron": "32.0.2",
|
||||
"electron-builder": "24.13.3",
|
||||
"electron-devtools-installer": "3.2.0",
|
||||
"electron-vite": "2.3.0",
|
||||
"esbuild": "0.23.0",
|
||||
"esbuild": "0.23.1",
|
||||
"eslint": "8.57.0",
|
||||
"eslint-import-resolver-exports": "1.0.0-beta.5",
|
||||
"eslint-import-resolver-typescript": "3.6.1",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-import-resolver-typescript": "3.6.3",
|
||||
"eslint-plugin-import": "2.30.0",
|
||||
"eslint-plugin-prettier": "5.2.1",
|
||||
"glob": "11.0.0",
|
||||
"node-gyp": "10.2.0",
|
||||
"playwright": "1.45.3",
|
||||
"rollup": "4.19.1",
|
||||
"playwright": "1.47.0",
|
||||
"rollup": "4.21.2",
|
||||
"typescript": "5.5.4",
|
||||
"utf-8-validate": "6.0.4",
|
||||
"vite": "5.3.5",
|
||||
"vite-plugin-inspect": "0.8.5",
|
||||
"vite": "5.4.3",
|
||||
"vite-plugin-inspect": "0.8.7",
|
||||
"vite-plugin-resolve": "2.5.2",
|
||||
"vite-plugin-solid": "2.10.2",
|
||||
"ws": "8.18.0"
|
||||
|
||||
@ -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
1653
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,9 +1,8 @@
|
||||
import Store from 'electron-store';
|
||||
import { deepmergeCustom } from 'deepmerge-ts';
|
||||
|
||||
import defaultConfig from './defaults';
|
||||
|
||||
import store from './store';
|
||||
import store, { IStore } from './store';
|
||||
import plugins from './plugins';
|
||||
|
||||
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]
|
||||
? never
|
||||
: T extends object
|
||||
? {
|
||||
[K in keyof T]-?: K extends string | number
|
||||
? `${K}` | Join<K, Paths<T[K], Prev[D]>>
|
||||
: never;
|
||||
}[keyof T]
|
||||
: '';
|
||||
? {
|
||||
[K in keyof T]-?: K extends string | number
|
||||
? `${K}` | Join<K, Paths<T[K], Prev[D]>>
|
||||
: never;
|
||||
}[keyof T]
|
||||
: '';
|
||||
|
||||
type SplitKey<K> = K extends `${infer A}.${infer B}` ? [A, B] : [K, string];
|
||||
type PathValue<T, K extends string> = SplitKey<K> extends [
|
||||
infer A extends keyof T,
|
||||
infer B extends string,
|
||||
]
|
||||
? PathValue<T[A], B>
|
||||
: T;
|
||||
type PathValue<T, K extends string> =
|
||||
SplitKey<K> extends [infer A extends keyof T, infer B extends string]
|
||||
? PathValue<T[A], B>
|
||||
: T;
|
||||
|
||||
const get = <Key extends Paths<typeof defaultConfig>>(key: Key) =>
|
||||
store.get(key) as PathValue<typeof defaultConfig, typeof key>;
|
||||
|
||||
@ -86,7 +84,7 @@ export default {
|
||||
setPartial,
|
||||
setMenuOption,
|
||||
edit: () => store.openInEditor(),
|
||||
watch(cb: Parameters<Store['onDidAnyChange']>[0]) {
|
||||
watch(cb: Parameters<IStore['onDidAnyChange']>[0]) {
|
||||
store.onDidAnyChange(cb);
|
||||
},
|
||||
plugins,
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
import Store from 'electron-store';
|
||||
import Conf from 'conf';
|
||||
|
||||
import defaults from './defaults';
|
||||
|
||||
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 = {
|
||||
'>=3.3.0'(store: Conf<Record<string, unknown>>) {
|
||||
'>=3.3.0'(store: IStore) {
|
||||
const lastfmConfig = store.get('plugins.lastfm') as {
|
||||
enabled?: boolean;
|
||||
token?: string;
|
||||
@ -16,21 +18,21 @@ const migrations = {
|
||||
secret?: string;
|
||||
};
|
||||
if (lastfmConfig) {
|
||||
let scrobblerConfig = store.get(
|
||||
'plugins.scrobbler',
|
||||
) as {
|
||||
enabled?: boolean;
|
||||
scrobblers?: {
|
||||
lastfm?: {
|
||||
let scrobblerConfig = store.get('plugins.scrobbler') as
|
||||
| {
|
||||
enabled?: boolean;
|
||||
token?: string;
|
||||
sessionKey?: string;
|
||||
apiRoot?: string;
|
||||
apiKey?: string;
|
||||
secret?: string;
|
||||
};
|
||||
};
|
||||
} | undefined;
|
||||
scrobblers?: {
|
||||
lastfm?: {
|
||||
enabled?: boolean;
|
||||
token?: string;
|
||||
sessionKey?: string;
|
||||
apiRoot?: string;
|
||||
apiKey?: string;
|
||||
secret?: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
| undefined;
|
||||
|
||||
if (!scrobblerConfig) {
|
||||
scrobblerConfig = {
|
||||
@ -56,7 +58,7 @@ const migrations = {
|
||||
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<
|
||||
string,
|
||||
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');
|
||||
if (listenAlong !== undefined) {
|
||||
store.set('plugins.discord.playOnYouTubeMusic', 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
|
||||
| string
|
||||
| undefined;
|
||||
@ -110,7 +112,7 @@ const migrations = {
|
||||
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
|
||||
|
||||
if (store.get('plugins.notifications.toastStyle') === undefined) {
|
||||
@ -125,14 +127,14 @@ const migrations = {
|
||||
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
|
||||
|
||||
if (store.get('plugins.video-toggle.mode') === undefined) {
|
||||
store.set('plugins.video-toggle.mode', 'custom');
|
||||
}
|
||||
},
|
||||
'>=1.14.0'(store: Conf<Record<string, unknown>>) {
|
||||
'>=1.14.0'(store: IStore) {
|
||||
if (
|
||||
typeof store.get('plugins.precise-volume.globalShortcuts') !== 'object'
|
||||
) {
|
||||
@ -144,12 +146,12 @@ const migrations = {
|
||||
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) {
|
||||
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
|
||||
| Record<
|
||||
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) {
|
||||
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[];
|
||||
if (!Array.isArray(enabledPlugins)) {
|
||||
console.warn('Plugins are not in array format, cannot migrate');
|
||||
@ -233,4 +235,4 @@ export default new Store({
|
||||
},
|
||||
clearInvalidConfig: false,
|
||||
migrations,
|
||||
});
|
||||
}) as Store & IStore;
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
},
|
||||
"language": {
|
||||
"code": "ca",
|
||||
"local-name": "català",
|
||||
"local-name": "Català",
|
||||
"name": "Catalan"
|
||||
},
|
||||
"main": {
|
||||
@ -84,9 +84,9 @@
|
||||
"label": "Navegació",
|
||||
"submenu": {
|
||||
"copy-current-url": "Copia l'URL actual",
|
||||
"go-back": "Anar enrere",
|
||||
"go-forward": "Anar endavant",
|
||||
"quit": "Sortir",
|
||||
"go-back": "Ves enrere",
|
||||
"go-forward": "Ves endavant",
|
||||
"quit": "Surt",
|
||||
"restart": "Reinicia l'aplicació"
|
||||
}
|
||||
},
|
||||
@ -102,11 +102,11 @@
|
||||
"override-user-agent": "Sobreescriu l'agent d'usuari (User-Agent)",
|
||||
"restart-on-config-changes": "Reinicia quan es canviï la configuració",
|
||||
"set-proxy": {
|
||||
"label": "Definir proxy",
|
||||
"label": "Definir servidor intermediari (proxy)",
|
||||
"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",
|
||||
"title": "Definir proxy"
|
||||
"title": "Definir servidor intermediari (proxy)"
|
||||
}
|
||||
},
|
||||
"toggle-dev-tools": "Commuta les DevTools"
|
||||
@ -144,11 +144,11 @@
|
||||
"disabled": "Deshabilitat",
|
||||
"enabled-and-hide-app": "Mostra la icona i amaga 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": {
|
||||
"label": "Configuració visual",
|
||||
"label": "Opcions visuals",
|
||||
"submenu": {
|
||||
"like-buttons": {
|
||||
"default": "Per defecte",
|
||||
@ -182,9 +182,9 @@
|
||||
"new": "NOU"
|
||||
},
|
||||
"view": {
|
||||
"label": "Mostra",
|
||||
"label": "Veure",
|
||||
"submenu": {
|
||||
"force-reload": "Força recàrrega",
|
||||
"force-reload": "Força la recàrrega",
|
||||
"reload": "Recarrega",
|
||||
"reset-zoom": "Mida real",
|
||||
"toggle-fullscreen": "Commuta la pantalla completa",
|
||||
@ -220,7 +220,7 @@
|
||||
},
|
||||
"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",
|
||||
"name": "Accions de l'àlbum"
|
||||
"name": "Accions a l'àlbum"
|
||||
},
|
||||
"album-color-theme": {
|
||||
"description": "Aplica un tema dinàmic i efectes visuals basats en la paleta de colors de l'àlbum",
|
||||
@ -281,7 +281,7 @@
|
||||
},
|
||||
"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)",
|
||||
"name": "Compressor d'àudio"
|
||||
"name": "Compressió d'àudio"
|
||||
},
|
||||
"blur-nav-bar": {
|
||||
"description": "Desenfoca i aplica transparència a la barra de navegació",
|
||||
@ -518,7 +518,7 @@
|
||||
},
|
||||
"no-google-login": {
|
||||
"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": {
|
||||
"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",
|
||||
"name": "Canvia la qualitat del vídeo"
|
||||
"name": "Botó de qualitat del vídeo"
|
||||
},
|
||||
"scrobbler": {
|
||||
"description": "Afegeix suport per scrobbling (Last.fm, ListenBrainz, etc.)",
|
||||
@ -657,8 +657,8 @@
|
||||
}
|
||||
},
|
||||
"skip-disliked-songs": {
|
||||
"description": "Omet cançons amb «No m'agrada»",
|
||||
"name": "Omet cançons amb «No m'agrada»"
|
||||
"description": "Salta les cançons amb «no m'agrada»",
|
||||
"name": "Salta les cançons que no t'agraden"
|
||||
},
|
||||
"skip-silences": {
|
||||
"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",
|
||||
"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": {
|
||||
"description": "Controla la reproducció des de la barra de tasques del Windows",
|
||||
"name": "Control multimèdia a la barra de tasques"
|
||||
@ -701,7 +754,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Commutador de vídeo",
|
||||
"name": "Botó de vídeo",
|
||||
"templates": {
|
||||
"button": "Cançó"
|
||||
}
|
||||
|
||||
@ -668,6 +668,54 @@
|
||||
"description": "Überspringt automatisch nicht-musikalische Teile wie Intro/Outro oder Teile von Musikvideos, in denen der Song nicht gespielt wird",
|
||||
"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": {
|
||||
"description": "Wiedergabe aus der Windows Taskleiste kontrollieren",
|
||||
"name": "Mediensteuerung in der Taskleiste"
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
"common": {
|
||||
"console": {
|
||||
"plugins": {
|
||||
"execute-failed": "Αποτυχία εκτέλεσης προσθέτου {{pluginName}}::{{contextName}}",
|
||||
"execute-failed": "Απέτυχε η εκτέλεση του πρόσθετου {{pluginName}}::{{contextName}}",
|
||||
"executed-at-ms": "Το πρόσθετο {{pluginName}}::{{contextName}} εκτελέστηκε σε {{ms}}ms",
|
||||
"initialize-failed": "Απέτυχε η αρχικοποίηση του πρόσθετου \"{{pluginName}}\"",
|
||||
"load-all": "Φόρτωση όλων των πρόσθετων",
|
||||
@ -36,7 +36,7 @@
|
||||
"details": "Σφάλμα ανταπόκρισης!\n{{error}}"
|
||||
},
|
||||
"when-ready": {
|
||||
"clearing-cache-after-20s": "Εκκαθάριση της cache της εφαρμογής"
|
||||
"clearing-cache-after-20s": "Εκκαθάριση μνήμης cache εφαρμογής"
|
||||
},
|
||||
"window": {
|
||||
"tried-to-render-offscreen": "Το παράθυρο προσπάθησε να απεικονίσει εκτός οθόνης, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
|
||||
@ -45,23 +45,23 @@
|
||||
"dialog": {
|
||||
"hide-menu-enabled": {
|
||||
"detail": "Το μενού είναι κρυμμένο, χρησιμοποιήστε το 'Alt' για να το εμφανίσετε (ή το 'Escape' αν χρησιμοποιείτε το μενού εφαρμογής)",
|
||||
"message": "Απόκρυψη μενού είναι ενεργοποιημένο",
|
||||
"title": "Ενεργοποιήθηκε η Απόκρυψη του Μενού"
|
||||
"message": "Η απόκρυψη μενού είναι ενεργοποιημένη",
|
||||
"title": "Η απόκρυψη μενού ενεργοποιήθηκε"
|
||||
},
|
||||
"need-to-restart": {
|
||||
"buttons": {
|
||||
"later": "Αργότερα",
|
||||
"restart-now": "Επανεκκίνηση Τώρα"
|
||||
"restart-now": "Επανεκκίνηση τώρα"
|
||||
},
|
||||
"detail": "Το πρόσθετο \"{{pluginName}}\" απαιτεί επανεκκίνηση για να ισχύσει",
|
||||
"message": "Το \"{{pluginName}}\" χρειάζεται επανεκκίνηση",
|
||||
"title": "Απαιτείται Επανεκκίνηση"
|
||||
"title": "Απαιτείται επανεκκίνηση"
|
||||
},
|
||||
"unresponsive": {
|
||||
"buttons": {
|
||||
"quit": "Έξοδος",
|
||||
"relaunch": "Επανεκκίνηση",
|
||||
"wait": "Περίμενε"
|
||||
"wait": "Περιμένετε"
|
||||
},
|
||||
"detail": "Λυπούμαστε για την ταλαιπωρία! Παρακαλούμε επιλέξτε τι να κάνετε:",
|
||||
"message": "Η εφαρμογή δεν ανταποκρίνεται",
|
||||
@ -69,13 +69,13 @@
|
||||
},
|
||||
"update-available": {
|
||||
"buttons": {
|
||||
"disable": "Απενεργοποίηση Ενημερώσεων",
|
||||
"disable": "Απενεργοποίηση ενημερώσεων",
|
||||
"download": "Λήψη",
|
||||
"ok": "Εντάξει"
|
||||
"ok": "OK"
|
||||
},
|
||||
"detail": "Μια νέα έκδοση είναι διαθέσιμη και μπορεί να ληφθεί από τον σύνδεσμο {{downloadLink}}",
|
||||
"message": "Μια νέα έκδοση είναι διαθέσιμη",
|
||||
"title": "Υπάρχει Διαθέσιμη Ενημέρωση"
|
||||
"title": "Υπάρχει διαθέσιμη ενημέρωση"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
@ -87,7 +87,7 @@
|
||||
"go-back": "Πήγαινε πίσω",
|
||||
"go-forward": "Πήγαινε μπροστά",
|
||||
"quit": "Έξοδος",
|
||||
"restart": "Επανεκκίνηση Εφαρμογής"
|
||||
"restart": "Επανεκκίνηση εφαρμογής"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
@ -106,7 +106,7 @@
|
||||
"prompt": {
|
||||
"label": "Εισαγωγή διεύθυνσης διακομιστή μεσολάβησης (proxy): (αφήστε κενό για απενεργοποίηση)",
|
||||
"placeholder": "Παράδειγμα: SOCKS5://127.0.0.1:9999",
|
||||
"title": "Ορισμός διακομιστή μεσολάβησης (proxy)"
|
||||
"title": "Ορισμός μεσολάβησης"
|
||||
}
|
||||
},
|
||||
"toggle-dev-tools": "Εναλλαγή DevTools"
|
||||
@ -135,8 +135,8 @@
|
||||
"single-instance-lock": "Κλείδωμα Μοναδικής Εκδοχής",
|
||||
"start-at-login": "Έναρξη κατά την σύνδεση",
|
||||
"starting-page": {
|
||||
"label": "Σελίδα Έναρξης",
|
||||
"unset": "Κατάργηση Ορισμού"
|
||||
"label": "Σελίδα έναρξης",
|
||||
"unset": "Κατάργηση ορισμού"
|
||||
},
|
||||
"tray": {
|
||||
"label": "Δίσκος",
|
||||
@ -148,13 +148,27 @@
|
||||
}
|
||||
},
|
||||
"visual-tweaks": {
|
||||
"label": "Τροποποιήσεις Εμφάνισης",
|
||||
"submenu": {
|
||||
"like-buttons": {
|
||||
"default": "Default"
|
||||
"default": "Default",
|
||||
"force-show": "Αναγκαστική Εμφάνιση",
|
||||
"hide": "Απόκρυψη",
|
||||
"label": "Μου αρέσει"
|
||||
},
|
||||
"remove-upgrade-button": "Αφαίρεση κουμπιού αναβάθμισης",
|
||||
"theme": {
|
||||
"label": "Theme",
|
||||
"dialog": {
|
||||
"button": {
|
||||
"cancel": "Άκυρο",
|
||||
"remove": "Αφαίρεση"
|
||||
},
|
||||
"remove-theme": "Είστε βέβαιοι ότι θέλετε να αφαιρέσετε το προσαρμοσμένο θέμα;",
|
||||
"remove-theme-message": "Αυτό θα αφαιρέσει το προσαρμοσμένο θέμα"
|
||||
},
|
||||
"label": "Θέμα",
|
||||
"submenu": {
|
||||
"import-css-file": "Εισαγωγή προσαρμοσμένου αρχείου CSS",
|
||||
"no-theme": "No theme"
|
||||
}
|
||||
}
|
||||
@ -163,29 +177,68 @@
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"label": "Plugins"
|
||||
"enabled": "Ενεργοποιημένο",
|
||||
"label": "Πρόσθετα",
|
||||
"new": "ΝΕΟ"
|
||||
},
|
||||
"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": {
|
||||
"ad-speedup": {
|
||||
"description": "Εαν παίξει διαφήμιση κάνει σίγαση του ήχου και θέτει την ταχύτητα αναπαραγωγής στο 16x",
|
||||
"name": "Γρήγορη Προώθηση Διαφημίσεων"
|
||||
},
|
||||
"adblocker": {
|
||||
"description": "Αποκλεισμός όλων των διαφημίσεων και tracker",
|
||||
"menu": {
|
||||
"blocker": "Μέθοδος αποκλεισμού"
|
||||
},
|
||||
"name": "Adblocker"
|
||||
"name": "Μπλοκάρισμα Διαφημίσεων"
|
||||
},
|
||||
"album-actions": {
|
||||
"description": "Προσθέτει κουμπιά Like/Unlike και Dislike/Undislike που δρουν συνολικά σε όλα τα κομμάτια μιας playlist ή ενός άλμπουμ",
|
||||
"name": "Ενέργειες σε Άλμπουμ"
|
||||
},
|
||||
"album-color-theme": {
|
||||
"description": "Εφαρμόζει ένα δυναμικό θέμα και εφέ με βάση τη χρωματική παλέτα του άλμπουμ",
|
||||
"menu": {
|
||||
"color-mix-ratio": {
|
||||
"label": "Αναλογία μίξης χρώματος",
|
||||
"submenu": {
|
||||
"percent": "{{ratio}}%"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Album Color Theme"
|
||||
},
|
||||
"ambient-mode": {
|
||||
"description": "Εφαρμόζει ένα εφέ φωτισμού ρίχνοντας απαλά χρώματα από το βίντεο, στο φόντο της οθόνης σας.",
|
||||
"menu": {
|
||||
"blur-amount": {
|
||||
"label": "Ένταση θαμπώματος",
|
||||
"submenu": {
|
||||
"pixels": "{{blurAmount}} pixels"
|
||||
}
|
||||
@ -197,25 +250,30 @@
|
||||
}
|
||||
},
|
||||
"opacity": {
|
||||
"label": "Ποσότητα αδιαφάνειας",
|
||||
"label": "Αδιαφάνεια",
|
||||
"submenu": {
|
||||
"percent": "{{opacity}}%"
|
||||
}
|
||||
},
|
||||
"quality": {
|
||||
"label": "Ποιότητα",
|
||||
"submenu": {
|
||||
"pixels": "{{quality}} pixels"
|
||||
}
|
||||
},
|
||||
"size": {
|
||||
"label": "Μέγεθος",
|
||||
"submenu": {
|
||||
"percent": "{{size}}%"
|
||||
}
|
||||
},
|
||||
"smoothness-transition": {
|
||||
"submenu": {
|
||||
"during": "Σε {{interpolationTime}} δεύτερα"
|
||||
"during": "Σε {{interpolationTime}} δευτερόλεπτα"
|
||||
}
|
||||
},
|
||||
"use-fullscreen": {
|
||||
"label": "Χρήση πλήρους οθόνης"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -257,11 +315,14 @@
|
||||
"description": "Κάνει τα τραγούδια να είναι αυτόματα σε παύση",
|
||||
"menu": {
|
||||
"apply-once": "Εφαρμόζεται μόνο στο πρώτο τραγούδι"
|
||||
}
|
||||
},
|
||||
"name": "Απενεργοποίηση αυτόματης αναπαραγωγής"
|
||||
},
|
||||
"discord": {
|
||||
"description": "Δείξτε στους φίλους σας τι ακούτε με το Rich Presence",
|
||||
"menu": {
|
||||
"auto-reconnect": "Αυτόματη επανασύνδεση",
|
||||
"clear-activity": "Εκκαθάριση δραστηριότητας",
|
||||
"hide-duration-left": "Απόκρυψη της διάρκειας που απομένει",
|
||||
"hide-github-button": "Απόκρυψη του συνδέσμου προς GitHub",
|
||||
"set-inactivity-timeout": "Ορισμός χρονικού ορίου αδράνειας"
|
||||
@ -280,34 +341,113 @@
|
||||
"buttons": {
|
||||
"ok": "OK"
|
||||
},
|
||||
"message": "Λήψη λίστας αναπαραγωγής {{playlistTitle}}",
|
||||
"title": "Λήψη ξεκίνησε"
|
||||
"detail": "{{playlistSize}} τραγούδια)",
|
||||
"message": "Λήψη της λίστας αναπαραγωγής {{playlistTitle}}",
|
||||
"title": "Η λήψη ξεκίνησε"
|
||||
}
|
||||
},
|
||||
"feedback": {
|
||||
"conversion-progress": "Μετατροπή: {{percent}}%",
|
||||
"download-progress": "Download: {{percent}}%",
|
||||
"preparing-file": "Προετοιμασία αρχείου…"
|
||||
"converting": "Μετατροπή…",
|
||||
"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": {
|
||||
"button": "Download"
|
||||
"button": "Λήψη"
|
||||
}
|
||||
},
|
||||
"music-together": {
|
||||
"internal": {
|
||||
"save": "Αποθήκευση",
|
||||
"unknown-user": "Άγνωστος χρήστης"
|
||||
},
|
||||
"menu": {
|
||||
"connected-users": "Συνδεδεμένοι χρήστες"
|
||||
},
|
||||
"toast": {
|
||||
"add-song-failed": "Απέτυχε η προσθήκη τραγουδιού",
|
||||
"remove-song-failed": "Απέτυχε η αφαίρεση τραγουδιού"
|
||||
}
|
||||
},
|
||||
"navigation": {
|
||||
"name": "Navigation"
|
||||
"name": "Πλοήγηση"
|
||||
},
|
||||
"no-google-login": {
|
||||
"name": "No Google Login"
|
||||
},
|
||||
"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": {
|
||||
"prompt": {
|
||||
"keybind": {
|
||||
"keybind-options": {
|
||||
"next": "Next"
|
||||
"next": "Επόμενο",
|
||||
"play-pause": "Αναπαραγωγή / Παύση",
|
||||
"previous": "Προηγούμενο"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -683,6 +683,42 @@
|
||||
"refetch-btn": {
|
||||
"normal": "Refetch lyrics",
|
||||
"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": {
|
||||
|
||||
@ -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",
|
||||
"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": {
|
||||
"description": "Controla la reproducción desde la barra de tareas de Windows",
|
||||
"name": "Control de medios de la barra de tareas"
|
||||
|
||||
182
src/i18n/resources/et.json
Normal file
182
src/i18n/resources/et.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -207,10 +207,12 @@
|
||||
},
|
||||
"plugins": {
|
||||
"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": {
|
||||
"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": {
|
||||
"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": {
|
||||
"label": "Kung natapos ang download",
|
||||
"prompt": {
|
||||
"last-percent": "Tapos ng x na porsyento",
|
||||
"last-seconds": "Huling x na segundo",
|
||||
"title": "I-configure kung kailan magda-download"
|
||||
},
|
||||
"submenu": {
|
||||
@ -406,6 +410,9 @@
|
||||
},
|
||||
"renderer": {
|
||||
"can-not-update-progress": "Hindi ma-update ang progress"
|
||||
},
|
||||
"templates": {
|
||||
"button": "Mag download"
|
||||
}
|
||||
},
|
||||
"exponential-volume": {
|
||||
@ -475,7 +482,8 @@
|
||||
"name": "Nabigasyon"
|
||||
},
|
||||
"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": {
|
||||
"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",
|
||||
"unpause-notification": "Ipakita ang notification sa pag-unpause"
|
||||
}
|
||||
},
|
||||
"name": "Mga Abiso"
|
||||
},
|
||||
"picture-in-picture": {
|
||||
"description": "Payagan ang pag-palit ng app sa picture-in-picture mode",
|
||||
@ -580,6 +589,7 @@
|
||||
"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",
|
||||
"menu": {
|
||||
"override-media-keys": "I-override ang mga Media Key",
|
||||
"set-keybinds": "I-set ang Global Song Control"
|
||||
},
|
||||
"name": "Mga shortcut (at MPRIS)",
|
||||
@ -587,6 +597,7 @@
|
||||
"keybind": {
|
||||
"keybind-options": {
|
||||
"next": "Susunod",
|
||||
"play-pause": "Mag-play / Mag-pause",
|
||||
"previous": "Nakaraan"
|
||||
},
|
||||
"label": "Pumili ng Global na Keybind para sa Songs Control:"
|
||||
@ -594,14 +605,65 @@
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"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": {
|
||||
"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": {
|
||||
"description": "Kontrolin ang pag-play mula sa iyong taskbar ng Windows"
|
||||
},
|
||||
|
||||
@ -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",
|
||||
"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": {
|
||||
"description": "Contrôlez la lecture depuis votre barre des tâches Windows",
|
||||
"name": "Contrôle multimédia de la barre des tâches"
|
||||
|
||||
@ -7,8 +7,8 @@
|
||||
"initialize-failed": "Nem sikerült inicializálni a \"{{pluginName}}\" plugint",
|
||||
"load-all": "Összes bővítmény betöltése",
|
||||
"load-failed": "Nem sikerült betölteni a \"{{pluginName}}\" plugint",
|
||||
"loaded": "\"{{pluginName}}\" nevű plugin betöltve",
|
||||
"unload-failed": "Nem sikerült a \"{{pluginName}}\" bővítményt letölteni",
|
||||
"loaded": "\"{{pluginName}}\" plugin betöltve",
|
||||
"unload-failed": "Nem sikerült a \"{{pluginName}}\" bővítményt kikapcsolni",
|
||||
"unloaded": "A \"{{pluginName}}\" bővítmény kikapcsolva"
|
||||
}
|
||||
}
|
||||
@ -207,6 +207,10 @@
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"description": "Alapértelmezés szerint blokkolja az összes hirdetést és nyomkövetést",
|
||||
"menu": {
|
||||
@ -407,7 +411,21 @@
|
||||
"description": "MP3 / forrás hanganyag letöltése közvetlenül az interfészről",
|
||||
"menu": {
|
||||
"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",
|
||||
"presets": "Előbeállítások",
|
||||
"skip-existing": "Meglévő fájlok kihagyása"
|
||||
},
|
||||
"name": "Letöltő",
|
||||
@ -489,6 +507,7 @@
|
||||
}
|
||||
},
|
||||
"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ó"
|
||||
},
|
||||
"no-google-login": {
|
||||
@ -502,10 +521,14 @@
|
||||
"interactive-settings": {
|
||||
"label": "Interaktív beállítások",
|
||||
"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"
|
||||
},
|
||||
@ -542,7 +565,9 @@
|
||||
"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",
|
||||
"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ő",
|
||||
"prompt": {
|
||||
@ -553,6 +578,10 @@
|
||||
},
|
||||
"label": "Válaszd ki a globális hangerő gyorsbillentyűket:",
|
||||
"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": {
|
||||
"description": "Scrobbling támogatás hozzáadása (pl. last.fm, ListenBrainz)",
|
||||
@ -613,6 +643,7 @@
|
||||
"play-pause": "Lejátszás / Szünet",
|
||||
"previous": "Előző"
|
||||
},
|
||||
"label": "Globális billentyűparancsok választása a dalok vezérléséhez:",
|
||||
"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",
|
||||
"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": {
|
||||
"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": {
|
||||
"description": "macOS felhasználók számára hozzáad egy widgetet a TouchBar-hoz",
|
||||
@ -667,6 +716,7 @@
|
||||
}
|
||||
},
|
||||
"visualizer": {
|
||||
"description": "Vizualizációt ad a lejátszóhoz",
|
||||
"menu": {
|
||||
"visualizer-type": "Vizualizáció típus"
|
||||
},
|
||||
|
||||
@ -207,6 +207,10 @@
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"ad-speedup": {
|
||||
"description": "Jika iklan diputar, audio akan dimatikan dan kecepatan pemutaran akan diatur ke 16x",
|
||||
"name": "Percepatan Iklan"
|
||||
},
|
||||
"adblocker": {
|
||||
"description": "Blokir semua iklan dan pelacakan di luar kotak",
|
||||
"menu": {
|
||||
@ -410,6 +414,21 @@
|
||||
"description": "Unduh MP3 / sumber suara secara langsung via antarmuka",
|
||||
"menu": {
|
||||
"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",
|
||||
"presets": "Prasetel",
|
||||
"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",
|
||||
"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": {
|
||||
"description": "Kendalikan pemutaran dari bilah alat Windows",
|
||||
"name": "Pengendali Media di Bilah Alat"
|
||||
|
||||
@ -207,6 +207,10 @@
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"description": "Blocca tutti gli annunci e i tracker",
|
||||
"menu": {
|
||||
@ -410,6 +414,21 @@
|
||||
"description": "Download MP3 / sorgenti audio direttamente dall'interfaccia",
|
||||
"menu": {
|
||||
"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",
|
||||
"presets": "Preimpostazioni",
|
||||
"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",
|
||||
"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": {
|
||||
"description": "Controlla riproduzione dalla taskbar di Windows",
|
||||
"name": "Controlli multimediali sulla taskbar"
|
||||
|
||||
@ -65,7 +65,7 @@
|
||||
},
|
||||
"detail": "ご不便をおかけして申し訳ございません! 何をするか選んでください:",
|
||||
"message": "アプリケーションは応答していません",
|
||||
"title": "ウィンドウが応答しません"
|
||||
"title": "ウィンドウが応答していません"
|
||||
},
|
||||
"update-available": {
|
||||
"buttons": {
|
||||
@ -207,6 +207,10 @@
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"ad-speedup": {
|
||||
"description": "広告が再生されると、自動的にミュートされ、再生速度が16倍に設定されます",
|
||||
"name": "広告のスピードを上げる"
|
||||
},
|
||||
"adblocker": {
|
||||
"description": "すべての広告とトラッカーをブロックj",
|
||||
"menu": {
|
||||
@ -410,6 +414,21 @@
|
||||
"description": "UIから直にMP3・ソースオーディオをダウンロードします",
|
||||
"menu": {
|
||||
"choose-download-folder": "ダウンロードフォルダ",
|
||||
"download-finish-settings": {
|
||||
"label": "完了時にダウンロード",
|
||||
"prompt": {
|
||||
"last-percent": "x パーセント後",
|
||||
"last-seconds": "最後の x 秒",
|
||||
"title": "保存するタイミング"
|
||||
},
|
||||
"submenu": {
|
||||
"advanced": "高度な設定",
|
||||
"enabled": "有効",
|
||||
"mode": "時間モード",
|
||||
"percent": "パーセント",
|
||||
"seconds": "秒"
|
||||
}
|
||||
},
|
||||
"download-playlist": "プレイリストをダウンロード",
|
||||
"presets": "プリセット",
|
||||
"skip-existing": "存在するファイルをスキップ"
|
||||
@ -649,6 +668,59 @@
|
||||
"description": "イントロ/アウトロなどの音楽以外の部分や、曲が再生されていないミュージック ビデオの部分を自動的にスキップします",
|
||||
"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": {
|
||||
"description": "Windowsタスクバーから再生をコントロール",
|
||||
"name": "タスクバーメディアコントロール"
|
||||
|
||||
@ -674,6 +674,42 @@
|
||||
"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": "가져오는 중...",
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
"execute-failed": "Pelaksaan plugin gagal {{pluginName}}::{{contextName}}",
|
||||
"executed-at-ms": "Plugin {{pluginName}}::{{contextName}} dilaksanakan pada {{ms}}ms",
|
||||
"initialize-failed": "Gagal untuk memulakan plugin \"{{pluginName}}\"",
|
||||
"load-all": "Memuatkan semua plugin",
|
||||
"loaded": "Plugin \"{{pluginName}}\" dimuatkan",
|
||||
"unload-failed": "Gagal untuk memunggah plugin \"{{pluginName}}\"",
|
||||
"unloaded": "Plugin \"{{pluginName}}\" dipunggahkan"
|
||||
@ -39,15 +40,22 @@
|
||||
"detail": "Menu telah disembunyikan, guna 'Alt' untuk menunjukkannya (atau 'Escape' jika menggunakan In-App Menu)"
|
||||
},
|
||||
"need-to-restart": {
|
||||
"buttons": {
|
||||
"later": "Nanti",
|
||||
"restart-now": "Restart Sekarang"
|
||||
},
|
||||
"message": "\"{{pluginName}}\" perlu dimulakan semula",
|
||||
"title": "Mulakan Semula Diperlukan"
|
||||
},
|
||||
"unresponsive": {
|
||||
"buttons": {
|
||||
"quit": "Berhenti",
|
||||
"relaunch": "Lancar Semula",
|
||||
"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": {
|
||||
"buttons": {
|
||||
@ -58,16 +66,137 @@
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"about": "Mengenai",
|
||||
"navigation": {
|
||||
"label": "Navigasi",
|
||||
"submenu": {
|
||||
"copy-current-url": "Salin URL semasa",
|
||||
"go-back": "Pulang",
|
||||
"go-back": "Belakang",
|
||||
"go-forward": "Depan",
|
||||
"quit": "Keluar"
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -158,6 +158,14 @@
|
||||
},
|
||||
"remove-upgrade-button": "Upgrade-knop verwijderen",
|
||||
"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",
|
||||
"submenu": {
|
||||
"import-css-file": "Aangepast CSS-bestand importeren",
|
||||
@ -199,6 +207,10 @@
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"ad-speedup": {
|
||||
"description": "Wanneer een advertentie afspeelt, dempt het geluid en versnelt de playback naar 16x",
|
||||
"name": "Snellere advertenties"
|
||||
},
|
||||
"adblocker": {
|
||||
"description": "Blokkeer alle advertenties en tracking vanuit de doos",
|
||||
"menu": {
|
||||
@ -402,6 +414,21 @@
|
||||
"description": "Download MP3 / bron-audio rechtstreeks vanuit de interface",
|
||||
"menu": {
|
||||
"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",
|
||||
"presets": "Voorinstellingen",
|
||||
"skip-existing": "Bestaande bestanden overslaan"
|
||||
@ -523,8 +550,20 @@
|
||||
"save-window-size": "Sla schermgrootte op"
|
||||
}
|
||||
},
|
||||
"video-toggle": {
|
||||
"menu": {
|
||||
"mode": {
|
||||
"submenu": {
|
||||
"disabled": "Uitgeschakeld"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"visualizer": {
|
||||
"description": "Voeg een visuele equalizer toe",
|
||||
"menu": {
|
||||
"visualizer-type": "Type visualisator"
|
||||
},
|
||||
"name": "Visualisator"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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",
|
||||
"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": {
|
||||
"description": "Controle a reprodução na barra de tarefas do Windows",
|
||||
"name": "Controle de mídia da barra de tarefas"
|
||||
|
||||
@ -207,6 +207,10 @@
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"ad-speedup": {
|
||||
"description": "Если воспроизводится реклама, аудио заглушается и скорость воспроизведения устанавливается на 16х",
|
||||
"name": "Ускоренная перемотка"
|
||||
},
|
||||
"adblocker": {
|
||||
"description": "Блокируйте всю рекламу и трекинг сразу после установки",
|
||||
"menu": {
|
||||
@ -277,7 +281,7 @@
|
||||
},
|
||||
"audio-compressor": {
|
||||
"description": "Применяет компрессию к аудио (уменьшает громкость самых громких частей сигнала и повышает громкость самых тихих частей)",
|
||||
"name": "Аудио компрессор"
|
||||
"name": "Нормализация аудио"
|
||||
},
|
||||
"blur-nav-bar": {
|
||||
"description": "Делает панель навигации прозрачной и размытой",
|
||||
@ -649,6 +653,58 @@
|
||||
"description": "Автоматически пропускает не музыкальные фрагменты, например интро/аутро или фрагменты музыкальных клипов, в которых песня не звучит (тишина)",
|
||||
"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": {
|
||||
"description": "Управляйте воспроизведением с панели задач Windows",
|
||||
"name": "Управление мультимедиа на панели задач"
|
||||
@ -692,7 +748,7 @@
|
||||
"menu": {
|
||||
"visualizer-type": "Вид визуализации"
|
||||
},
|
||||
"name": "Визуалайзер"
|
||||
"name": "Визуализатор"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -207,6 +207,10 @@
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"description": "Tüm reklamları ve izleyicileri engelle",
|
||||
"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",
|
||||
"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": {
|
||||
"description": "Windows görev çubuğu üzerinden oynatmayı kontrol edebilmenize olanak sağlar",
|
||||
"name": "Görev Çubuğu Medya Kontrolü"
|
||||
|
||||
@ -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",
|
||||
"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": {
|
||||
"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ụ"
|
||||
|
||||
@ -207,6 +207,10 @@
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"ad-speedup": {
|
||||
"description": "使用静音以及 16 倍速播放跳过广告片段",
|
||||
"name": "广告加速跳过"
|
||||
},
|
||||
"adblocker": {
|
||||
"description": "屏蔽所有广告与跟踪器",
|
||||
"menu": {
|
||||
@ -664,6 +668,59 @@
|
||||
"description": "自动跳过非音乐部分,如 MV 的介绍/结语以及歌曲未开始的部分",
|
||||
"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": {
|
||||
"description": "从 Windows 任务栏控制音乐回放",
|
||||
"name": "任务栏媒体控件"
|
||||
|
||||
@ -207,6 +207,10 @@
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"ad-speedup": {
|
||||
"description": "使用 16 倍速播放及靜音來跳過廣告片段",
|
||||
"name": "加速略過"
|
||||
},
|
||||
"adblocker": {
|
||||
"description": "阻擋所有廣告",
|
||||
"menu": {
|
||||
@ -341,10 +345,10 @@
|
||||
"discord": {
|
||||
"backend": {
|
||||
"already-connected": "已嘗試可用連接",
|
||||
"connected": "已連接至Discord",
|
||||
"disconnected": "與Discord斷開連接"
|
||||
"connected": "已連接至 Discord",
|
||||
"disconnected": "與 Discord 斷開連接"
|
||||
},
|
||||
"description": "使用Discord狀態與你的好友分享你正在收聽的音樂",
|
||||
"description": "使用 Discord 狀態與你的好友分享你正在收聽的音樂",
|
||||
"menu": {
|
||||
"auto-reconnect": "自動重新連接",
|
||||
"clear-activity": "清除狀態",
|
||||
@ -352,11 +356,11 @@
|
||||
"connected": "已連接",
|
||||
"disconnected": "已斷開連接",
|
||||
"hide-duration-left": "隱藏音樂剩餘時間狀態",
|
||||
"hide-github-button": "隱藏Github頁面按鈕",
|
||||
"play-on-youtube-music": "顯示Play on YouTube Music按鈕",
|
||||
"hide-github-button": "隱藏 Github 頁面按鈕",
|
||||
"play-on-youtube-music": "顯示 Play on YouTube Music 按鈕",
|
||||
"set-inactivity-timeout": "設定閒置狀態時長"
|
||||
},
|
||||
"name": "Discord狀態",
|
||||
"name": "Discord 狀態",
|
||||
"prompt": {
|
||||
"set-inactivity-timeout": {
|
||||
"label": "設定多少秒後清除狀態:",
|
||||
@ -513,8 +517,8 @@
|
||||
"name": "導覽列"
|
||||
},
|
||||
"no-google-login": {
|
||||
"description": "移除Google登入按鈕及連結",
|
||||
"name": "停用Google登入"
|
||||
"description": "移除 Google 登入按鈕及連結",
|
||||
"name": "停用 Google 登入"
|
||||
},
|
||||
"notifications": {
|
||||
"description": "在歌曲播放時發送一個系統通知 (可互動通知僅限Windows)",
|
||||
@ -664,6 +668,59 @@
|
||||
"description": "自動跳過贊助片段",
|
||||
"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": {
|
||||
"description": "允許工作列應用程式預覽介面顯示媒體控制相關按鈕",
|
||||
"name": "工作列媒體控制"
|
||||
|
||||
47
src/index.ts
47
src/index.ts
@ -56,6 +56,7 @@ import { loadI18n, setLanguage, t } from '@/i18n';
|
||||
import ErrorHtmlAsset from '@assets/error.html?asset';
|
||||
|
||||
import type { PluginConfig } from '@/types/plugins';
|
||||
import BrowserWindowConstructorOptions = Electron.BrowserWindowConstructorOptions;
|
||||
|
||||
if (!is.macOS()) {
|
||||
delete allPlugins['touchbar'];
|
||||
@ -286,6 +287,23 @@ async function createMainWindow() {
|
||||
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({
|
||||
icon,
|
||||
width: windowSize.width,
|
||||
@ -303,14 +321,7 @@ async function createMainWindow() {
|
||||
sandbox: false,
|
||||
}),
|
||||
},
|
||||
frame: !is.macOS() && !useInlineMenu,
|
||||
titleBarOverlay: defaultTitleBarOverlayOptions,
|
||||
titleBarStyle: useInlineMenu
|
||||
? 'hidden'
|
||||
: is.macOS()
|
||||
? 'hiddenInset'
|
||||
: 'default',
|
||||
autoHideMenuBar: config.get('options.hideMenu'),
|
||||
...decorations,
|
||||
});
|
||||
initHook(win);
|
||||
initTheme(win);
|
||||
@ -321,27 +332,29 @@ async function createMainWindow() {
|
||||
const { x: windowX, y: windowY } = windowPosition;
|
||||
const winSize = win.getSize();
|
||||
const display = screen.getDisplayNearestPoint(windowPosition);
|
||||
const primaryDisplay = screen.getPrimaryDisplay();
|
||||
|
||||
const scaledWidth = windowSize.width;
|
||||
const scaledHeight = windowSize.height;
|
||||
const scaleFactor = is.windows() ? primaryDisplay.scaleFactor / display.scaleFactor : 1;
|
||||
const scaledWidth = Math.floor(windowSize.width * scaleFactor);
|
||||
const scaledHeight = Math.floor(windowSize.height * scaleFactor);
|
||||
|
||||
const scaledX = windowX;
|
||||
const scaledY = windowY;
|
||||
|
||||
if (
|
||||
scaledX + scaledWidth < display.bounds.x - 8 ||
|
||||
scaledX - scaledWidth > display.bounds.x + display.bounds.width ||
|
||||
scaledY < display.bounds.y - 8 ||
|
||||
scaledY > display.bounds.y + display.bounds.height
|
||||
scaledX + (scaledWidth / 2) < display.bounds.x - 8 || // Left
|
||||
scaledX + (scaledWidth / 2) > display.bounds.x + display.bounds.width || // Right
|
||||
scaledY < display.bounds.y - 8 || // Top
|
||||
scaledY + (scaledHeight / 2) > display.bounds.y + display.bounds.height // Bottom
|
||||
) {
|
||||
// Window is offscreen
|
||||
if (is.dev()) {
|
||||
console.warn(
|
||||
LoggerPrefix,
|
||||
t('main.console.window.tried-to-render-offscreen', {
|
||||
winSize: String(winSize),
|
||||
displaySize: String(display.bounds),
|
||||
windowPosition: String(windowPosition),
|
||||
windowSize: String(winSize),
|
||||
displaySize: JSON.stringify(display.bounds),
|
||||
position: JSON.stringify(windowPosition),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@ -10,11 +10,11 @@ import {
|
||||
|
||||
import injectCliqzPreload from './injectors/inject-cliqz-preload';
|
||||
import { inject, isInjected } from './injectors/inject';
|
||||
import { loadAdSpeedup } from './adSpeedup';
|
||||
|
||||
import { t } from '@/i18n';
|
||||
|
||||
import type { BrowserWindow } from 'electron';
|
||||
import { loadAdSpeedup } from './adSpeedup';
|
||||
|
||||
interface AdblockerConfig {
|
||||
/**
|
||||
@ -74,7 +74,7 @@ export default createPlugin({
|
||||
];
|
||||
},
|
||||
renderer: {
|
||||
async onPlayerApiReady(_, {getConfig}) {
|
||||
async onPlayerApiReady(_, { getConfig }) {
|
||||
const config = await getConfig();
|
||||
if (config.blocker === blockers.AdSpeedup) {
|
||||
await loadAdSpeedup();
|
||||
@ -118,7 +118,19 @@ export default createPlugin({
|
||||
},
|
||||
},
|
||||
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 }) {
|
||||
const config = await getConfig();
|
||||
|
||||
|
||||
@ -37,19 +37,9 @@ export const inject = (contextBridge) => {
|
||||
|
||||
//
|
||||
return o;
|
||||
};
|
||||
}
|
||||
|
||||
contextBridge.exposeInMainWorld('_proxyJsonParse', new Proxy(JSON.parse, {
|
||||
apply() {
|
||||
return pruner(Reflect.apply(...arguments));
|
||||
},
|
||||
}));
|
||||
|
||||
contextBridge.exposeInMainWorld('_proxyResponseJson', new Proxy(Response.prototype.json, {
|
||||
apply() {
|
||||
return Reflect.apply(...arguments).then((o) => pruner(o));
|
||||
},
|
||||
}));
|
||||
contextBridge.exposeInMainWorld('_pruner', pruner);
|
||||
}
|
||||
|
||||
const chains = [
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { t } from '@/i18n';
|
||||
import { createPlugin } from '@/utils';
|
||||
import { ElementFromHtml } from '@/plugins/utils/renderer';
|
||||
import { waitForElement } from '@/utils/wait-for-element';
|
||||
|
||||
import undislikeHTML from './templates/undislike.html?raw';
|
||||
import dislikeHTML from './templates/dislike.html?raw';
|
||||
@ -16,7 +17,6 @@ export default createPlugin<
|
||||
changeObserver?: MutationObserver;
|
||||
waiting: boolean;
|
||||
onPageChange(): void;
|
||||
waitForElem(selector: string): Promise<HTMLElement>;
|
||||
loadFullList: (event: MouseEvent) => void;
|
||||
applyToList(id: string, loader: HTMLElement): void;
|
||||
start(): void;
|
||||
@ -50,7 +50,7 @@ export default createPlugin<
|
||||
} else {
|
||||
this.waiting = true;
|
||||
}
|
||||
const continuations = await this.waitForElem('#continuations');
|
||||
const continuations = await waitForElement<HTMLElement>('#continuations');
|
||||
this.waiting = false;
|
||||
//Gets the for buttons
|
||||
const buttons: Array<HTMLElement> = [
|
||||
@ -183,16 +183,5 @@ export default createPlugin<
|
||||
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);
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@ -4,6 +4,7 @@ import { t } from '@/i18n';
|
||||
import { createPlugin } from '@/utils';
|
||||
import { menu } from './menu';
|
||||
import { AmbientModePluginConfig } from './types';
|
||||
import { waitForElement } from '@/utils/wait-for-element';
|
||||
|
||||
const defaultConfig: AmbientModePluginConfig = {
|
||||
enabled: false,
|
||||
@ -36,7 +37,7 @@ export default createPlugin({
|
||||
unregister: null as (() => void) | null,
|
||||
update: null as (() => void) | null,
|
||||
interval: null as NodeJS.Timeout | null,
|
||||
lastMediaType: null as "video" | "image" | null,
|
||||
lastMediaType: null as 'video' | 'image' | null,
|
||||
lastVideoSource: null as string | null,
|
||||
lastImageSource: null as string | null,
|
||||
|
||||
@ -53,7 +54,8 @@ export default createPlugin({
|
||||
const songImage = document.querySelector<HTMLImageElement>('#song-image');
|
||||
const songVideo = document.querySelector<HTMLDivElement>('#song-video');
|
||||
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 injectBlurImage = () => {
|
||||
@ -179,12 +181,12 @@ export default createPlugin({
|
||||
const isVideoMode = () => {
|
||||
const songVideo = document.querySelector<HTMLDivElement>('#song-video');
|
||||
if (!songVideo) {
|
||||
this.lastMediaType = "image";
|
||||
this.lastMediaType = 'image';
|
||||
return false;
|
||||
}
|
||||
|
||||
const isVideo = getComputedStyle(songVideo).display !== 'none';
|
||||
this.lastMediaType = isVideo ? "video" : "image";
|
||||
this.lastMediaType = isVideo ? 'video' : 'image';
|
||||
return isVideo;
|
||||
};
|
||||
|
||||
@ -196,8 +198,8 @@ export default createPlugin({
|
||||
if (isPageOpen) {
|
||||
const isVideo = isVideoMode();
|
||||
if (!force) {
|
||||
if (this.lastMediaType === "video" && this.lastVideoSource === video?.src) return false;
|
||||
if (this.lastMediaType === "image" && this.lastImageSource === image?.src) return false;
|
||||
if (this.lastMediaType === 'video' && this.lastVideoSource === video?.src) return false;
|
||||
if (this.lastMediaType === 'image' && this.lastImageSource === image?.src) return false;
|
||||
}
|
||||
this.unregister?.();
|
||||
this.unregister = (isVideo ? injectBlurVideo() : injectBlurImage()) ?? null;
|
||||
@ -205,7 +207,7 @@ export default createPlugin({
|
||||
this.unregister?.();
|
||||
this.unregister = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/* needed for switching between different views (e.g. miniplayer) */
|
||||
const observer = new MutationObserver((mutationsList) => {
|
||||
|
||||
@ -209,8 +209,8 @@ export const backend = createBackend<
|
||||
|
||||
info.rpc.user?.setActivity(activityInfo).catch(console.error);
|
||||
},
|
||||
async start({ window: win, getConfig }) {
|
||||
this.config = await getConfig();
|
||||
async start(ctx) {
|
||||
this.config = await ctx.getConfig();
|
||||
|
||||
info.rpc.on('connected', () => {
|
||||
if (dev()) {
|
||||
@ -239,10 +239,10 @@ export const backend = createBackend<
|
||||
|
||||
info.autoReconnect = this.config.autoReconnect;
|
||||
|
||||
window = win;
|
||||
window = ctx.window;
|
||||
|
||||
// If the page is ready, register the callback
|
||||
win.once('ready-to-show', () => {
|
||||
ctx.window.once('ready-to-show', () => {
|
||||
let lastSongInfo: SongInfo;
|
||||
registerCallback((songInfo) => {
|
||||
lastSongInfo = songInfo;
|
||||
|
||||
@ -261,12 +261,12 @@ async function downloadSongUnsafe(
|
||||
|
||||
let playabilityStatus = info.playability_status;
|
||||
let bypassedResult = null;
|
||||
if (playabilityStatus.status === 'LOGIN_REQUIRED') {
|
||||
if (playabilityStatus?.status === 'LOGIN_REQUIRED') {
|
||||
// Try to bypass the age restriction
|
||||
bypassedResult = await getAndroidTvInfo(id);
|
||||
playabilityStatus = bypassedResult.playability_status;
|
||||
|
||||
if (playabilityStatus.status === 'LOGIN_REQUIRED') {
|
||||
if (playabilityStatus?.status === 'LOGIN_REQUIRED') {
|
||||
throw new Error(
|
||||
`[${playabilityStatus.status}] ${playabilityStatus.reason}`,
|
||||
);
|
||||
@ -275,7 +275,7 @@ async function downloadSongUnsafe(
|
||||
info = bypassedResult;
|
||||
}
|
||||
|
||||
if (playabilityStatus.status === 'UNPLAYABLE') {
|
||||
if (playabilityStatus?.status === 'UNPLAYABLE') {
|
||||
const errorScreen =
|
||||
playabilityStatus.error_screen as PlayerErrorMessage | null;
|
||||
throw new Error(
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { JSX } from 'solid-js';
|
||||
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;
|
||||
|
||||
background: transparent;
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { JSX, splitProps } from 'solid-js';
|
||||
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;
|
||||
|
||||
display: flex;
|
||||
|
||||
@ -5,9 +5,9 @@ import { Transition } from 'solid-transition-group';
|
||||
import { autoUpdate, flip, offset, OffsetOptions, size } from '@floating-ui/dom';
|
||||
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;
|
||||
top: var(--offset-y, 0);
|
||||
left: var(--offset-x, 0);
|
||||
@ -36,7 +36,7 @@ const panelStyle = cache(() => css`
|
||||
transform-origin: var(--origin-x, 50%) var(--origin-y, 50%);
|
||||
`);
|
||||
|
||||
const animationStyle = cache(() => ({
|
||||
const animationStyle = cacheNoArgs(() => ({
|
||||
enter: css`
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
|
||||
@ -8,9 +8,9 @@ import { useFloating } from 'solid-floating-ui';
|
||||
import { autoUpdate, offset, size } from '@floating-ui/dom';
|
||||
|
||||
import { Panel } from './Panel';
|
||||
import { cache } from '@/providers/decorators';
|
||||
import { cacheNoArgs } from '@/providers/decorators';
|
||||
|
||||
const itemStyle = cache(() => css`
|
||||
const itemStyle = cacheNoArgs(() => css`
|
||||
position: relative;
|
||||
|
||||
-webkit-app-region: none;
|
||||
@ -47,18 +47,18 @@ const itemStyle = cache(() => css`
|
||||
}
|
||||
`);
|
||||
|
||||
const itemIconStyle = cache(() => css`
|
||||
const itemIconStyle = cacheNoArgs(() => css`
|
||||
height: 32px;
|
||||
padding: 4px;
|
||||
color: white;
|
||||
`);
|
||||
|
||||
const itemLabelStyle = cache(() => css`
|
||||
const itemLabelStyle = cacheNoArgs(() => css`
|
||||
font-size: 12px;
|
||||
color: white;
|
||||
`);
|
||||
|
||||
const itemChipStyle = cache(() => css`
|
||||
const itemChipStyle = cacheNoArgs(() => css`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
@ -76,7 +76,7 @@ const itemChipStyle = cache(() => css`
|
||||
line-height: 1;
|
||||
`);
|
||||
|
||||
const toolTipStyle = cache(() => css`
|
||||
const toolTipStyle = cacheNoArgs(() => css`
|
||||
min-width: 32px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@ -92,7 +92,7 @@ const toolTipStyle = cache(() => css`
|
||||
font-size: 10px;
|
||||
`);
|
||||
|
||||
const popupStyle = cache(() => css`
|
||||
const popupStyle = cacheNoArgs(() => css`
|
||||
position: fixed;
|
||||
top: var(--offset-y, 0);
|
||||
left: var(--offset-x, 0);
|
||||
@ -105,7 +105,7 @@ const popupStyle = cache(() => css`
|
||||
|
||||
`);
|
||||
|
||||
const animationStyle = cache(() => ({
|
||||
const animationStyle = cacheNoArgs(() => ({
|
||||
enter: css`
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
|
||||
@ -9,12 +9,12 @@ import { PanelItem } from './PanelItem';
|
||||
import { IconButton } from './IconButton';
|
||||
import { WindowController } from './WindowController';
|
||||
|
||||
import { cache } from '@/providers/decorators';
|
||||
import { cacheNoArgs } from '@/providers/decorators';
|
||||
|
||||
import type { RendererContext } from '@/types/contexts';
|
||||
import type { InAppMenuConfig } from '../constants';
|
||||
|
||||
const titleStyle = cache(() => css`
|
||||
const titleStyle = cacheNoArgs(() => css`
|
||||
-webkit-app-region: drag;
|
||||
box-sizing: border-box;
|
||||
|
||||
@ -50,7 +50,7 @@ const titleStyle = cache(() => css`
|
||||
}
|
||||
`);
|
||||
|
||||
const separatorStyle = cache(() => css`
|
||||
const separatorStyle = cacheNoArgs(() => css`
|
||||
min-height: 1px;
|
||||
height: 1px;
|
||||
margin: 4px 0;
|
||||
@ -58,7 +58,7 @@ const separatorStyle = cache(() => css`
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
`);
|
||||
|
||||
const animationStyle = cache(() => ({
|
||||
const animationStyle = cacheNoArgs(() => ({
|
||||
enter: css`
|
||||
opacity: 0;
|
||||
transform: translateX(-50%) scale(0.8);
|
||||
@ -271,19 +271,18 @@ export const TitleBar = (props: TitleBarProps) => {
|
||||
// tracking mouse position
|
||||
window.addEventListener('mousemove', listener);
|
||||
const ytmusicAppLayout = document.querySelector<HTMLElement>('#layout');
|
||||
ytmusicAppLayout?.addEventListener("scroll",()=>{
|
||||
ytmusicAppLayout?.addEventListener('scroll', () => {
|
||||
const scrollValue = ytmusicAppLayout.scrollTop;
|
||||
if (scrollValue > 20){
|
||||
ytmusicAppLayout.classList.add("content-scrolled");
|
||||
ytmusicAppLayout.classList.add('content-scrolled');
|
||||
}
|
||||
else{
|
||||
ytmusicAppLayout.classList.remove("content-scrolled");
|
||||
ytmusicAppLayout.classList.remove('content-scrolled');
|
||||
}
|
||||
})
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
createEffect(() => {
|
||||
if (!menu() && data()) {
|
||||
|
||||
@ -2,9 +2,9 @@ import { css } from 'solid-styled-components';
|
||||
import { Show } from 'solid-js';
|
||||
|
||||
import { IconButton } from './IconButton';
|
||||
import { cache } from '@/providers/decorators';
|
||||
import { cacheNoArgs } from '@/providers/decorators';
|
||||
|
||||
const containerStyle = cache(() => css`
|
||||
const containerStyle = cacheNoArgs(() => css`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { BrowserWindow, ipcMain, globalShortcut } from 'electron';
|
||||
import { BrowserWindow, globalShortcut } from 'electron';
|
||||
import is from 'electron-is';
|
||||
import { register as registerElectronLocalShortcut } from 'electron-localshortcut';
|
||||
|
||||
@ -48,9 +48,7 @@ export const onMainLoad = async ({
|
||||
_registerLocalShortcut(window, 'CommandOrControl+L', search);
|
||||
|
||||
if (is.linux()) {
|
||||
ipcMain.once('ytmd:video-src-changed', (_) => {
|
||||
registerMPRIS(window);
|
||||
});
|
||||
registerMPRIS(window);
|
||||
}
|
||||
|
||||
const { global, local } = config;
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { t } from '@/i18n';
|
||||
import { createPlugin } from '@/utils';
|
||||
import { waitForElement } from '@/utils/wait-for-element';
|
||||
|
||||
export default createPlugin<
|
||||
unknown,
|
||||
unknown,
|
||||
{
|
||||
observer?: MutationObserver;
|
||||
waitForElem(selector: string): Promise<HTMLElement>;
|
||||
start(): void;
|
||||
stop(): void;
|
||||
}
|
||||
@ -15,19 +15,8 @@ export default createPlugin<
|
||||
description: () => t('plugins.skip-disliked-songs.description'),
|
||||
restartNeeded: false,
|
||||
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() {
|
||||
this.waitForElem('#dislike-button-renderer').then((dislikeBtn) => {
|
||||
waitForElement<HTMLElement>('#dislike-button-renderer').then((dislikeBtn) => {
|
||||
this.observer = new MutationObserver(() => {
|
||||
if (dislikeBtn?.getAttribute('like-status') == 'DISLIKE') {
|
||||
document
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
import style from './style.css?inline';
|
||||
import { createPlugin } from '@/utils';
|
||||
|
||||
import { SyncedLyricsPluginConfig } from './types';
|
||||
import { t } from '@/i18n';
|
||||
|
||||
import { menu } from './menu';
|
||||
import { renderer } from './renderer';
|
||||
|
||||
import { t } from '@/i18n';
|
||||
import type { SyncedLyricsPluginConfig } from './types';
|
||||
|
||||
export default createPlugin({
|
||||
name: () => t('plugins.synced-lyrics.name'),
|
||||
@ -15,12 +14,13 @@ export default createPlugin({
|
||||
restartNeeded: true,
|
||||
addedVersion: '3.5.X',
|
||||
config: {
|
||||
enabled: false,
|
||||
preciseTiming: true,
|
||||
showLyricsEvenIfInexact: true,
|
||||
showTimeCodes: false,
|
||||
defaultTextString: '♪',
|
||||
lineEffect: 'scale',
|
||||
} as SyncedLyricsPluginConfig,
|
||||
} satisfies SyncedLyricsPluginConfig,
|
||||
|
||||
menu,
|
||||
renderer,
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { MenuItemConstructorOptions } from 'electron';
|
||||
|
||||
import { MenuContext } from '@/types/contexts';
|
||||
import { SyncedLyricsPluginConfig } from './types';
|
||||
import { t } from '@/i18n';
|
||||
|
||||
import type { MenuContext } from '@/types/contexts';
|
||||
import type { SyncedLyricsPluginConfig } from './types';
|
||||
|
||||
export const menu = async ({
|
||||
getConfig,
|
||||
@ -13,9 +15,8 @@ export const menu = async ({
|
||||
|
||||
return [
|
||||
{
|
||||
label: 'Make the lyrics perfectly synced',
|
||||
toolTip:
|
||||
'Calculate to the milisecond the display of the next line (can have a small impact on performance)',
|
||||
label: t('plugins.synced-lyrics.menu.precise-timing.label'),
|
||||
toolTip: t('plugins.synced-lyrics.menu.precise-timing.tooltip'),
|
||||
type: 'checkbox',
|
||||
checked: config.preciseTiming,
|
||||
click(item) {
|
||||
@ -25,13 +26,13 @@ export const menu = async ({
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Line effect',
|
||||
toolTip: 'Choose the effect to apply to the current line',
|
||||
label: t('plugins.synced-lyrics.menu.line-effect.label'),
|
||||
toolTip: t('plugins.synced-lyrics.menu.line-effect.tooltip'),
|
||||
type: 'submenu',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Scale',
|
||||
toolTip: 'Scale the current line',
|
||||
label: t('plugins.synced-lyrics.menu.line-effect.submenu.scale.label'),
|
||||
toolTip: t('plugins.synced-lyrics.menu.line-effect.submenu.scale.tooltip'),
|
||||
type: 'radio',
|
||||
checked: config.lineEffect === 'scale',
|
||||
click() {
|
||||
@ -41,8 +42,8 @@ export const menu = async ({
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Offset',
|
||||
toolTip: 'Offset on the right the current line',
|
||||
label: t('plugins.synced-lyrics.menu.line-effect.submenu.offset.label'),
|
||||
toolTip: t('plugins.synced-lyrics.menu.line-effect.submenu.offset.tooltip'),
|
||||
type: 'radio',
|
||||
checked: config.lineEffect === 'offset',
|
||||
click() {
|
||||
@ -52,8 +53,8 @@ export const menu = async ({
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Focus',
|
||||
toolTip: 'Make only the current line white',
|
||||
label: t('plugins.synced-lyrics.menu.line-effect.submenu.focus.label'),
|
||||
toolTip: t('plugins.synced-lyrics.menu.line-effect.submenu.focus.tooltip'),
|
||||
type: 'radio',
|
||||
checked: config.lineEffect === 'focus',
|
||||
click() {
|
||||
@ -65,8 +66,8 @@ export const menu = async ({
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Default character between lyrics',
|
||||
toolTip: 'Choose the default string to use for the gap between lyrics',
|
||||
label: t('plugins.synced-lyrics.menu.default-text-string.label'),
|
||||
toolTip: t('plugins.synced-lyrics.menu.default-text-string.tooltip'),
|
||||
type: 'submenu',
|
||||
submenu: [
|
||||
{
|
||||
@ -80,7 +81,7 @@ export const menu = async ({
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '[SPACE]',
|
||||
label: '" "',
|
||||
type: 'radio',
|
||||
checked: config.defaultTextString === ' ',
|
||||
click() {
|
||||
@ -112,8 +113,8 @@ export const menu = async ({
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Show time codes',
|
||||
toolTip: 'Show the time codes next to the lyrics',
|
||||
label: t('plugins.synced-lyrics.menu.show-time-codes.label'),
|
||||
toolTip: t('plugins.synced-lyrics.menu.show-time-codes.tooltip'),
|
||||
type: 'checkbox',
|
||||
checked: config.showTimeCodes,
|
||||
click(item) {
|
||||
@ -123,9 +124,8 @@ export const menu = async ({
|
||||
},
|
||||
},
|
||||
{
|
||||
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.',
|
||||
label: t('plugins.synced-lyrics.menu.show-lyrics-even-if-inexact.label'),
|
||||
toolTip: t('plugins.synced-lyrics.menu.show-lyrics-even-if-inexact.tooltip'),
|
||||
type: 'checkbox',
|
||||
checked: config.showLyricsEvenIfInexact,
|
||||
click(item) {
|
||||
|
||||
@ -5,7 +5,6 @@ import { SyncedLine } from './SyncedLine';
|
||||
import { t } from '@/i18n';
|
||||
import { getSongInfo } from '@/providers/song-info-front';
|
||||
|
||||
import { LineLyrics } from '../../types';
|
||||
import {
|
||||
differentDuration,
|
||||
hadSecondAttempt,
|
||||
@ -14,6 +13,8 @@ import {
|
||||
makeLyricsRequest,
|
||||
} from '../lyrics/fetch';
|
||||
|
||||
import type { LineLyrics } from '../../types';
|
||||
|
||||
export const [debugInfo, setDebugInfo] = createSignal<string>();
|
||||
export const [lineLyrics, setLineLyrics] = createSignal<LineLyrics[]>([]);
|
||||
export const [currentTime, setCurrentTime] = createSignal<number>(-1);
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { createRenderer } from '@/utils';
|
||||
import { waitForElement } from '@/utils/wait-for-element';
|
||||
|
||||
import { makeLyricsRequest } from './lyrics';
|
||||
import { selectors, tabStates } from './utils';
|
||||
@ -15,11 +16,9 @@ export let _ytAPI: YoutubePlayer | null = null;
|
||||
|
||||
export const renderer = createRenderer<{
|
||||
observerCallback: MutationCallback;
|
||||
onPlayerApiReady: (api: YoutubePlayer) => void;
|
||||
hasAddedEvents: boolean;
|
||||
observer?: MutationObserver;
|
||||
videoDataChange: () => void;
|
||||
progressCallback: (evt: Event) => void;
|
||||
videoDataChange: () => Promise<void>;
|
||||
updateTimestampInterval?: NodeJS.Timeout | string | number;
|
||||
}, SyncedLyricsPluginConfig>({
|
||||
onConfigChange(newConfig) {
|
||||
setConfig(newConfig);
|
||||
@ -42,48 +41,34 @@ export const renderer = createRenderer<{
|
||||
}
|
||||
},
|
||||
|
||||
onPlayerApiReady(api: YoutubePlayer) {
|
||||
async onPlayerApiReady(api: YoutubePlayer) {
|
||||
_ytAPI = api;
|
||||
|
||||
api.addEventListener('videodatachange', this.videoDataChange);
|
||||
|
||||
this.videoDataChange();
|
||||
await this.videoDataChange();
|
||||
},
|
||||
|
||||
hasAddedEvents: false,
|
||||
|
||||
videoDataChange() {
|
||||
if (!this.hasAddedEvents) {
|
||||
const video = document.querySelector('video');
|
||||
|
||||
video?.addEventListener('timeupdate', this.progressCallback);
|
||||
|
||||
if (video) this.hasAddedEvents = true;
|
||||
async videoDataChange() {
|
||||
if (!this.updateTimestampInterval) {
|
||||
this.updateTimestampInterval = setInterval(
|
||||
() => setCurrentTime((_ytAPI?.getCurrentTime() ?? 0) * 1000),
|
||||
100,
|
||||
);
|
||||
}
|
||||
|
||||
const header = document.querySelector<HTMLElement>(selectors.head);
|
||||
if (!header) return;
|
||||
|
||||
this.observer ??= new MutationObserver(
|
||||
this.observerCallback,
|
||||
);
|
||||
|
||||
// Force the lyrics tab to be enabled at all times.
|
||||
this.observer.disconnect();
|
||||
|
||||
const header = await waitForElement<HTMLElement>(selectors.head);
|
||||
this.observer.observe(header, { attributes: true });
|
||||
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>) {
|
||||
setConfig(await ctx.getConfig());
|
||||
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
import { createSignal } from 'solid-js';
|
||||
import { jaroWinkler } from '@skyra/jaro-winkler';
|
||||
|
||||
import { SongInfo } from '@/providers/song-info';
|
||||
|
||||
import { LineLyrics, LRCLIBSearchResponse } from '../../types';
|
||||
import { config } from '../renderer';
|
||||
|
||||
import { setDebugInfo, setLineLyrics } from '../components/LyricsContainer';
|
||||
|
||||
import type { SongInfo } from '@/providers/song-info';
|
||||
import type { LineLyrics, LRCLIBSearchResponse } from '../../types';
|
||||
|
||||
// prettier-ignore
|
||||
export const [isInstrumental, setIsInstrumental] = createSignal(false);
|
||||
// prettier-ignore
|
||||
@ -16,14 +17,6 @@ export const [hadSecondAttempt, setHadSecondAttempt] = createSignal(false);
|
||||
// prettier-ignore
|
||||
export const [differentDuration, setDifferentDuration] = createSignal(false);
|
||||
// eslint-disable-next-line prefer-const
|
||||
export let foundPlainTextLyrics = false;
|
||||
|
||||
export type SongData = {
|
||||
title: string;
|
||||
artist: string;
|
||||
album: string;
|
||||
songDuration: number;
|
||||
};
|
||||
|
||||
export const extractTimeAndText = (
|
||||
line: string,
|
||||
@ -32,7 +25,7 @@ export const extractTimeAndText = (
|
||||
const groups = /\[(\d+):(\d+)\.(\d+)\](.+)/.exec(line);
|
||||
if (!groups) return null;
|
||||
|
||||
const [_, rMinutes, rSeconds, rMillis, text] = groups;
|
||||
const [, rMinutes, rSeconds, rMillis, text] = groups;
|
||||
const [minutes, seconds, millis] = [
|
||||
parseInt(rMinutes),
|
||||
parseInt(rSeconds),
|
||||
@ -45,7 +38,7 @@ export const extractTimeAndText = (
|
||||
return {
|
||||
index,
|
||||
timeInMs,
|
||||
time: `${minutes}:${seconds}:${millis}`,
|
||||
time: `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}:${millis}`,
|
||||
text: text?.trim() ?? config()!.defaultTextString,
|
||||
status: 'upcoming',
|
||||
duration: 0,
|
||||
@ -53,22 +46,31 @@ export const extractTimeAndText = (
|
||||
};
|
||||
|
||||
export const makeLyricsRequest = async (extractedSongInfo: SongInfo) => {
|
||||
setIsFetching(true);
|
||||
setLineLyrics([]);
|
||||
const songData: SongData = {
|
||||
|
||||
const songData: Parameters<typeof getLyricsList>[0] = {
|
||||
title: `${extractedSongInfo.title}`,
|
||||
artist: `${extractedSongInfo.artist}`,
|
||||
album: `${extractedSongInfo.album}`,
|
||||
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 ?? []);
|
||||
setIsFetching(false);
|
||||
};
|
||||
|
||||
export const getLyricsList = async (
|
||||
songData: SongData,
|
||||
songData: Pick<SongInfo, 'title' | 'artist' | 'album' | 'songDuration'>,
|
||||
): Promise<LineLyrics[] | null> => {
|
||||
setIsFetching(true);
|
||||
setIsInstrumental(false);
|
||||
setHadSecondAttempt(false);
|
||||
setDifferentDuration(false);
|
||||
@ -79,6 +81,7 @@ export const getLyricsList = async (
|
||||
track_name: songData.title,
|
||||
});
|
||||
|
||||
|
||||
if (songData.album) {
|
||||
query.set('album_name', songData.album);
|
||||
}
|
||||
@ -87,18 +90,12 @@ export const getLyricsList = async (
|
||||
let response = await fetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
setIsFetching(false);
|
||||
setDebugInfo('Got non-OK response from server.');
|
||||
return null;
|
||||
}
|
||||
|
||||
let data = (await response.json().catch((e: Error) => {
|
||||
setDebugInfo(`Error: ${e.message}\n\n${e.stack}`);
|
||||
|
||||
return null;
|
||||
})) as LRCLIBSearchResponse | null;
|
||||
let data = await response.json() as LRCLIBSearchResponse;
|
||||
if (!data || !Array.isArray(data)) {
|
||||
setIsFetching(false);
|
||||
setDebugInfo('Unexpected server response.');
|
||||
return null;
|
||||
}
|
||||
@ -114,14 +111,12 @@ export const getLyricsList = async (
|
||||
|
||||
response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
setIsFetching(false);
|
||||
setDebugInfo('Got non-OK response from server. (2)');
|
||||
return null;
|
||||
}
|
||||
|
||||
data = (await response.json()) as LRCLIBSearchResponse;
|
||||
if (!Array.isArray(data)) {
|
||||
setIsFetching(false);
|
||||
setDebugInfo('Unexpected server response. (2)');
|
||||
return null;
|
||||
}
|
||||
@ -131,15 +126,18 @@ export const getLyricsList = async (
|
||||
|
||||
const filteredResults = [];
|
||||
for (const item of data) {
|
||||
if (!item.syncedLyrics) continue;
|
||||
|
||||
const { artist } = songData;
|
||||
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;
|
||||
filteredResults.push(item);
|
||||
const permutations = artists.flatMap((artistA) =>
|
||||
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;
|
||||
@ -152,35 +150,43 @@ export const getLyricsList = async (
|
||||
|
||||
const closestResult = filteredResults[0];
|
||||
if (!closestResult) {
|
||||
setIsFetching(false);
|
||||
setDebugInfo('No search result matched the criteria.');
|
||||
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) {
|
||||
// show message that the timings may be wrong
|
||||
setDifferentDuration(true);
|
||||
}
|
||||
|
||||
setIsInstrumental(closestResult.instrumental);
|
||||
if (closestResult.instrumental) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 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
|
||||
raw.unshift('[0:0.0] ');
|
||||
|
||||
const syncedLyricList = [];
|
||||
|
||||
for (let idx = 0; idx < raw.length; idx++) {
|
||||
const syncedLine = extractTimeAndText(raw[idx], idx);
|
||||
const syncedLyricList = raw.reduce<LineLyrics[]>((acc, line, index) => {
|
||||
const syncedLine = extractTimeAndText(line, index);
|
||||
if (syncedLine) {
|
||||
syncedLyricList.push(syncedLine);
|
||||
acc.push(syncedLine);
|
||||
}
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
for (const line of syncedLyricList) {
|
||||
const next = syncedLyricList[line.index + 1];
|
||||
@ -192,6 +198,5 @@ export const getLyricsList = async (
|
||||
line.duration = next.timeInMs - line.timeInMs;
|
||||
}
|
||||
|
||||
setIsFetching(false);
|
||||
return syncedLyricList;
|
||||
};
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
import { createSignal, Show } from 'solid-js';
|
||||
|
||||
import { VideoDetails } from '@/types/video-details';
|
||||
|
||||
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);
|
||||
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
import { render } from 'solid-js/web';
|
||||
|
||||
import { waitForElement } from '@/utils/wait-for-element';
|
||||
|
||||
import { LyricsRenderer, setIsVisible, setPlayerState } from './renderer';
|
||||
import { VideoDetails } from '@/types/video-details';
|
||||
|
||||
import type { VideoDetails } from '@/types/video-details';
|
||||
|
||||
export const selectors = {
|
||||
head: '#tabsContent > .tab-header:nth-of-type(2)',
|
||||
@ -12,18 +15,17 @@ export const selectors = {
|
||||
};
|
||||
|
||||
export const tabStates = {
|
||||
true: (data?: VideoDetails) => {
|
||||
true: async (data?: VideoDetails) => {
|
||||
setIsVisible(true);
|
||||
setPlayerState(data ?? null);
|
||||
|
||||
const tabRenderer = document.querySelector<HTMLElement>(
|
||||
selectors.body.tabRenderer,
|
||||
);
|
||||
if (!tabRenderer) return;
|
||||
|
||||
let container = document.querySelector('#synced-lyrics-container');
|
||||
if (container) return;
|
||||
|
||||
const tabRenderer = await waitForElement<HTMLElement>(
|
||||
selectors.body.tabRenderer,
|
||||
);
|
||||
|
||||
container = Object.assign(document.createElement('div'), {
|
||||
id: 'synced-lyrics-container',
|
||||
});
|
||||
|
||||
@ -40,6 +40,16 @@ export function cache<T extends (...params: P) => R, P extends never[], R>(
|
||||
}) 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
|
||||
*/
|
||||
|
||||
@ -200,6 +200,26 @@ export default (api: YoutubePlayer) => {
|
||||
for (const status of ['playing', 'pause'] as const) {
|
||||
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) {
|
||||
|
||||
@ -214,14 +214,14 @@ const suffixesToRemove = [
|
||||
/\s*vevo$/i,
|
||||
|
||||
// Video titles
|
||||
/\s*[(|\[]official(.*?)[)|\]]/i, // (Official Music Video), [Official Visualizer], etc...
|
||||
/\s*[(|\[]((lyrics?|visualizer|audio)\s*(video)?)[)|\]]/i,
|
||||
/\s*[(|\[](performance video)[)|\]]/i,
|
||||
/\s*[(|\[](clip official)[)|\]]/i,
|
||||
/\s*[(|\[](video version)[)|\]]/i,
|
||||
/\s*[(|\[](HD|HQ)\s*?(?:audio)?[)|\]]$/i,
|
||||
/\s*[(|\[](live)[)|\]]$/i,
|
||||
/\s*[(|\[]4K\s*?(?:upgrade)?[)|\]]$/i,
|
||||
/\s*[(|[]official(.*?)[)|\]]/i, // (Official Music Video), [Official Visualizer], etc...
|
||||
/\s*[(|[]((lyrics?|visualizer|audio)\s*(video)?)[)|\]]/i,
|
||||
/\s*[(|[](performance video)[)|\]]/i,
|
||||
/\s*[(|[](clip official)[)|\]]/i,
|
||||
/\s*[(|[](video version)[)|\]]/i,
|
||||
/\s*[(|[](HD|HQ)\s*?(?:audio)?[)|\]]$/i,
|
||||
/\s*[(|[](live)[)|\]]$/i,
|
||||
/\s*[(|[]4K\s*?(?:upgrade)?[)|\]]$/i,
|
||||
];
|
||||
|
||||
export function cleanupName(name: string): string {
|
||||
|
||||
@ -172,10 +172,19 @@ async function onApiLoaded() {
|
||||
|
||||
// Remove upgrade button
|
||||
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');
|
||||
styles.innerHTML = `ytmusic-guide-section-renderer #items ytmusic-guide-entry-renderer:last-child {
|
||||
display: none;
|
||||
}`;
|
||||
styles.textContent = `${itemsSelector} ${selector} { display: none; }`;
|
||||
|
||||
document.head.appendChild(styles);
|
||||
}
|
||||
|
||||
|
||||
@ -241,7 +241,8 @@ export interface FlagEndpoint {
|
||||
flagAction: string;
|
||||
}
|
||||
|
||||
export type VideoDataChangeValue = {
|
||||
// see song-info-front.ts
|
||||
export type VideoDataChangeValue = Record<string, unknown> & {
|
||||
videoId: string;
|
||||
title: string;
|
||||
author: string;
|
||||
|
||||
@ -5,3 +5,17 @@ export interface QueueResponse {
|
||||
autoPlaying?: boolean;
|
||||
continuation?: string;
|
||||
}
|
||||
|
||||
export interface WatchNextResponse {
|
||||
playerOverlays: {
|
||||
playerOverlayRenderer: {
|
||||
browserMediaSession: {
|
||||
browserMediaSessionRenderer: {
|
||||
album: {
|
||||
runs: { text: string; }[]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
import { VideoDetails } from './video-details';
|
||||
import { GetPlayerResponse } from './get-player-response';
|
||||
import { PlayerAPIEvents } from './player-api-events';
|
||||
import { WatchNextResponse } from '@/types/youtube-music-desktop-internal';
|
||||
|
||||
export interface YoutubePlayer {
|
||||
getInternalApiInterface: <Parameters extends unknown[], Return>(
|
||||
@ -427,4 +428,6 @@ export interface YoutubePlayer {
|
||||
addEmbedsConversionTrackingParams: <Parameters extends unknown[], Return>(
|
||||
...params: Parameters
|
||||
) => Return;
|
||||
|
||||
getWatchNextResponse(): WatchNextResponse;
|
||||
}
|
||||
|
||||
11
src/utils/wait-for-element.ts
Normal file
11
src/utils/wait-for-element.ts
Normal 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 */);
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user