mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-10 04:30:25 +02:00
Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| be0ebac362 | |||
| 8eb8290fc4 | |||
| fac1d3fb62 | |||
| 93fbe1f49a | |||
| 59f17a4faa | |||
| d9e41720c8 | |||
| 8452780602 | |||
| 96b5b660fb | |||
| 610138c05c | |||
| 6a619240fa | |||
| b65c972da1 | |||
| 8ec4551b46 | |||
| 21f4a78dd7 | |||
| 61d7e7c390 | |||
| 993841ddbf | |||
| 98b8409592 | |||
| d3480a86c3 | |||
| 3a63ee4b95 | |||
| 876376d65f | |||
| 215abf615d | |||
| afad2843c6 | |||
| 958ab1f31f | |||
| 0ca325aac2 | |||
| 12b66e5fa0 | |||
| 7e78478fbe | |||
| f783a6360e | |||
| 8eb6c6a213 | |||
| ea5f0268cb | |||
| 427272f8c8 | |||
| 40d09404b3 | |||
| f3ee198833 | |||
| 5416d6e596 | |||
| a0639cbd27 | |||
| 790961f29a | |||
| 18027d4292 | |||
| a8b3944c66 | |||
| a00385e78f | |||
| 5e628d96c7 | |||
| ad34d8553e | |||
| a89b6640a9 | |||
| b3b810c62c | |||
| ecef9bea5e | |||
| c7214fc7ce | |||
| 84bcfb6eb9 | |||
| 0ca7a0efc9 | |||
| cb76436a2a | |||
| 88a951e2e7 | |||
| 6f1b78c2d6 | |||
| 107074b240 | |||
| 6e8ca7e035 |
@@ -1,5 +1,5 @@
|
|||||||
name: Feature request
|
name: Feature request - NOT ACCEPTING NEW FEATURE REQUESTS
|
||||||
description: Request a feature to be added to Feishin 🎉
|
description: Feature requests are currently closed. The application is actively being rewritten https://github.com/audioling/audioling.
|
||||||
labels: ['enhancement']
|
labels: ['enhancement']
|
||||||
body:
|
body:
|
||||||
- type: textarea
|
- type: textarea
|
||||||
@@ -18,5 +18,3 @@ body:
|
|||||||
options:
|
options:
|
||||||
- label: 'Yes'
|
- label: 'Yes'
|
||||||
required: false
|
required: false
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
|
|||||||
@@ -27,6 +27,16 @@
|
|||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## MAINTENANCE NOTICE
|
||||||
|
|
||||||
|
Feishin is currently undergoing a major rewrite. New feature requests will not be accepted. The rewrite is being actively developed at the [audioling](https://github.com/audioling/audioling) repository.
|
||||||
|
|
||||||
|
Follow the repository or join the discord/matrix server for updates.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
Rewrite of [Sonixd](https://github.com/jeffvli/sonixd).
|
Rewrite of [Sonixd](https://github.com/jeffvli/sonixd).
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
@@ -49,7 +59,7 @@ Rewrite of [Sonixd](https://github.com/jeffvli/sonixd).
|
|||||||
|
|
||||||
Download the [latest desktop client](https://github.com/jeffvli/feishin/releases). The desktop client is the recommended way to use Feishin. It supports both the MPV and web player backends, as well as includes built-in fetching for lyrics.
|
Download the [latest desktop client](https://github.com/jeffvli/feishin/releases). The desktop client is the recommended way to use Feishin. It supports both the MPV and web player backends, as well as includes built-in fetching for lyrics.
|
||||||
|
|
||||||
#### MacOS Notes
|
#### macOS Notes
|
||||||
|
|
||||||
If you're using a device running macOS 12 (Monterey) or higher, [check here](https://github.com/jeffvli/feishin/issues/104#issuecomment-1553914730) for instructions on how to remove the app from quarantine.
|
If you're using a device running macOS 12 (Monterey) or higher, [check here](https://github.com/jeffvli/feishin/issues/104#issuecomment-1553914730) for instructions on how to remove the app from quarantine.
|
||||||
|
|
||||||
@@ -74,8 +84,7 @@ docker run --name feishin -p 9180:9180 feishin
|
|||||||
|
|
||||||
To install via Docker Compose use the following snippit. This also works on Portainer.
|
To install via Docker Compose use the following snippit. This also works on Portainer.
|
||||||
|
|
||||||
```
|
```yaml
|
||||||
version: '3'
|
|
||||||
services:
|
services:
|
||||||
feishin:
|
feishin:
|
||||||
container_name: feishin
|
container_name: feishin
|
||||||
@@ -119,8 +128,16 @@ Feishin supports any music server that implements a [Navidrome](https://www.navi
|
|||||||
|
|
||||||
- [Navidrome](https://github.com/navidrome/navidrome)
|
- [Navidrome](https://github.com/navidrome/navidrome)
|
||||||
- [Jellyfin](https://github.com/jellyfin/jellyfin)
|
- [Jellyfin](https://github.com/jellyfin/jellyfin)
|
||||||
- [Funkwhale](https://funkwhale.audio/) - TBD
|
- Subsonic-compatible servers
|
||||||
- Subsonic-compatible servers - TBD
|
- [Airsonic-Advanced](https://github.com/airsonic-advanced/airsonic-advanced)
|
||||||
|
- [Ampache](https://ampache.org)
|
||||||
|
- [Astiga](https://asti.ga/)
|
||||||
|
- [Funkwhale](https://www.funkwhale.audio/)
|
||||||
|
- [Gonic](https://github.com/sentriz/gonic)
|
||||||
|
- [LMS](https://github.com/epoupon/lms)
|
||||||
|
- [Nextcloud Music](https://apps.nextcloud.com/apps/music)
|
||||||
|
- [Supysonic](https://github.com/spl0k/supysonic)
|
||||||
|
- More (?)
|
||||||
|
|
||||||
### I have the issue "The SUID sandbox helper binary was found, but is not configured correctly" on Linux
|
### I have the issue "The SUID sandbox helper binary was found, but is not configured correctly" on Linux
|
||||||
|
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "feishin",
|
"name": "feishin",
|
||||||
"version": "0.10.0",
|
"version": "0.12.1",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "feishin",
|
"name": "feishin",
|
||||||
"version": "0.10.0",
|
"version": "0.12.1",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
+11
-3
@@ -2,7 +2,7 @@
|
|||||||
"name": "feishin",
|
"name": "feishin",
|
||||||
"productName": "Feishin",
|
"productName": "Feishin",
|
||||||
"description": "Feishin music server",
|
"description": "Feishin music server",
|
||||||
"version": "0.10.0",
|
"version": "0.12.1",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "concurrently \"npm run build:main\" \"npm run build:renderer\" \"npm run build:remote\"",
|
"build": "concurrently \"npm run build:main\" \"npm run build:renderer\" \"npm run build:remote\"",
|
||||||
"build:main": "cross-env NODE_ENV=production TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.main.prod.ts",
|
"build:main": "cross-env NODE_ENV=production TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.main.prod.ts",
|
||||||
@@ -360,8 +360,16 @@
|
|||||||
"styled-components": "^6"
|
"styled-components": "^6"
|
||||||
},
|
},
|
||||||
"devEngines": {
|
"devEngines": {
|
||||||
"node": ">=18.x",
|
"runtime": {
|
||||||
"npm": ">=7.x"
|
"name": "node",
|
||||||
|
"version": ">=18.x",
|
||||||
|
"onFail": "error"
|
||||||
|
},
|
||||||
|
"packageManager": {
|
||||||
|
"name": "npm",
|
||||||
|
"version": ">=7.x",
|
||||||
|
"onFail": "error"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"browserslist": [],
|
"browserslist": [],
|
||||||
"electronmon": {
|
"electronmon": {
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "feishin",
|
"name": "feishin",
|
||||||
"version": "0.10.0",
|
"version": "0.12.1",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "feishin",
|
"name": "feishin",
|
||||||
"version": "0.10.0",
|
"version": "0.12.1",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "feishin",
|
"name": "feishin",
|
||||||
"version": "0.10.0",
|
"version": "0.12.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "./dist/main/main.js",
|
"main": "./dist/main/main.js",
|
||||||
"author": {
|
"author": {
|
||||||
|
|||||||
@@ -29,7 +29,8 @@
|
|||||||
"addLast": "přidat poslední",
|
"addLast": "přidat poslední",
|
||||||
"mute": "ztlumit",
|
"mute": "ztlumit",
|
||||||
"skip_forward": "přeskočit dopředu",
|
"skip_forward": "přeskočit dopředu",
|
||||||
"playSimilarSongs": "přehrát podobné skladby"
|
"playSimilarSongs": "přehrát podobné skladby",
|
||||||
|
"viewQueue": "zobrazit frontu"
|
||||||
},
|
},
|
||||||
"setting": {
|
"setting": {
|
||||||
"crossfadeStyle_description": "vyberte způsob prolnutí u přehrávače zvuku",
|
"crossfadeStyle_description": "vyberte způsob prolnutí u přehrávače zvuku",
|
||||||
@@ -221,7 +222,7 @@
|
|||||||
"volumeWidth": "šířka posuvníku hlasitosti",
|
"volumeWidth": "šířka posuvníku hlasitosti",
|
||||||
"volumeWidth_description": "horizontální velikost posuvníku hlasitosti",
|
"volumeWidth_description": "horizontální velikost posuvníku hlasitosti",
|
||||||
"discordListening": "zobrazit stav jako „Poslouchá“",
|
"discordListening": "zobrazit stav jako „Poslouchá“",
|
||||||
"discordListening_description": "zobrazit stav jako „Poslouchá“ namísto „Hraje“. tato funkce v současné době není kompatibilní s lištou s časem",
|
"discordListening_description": "zobrazit stav jako „Poslouchá“ namísto „Hraje“",
|
||||||
"contextMenu": "nastavení kontextové nabídky (kliknutí pravým)",
|
"contextMenu": "nastavení kontextové nabídky (kliknutí pravým)",
|
||||||
"contextMenu_description": "umožňuje skrýt položky, které se zobrazí v nabídce po kliknutí pravým tlačítkem myši na položku. položky, které nejsou zaškrtnuté, se skryjí",
|
"contextMenu_description": "umožňuje skrýt položky, které se zobrazí v nabídce po kliknutí pravým tlačítkem myši na položku. položky, které nejsou zaškrtnuté, se skryjí",
|
||||||
"customCssEnable": "povolit vlastní CSS",
|
"customCssEnable": "povolit vlastní CSS",
|
||||||
@@ -254,7 +255,9 @@
|
|||||||
"translationApiKey": "klíč api překladů",
|
"translationApiKey": "klíč api překladů",
|
||||||
"translationApiKey_description": "klíč api pro překlady (podporuje pouze koncový bod globální služby)",
|
"translationApiKey_description": "klíč api pro překlady (podporuje pouze koncový bod globální služby)",
|
||||||
"translationTargetLanguage": "cílový jazyk překladu",
|
"translationTargetLanguage": "cílový jazyk překladu",
|
||||||
"translationTargetLanguage_description": "cílový jazyk pro překlad"
|
"translationTargetLanguage_description": "cílový jazyk pro překlad",
|
||||||
|
"lastfmApiKey": "klíč API {{lastfm}}",
|
||||||
|
"lastfmApiKey_description": "klíč API pro {{lastfm}}. vyžadováno pro obaly alb"
|
||||||
},
|
},
|
||||||
"action": {
|
"action": {
|
||||||
"editPlaylist": "upravit $t(entity.playlist_one)",
|
"editPlaylist": "upravit $t(entity.playlist_one)",
|
||||||
@@ -277,7 +280,8 @@
|
|||||||
"openIn": {
|
"openIn": {
|
||||||
"lastfm": "Otevřít v Last.fm",
|
"lastfm": "Otevřít v Last.fm",
|
||||||
"musicbrainz": "Otevřít v MusicBrainz"
|
"musicbrainz": "Otevřít v MusicBrainz"
|
||||||
}
|
},
|
||||||
|
"moveToNext": "přesunout na další"
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"backward": "zpátky",
|
"backward": "zpátky",
|
||||||
@@ -387,7 +391,8 @@
|
|||||||
"autoFitColumns": "automaticky přizpůsobit sloupce",
|
"autoFitColumns": "automaticky přizpůsobit sloupce",
|
||||||
"size": "$t(common.size)",
|
"size": "$t(common.size)",
|
||||||
"itemGap": "mezera mezi položkami (px)",
|
"itemGap": "mezera mezi položkami (px)",
|
||||||
"itemSize": "velikost položek (px)"
|
"itemSize": "velikost položek (px)",
|
||||||
|
"followCurrentSong": "následovat aktuální skladbu"
|
||||||
},
|
},
|
||||||
"label": {
|
"label": {
|
||||||
"releaseDate": "datum vydání",
|
"releaseDate": "datum vydání",
|
||||||
@@ -544,12 +549,14 @@
|
|||||||
"useImageAspectRatio": "použít poměr stran obrázku",
|
"useImageAspectRatio": "použít poměr stran obrázku",
|
||||||
"lyricGap": "mezera textů",
|
"lyricGap": "mezera textů",
|
||||||
"dynamicImageBlur": "velikost rozostření obrázku",
|
"dynamicImageBlur": "velikost rozostření obrázku",
|
||||||
"dynamicIsImage": "povolit obrázek na pozadí"
|
"dynamicIsImage": "povolit obrázek na pozadí",
|
||||||
|
"lyricOffset": "posunutí textů (ms)"
|
||||||
},
|
},
|
||||||
"upNext": "další",
|
"upNext": "další",
|
||||||
"lyrics": "texty",
|
"lyrics": "texty",
|
||||||
"related": "související",
|
"related": "související",
|
||||||
"visualizer": "vizualizér"
|
"visualizer": "vizualizér",
|
||||||
|
"noLyrics": "nenalezeny žádné texty"
|
||||||
},
|
},
|
||||||
"appMenu": {
|
"appMenu": {
|
||||||
"selectServer": "vybrat server",
|
"selectServer": "vybrat server",
|
||||||
@@ -584,7 +591,8 @@
|
|||||||
"shareItem": "sdílet položku",
|
"shareItem": "sdílet položku",
|
||||||
"playSimilarSongs": "$t(player.playSimilarSongs)",
|
"playSimilarSongs": "$t(player.playSimilarSongs)",
|
||||||
"download": "stáhnout",
|
"download": "stáhnout",
|
||||||
"playShuffled": "$t(player.shuffle)"
|
"playShuffled": "$t(player.shuffle)",
|
||||||
|
"moveToNext": "$t(action.moveToNext)"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"mostPlayed": "nejpřehrávanější",
|
"mostPlayed": "nejpřehrávanější",
|
||||||
@@ -652,6 +660,14 @@
|
|||||||
},
|
},
|
||||||
"playlist": {
|
"playlist": {
|
||||||
"reorder": "změna pořadí povolena pouze při řazení podle id"
|
"reorder": "změna pořadí povolena pouze při řazení podle id"
|
||||||
|
},
|
||||||
|
"manageServers": {
|
||||||
|
"url": "URL",
|
||||||
|
"username": "uživatelské jméno",
|
||||||
|
"editServerDetailsTooltip": "upravit podrobnosti o serveru",
|
||||||
|
"removeServer": "odstranit server",
|
||||||
|
"serverDetails": "podrobnosti o serveru",
|
||||||
|
"title": "správa serverů"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"form": {
|
"form": {
|
||||||
@@ -763,6 +779,9 @@
|
|||||||
"trackWithCount_other": "{{count}} skladeb",
|
"trackWithCount_other": "{{count}} skladeb",
|
||||||
"play_one": "{{count}} přehrání",
|
"play_one": "{{count}} přehrání",
|
||||||
"play_few": "{{count}} přehrání",
|
"play_few": "{{count}} přehrání",
|
||||||
"play_other": "{{count}} přehrání"
|
"play_other": "{{count}} přehrání",
|
||||||
|
"song_one": "píseň",
|
||||||
|
"song_few": "písničky",
|
||||||
|
"song_other": "písní"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+58
-18
@@ -107,7 +107,13 @@
|
|||||||
"reload": "Neu Laden",
|
"reload": "Neu Laden",
|
||||||
"mbid": "MusicBrainz ID",
|
"mbid": "MusicBrainz ID",
|
||||||
"close": "schliessen",
|
"close": "schliessen",
|
||||||
"share": "Teilen"
|
"share": "Teilen",
|
||||||
|
"translation": "Übersetzung",
|
||||||
|
"trackGain": "Track-Pegelverstärkung",
|
||||||
|
"trackPeak": "Track-Spitzenpegel",
|
||||||
|
"codec": "Codec",
|
||||||
|
"albumPeak": "Album-Spitzenpegel",
|
||||||
|
"albumGain": "Album-Pegelverstärkung"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"remotePortWarning": "Starten Sie den Server neu, um den neuen Port anzuwenden",
|
"remotePortWarning": "Starten Sie den Server neu, um den neuen Port anzuwenden",
|
||||||
@@ -179,13 +185,13 @@
|
|||||||
},
|
},
|
||||||
"form": {
|
"form": {
|
||||||
"deletePlaylist": {
|
"deletePlaylist": {
|
||||||
"title": "Lösche $t(entity.playlist_one)",
|
"title": "$t(entity.playlist_one) löschen",
|
||||||
"success": "$t(entity.playlist_one) erfolgreich gelöscht",
|
"success": "$t(entity.playlist_one) erfolgreich gelöscht",
|
||||||
"input_confirm": "Geben Sie zur Bestätigung den Namen von $t(entity.playlist_one) ein"
|
"input_confirm": "Geben Sie zur Bestätigung den Namen von $t(entity.playlist_one) ein"
|
||||||
},
|
},
|
||||||
"createPlaylist": {
|
"createPlaylist": {
|
||||||
"input_description": "$t(common.description)",
|
"input_description": "$t(common.description)",
|
||||||
"title": "Erstellen $t(entity.playlist_one)",
|
"title": "$t(entity.playlist_one) erstellen",
|
||||||
"input_public": "öffentlich",
|
"input_public": "öffentlich",
|
||||||
"success": "$t(entity.playlist_one) erfolgreich erstellt",
|
"success": "$t(entity.playlist_one) erfolgreich erstellt",
|
||||||
"input_name": "$t(common.name)",
|
"input_name": "$t(common.name)",
|
||||||
@@ -267,7 +273,9 @@
|
|||||||
"trackWithCount_other": "{{count}} Tracks",
|
"trackWithCount_other": "{{count}} Tracks",
|
||||||
"smartPlaylist": "Smart $t(entity.playlist_one)",
|
"smartPlaylist": "Smart $t(entity.playlist_one)",
|
||||||
"play_one": "{{count}} Wiedergabe",
|
"play_one": "{{count}} Wiedergabe",
|
||||||
"play_other": "{{count}} Wiedergaben"
|
"play_other": "{{count}} Wiedergaben",
|
||||||
|
"song_one": "Lied",
|
||||||
|
"song_other": "Lieder"
|
||||||
},
|
},
|
||||||
"table": {
|
"table": {
|
||||||
"config": {
|
"config": {
|
||||||
@@ -348,11 +356,13 @@
|
|||||||
"unsynchronized": "nicht synchronisiert",
|
"unsynchronized": "nicht synchronisiert",
|
||||||
"lyricAlignment": "Songtext-Ausrichtung",
|
"lyricAlignment": "Songtext-Ausrichtung",
|
||||||
"useImageAspectRatio": "Bildseitenverhältnis verwenden",
|
"useImageAspectRatio": "Bildseitenverhältnis verwenden",
|
||||||
"lyricGap": "Songtext-Lücke"
|
"lyricGap": "Songtext-Lücke",
|
||||||
|
"dynamicIsImage": "Hintergrundbild aktivieren"
|
||||||
},
|
},
|
||||||
"upNext": "als nächstes",
|
"upNext": "als nächstes",
|
||||||
"lyrics": "Songtexte",
|
"lyrics": "Songtexte",
|
||||||
"related": "Ähnliche"
|
"related": "Ähnliche",
|
||||||
|
"noLyrics": "Keine Liedtexte gefunden"
|
||||||
},
|
},
|
||||||
"appMenu": {
|
"appMenu": {
|
||||||
"selectServer": "Server auswählen",
|
"selectServer": "Server auswählen",
|
||||||
@@ -375,18 +385,19 @@
|
|||||||
},
|
},
|
||||||
"albumDetail": {
|
"albumDetail": {
|
||||||
"moreFromArtist": "Mehr von diesem $t(entity.artist_one)",
|
"moreFromArtist": "Mehr von diesem $t(entity.artist_one)",
|
||||||
"moreFromGeneric": "Mehr von {{item}}"
|
"moreFromGeneric": "Mehr von {{item}}",
|
||||||
|
"released": "erschienen"
|
||||||
},
|
},
|
||||||
"globalSearch": {
|
"globalSearch": {
|
||||||
"commands": {
|
"commands": {
|
||||||
"serverCommands": "Serverbefehle",
|
"serverCommands": "Serverbefehle",
|
||||||
"goToPage": "Gehe zur Seite",
|
"goToPage": "Gehe zur Seite",
|
||||||
"searchFor": "Suche nach {{query}}"
|
"searchFor": "Nach {{query}} suchen"
|
||||||
},
|
},
|
||||||
"title": "Befehle"
|
"title": "Befehle"
|
||||||
},
|
},
|
||||||
"contextMenu": {
|
"contextMenu": {
|
||||||
"numberSelected": "{{count}} Ausgewählte",
|
"numberSelected": "{{count}} ausgewählt",
|
||||||
"addToPlaylist": "$t(action.addToPlaylist)",
|
"addToPlaylist": "$t(action.addToPlaylist)",
|
||||||
"addToFavorites": "$t(action.addToFavorites)",
|
"addToFavorites": "$t(action.addToFavorites)",
|
||||||
"setRating": "$t(action.setRating)",
|
"setRating": "$t(action.setRating)",
|
||||||
@@ -401,7 +412,10 @@
|
|||||||
"addLast": "$t(player.addLast)",
|
"addLast": "$t(player.addLast)",
|
||||||
"addFavorite": "$t(action.addToFavorites)",
|
"addFavorite": "$t(action.addToFavorites)",
|
||||||
"play": "$t(player.play)",
|
"play": "$t(player.play)",
|
||||||
"removeFromQueue": "$t(action.removeFromQueue)"
|
"removeFromQueue": "$t(action.removeFromQueue)",
|
||||||
|
"playShuffled": "$t(player.shuffle)",
|
||||||
|
"download": "Download",
|
||||||
|
"playSimilarSongs": "$t(player.playSimilarSongs)"
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar": {
|
||||||
"nowPlaying": "läuft gerade",
|
"nowPlaying": "läuft gerade",
|
||||||
@@ -414,34 +428,59 @@
|
|||||||
"settings": "$t(common.setting_other)",
|
"settings": "$t(common.setting_other)",
|
||||||
"home": "$t(common.home)",
|
"home": "$t(common.home)",
|
||||||
"artists": "$t(entity.artist_other)",
|
"artists": "$t(entity.artist_other)",
|
||||||
"albumArtists": "$t(entity.albumArtist_other)"
|
"albumArtists": "$t(entity.albumArtist_other)",
|
||||||
|
"shared": "$t(entity.playlist_other) geteilt"
|
||||||
},
|
},
|
||||||
"setting": {
|
"setting": {
|
||||||
"playbackTab": "Wiedergabe",
|
"playbackTab": "Wiedergabe",
|
||||||
"generalTab": "allgemein",
|
"generalTab": "Allgemein",
|
||||||
"hotkeysTab": "Kurzbefehle",
|
"hotkeysTab": "Kurzbefehle",
|
||||||
"windowTab": "Fenster"
|
"windowTab": "Fenster",
|
||||||
|
"advanced": "Erweitert"
|
||||||
},
|
},
|
||||||
"albumArtistList": {
|
"albumArtistList": {
|
||||||
"title": "$t(entity.albumArtist_other)"
|
"title": "$t(entity.albumArtist_other)"
|
||||||
},
|
},
|
||||||
"genreList": {
|
"genreList": {
|
||||||
"title": "$t(entity.genre_other)"
|
"title": "$t(entity.genre_other)",
|
||||||
|
"showTracks": "$t(entity.genre_one) $t(entity.track_other) anzeigen",
|
||||||
|
"showAlbums": "$t(entity.genre_one) $t(entity.album_other) anzeigen"
|
||||||
},
|
},
|
||||||
"trackList": {
|
"trackList": {
|
||||||
"title": "$t(entity.track_other)"
|
"title": "$t(entity.track_other)",
|
||||||
|
"artistTracks": "Tracks von {{artist}}",
|
||||||
|
"genreTracks": "\"{{genre}}\" $t(entity.track_other)"
|
||||||
},
|
},
|
||||||
"playlistList": {
|
"playlistList": {
|
||||||
"title": "$t(entity.playlist_other)"
|
"title": "$t(entity.playlist_other)"
|
||||||
},
|
},
|
||||||
"albumList": {
|
"albumList": {
|
||||||
"title": "$t(entity.album_other)"
|
"title": "$t(entity.album_other)",
|
||||||
|
"artistAlbums": "Alben von {{artist}}",
|
||||||
|
"genreAlbums": "\"{{genre}}\" $t(entity.album_other)"
|
||||||
},
|
},
|
||||||
"albumArtistDetail": {
|
"albumArtistDetail": {
|
||||||
"about": "Über {{artist}}",
|
"about": "Über {{artist}}",
|
||||||
"appearsOn": "erscheint auf",
|
"appearsOn": "erscheint auf",
|
||||||
"recentReleases": "Kürzliche Veröffentlichungen",
|
"recentReleases": "Kürzliche Veröffentlichungen",
|
||||||
"viewDiscography": "Diskographie ansehen"
|
"viewDiscography": "Diskographie ansehen",
|
||||||
|
"viewAllTracks": "Alle $t(entity.track_other) ansehen",
|
||||||
|
"topSongsFrom": "Toplieder von {{title}}",
|
||||||
|
"viewAll": "Alles ansehen",
|
||||||
|
"topSongs": "Toplieder"
|
||||||
|
},
|
||||||
|
"manageServers": {
|
||||||
|
"title": "Servers verwalten",
|
||||||
|
"editServerDetailsTooltip": "Serverdetails editieren",
|
||||||
|
"removeServer": "Server entfernen",
|
||||||
|
"url": "URL",
|
||||||
|
"serverDetails": "Serverdetails",
|
||||||
|
"username": "Benutzername"
|
||||||
|
},
|
||||||
|
"itemDetail": {
|
||||||
|
"copyPath": "Pfad in Zwischenablage kopieren",
|
||||||
|
"copiedPath": "Pfad erfolgreich kopiert",
|
||||||
|
"openFile": "Track im Dateiexplorer anzeigen"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"player": {
|
"player": {
|
||||||
@@ -473,7 +512,8 @@
|
|||||||
"pause": "Pause",
|
"pause": "Pause",
|
||||||
"unfavorite": "Aus Favoriten entfernen",
|
"unfavorite": "Aus Favoriten entfernen",
|
||||||
"skip_forward": "Vorspulen",
|
"skip_forward": "Vorspulen",
|
||||||
"skip": "Überspringen"
|
"skip": "Überspringen",
|
||||||
|
"playSimilarSongs": "Ähnliche Lieder abspielen"
|
||||||
},
|
},
|
||||||
"setting": {
|
"setting": {
|
||||||
"audioDevice_description": "Wählen Sie das Audiogerät aus, das für die Wiedergabe verwendet werden soll (nur Webplayer).",
|
"audioDevice_description": "Wählen Sie das Audiogerät aus, das für die Wiedergabe verwendet werden soll (nur Webplayer).",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
"deselectAll": "deselect all",
|
"deselectAll": "deselect all",
|
||||||
"editPlaylist": "edit $t(entity.playlist_one)",
|
"editPlaylist": "edit $t(entity.playlist_one)",
|
||||||
"goToPage": "go to page",
|
"goToPage": "go to page",
|
||||||
|
"moveToNext": "move to next",
|
||||||
"moveToBottom": "move to bottom",
|
"moveToBottom": "move to bottom",
|
||||||
"moveToTop": "move to top",
|
"moveToTop": "move to top",
|
||||||
"refresh": "$t(common.refresh)",
|
"refresh": "$t(common.refresh)",
|
||||||
@@ -147,8 +148,8 @@
|
|||||||
"smartPlaylist": "smart $t(entity.playlist_one)",
|
"smartPlaylist": "smart $t(entity.playlist_one)",
|
||||||
"track_one": "track",
|
"track_one": "track",
|
||||||
"track_other": "tracks",
|
"track_other": "tracks",
|
||||||
"song_one": "",
|
"song_one": "song",
|
||||||
"song_other": "",
|
"song_other": "songs",
|
||||||
"trackWithCount_one": "{{count}} track",
|
"trackWithCount_one": "{{count}} track",
|
||||||
"trackWithCount_other": "{{count}} tracks"
|
"trackWithCount_other": "{{count}} tracks"
|
||||||
},
|
},
|
||||||
@@ -335,6 +336,7 @@
|
|||||||
"deletePlaylist": "$t(action.deletePlaylist)",
|
"deletePlaylist": "$t(action.deletePlaylist)",
|
||||||
"deselectAll": "$t(action.deselectAll)",
|
"deselectAll": "$t(action.deselectAll)",
|
||||||
"download": "download",
|
"download": "download",
|
||||||
|
"moveToNext": "$t(action.moveToNext)",
|
||||||
"moveToBottom": "$t(action.moveToBottom)",
|
"moveToBottom": "$t(action.moveToBottom)",
|
||||||
"moveToTop": "$t(action.moveToTop)",
|
"moveToTop": "$t(action.moveToTop)",
|
||||||
"numberSelected": "{{count}} selected",
|
"numberSelected": "{{count}} selected",
|
||||||
@@ -508,7 +510,7 @@
|
|||||||
"discordIdleStatus": "show rich presence idle status",
|
"discordIdleStatus": "show rich presence idle status",
|
||||||
"discordIdleStatus_description": "when enabled, update status while player is idle",
|
"discordIdleStatus_description": "when enabled, update status while player is idle",
|
||||||
"discordListening": "show status as listening",
|
"discordListening": "show status as listening",
|
||||||
"discordListening_description": "show status as listening instead of playing. note that this currently breaks timer bar",
|
"discordListening_description": "show status as listening instead of playing",
|
||||||
"discordRichPresence": "{{discord}} rich presence",
|
"discordRichPresence": "{{discord}} rich presence",
|
||||||
"discordRichPresence_description": "enable playback status in {{discord}} rich presence. Image keys are: {{icon}}, {{playing}}, and {{paused}} ",
|
"discordRichPresence_description": "enable playback status in {{discord}} rich presence. Image keys are: {{icon}}, {{playing}}, and {{paused}} ",
|
||||||
"discordUpdateInterval": "{{discord}} rich presence update interval",
|
"discordUpdateInterval": "{{discord}} rich presence update interval",
|
||||||
@@ -580,6 +582,8 @@
|
|||||||
"imageAspectRatio_description": "if enabled, cover art will be shown using their native aspect ratio. for art that is not 1:1, the remaining space will be empty",
|
"imageAspectRatio_description": "if enabled, cover art will be shown using their native aspect ratio. for art that is not 1:1, the remaining space will be empty",
|
||||||
"language": "language",
|
"language": "language",
|
||||||
"language_description": "sets the language for the application ($t(common.restartRequired))",
|
"language_description": "sets the language for the application ($t(common.restartRequired))",
|
||||||
|
"lastfmApiKey": "{{lastfm}} API key",
|
||||||
|
"lastfmApiKey_description": "the API key for {{lastfm}}. required for cover art",
|
||||||
"lyricFetch": "fetch lyrics from the internet",
|
"lyricFetch": "fetch lyrics from the internet",
|
||||||
"lyricFetch_description": "fetch lyrics from various internet sources",
|
"lyricFetch_description": "fetch lyrics from various internet sources",
|
||||||
"lyricFetchProvider": "providers to fetch lyrics from",
|
"lyricFetchProvider": "providers to fetch lyrics from",
|
||||||
|
|||||||
@@ -29,7 +29,8 @@
|
|||||||
"mute": "silencio",
|
"mute": "silencio",
|
||||||
"skip_forward": "saltar hacia delante",
|
"skip_forward": "saltar hacia delante",
|
||||||
"pause": "pausa",
|
"pause": "pausa",
|
||||||
"playSimilarSongs": "Reproducir canciones similares"
|
"playSimilarSongs": "Reproducir canciones similares",
|
||||||
|
"viewQueue": "ver cola"
|
||||||
},
|
},
|
||||||
"setting": {
|
"setting": {
|
||||||
"crossfadeStyle_description": "selecciona el estilo de crossfade a usar por el reproductor de audio",
|
"crossfadeStyle_description": "selecciona el estilo de crossfade a usar por el reproductor de audio",
|
||||||
@@ -220,7 +221,7 @@
|
|||||||
"doubleClickBehavior_description": "si es true, se pondrán en cola todas las pistas que coincidan en una búsqueda de pistas. De lo contrario, solo se pondrá en cola la pista seleccionada",
|
"doubleClickBehavior_description": "si es true, se pondrán en cola todas las pistas que coincidan en una búsqueda de pistas. De lo contrario, solo se pondrá en cola la pista seleccionada",
|
||||||
"volumeWidth": "Ancho del deslizador de volumen",
|
"volumeWidth": "Ancho del deslizador de volumen",
|
||||||
"volumeWidth_description": "La anchura del deslizador de volumen",
|
"volumeWidth_description": "La anchura del deslizador de volumen",
|
||||||
"discordListening_description": "Muestra el estado como escuchando en lugar de reproduciendo. Ten en cuenta que esto actualmente rompe la barra de tiempo",
|
"discordListening_description": "mostrar el estado como escuchando en lugar de jugando",
|
||||||
"discordListening": "Mostrar estado como escuchando",
|
"discordListening": "Mostrar estado como escuchando",
|
||||||
"contextMenu": "Configuración del menú de contexto (clic derecho)",
|
"contextMenu": "Configuración del menú de contexto (clic derecho)",
|
||||||
"contextMenu_description": "Te permite esconder elementos que son mostrados en el menú cuando haces clic derecho en un elemento. Los elementos que no estén seleccionados serán escondidos",
|
"contextMenu_description": "Te permite esconder elementos que son mostrados en el menú cuando haces clic derecho en un elemento. Los elementos que no estén seleccionados serán escondidos",
|
||||||
@@ -254,7 +255,9 @@
|
|||||||
"translationApiKey": "clave api de traducción",
|
"translationApiKey": "clave api de traducción",
|
||||||
"translationApiKey_description": "Clave API para la traducción (solo para el punto final del servicio global)",
|
"translationApiKey_description": "Clave API para la traducción (solo para el punto final del servicio global)",
|
||||||
"translationTargetLanguage": "idioma final de la traducción",
|
"translationTargetLanguage": "idioma final de la traducción",
|
||||||
"translationTargetLanguage_description": "lengua de destino de la traducción"
|
"translationTargetLanguage_description": "lengua de destino de la traducción",
|
||||||
|
"lastfmApiKey_description": "la clave API para {{lastfm}}. Requerida para la portada",
|
||||||
|
"lastfmApiKey": "Clave API para {{lastfm}}"
|
||||||
},
|
},
|
||||||
"action": {
|
"action": {
|
||||||
"editPlaylist": "editar $t(entity.playlist_one)",
|
"editPlaylist": "editar $t(entity.playlist_one)",
|
||||||
@@ -277,7 +280,8 @@
|
|||||||
"openIn": {
|
"openIn": {
|
||||||
"lastfm": "Abrir en Last.fm",
|
"lastfm": "Abrir en Last.fm",
|
||||||
"musicbrainz": "Abrir en MusicBrainz"
|
"musicbrainz": "Abrir en MusicBrainz"
|
||||||
}
|
},
|
||||||
|
"moveToNext": "pasar al siguiente"
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"backward": "hacia atrás",
|
"backward": "hacia atrás",
|
||||||
@@ -489,7 +493,8 @@
|
|||||||
"showDetails": "Obtener información",
|
"showDetails": "Obtener información",
|
||||||
"playSimilarSongs": "$t(player.playSimilarSongs)",
|
"playSimilarSongs": "$t(player.playSimilarSongs)",
|
||||||
"download": "descargar",
|
"download": "descargar",
|
||||||
"playShuffled": "$t(player.shuffle)"
|
"playShuffled": "$t(player.shuffle)",
|
||||||
|
"moveToNext": "$t(action.moveToNext)"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"mostPlayed": "más reproducidos",
|
"mostPlayed": "más reproducidos",
|
||||||
@@ -513,11 +518,13 @@
|
|||||||
"showLyricMatch": "mostrar coincidencia de letras",
|
"showLyricMatch": "mostrar coincidencia de letras",
|
||||||
"lyricGap": "desfase de letra",
|
"lyricGap": "desfase de letra",
|
||||||
"dynamicImageBlur": "tamaño de desenfoque de imagen",
|
"dynamicImageBlur": "tamaño de desenfoque de imagen",
|
||||||
"dynamicIsImage": "habilitar imagen de fondo"
|
"dynamicIsImage": "habilitar imagen de fondo",
|
||||||
|
"lyricOffset": "compensación de letras (ms)"
|
||||||
},
|
},
|
||||||
"lyrics": "letras",
|
"lyrics": "letras",
|
||||||
"related": "relacionado",
|
"related": "relacionado",
|
||||||
"visualizer": "visualizador"
|
"visualizer": "visualizador",
|
||||||
|
"noLyrics": "sin letras"
|
||||||
},
|
},
|
||||||
"albumDetail": {
|
"albumDetail": {
|
||||||
"moreFromArtist": "más de este $t(entity.artist_one)",
|
"moreFromArtist": "más de este $t(entity.artist_one)",
|
||||||
@@ -578,6 +585,14 @@
|
|||||||
},
|
},
|
||||||
"playlist": {
|
"playlist": {
|
||||||
"reorder": "la reordenación solo se activa al ordenar por id"
|
"reorder": "la reordenación solo se activa al ordenar por id"
|
||||||
|
},
|
||||||
|
"manageServers": {
|
||||||
|
"removeServer": "eliminar servidor",
|
||||||
|
"title": "administrar servidores",
|
||||||
|
"serverDetails": "detalles del servidor",
|
||||||
|
"username": "nombre de usuario",
|
||||||
|
"editServerDetailsTooltip": "editar detalles del servidor",
|
||||||
|
"url": "URL"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"form": {
|
"form": {
|
||||||
@@ -705,7 +720,8 @@
|
|||||||
"size": "$t(common.size)",
|
"size": "$t(common.size)",
|
||||||
"displayType": "tipo de visualización",
|
"displayType": "tipo de visualización",
|
||||||
"itemGap": "espacio entre elementos (px)",
|
"itemGap": "espacio entre elementos (px)",
|
||||||
"itemSize": "tamaño del elemento (px)"
|
"itemSize": "tamaño del elemento (px)",
|
||||||
|
"followCurrentSong": "seguir la canción actual"
|
||||||
},
|
},
|
||||||
"view": {
|
"view": {
|
||||||
"card": "tarjeta",
|
"card": "tarjeta",
|
||||||
@@ -763,6 +779,9 @@
|
|||||||
"trackWithCount_other": "{{count}} pistas",
|
"trackWithCount_other": "{{count}} pistas",
|
||||||
"play_one": "Reproducir {{count}}",
|
"play_one": "Reproducir {{count}}",
|
||||||
"play_many": "Reproducir {{count}}",
|
"play_many": "Reproducir {{count}}",
|
||||||
"play_other": "Reproducir {{count}}"
|
"play_other": "Reproducir {{count}}",
|
||||||
|
"song_one": "canción",
|
||||||
|
"song_many": "canciones",
|
||||||
|
"song_other": "canciones"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+61
-23
@@ -11,7 +11,7 @@
|
|||||||
"skip_back": "reculer",
|
"skip_back": "reculer",
|
||||||
"favorite": "favori",
|
"favorite": "favori",
|
||||||
"next": "suivant",
|
"next": "suivant",
|
||||||
"shuffle": "aléatoire",
|
"shuffle": "lecture aléatoire",
|
||||||
"playbackFetchNoResults": "aucune chansons trouvées",
|
"playbackFetchNoResults": "aucune chansons trouvées",
|
||||||
"playbackFetchInProgress": "chargement des chansons…",
|
"playbackFetchInProgress": "chargement des chansons…",
|
||||||
"addNext": "ajouter ensuite",
|
"addNext": "ajouter ensuite",
|
||||||
@@ -29,13 +29,14 @@
|
|||||||
"skip_forward": "avancer",
|
"skip_forward": "avancer",
|
||||||
"pause": "pause",
|
"pause": "pause",
|
||||||
"unfavorite": "retirer des favoris",
|
"unfavorite": "retirer des favoris",
|
||||||
"playSimilarSongs": "jouer des chansons similaires"
|
"playSimilarSongs": "jouer des chansons similaires",
|
||||||
|
"viewQueue": "voir la file d'attente"
|
||||||
},
|
},
|
||||||
"action": {
|
"action": {
|
||||||
"editPlaylist": "éditer $t(entity.playlist_one)",
|
"editPlaylist": "éditer $t(entity.playlist_one)",
|
||||||
"goToPage": "aller à la page",
|
"goToPage": "aller à la page",
|
||||||
"moveToTop": "déplacer en haut",
|
"moveToTop": "déplacer en haut",
|
||||||
"clearQueue": "effacer la liste de lecture",
|
"clearQueue": "vider la file d'attente",
|
||||||
"addToFavorites": "ajouter aux $t(entity.favorite_other)",
|
"addToFavorites": "ajouter aux $t(entity.favorite_other)",
|
||||||
"addToPlaylist": "ajouter à $t(entity.playlist_one)",
|
"addToPlaylist": "ajouter à $t(entity.playlist_one)",
|
||||||
"createPlaylist": "créer $t(entity.playlist_one)",
|
"createPlaylist": "créer $t(entity.playlist_one)",
|
||||||
@@ -65,7 +66,7 @@
|
|||||||
"edit": "éditer",
|
"edit": "éditer",
|
||||||
"favorite": "favoris",
|
"favorite": "favoris",
|
||||||
"left": "gauche",
|
"left": "gauche",
|
||||||
"save": "sauvegarder",
|
"save": "enregistrer",
|
||||||
"right": "droite",
|
"right": "droite",
|
||||||
"currentSong": "$t(entity.track_one) actuelle",
|
"currentSong": "$t(entity.track_one) actuelle",
|
||||||
"collapse": "réduire",
|
"collapse": "réduire",
|
||||||
@@ -92,7 +93,7 @@
|
|||||||
"no": "non",
|
"no": "non",
|
||||||
"owner": "propriétaire",
|
"owner": "propriétaire",
|
||||||
"enable": "activer",
|
"enable": "activer",
|
||||||
"clear": "effacer",
|
"clear": "vider",
|
||||||
"forward": "avancer",
|
"forward": "avancer",
|
||||||
"delete": "supprimer",
|
"delete": "supprimer",
|
||||||
"cancel": "annuler",
|
"cancel": "annuler",
|
||||||
@@ -106,7 +107,7 @@
|
|||||||
"filters": "filtres",
|
"filters": "filtres",
|
||||||
"create": "créer",
|
"create": "créer",
|
||||||
"bitrate": "bitrate",
|
"bitrate": "bitrate",
|
||||||
"saveAndReplace": "sauvegarder et remplacer",
|
"saveAndReplace": "enregistrer et remplacer",
|
||||||
"action_one": "action",
|
"action_one": "action",
|
||||||
"action_many": "actions",
|
"action_many": "actions",
|
||||||
"action_other": "actions",
|
"action_other": "actions",
|
||||||
@@ -124,12 +125,12 @@
|
|||||||
"none": "aucun",
|
"none": "aucun",
|
||||||
"menu": "menu",
|
"menu": "menu",
|
||||||
"restartRequired": "redémarrage requis",
|
"restartRequired": "redémarrage requis",
|
||||||
"previousSong": "précédant $t(entity.track_one)",
|
"previousSong": "$t(entity.track_one) précédente",
|
||||||
"noResultsFromQuery": "la requête n'a retourné aucun résultat",
|
"noResultsFromQuery": "la requête n'a retourné aucun résultat",
|
||||||
"quit": "quitter",
|
"quit": "quitter",
|
||||||
"expand": "étendre",
|
"expand": "étendre",
|
||||||
"search": "recherche",
|
"search": "recherche",
|
||||||
"saveAs": "sauvegarder en tant que",
|
"saveAs": "enregistrer en tant que",
|
||||||
"disc": "disque",
|
"disc": "disque",
|
||||||
"yes": "oui",
|
"yes": "oui",
|
||||||
"random": "aléatoire",
|
"random": "aléatoire",
|
||||||
@@ -152,29 +153,29 @@
|
|||||||
"remotePortWarning": "redémarrer le serveur pour appliquer le nouveau port",
|
"remotePortWarning": "redémarrer le serveur pour appliquer le nouveau port",
|
||||||
"systemFontError": "une erreur s’est produite lors de la tentative d’obtenir les polices système",
|
"systemFontError": "une erreur s’est produite lors de la tentative d’obtenir les polices système",
|
||||||
"playbackError": "une erreur s'est produite lors de la tentative de lecture du média",
|
"playbackError": "une erreur s'est produite lors de la tentative de lecture du média",
|
||||||
"endpointNotImplementedError": "endpoint {{endpoint}} n'est pas implémenté pour {{serverType}}",
|
"endpointNotImplementedError": "l'endpoint {{endpoint}} n'est pas implémenté pour {{serverType}}",
|
||||||
"remotePortError": "une erreur s'est produite lors de la tentative de définir le port du serveur distant",
|
"remotePortError": "une erreur s'est produite lors de la tentative de définir le port du serveur distant",
|
||||||
"serverRequired": "serveur requis",
|
"serverRequired": "serveur requis",
|
||||||
"authenticationFailed": "l'authentification à échoué",
|
"authenticationFailed": "l'authentification a échoué",
|
||||||
"apiRouteError": "incapable d’acheminer la demande",
|
"apiRouteError": "incapable d’acheminer la demande",
|
||||||
"genericError": "une erreur s'est produite",
|
"genericError": "une erreur s'est produite",
|
||||||
"credentialsRequired": "identifiants requis",
|
"credentialsRequired": "identifiants requis",
|
||||||
"sessionExpiredError": "votre session a expiré",
|
"sessionExpiredError": "votre session a expiré",
|
||||||
"remoteEnableError": "une erreur s'est produite lors de la tentative de $t(common.enable) le serveur distant",
|
"remoteEnableError": "une erreur s'est produite lors de la tentative de $t(common.enable) le serveur distant",
|
||||||
"localFontAccessDenied": "accès refusé aux polices locales",
|
"localFontAccessDenied": "accès refusé aux polices locales",
|
||||||
"serverNotSelectedError": "aucun serveur sélectionner",
|
"serverNotSelectedError": "aucun serveur sélectionné",
|
||||||
"remoteDisableError": "une erreur s'est produite lors de la tentative de $t(common.disable) le serveur distant",
|
"remoteDisableError": "une erreur s'est produite lors de la tentative de $t(common.disable) le serveur distant",
|
||||||
"mpvRequired": "MPV requis",
|
"mpvRequired": "MPV requis",
|
||||||
"audioDeviceFetchError": "une erreur s’est produite lors de la tentative d’obtenir les périphériques audio",
|
"audioDeviceFetchError": "une erreur s’est produite lors de la tentative d’obtenir les périphériques audio",
|
||||||
"invalidServer": "serveur invalide",
|
"invalidServer": "serveur invalide",
|
||||||
"loginRateError": "trop de tentative de connexion, merci d'essayer dans quelque secondes",
|
"loginRateError": "trop de tentative de connexion, merci de réessayer dans quelques secondes",
|
||||||
"openError": "impossible d'ouvrir le fichier",
|
"openError": "impossible d'ouvrir le fichier",
|
||||||
"networkError": "une erreur de réseau est survenue",
|
"networkError": "une erreur de réseau est survenue",
|
||||||
"badAlbum": "vous voyez cette page parce que cette chanson ne fait pas parti d'un album. vous rencontrez probablement cette erreur si vous avez une chanson qui n'est pas dans votre répertoire de musique. jellyfin gère les chansons uniquement si elles sont dans un sous-dossier, qui est lui-même dans un dossier \"Musique(s)\"."
|
"badAlbum": "vous voyez cette page parce que cette chanson ne fait pas parti d'un album. vous rencontrez probablement cette erreur si vous avez une chanson qui n'est pas dans votre répertoire de musique. jellyfin gère les chansons uniquement si elles sont dans un sous-dossier, qui est lui-même dans un dossier \"Musique(s)\"."
|
||||||
},
|
},
|
||||||
"filter": {
|
"filter": {
|
||||||
"mostPlayed": "plus joués",
|
"mostPlayed": "plus joués",
|
||||||
"playCount": "nombre d'écoutes",
|
"playCount": "nombre d'écoute",
|
||||||
"isCompilation": "est une compilation",
|
"isCompilation": "est une compilation",
|
||||||
"recentlyPlayed": "récemment joué",
|
"recentlyPlayed": "récemment joué",
|
||||||
"isRated": "est noté",
|
"isRated": "est noté",
|
||||||
@@ -191,7 +192,7 @@
|
|||||||
"path": "chemin",
|
"path": "chemin",
|
||||||
"favorited": "favoris",
|
"favorited": "favoris",
|
||||||
"isRecentlyPlayed": "est récemment joué",
|
"isRecentlyPlayed": "est récemment joué",
|
||||||
"isFavorited": "est favoris",
|
"isFavorited": "est favori",
|
||||||
"bpm": "bpm",
|
"bpm": "bpm",
|
||||||
"releaseYear": "année de sortie",
|
"releaseYear": "année de sortie",
|
||||||
"disc": "disque",
|
"disc": "disque",
|
||||||
@@ -199,7 +200,7 @@
|
|||||||
"songCount": "nombre de chansons",
|
"songCount": "nombre de chansons",
|
||||||
"duration": "durée",
|
"duration": "durée",
|
||||||
"random": "aléatoire",
|
"random": "aléatoire",
|
||||||
"lastPlayed": "dernière joué",
|
"lastPlayed": "dernier joué",
|
||||||
"toYear": "à l'année",
|
"toYear": "à l'année",
|
||||||
"fromYear": "depuis l'année",
|
"fromYear": "depuis l'année",
|
||||||
"criticRating": "note des critiques",
|
"criticRating": "note des critiques",
|
||||||
@@ -245,12 +246,14 @@
|
|||||||
"lyricSize": "Taille des paroles",
|
"lyricSize": "Taille des paroles",
|
||||||
"lyricGap": "espacement des lettres",
|
"lyricGap": "espacement des lettres",
|
||||||
"dynamicIsImage": "activer l'image d'arrière-plan",
|
"dynamicIsImage": "activer l'image d'arrière-plan",
|
||||||
"dynamicImageBlur": "intensité de flou sur image d'arrière-plan"
|
"dynamicImageBlur": "intensité de flou sur image d'arrière-plan",
|
||||||
|
"lyricOffset": "paroles décalées (ms)"
|
||||||
},
|
},
|
||||||
"upNext": "à suivre",
|
"upNext": "à suivre",
|
||||||
"lyrics": "paroles",
|
"lyrics": "paroles",
|
||||||
"related": "similaire",
|
"related": "similaire",
|
||||||
"visualizer": "visualisateur"
|
"visualizer": "visualisateur",
|
||||||
|
"noLyrics": "aucune parole trouvée"
|
||||||
},
|
},
|
||||||
"appMenu": {
|
"appMenu": {
|
||||||
"selectServer": "sélectionner le serveur",
|
"selectServer": "sélectionner le serveur",
|
||||||
@@ -273,7 +276,8 @@
|
|||||||
},
|
},
|
||||||
"albumDetail": {
|
"albumDetail": {
|
||||||
"moreFromArtist": "plus de $t(entity.artist_one)",
|
"moreFromArtist": "plus de $t(entity.artist_one)",
|
||||||
"moreFromGeneric": "plus de {{item}}"
|
"moreFromGeneric": "plus de {{item}}",
|
||||||
|
"released": "publié"
|
||||||
},
|
},
|
||||||
"setting": {
|
"setting": {
|
||||||
"generalTab": "général",
|
"generalTab": "général",
|
||||||
@@ -310,7 +314,8 @@
|
|||||||
"shareItem": "partager un élément",
|
"shareItem": "partager un élément",
|
||||||
"playSimilarSongs": "$t(player.playSimilarSongs)",
|
"playSimilarSongs": "$t(player.playSimilarSongs)",
|
||||||
"showDetails": "obtenir des informations",
|
"showDetails": "obtenir des informations",
|
||||||
"download": "télécharger"
|
"download": "télécharger",
|
||||||
|
"playShuffled": "$t(player.shuffle)"
|
||||||
},
|
},
|
||||||
"albumArtistList": {
|
"albumArtistList": {
|
||||||
"title": "$t(entity.albumArtist_other)"
|
"title": "$t(entity.albumArtist_other)"
|
||||||
@@ -351,6 +356,14 @@
|
|||||||
},
|
},
|
||||||
"playlist": {
|
"playlist": {
|
||||||
"reorder": "le tri n'est possible que lorsque l'on trie par identifiant"
|
"reorder": "le tri n'est possible que lorsque l'on trie par identifiant"
|
||||||
|
},
|
||||||
|
"manageServers": {
|
||||||
|
"serverDetails": "détails du serveur",
|
||||||
|
"removeServer": "supprimer le serveur",
|
||||||
|
"url": "URL du serveur",
|
||||||
|
"title": "gérer les serveurs",
|
||||||
|
"username": "nom d'utilisateur",
|
||||||
|
"editServerDetailsTooltip": "modifier les détails du serveur"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"setting": {
|
"setting": {
|
||||||
@@ -559,7 +572,24 @@
|
|||||||
"contextMenu_description": "permet de masquer les éléments qui s'affichent dans le menu lorsque vous cliquez avec le bouton droit de la souris sur un élément. les éléments qui ne sont pas cochés seront masqués",
|
"contextMenu_description": "permet de masquer les éléments qui s'affichent dans le menu lorsque vous cliquez avec le bouton droit de la souris sur un élément. les éléments qui ne sont pas cochés seront masqués",
|
||||||
"albumBackground": "image d'arrière-plan de l'album",
|
"albumBackground": "image d'arrière-plan de l'album",
|
||||||
"albumBackground_description": "ajoute une image d'arrière-plan pour les pages de l'album contenant les illustrations de l'album",
|
"albumBackground_description": "ajoute une image d'arrière-plan pour les pages de l'album contenant les illustrations de l'album",
|
||||||
"albumBackgroundBlur_description": "ajuste le niveau de flou appliqué à l'image d'arrière-plan de l'album"
|
"albumBackgroundBlur_description": "ajuste le niveau de flou appliqué à l'image d'arrière-plan de l'album",
|
||||||
|
"playButtonBehavior_optionPlayShuffled": "$t(player.shuffle)",
|
||||||
|
"playerbarOpenDrawer": "basculement plein écran de la barre de lecteur",
|
||||||
|
"playerbarOpenDrawer_description": "permet de cliquer sur la barre du lecteur pour ouvrir le lecteur plein écran",
|
||||||
|
"translationApiProvider": "fournisseur d'api de traduction",
|
||||||
|
"discordListening": "afficher le statut d'écoute",
|
||||||
|
"discordListening_description": "afficher le statut comme étant en écoute au lieu de lecture",
|
||||||
|
"translationApiKey_description": "clé api à utiliser pour traduire les paroles (ne prend en charge que les points de terminaison de service globaux)",
|
||||||
|
"translationTargetLanguage": "traduction langue cible",
|
||||||
|
"trayEnabled": "montrer le plateau",
|
||||||
|
"translationApiProvider_description": "le fournisseur d'api à utiliser pour la traduction des paroles",
|
||||||
|
"customCss_description": "contenu css personnalisé. Remarque : le contenu et les URL distantes sont des propriétés non autorisées. Un aperçu de votre contenu est affiché ci-dessous. Des champs supplémentaires que vous n'avez pas définis sont présents en raison de la vérification.",
|
||||||
|
"translationApiKey": "clé api de traduction",
|
||||||
|
"translationTargetLanguage_description": "langue cible pour la traduction des paroles",
|
||||||
|
"transcodeNote": "prend effet après 1 (web) - 2 (mpv) chansons",
|
||||||
|
"trayEnabled_description": "afficher ou masquer l'icône et le menu de la barre d'état système. si désactivé, désactive également la réduction et la sortie vers la barre d'état système",
|
||||||
|
"doubleClickBehavior_description": "si vrai, toutes les pistes correspondantes dans une recherche de piste seront mises en file d'attente. sinon, seule celle sur laquelle vous avez cliqué sera mise en file d'attente",
|
||||||
|
"albumBackgroundBlur": "taille du flou de l'image d'arrière-plan de l'album"
|
||||||
},
|
},
|
||||||
"form": {
|
"form": {
|
||||||
"deletePlaylist": {
|
"deletePlaylist": {
|
||||||
@@ -604,7 +634,8 @@
|
|||||||
},
|
},
|
||||||
"editPlaylist": {
|
"editPlaylist": {
|
||||||
"title": "modifier $t(entity.playlist_one)",
|
"title": "modifier $t(entity.playlist_one)",
|
||||||
"publicJellyfinNote": "Jellyfin n'indique pas si une playlist est publique ou non. Si vous souhaitez que cette playlist reste publique, veuillez sélectionner l'entrée suivante"
|
"publicJellyfinNote": "Jellyfin n'indique pas si une playlist est publique ou non. Si vous souhaitez que cette playlist reste publique, veuillez sélectionner l'entrée suivante",
|
||||||
|
"success": "$t(entity.playlist_one) mis à jour avec succès"
|
||||||
},
|
},
|
||||||
"lyricSearch": {
|
"lyricSearch": {
|
||||||
"title": "rechercher parole",
|
"title": "rechercher parole",
|
||||||
@@ -666,7 +697,13 @@
|
|||||||
"genreWithCount_other": "{{count}} genres",
|
"genreWithCount_other": "{{count}} genres",
|
||||||
"trackWithCount_one": "{{count}} piste",
|
"trackWithCount_one": "{{count}} piste",
|
||||||
"trackWithCount_many": "{{count}} pistes",
|
"trackWithCount_many": "{{count}} pistes",
|
||||||
"trackWithCount_other": "{{count}} pistes"
|
"trackWithCount_other": "{{count}} pistes",
|
||||||
|
"play_one": "{{count}} écouter",
|
||||||
|
"play_many": "{{count}} écoute",
|
||||||
|
"play_other": "{{count}} écoute",
|
||||||
|
"song_one": "chanson",
|
||||||
|
"song_many": "chansons",
|
||||||
|
"song_other": "chansons"
|
||||||
},
|
},
|
||||||
"table": {
|
"table": {
|
||||||
"config": {
|
"config": {
|
||||||
@@ -677,7 +714,8 @@
|
|||||||
"gap": "$t(common.gap)",
|
"gap": "$t(common.gap)",
|
||||||
"size": "$t(common.size)",
|
"size": "$t(common.size)",
|
||||||
"itemGap": "écart entre les éléments (en pixel)",
|
"itemGap": "écart entre les éléments (en pixel)",
|
||||||
"itemSize": "taille des élements (en pixel)"
|
"itemSize": "taille des élements (en pixel)",
|
||||||
|
"followCurrentSong": "suivre la chanson actuelle"
|
||||||
},
|
},
|
||||||
"view": {
|
"view": {
|
||||||
"table": "liste",
|
"table": "liste",
|
||||||
|
|||||||
+273
-1
@@ -9,6 +9,278 @@
|
|||||||
"editPlaylist": "$t(entity.playlist_one) 편집",
|
"editPlaylist": "$t(entity.playlist_one) 편집",
|
||||||
"goToPage": "페이지 이동",
|
"goToPage": "페이지 이동",
|
||||||
"moveToBottom": "맨 아래로 이동",
|
"moveToBottom": "맨 아래로 이동",
|
||||||
"moveToTop": "맨 위로 이동"
|
"moveToTop": "맨 위로 이동",
|
||||||
|
"moveToNext": "다음으로 이동",
|
||||||
|
"removeFromQueue": "대기열에서 제거",
|
||||||
|
"refresh": "$t(common.refresh)",
|
||||||
|
"removeFromFavorites": "$t(entity.favorite_other)에서 제거",
|
||||||
|
"removeFromPlaylist": "$t(entity.playlist_one)에서 제거",
|
||||||
|
"openIn": {
|
||||||
|
"musicbrainz": "MusicBrainz에서 보기",
|
||||||
|
"lastfm": "Last.fm에서 보기"
|
||||||
|
},
|
||||||
|
"viewPlaylists": "$t(entity.playlist_other) 보기",
|
||||||
|
"setRating": "평점 지정"
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"translation": "번역",
|
||||||
|
"resetToDefault": "기본 설정으로 되돌리기",
|
||||||
|
"right": "오른쪽",
|
||||||
|
"save": "저장",
|
||||||
|
"increase": "증가",
|
||||||
|
"version": "버전",
|
||||||
|
"year": "년",
|
||||||
|
"reset": "초기화",
|
||||||
|
"random": "랜덤",
|
||||||
|
"close": "닫기",
|
||||||
|
"codec": "코덱",
|
||||||
|
"create": "만들기",
|
||||||
|
"disc": "디스크",
|
||||||
|
"gap": "갭",
|
||||||
|
"left": "왼쪽",
|
||||||
|
"add": "추가",
|
||||||
|
"backward": "뒤로",
|
||||||
|
"saveAs": "(으)로 저장하기",
|
||||||
|
"search": "검색",
|
||||||
|
"setting": "설정",
|
||||||
|
"share": "공유",
|
||||||
|
"size": "크기",
|
||||||
|
"sortOrder": "순서",
|
||||||
|
"title": "곡명",
|
||||||
|
"trackNumber": "트랙번호",
|
||||||
|
"trackGain": "트랙 게인",
|
||||||
|
"trackPeak": "트랙 피크",
|
||||||
|
"unknown": "알 수 없음",
|
||||||
|
"cancel": "취소",
|
||||||
|
"clear": "지우기",
|
||||||
|
"collapse": "접기",
|
||||||
|
"comingSoon": "조만간…",
|
||||||
|
"configure": "설정",
|
||||||
|
"confirm": "확인",
|
||||||
|
"currentSong": "현재 $t(entity.track_one)",
|
||||||
|
"decrease": "감소",
|
||||||
|
"delete": "삭제",
|
||||||
|
"descending": "내림차순",
|
||||||
|
"description": "설명",
|
||||||
|
"disable": "비활성",
|
||||||
|
"edit": "편집",
|
||||||
|
"enable": "활성",
|
||||||
|
"expand": "확장",
|
||||||
|
"favorite": "즐겨찾기",
|
||||||
|
"forceRestartRequired": "변경 사항을 적용하려면 재실행 하세요... 알림을 닫으면 재실행합니다",
|
||||||
|
"forward": "앞으로",
|
||||||
|
"limit": "제한",
|
||||||
|
"manage": "관리하다",
|
||||||
|
"maximize": "최대화",
|
||||||
|
"menu": "메뉴",
|
||||||
|
"minimize": "최소화",
|
||||||
|
"modified": "수정된",
|
||||||
|
"name": "이름",
|
||||||
|
"path": "경로",
|
||||||
|
"playerMustBePaused": "플레이어가 일시정지 되어야 합니다",
|
||||||
|
"preview": "미리보기",
|
||||||
|
"previousSong": "이전곡 $t(entity.track_one)",
|
||||||
|
"quit": "종료",
|
||||||
|
"refresh": "새로고침",
|
||||||
|
"reload": "리로드",
|
||||||
|
"restartRequired": "반드시 재실행되어야 합니다",
|
||||||
|
"saveAndReplace": "저장하고 변경하기",
|
||||||
|
"yes": "네",
|
||||||
|
"ascending": "오름차순",
|
||||||
|
"areYouSure": "확실한가요?",
|
||||||
|
"bitrate": "비트 전송률",
|
||||||
|
"bpm": "bpm",
|
||||||
|
"biography": "바이오그래피",
|
||||||
|
"center": "중앙",
|
||||||
|
"channel_other": "채널",
|
||||||
|
"filter_other": "필터",
|
||||||
|
"mbid": "MusicBrainz ID",
|
||||||
|
"dismiss": "닫기",
|
||||||
|
"duration": "길이",
|
||||||
|
"home": "홈",
|
||||||
|
"no": "아니오",
|
||||||
|
"none": "없음",
|
||||||
|
"rating": "평점"
|
||||||
|
},
|
||||||
|
"entity": {
|
||||||
|
"albumWithCount_other": "{{count}} 앨범",
|
||||||
|
"artist_other": "아티스트",
|
||||||
|
"artistWithCount_other": "{{count}} 아티스트",
|
||||||
|
"favorite_other": "즐겨찾기",
|
||||||
|
"folder_other": "폴더",
|
||||||
|
"genre_other": "장르",
|
||||||
|
"genreWithCount_other": "{{count}} 장르",
|
||||||
|
"playlist_other": "플레이리스트",
|
||||||
|
"album_other": "앨범",
|
||||||
|
"albumArtist_other": "앨범 아티스트",
|
||||||
|
"albumArtistCount_other": "{{count}} 앨범 아티스트",
|
||||||
|
"folderWithCount_other": "{{count}} 폴더",
|
||||||
|
"trackWithCount_other": "{{count}} 트랙",
|
||||||
|
"song_other": "곡",
|
||||||
|
"play_other": "{{count}} 재생",
|
||||||
|
"playlistWithCount_other": "{{count}} 재생목록",
|
||||||
|
"smartPlaylist": "스마트 $t(entity.playlist_one)",
|
||||||
|
"track_other": "트랙"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"systemFontError": "시스템 폰트를 가져오는데 실패하였습니다",
|
||||||
|
"loginRateError": "너무 많은 로그인 시도하였습니다 잠시 후 다시 시도해 주세요",
|
||||||
|
"mpvRequired": "MPV 필요",
|
||||||
|
"openError": "파일을 열 수 없습니다",
|
||||||
|
"remoteDisableError": "원격 서버를 $t(common.disable) 하는데 실패하였습니다",
|
||||||
|
"playbackError": "미디어를 재생하는 도중에 에러가 발생하였습니다",
|
||||||
|
"remoteEnableError": "원격 서버를 $t(common.enable) 하는데 실패하였습니다",
|
||||||
|
"serverNotSelectedError": "선택된 서버가 없습니다",
|
||||||
|
"serverRequired": "서버가 필요합니다",
|
||||||
|
"sessionExpiredError": "세션이 만료되었습니다",
|
||||||
|
"networkError": "네트워크 에러가 발생하였습니다",
|
||||||
|
"remotePortError": "원격 서버의 포트 설정하는데 실패하였습니다",
|
||||||
|
"remotePortWarning": "새로 설정한 포트를 적용하기 위해 서버를 재실행 해 주세요",
|
||||||
|
"audioDeviceFetchError": "오디오 장치를 불러올 수 없습니다",
|
||||||
|
"authenticationFailed": "인증 실패",
|
||||||
|
"badAlbum": "이 곡은 앨범의 일부가 아니기 때문에 표시되는 것입니다. 음악 폴더의 최상위에 곡이 있는 경우 이런 문제가 발생할 가능성이 높습니다. Jellyfin은 폴더 내 그룹만 추적합니다.",
|
||||||
|
"credentialsRequired": "인증서가 필요함",
|
||||||
|
"endpointNotImplementedError": "엔드포인트 {{endpoint}} 는 {{serverType}} 에 대해 구현되지 않았습니다",
|
||||||
|
"genericError": "에러가 발생했습니다",
|
||||||
|
"invalidServer": "잘못된 서버",
|
||||||
|
"localFontAccessDenied": "로컬 글꼴에 접근 거부되었습니다"
|
||||||
|
},
|
||||||
|
"filter": {
|
||||||
|
"title": "곡명",
|
||||||
|
"isRecentlyPlayed": "최근에 재생한",
|
||||||
|
"name": "이름",
|
||||||
|
"path": "경로",
|
||||||
|
"playCount": "재생 횟수",
|
||||||
|
"random": "무작위",
|
||||||
|
"recentlyAdded": "최근에 추가된",
|
||||||
|
"releaseDate": "발매일",
|
||||||
|
"recentlyPlayed": "최근에 재생된",
|
||||||
|
"recentlyUpdated": "최근에 업데이트된",
|
||||||
|
"search": "검색",
|
||||||
|
"dateAdded": "추가된 날짜",
|
||||||
|
"lastPlayed": "마지막으로 재생한",
|
||||||
|
"mostPlayed": "가장 많이 재생한",
|
||||||
|
"album": "$t(entity.album_one)",
|
||||||
|
"albumArtist": "$t(entity.albumArtist_one)",
|
||||||
|
"artist": "$t(entity.artist_one)",
|
||||||
|
"communityRating": "커뮤니티 평점",
|
||||||
|
"criticRating": "비평가 평점",
|
||||||
|
"disc": "디스크",
|
||||||
|
"bitrate": "비트 전송률",
|
||||||
|
"biography": "바이오그래피",
|
||||||
|
"channels": "$t(common.channel_other)",
|
||||||
|
"duration": "길이",
|
||||||
|
"bpm": "bpm"
|
||||||
|
},
|
||||||
|
"form": {
|
||||||
|
"addServer": {
|
||||||
|
"title": "서버 추가하기",
|
||||||
|
"success": "서버 추가하였습니다",
|
||||||
|
"input_name": "서버 이름",
|
||||||
|
"input_password": "비밀번호",
|
||||||
|
"input_savePassword": "비밀번호 저장하기",
|
||||||
|
"input_url": "url",
|
||||||
|
"error_savePassword": "비밀번호를 저장하는 도중 오류가 발생했습니다",
|
||||||
|
"ignoreCors": "CORS 무시 ($t(common.restartRequired))",
|
||||||
|
"ignoreSsl": "SSL 무시 ($t(common.restartRequired))",
|
||||||
|
"input_legacyAuthentication": "레거시 인증 사용",
|
||||||
|
"input_username": "유저 이름"
|
||||||
|
},
|
||||||
|
"addToPlaylist": {
|
||||||
|
"input_skipDuplicates": "중복 건너뛰기",
|
||||||
|
"title": "$t(entity.playlist_one) 에 추가",
|
||||||
|
"input_playlists": "$t(entity.playlist_other)"
|
||||||
|
},
|
||||||
|
"lyricSearch": {
|
||||||
|
"title": "가사 검색",
|
||||||
|
"input_name": "$t(common.name)",
|
||||||
|
"input_artist": "$t(entity.artist_one)"
|
||||||
|
},
|
||||||
|
"queryEditor": {
|
||||||
|
"input_optionMatchAll": "모두 일치",
|
||||||
|
"input_optionMatchAny": "무엇이든 일치"
|
||||||
|
},
|
||||||
|
"editPlaylist": {
|
||||||
|
"title": "$t(entity.playlist_one) 편집",
|
||||||
|
"publicJellyfinNote": "Jellyfin은 재생목록 공개 여부를 노출하지 않습니다. 만약 공개되길 원한다면 다음을 선택하세요",
|
||||||
|
"success": "$t(entity.playlist_one) 업데이트 되었습니다"
|
||||||
|
},
|
||||||
|
"shareItem": {
|
||||||
|
"allowDownloading": "다운로드 허용",
|
||||||
|
"description": "설명",
|
||||||
|
"success": "클립보드에 공유 링크를 복사했습니다 (또는 열어보려면 클릭하세요)",
|
||||||
|
"expireInvalid": "만료 날짜는 미래 날짜여야만 합니다",
|
||||||
|
"createFailed": "공유 링크를 생성하는데 실패하였습니다 (혹시 공유하기 설정되어 있나요?)",
|
||||||
|
"setExpiration": "만료 기간 설정하기"
|
||||||
|
},
|
||||||
|
"updateServer": {
|
||||||
|
"title": "서버 업데이트",
|
||||||
|
"success": "서버 업데이트 되었습니다"
|
||||||
|
},
|
||||||
|
"createPlaylist": {
|
||||||
|
"input_description": "$t(common.description)",
|
||||||
|
"input_name": "$t(common.name)",
|
||||||
|
"success": "$t(entity.playlist_one)를 생성했습니다",
|
||||||
|
"input_owner": "$t(common.owner)",
|
||||||
|
"input_public": "공개",
|
||||||
|
"title": "$t(entity.playlist_one) 생성"
|
||||||
|
},
|
||||||
|
"deletePlaylist": {
|
||||||
|
"input_confirm": "확인을 위해 $t(entity.playlist_one)의 이름을 적어주세요",
|
||||||
|
"success": "$t(entity.playlist_one)가 삭제되었습니다",
|
||||||
|
"title": "$t(entity.playlist_one) 삭제"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"page": {
|
||||||
|
"appMenu": {
|
||||||
|
"goBack": "뒤로",
|
||||||
|
"selectServer": "서버를 선택하세요",
|
||||||
|
"goForward": "앞으로",
|
||||||
|
"manageServers": "서버 설정하기",
|
||||||
|
"openBrowserDevtools": "브라우저 개발자 도구 열기",
|
||||||
|
"version": "버전 {{version}}"
|
||||||
|
},
|
||||||
|
"manageServers": {
|
||||||
|
"title": "서버 설정하기",
|
||||||
|
"serverDetails": "서버 세부설정",
|
||||||
|
"editServerDetailsTooltip": "서버 세부설정 편집하기",
|
||||||
|
"url": "URL",
|
||||||
|
"username": "username",
|
||||||
|
"removeServer": "서버 제거하기"
|
||||||
|
},
|
||||||
|
"fullscreenPlayer": {
|
||||||
|
"config": {
|
||||||
|
"opacity": "투명도",
|
||||||
|
"lyricAlignment": "가사 정렬",
|
||||||
|
"useImageAspectRatio": "이미지 종횡비 사용",
|
||||||
|
"synchronized": "동기화",
|
||||||
|
"unsynchronized": "비동기화"
|
||||||
|
},
|
||||||
|
"lyrics": "가사"
|
||||||
|
},
|
||||||
|
"contextMenu": {
|
||||||
|
"download": "다운로드",
|
||||||
|
"numberSelected": "{{count}}개 선택됨"
|
||||||
|
},
|
||||||
|
"albumArtistDetail": {
|
||||||
|
"about": "{{artist}}에 대해",
|
||||||
|
"viewDiscography": "디스코그래피 보기",
|
||||||
|
"appearsOn": "참여 앨범",
|
||||||
|
"recentReleases": "최근 앨범",
|
||||||
|
"relatedArtists": "연관 $t(entity.artist_other)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"table": {
|
||||||
|
"config": {
|
||||||
|
"label": {
|
||||||
|
"playCount": "재생 횟수",
|
||||||
|
"dateAdded": "추가된 날짜"
|
||||||
|
},
|
||||||
|
"view": {
|
||||||
|
"card": "카드",
|
||||||
|
"poster": "포스터",
|
||||||
|
"table": "표"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,7 +86,8 @@
|
|||||||
"codec": "codec",
|
"codec": "codec",
|
||||||
"preview": "pré-visualizar",
|
"preview": "pré-visualizar",
|
||||||
"share": "compartilhar",
|
"share": "compartilhar",
|
||||||
"close": "fechar"
|
"close": "fechar",
|
||||||
|
"translation": "tradução"
|
||||||
},
|
},
|
||||||
"action": {
|
"action": {
|
||||||
"goToPage": "vá para página",
|
"goToPage": "vá para página",
|
||||||
@@ -108,7 +109,9 @@
|
|||||||
"openIn": {
|
"openIn": {
|
||||||
"lastfm": "Abrir em Last.fm",
|
"lastfm": "Abrir em Last.fm",
|
||||||
"musicbrainz": "Abrir em MusicBrainz"
|
"musicbrainz": "Abrir em MusicBrainz"
|
||||||
}
|
},
|
||||||
|
"toggleSmartPlaylistEditor": "alternar editor $t(entity.smartPlaylist)",
|
||||||
|
"moveToNext": "mover para o próximo"
|
||||||
},
|
},
|
||||||
"form": {
|
"form": {
|
||||||
"deletePlaylist": {
|
"deletePlaylist": {
|
||||||
|
|||||||
+14
-14
@@ -67,7 +67,8 @@
|
|||||||
"forceRestartRequired": "перезапустите приложение, чтобы применить изменения... закройте уведомление для перезапуска",
|
"forceRestartRequired": "перезапустите приложение, чтобы применить изменения... закройте уведомление для перезапуска",
|
||||||
"setting": "настройка",
|
"setting": "настройка",
|
||||||
"setting_one": "настройка",
|
"setting_one": "настройка",
|
||||||
"setting_other": "настройки",
|
"setting_few": "",
|
||||||
|
"setting_many": "",
|
||||||
"version": "версия",
|
"version": "версия",
|
||||||
"title": "название",
|
"title": "название",
|
||||||
"filter_one": "фильтр",
|
"filter_one": "фильтр",
|
||||||
@@ -111,16 +112,19 @@
|
|||||||
"preview": "просмотр",
|
"preview": "просмотр",
|
||||||
"codec": "кодек",
|
"codec": "кодек",
|
||||||
"share": "поделиться",
|
"share": "поделиться",
|
||||||
"close": "закрыть"
|
"close": "закрыть",
|
||||||
|
"albumGain": "альбом усиление",
|
||||||
|
"trackGain": "усиление трека",
|
||||||
|
"translation": "перевод",
|
||||||
|
"albumPeak": "пик альбома",
|
||||||
|
"trackPeak": "пик трека"
|
||||||
},
|
},
|
||||||
"entity": {
|
"entity": {
|
||||||
"album_one": "альбом",
|
"album_one": "альбом",
|
||||||
"album_few": "альбома",
|
"album_few": "альбома",
|
||||||
"album_other": "альбомы",
|
|
||||||
"album_many": "альбомов",
|
"album_many": "альбомов",
|
||||||
"genre_one": "жанр",
|
"genre_one": "жанр",
|
||||||
"genre_few": "жанра",
|
"genre_few": "жанра",
|
||||||
"genre_other": "жанры",
|
|
||||||
"genre_many": "жанров",
|
"genre_many": "жанров",
|
||||||
"playlistWithCount_one": "{{count}} плейлист",
|
"playlistWithCount_one": "{{count}} плейлист",
|
||||||
"playlistWithCount_few": "{{count}} плейлиста",
|
"playlistWithCount_few": "{{count}} плейлиста",
|
||||||
@@ -128,26 +132,25 @@
|
|||||||
"playlist_one": "плейлист",
|
"playlist_one": "плейлист",
|
||||||
"playlist_few": "плейлиста",
|
"playlist_few": "плейлиста",
|
||||||
"playlist_many": "плейлистов",
|
"playlist_many": "плейлистов",
|
||||||
"playlist_other": "плейлисты",
|
|
||||||
"play": "{{count}} прослушиваний",
|
"play": "{{count}} прослушиваний",
|
||||||
"play_one": "{{count}} прослушивание",
|
"play_one": "{{count}} прослушивание",
|
||||||
"play_other": "{{count}} прослушиваний",
|
"play_few": "",
|
||||||
|
"play_many": "",
|
||||||
"artist_one": "автор",
|
"artist_one": "автор",
|
||||||
"artist_few": "автора",
|
"artist_few": "автора",
|
||||||
"artist_other": "исполнители",
|
|
||||||
"artist_many": "исполнителей",
|
"artist_many": "исполнителей",
|
||||||
"folderWithCount_one": "{{count}} папка",
|
"folderWithCount_one": "{{count}} папка",
|
||||||
"folderWithCount_few": "{{count}} папки",
|
"folderWithCount_few": "{{count}} папки",
|
||||||
"folderWithCount_many": "{{count}} папок",
|
"folderWithCount_many": "{{count}} папок",
|
||||||
"albumArtist_one": "исполнитель альбома",
|
"albumArtist_one": "исполнитель альбома",
|
||||||
"albumArtist_few": "исполнители альбома",
|
"albumArtist_few": "исполнители альбома",
|
||||||
"albumArtist_other": "исполнители альбомов",
|
|
||||||
"albumArtist_many": "исполнителей альбома",
|
"albumArtist_many": "исполнителей альбома",
|
||||||
"track_one": "трек",
|
"track_one": "трек",
|
||||||
"track_few": "трека",
|
"track_few": "трека",
|
||||||
"track_many": "треков",
|
"track_many": "треков",
|
||||||
"track_other": "треки",
|
"song_one": "песня",
|
||||||
"song_many": "{{ count }} композиций",
|
"song_few": "{{count}} песни",
|
||||||
|
"song_many": "{{count}} песен",
|
||||||
"albumArtistCount_one": "{{count}} автор альбома",
|
"albumArtistCount_one": "{{count}} автор альбома",
|
||||||
"albumArtistCount_few": "{{count}} автора альбома",
|
"albumArtistCount_few": "{{count}} автора альбома",
|
||||||
"albumArtistCount_many": "{{count}} авторов альбома",
|
"albumArtistCount_many": "{{count}} авторов альбома",
|
||||||
@@ -157,22 +160,19 @@
|
|||||||
"favorite_one": "любимый",
|
"favorite_one": "любимый",
|
||||||
"favorite_few": "любимых",
|
"favorite_few": "любимых",
|
||||||
"favorite_many": "любимые",
|
"favorite_many": "любимые",
|
||||||
"favorite_other": "любимые",
|
|
||||||
"artistWithCount_one": "{{count}} автор",
|
"artistWithCount_one": "{{count}} автор",
|
||||||
"artistWithCount_few": "{{count}} автора",
|
"artistWithCount_few": "{{count}} автора",
|
||||||
"artistWithCount_many": "{{count}} авторов",
|
"artistWithCount_many": "{{count}} авторов",
|
||||||
"folder_one": "папка",
|
"folder_one": "папка",
|
||||||
"folder_few": "папки",
|
"folder_few": "папки",
|
||||||
"folder_many": "папок",
|
"folder_many": "папок",
|
||||||
"folder_other": "папки",
|
|
||||||
"smartPlaylist": "умный $t(entity.playlist_one)",
|
"smartPlaylist": "умный $t(entity.playlist_one)",
|
||||||
"genreWithCount_one": "{{count}} жанр",
|
"genreWithCount_one": "{{count}} жанр",
|
||||||
"genreWithCount_few": "{{count}} жанра",
|
"genreWithCount_few": "{{count}} жанра",
|
||||||
"genreWithCount_many": "{{count}} жанров",
|
"genreWithCount_many": "{{count}} жанров",
|
||||||
"trackWithCount_one": "{{count}} трек",
|
"trackWithCount_one": "{{count}} трек",
|
||||||
"trackWithCount_few": "{{count}} трека",
|
"trackWithCount_few": "{{count}} трека",
|
||||||
"trackWithCount_many": "{{count}} треков",
|
"trackWithCount_many": "{{count}} треков"
|
||||||
"trackWithCount_other": "{{count}} треков"
|
|
||||||
},
|
},
|
||||||
"table": {
|
"table": {
|
||||||
"config": {
|
"config": {
|
||||||
|
|||||||
@@ -209,7 +209,11 @@
|
|||||||
"moveToBottom": "idi na dno",
|
"moveToBottom": "idi na dno",
|
||||||
"setRating": "oceni",
|
"setRating": "oceni",
|
||||||
"toggleSmartPlaylistEditor": "pokreni $t(entity.smartPlaylist) editor",
|
"toggleSmartPlaylistEditor": "pokreni $t(entity.smartPlaylist) editor",
|
||||||
"removeFromFavorites": "ukloni iz $t(entity.favorite_other)"
|
"removeFromFavorites": "ukloni iz $t(entity.favorite_other)",
|
||||||
|
"openIn": {
|
||||||
|
"lastfm": "Otvori u Last.fm",
|
||||||
|
"musicbrainz": "Otvori u MusicBrainz"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"backward": "nazad",
|
"backward": "nazad",
|
||||||
|
|||||||
@@ -20,7 +20,8 @@
|
|||||||
"openIn": {
|
"openIn": {
|
||||||
"lastfm": "在 Last.fm 中打开",
|
"lastfm": "在 Last.fm 中打开",
|
||||||
"musicbrainz": "在 MusicBrainz 中打开"
|
"musicbrainz": "在 MusicBrainz 中打开"
|
||||||
}
|
},
|
||||||
|
"moveToNext": "移至下一首"
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"increase": "增高",
|
"increase": "增高",
|
||||||
@@ -127,7 +128,8 @@
|
|||||||
"smartPlaylist": "智能$t(entity.playlist_one)",
|
"smartPlaylist": "智能$t(entity.playlist_one)",
|
||||||
"genreWithCount_other": "{{count}} 种流派",
|
"genreWithCount_other": "{{count}} 种流派",
|
||||||
"trackWithCount_other": "{{count}} 首乐曲",
|
"trackWithCount_other": "{{count}} 首乐曲",
|
||||||
"play_other": "{{count}} 次播放"
|
"play_other": "{{count}} 次播放",
|
||||||
|
"song_other": "歌曲"
|
||||||
},
|
},
|
||||||
"player": {
|
"player": {
|
||||||
"repeat_all": "循环全部",
|
"repeat_all": "循环全部",
|
||||||
@@ -159,7 +161,8 @@
|
|||||||
"skip_forward": "向前跳过",
|
"skip_forward": "向前跳过",
|
||||||
"playbackSpeed": "播放速度",
|
"playbackSpeed": "播放速度",
|
||||||
"pause": "暂停",
|
"pause": "暂停",
|
||||||
"playSimilarSongs": "播放类似的曲目"
|
"playSimilarSongs": "播放类似的曲目",
|
||||||
|
"viewQueue": "查看播放队列"
|
||||||
},
|
},
|
||||||
"setting": {
|
"setting": {
|
||||||
"crossfadeStyle_description": "选择用于音频播放器的淡入淡出风格",
|
"crossfadeStyle_description": "选择用于音频播放器的淡入淡出风格",
|
||||||
@@ -351,7 +354,7 @@
|
|||||||
"volumeWidth": "音量滑块宽度",
|
"volumeWidth": "音量滑块宽度",
|
||||||
"volumeWidth_description": "音量滑块的宽度",
|
"volumeWidth_description": "音量滑块的宽度",
|
||||||
"discordListening": "显示状态为正在监听",
|
"discordListening": "显示状态为正在监听",
|
||||||
"discordListening_description": "将状态显示为 “正在监听”,而不是 “正在播放”。请注意,这当前会破坏计时器栏",
|
"discordListening_description": "将状态显示为正在监听,而不是正在播放",
|
||||||
"contextMenu_description": "允许您隐藏右键单击项目时显示在菜单中的项目。未选中的项目将被隐藏",
|
"contextMenu_description": "允许您隐藏右键单击项目时显示在菜单中的项目。未选中的项目将被隐藏",
|
||||||
"customCssEnable_description": "允许编写自定义 css。",
|
"customCssEnable_description": "允许编写自定义 css。",
|
||||||
"customCss": "自定义css",
|
"customCss": "自定义css",
|
||||||
@@ -384,7 +387,9 @@
|
|||||||
"translationApiKey": "翻译api密钥",
|
"translationApiKey": "翻译api密钥",
|
||||||
"translationApiKey_description": "翻译api密钥(仅支持全球服务节点)",
|
"translationApiKey_description": "翻译api密钥(仅支持全球服务节点)",
|
||||||
"translationTargetLanguage": "目标翻译语言",
|
"translationTargetLanguage": "目标翻译语言",
|
||||||
"translationTargetLanguage_description": "目标翻译语言"
|
"translationTargetLanguage_description": "目标翻译语言",
|
||||||
|
"lastfmApiKey": "{{lastfm}} API 密钥",
|
||||||
|
"lastfmApiKey_description": "{{lastfm}} 的 API 密钥。封面艺术图所需"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"remotePortWarning": "重启服务器使新端口生效",
|
"remotePortWarning": "重启服务器使新端口生效",
|
||||||
@@ -483,12 +488,14 @@
|
|||||||
"lyricGap": "歌词间距",
|
"lyricGap": "歌词间距",
|
||||||
"followCurrentLyric": "跟随当前歌词",
|
"followCurrentLyric": "跟随当前歌词",
|
||||||
"dynamicImageBlur": "图像模糊大小",
|
"dynamicImageBlur": "图像模糊大小",
|
||||||
"dynamicIsImage": "启用背景图像"
|
"dynamicIsImage": "启用背景图像",
|
||||||
|
"lyricOffset": "歌词延迟补偿(毫秒)"
|
||||||
},
|
},
|
||||||
"lyrics": "歌词",
|
"lyrics": "歌词",
|
||||||
"related": "相关",
|
"related": "相关",
|
||||||
"upNext": "即将播放",
|
"upNext": "即将播放",
|
||||||
"visualizer": "可视化"
|
"visualizer": "可视化",
|
||||||
|
"noLyrics": "未找到歌词"
|
||||||
},
|
},
|
||||||
"appMenu": {
|
"appMenu": {
|
||||||
"selectServer": "选择服务器",
|
"selectServer": "选择服务器",
|
||||||
@@ -550,7 +557,8 @@
|
|||||||
"shareItem": "分享项目",
|
"shareItem": "分享项目",
|
||||||
"playSimilarSongs": "$t(player.playSimilarSongs)",
|
"playSimilarSongs": "$t(player.playSimilarSongs)",
|
||||||
"download": "下载",
|
"download": "下载",
|
||||||
"playShuffled": "$t(player.shuffle)"
|
"playShuffled": "$t(player.shuffle)",
|
||||||
|
"moveToNext": "$t(action.moveToNext)"
|
||||||
},
|
},
|
||||||
"trackList": {
|
"trackList": {
|
||||||
"title": "$t(entity.track_other)",
|
"title": "$t(entity.track_other)",
|
||||||
@@ -591,6 +599,14 @@
|
|||||||
},
|
},
|
||||||
"playlist": {
|
"playlist": {
|
||||||
"reorder": "仅在按 ID 排序时启用重排序"
|
"reorder": "仅在按 ID 排序时启用重排序"
|
||||||
|
},
|
||||||
|
"manageServers": {
|
||||||
|
"url": "URL",
|
||||||
|
"title": "管理服务器",
|
||||||
|
"serverDetails": "服务器详细信息",
|
||||||
|
"username": "用户名",
|
||||||
|
"editServerDetailsTooltip": "编辑服务器详细信息",
|
||||||
|
"removeServer": "移除服务器"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"form": {
|
"form": {
|
||||||
@@ -662,7 +678,8 @@
|
|||||||
"autoFitColumns": "列宽自适应",
|
"autoFitColumns": "列宽自适应",
|
||||||
"size": "$t(common.size)",
|
"size": "$t(common.size)",
|
||||||
"itemGap": "项目间隙(px)",
|
"itemGap": "项目间隙(px)",
|
||||||
"itemSize": "项目大小 (px)"
|
"itemSize": "项目大小 (px)",
|
||||||
|
"followCurrentSong": "关注当前播放的歌曲"
|
||||||
},
|
},
|
||||||
"view": {
|
"view": {
|
||||||
"table": "表格",
|
"table": "表格",
|
||||||
|
|||||||
+8
-8
@@ -147,27 +147,27 @@ export default class MenuBuilder {
|
|||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
click() {
|
click() {
|
||||||
shell.openExternal('https://electronjs.org');
|
shell.openExternal('https://github.com/jeffvli/feishin');
|
||||||
},
|
},
|
||||||
label: 'Learn More',
|
label: 'Learn More',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
click() {
|
click() {
|
||||||
shell.openExternal(
|
shell.openExternal(
|
||||||
'https://github.com/electron/electron/tree/main/docs#readme',
|
'https://github.com/jeffvli/feishin?tab=readme-ov-file#getting-started',
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
label: 'Documentation',
|
label: 'Documentation',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
click() {
|
click() {
|
||||||
shell.openExternal('https://www.electronjs.org/community');
|
shell.openExternal('https://github.com/jeffvli/feishin/discussions');
|
||||||
},
|
},
|
||||||
label: 'Community Discussions',
|
label: 'Community Discussions',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
click() {
|
click() {
|
||||||
shell.openExternal('https://github.com/electron/electron/issues');
|
shell.openExternal('https://github.com/jeffvli/feishin/issues');
|
||||||
},
|
},
|
||||||
label: 'Search Issues',
|
label: 'Search Issues',
|
||||||
},
|
},
|
||||||
@@ -246,27 +246,27 @@ export default class MenuBuilder {
|
|||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
click() {
|
click() {
|
||||||
shell.openExternal('https://electronjs.org');
|
shell.openExternal('https://github.com/jeffvli/feishin');
|
||||||
},
|
},
|
||||||
label: 'Learn More',
|
label: 'Learn More',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
click() {
|
click() {
|
||||||
shell.openExternal(
|
shell.openExternal(
|
||||||
'https://github.com/electron/electron/tree/main/docs#readme',
|
'https://github.com/jeffvli/feishin?tab=readme-ov-file#getting-started',
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
label: 'Documentation',
|
label: 'Documentation',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
click() {
|
click() {
|
||||||
shell.openExternal('https://www.electronjs.org/community');
|
shell.openExternal('https://github.com/jeffvli/feishin/discussions');
|
||||||
},
|
},
|
||||||
label: 'Community Discussions',
|
label: 'Community Discussions',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
click() {
|
click() {
|
||||||
shell.openExternal('https://github.com/electron/electron/issues');
|
shell.openExternal('https://github.com/jeffvli/feishin/issues');
|
||||||
},
|
},
|
||||||
label: 'Search Issues',
|
label: 'Search Issues',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -293,10 +293,14 @@ export const JellyfinController: ControllerEndpoint = {
|
|||||||
userId: apiClientProps.server?.userId,
|
userId: apiClientProps.server?.userId,
|
||||||
},
|
},
|
||||||
query: {
|
query: {
|
||||||
AlbumArtistIds: query.artistIds
|
...(!query.compilation &&
|
||||||
? formatCommaDelimitedString(query.artistIds)
|
query.artistIds && {
|
||||||
: undefined,
|
AlbumArtistIds: formatCommaDelimitedString(query.artistIds),
|
||||||
ContributingArtistIds: query.compilation ? query.artistIds?.[0] : undefined,
|
}),
|
||||||
|
...(query.compilation &&
|
||||||
|
query.artistIds && {
|
||||||
|
ContributingArtistIds: query.artistIds[0],
|
||||||
|
}),
|
||||||
GenreIds: query.genres ? query.genres.join(',') : undefined,
|
GenreIds: query.genres ? query.genres.join(',') : undefined,
|
||||||
IncludeItemTypes: 'MusicAlbum',
|
IncludeItemTypes: 'MusicAlbum',
|
||||||
IsFavorite: query.favorite,
|
IsFavorite: query.favorite,
|
||||||
@@ -450,7 +454,6 @@ export const JellyfinController: ControllerEndpoint = {
|
|||||||
Fields: 'ChildCount, Genres, DateCreated, ParentId, Overview',
|
Fields: 'ChildCount, Genres, DateCreated, ParentId, Overview',
|
||||||
IncludeItemTypes: 'Playlist',
|
IncludeItemTypes: 'Playlist',
|
||||||
Limit: query.limit,
|
Limit: query.limit,
|
||||||
MediaTypes: 'Audio',
|
|
||||||
Recursive: true,
|
Recursive: true,
|
||||||
SearchTerm: query.searchTerm,
|
SearchTerm: query.searchTerm,
|
||||||
SortBy: playlistListSortMap.jellyfin[query.sortBy],
|
SortBy: playlistListSortMap.jellyfin[query.sortBy],
|
||||||
|
|||||||
@@ -337,7 +337,7 @@ export const NavidromeController: ControllerEndpoint = {
|
|||||||
id: query.id,
|
id: query.id,
|
||||||
},
|
},
|
||||||
query: {
|
query: {
|
||||||
_end: query.limit,
|
_end: query.startIndex + (query.limit || -1),
|
||||||
_order: query.sortOrder ? sortOrderMap.navidrome[query.sortOrder] : 'ASC',
|
_order: query.sortOrder ? sortOrderMap.navidrome[query.sortOrder] : 'ASC',
|
||||||
_sort: query.sortBy
|
_sort: query.sortBy
|
||||||
? songListSortMap.navidrome[query.sortBy]
|
? songListSortMap.navidrome[query.sortBy]
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ const normalizeSong = (
|
|||||||
playlistItemId,
|
playlistItemId,
|
||||||
releaseDate: (item.releaseDate
|
releaseDate: (item.releaseDate
|
||||||
? new Date(item.releaseDate)
|
? new Date(item.releaseDate)
|
||||||
: new Date(item.year, 0, 1)
|
: new Date(Date.UTC(item.year, 0, 1))
|
||||||
).toISOString(),
|
).toISOString(),
|
||||||
releaseYear: String(item.year),
|
releaseYear: String(item.year),
|
||||||
serverId: server?.id || 'unknown',
|
serverId: server?.id || 'unknown',
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
GenreListSort,
|
GenreListSort,
|
||||||
AlbumListSort,
|
AlbumListSort,
|
||||||
sortAlbumList,
|
sortAlbumList,
|
||||||
|
SortOrder,
|
||||||
} from '/@/renderer/api/types';
|
} from '/@/renderer/api/types';
|
||||||
import { randomString } from '/@/renderer/utils';
|
import { randomString } from '/@/renderer/utils';
|
||||||
import { ServerFeatures } from '/@/renderer/api/features-types';
|
import { ServerFeatures } from '/@/renderer/api/features-types';
|
||||||
@@ -25,14 +26,14 @@ const ALBUM_LIST_SORT_MAPPING: Record<AlbumListSort, AlbumListSortType | undefin
|
|||||||
[AlbumListSort.PLAY_COUNT]: AlbumListSortType.FREQUENT,
|
[AlbumListSort.PLAY_COUNT]: AlbumListSortType.FREQUENT,
|
||||||
[AlbumListSort.RECENTLY_ADDED]: AlbumListSortType.NEWEST,
|
[AlbumListSort.RECENTLY_ADDED]: AlbumListSortType.NEWEST,
|
||||||
[AlbumListSort.FAVORITED]: AlbumListSortType.STARRED,
|
[AlbumListSort.FAVORITED]: AlbumListSortType.STARRED,
|
||||||
[AlbumListSort.YEAR]: AlbumListSortType.RECENT,
|
[AlbumListSort.YEAR]: AlbumListSortType.BY_YEAR,
|
||||||
[AlbumListSort.NAME]: AlbumListSortType.ALPHABETICAL_BY_NAME,
|
[AlbumListSort.NAME]: AlbumListSortType.ALPHABETICAL_BY_NAME,
|
||||||
[AlbumListSort.COMMUNITY_RATING]: undefined,
|
[AlbumListSort.COMMUNITY_RATING]: undefined,
|
||||||
[AlbumListSort.DURATION]: undefined,
|
[AlbumListSort.DURATION]: undefined,
|
||||||
[AlbumListSort.CRITIC_RATING]: undefined,
|
[AlbumListSort.CRITIC_RATING]: undefined,
|
||||||
[AlbumListSort.RATING]: undefined,
|
[AlbumListSort.RATING]: undefined,
|
||||||
[AlbumListSort.ARTIST]: undefined,
|
[AlbumListSort.ARTIST]: undefined,
|
||||||
[AlbumListSort.RECENTLY_PLAYED]: undefined,
|
[AlbumListSort.RECENTLY_PLAYED]: AlbumListSortType.RECENT,
|
||||||
[AlbumListSort.RELEASE_DATE]: undefined,
|
[AlbumListSort.RELEASE_DATE]: undefined,
|
||||||
[AlbumListSort.SONG_COUNT]: undefined,
|
[AlbumListSort.SONG_COUNT]: undefined,
|
||||||
};
|
};
|
||||||
@@ -64,7 +65,7 @@ export const SubsonicController: ControllerEndpoint = {
|
|||||||
const cleanServerUrl = `${url.replace(/\/$/, '')}/rest`;
|
const cleanServerUrl = `${url.replace(/\/$/, '')}/rest`;
|
||||||
|
|
||||||
if (body.legacy) {
|
if (body.legacy) {
|
||||||
credential = `u=${body.username}&p=${body.password}`;
|
credential = `u=${encodeURIComponent(body.username)}&p=${encodeURIComponent(body.password)}`;
|
||||||
credentialParams = {
|
credentialParams = {
|
||||||
p: body.password,
|
p: body.password,
|
||||||
u: body.username,
|
u: body.username,
|
||||||
@@ -72,7 +73,7 @@ export const SubsonicController: ControllerEndpoint = {
|
|||||||
} else {
|
} else {
|
||||||
const salt = randomString(12);
|
const salt = randomString(12);
|
||||||
const hash = md5(body.password + salt);
|
const hash = md5(body.password + salt);
|
||||||
credential = `u=${body.username}&s=${salt}&t=${hash}`;
|
credential = `u=${encodeURIComponent(body.username)}&s=${encodeURIComponent(salt)}&t=${encodeURIComponent(hash)}`;
|
||||||
credentialParams = {
|
credentialParams = {
|
||||||
s: salt,
|
s: salt,
|
||||||
t: hash,
|
t: hash,
|
||||||
@@ -124,7 +125,7 @@ export const SubsonicController: ControllerEndpoint = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: res.body.playlist.id,
|
id: res.body.playlist.id.toString(),
|
||||||
name: res.body.playlist.name,
|
name: res.body.playlist.name,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -360,6 +361,16 @@ export const SubsonicController: ControllerEndpoint = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type === AlbumListSortType.BY_YEAR && !fromYear && !toYear) {
|
||||||
|
if (query.sortOrder === SortOrder.ASC) {
|
||||||
|
fromYear = 0;
|
||||||
|
toYear = dayjs().year();
|
||||||
|
} else {
|
||||||
|
fromYear = dayjs().year();
|
||||||
|
toYear = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const res = await ssApiClient(apiClientProps).getAlbumList2({
|
const res = await ssApiClient(apiClientProps).getAlbumList2({
|
||||||
query: {
|
query: {
|
||||||
fromYear,
|
fromYear,
|
||||||
@@ -436,7 +447,7 @@ export const SubsonicController: ControllerEndpoint = {
|
|||||||
return (res.body.starred?.album || []).length || 0;
|
return (res.body.starred?.album || []).length || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let type = ALBUM_LIST_SORT_MAPPING[query.sortBy] ?? AlbumListSortType.ALPHABETICAL_BY_NAME;
|
let type = AlbumListSortType.ALPHABETICAL_BY_NAME;
|
||||||
|
|
||||||
let fetchNextPage = true;
|
let fetchNextPage = true;
|
||||||
let startIndex = 0;
|
let startIndex = 0;
|
||||||
@@ -559,7 +570,10 @@ export const SubsonicController: ControllerEndpoint = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items: res.body.musicFolders.musicFolder,
|
items: res.body.musicFolders.musicFolder.map((folder) => ({
|
||||||
|
id: folder.id.toString(),
|
||||||
|
name: folder.name,
|
||||||
|
})),
|
||||||
startIndex: 0,
|
startIndex: 0,
|
||||||
totalRecordCount: res.body.musicFolders.musicFolder.length,
|
totalRecordCount: res.body.musicFolders.musicFolder.length,
|
||||||
};
|
};
|
||||||
@@ -891,7 +905,7 @@ export const SubsonicController: ControllerEndpoint = {
|
|||||||
fromAlbumPromises.push(
|
fromAlbumPromises.push(
|
||||||
ssApiClient(apiClientProps).getAlbum({
|
ssApiClient(apiClientProps).getAlbum({
|
||||||
query: {
|
query: {
|
||||||
id: albumId,
|
id: albumId.toString(),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ const normalizeSong = (
|
|||||||
const imageUrl =
|
const imageUrl =
|
||||||
getCoverArtUrl({
|
getCoverArtUrl({
|
||||||
baseUrl: server?.url,
|
baseUrl: server?.url,
|
||||||
coverArtId: item.coverArt,
|
coverArtId: item.coverArt?.toString(),
|
||||||
credential: server?.credential,
|
credential: server?.credential,
|
||||||
size: size || 300,
|
size: size || 300,
|
||||||
}) || null;
|
}) || null;
|
||||||
@@ -54,16 +54,16 @@ const normalizeSong = (
|
|||||||
album: item.album || '',
|
album: item.album || '',
|
||||||
albumArtists: [
|
albumArtists: [
|
||||||
{
|
{
|
||||||
id: item.artistId || '',
|
id: item.artistId?.toString() || '',
|
||||||
imageUrl: null,
|
imageUrl: null,
|
||||||
name: item.artist || '',
|
name: item.artist || '',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
albumId: item.albumId || '',
|
albumId: item.albumId?.toString() || '',
|
||||||
artistName: item.artist || '',
|
artistName: item.artist || '',
|
||||||
artists: [
|
artists: [
|
||||||
{
|
{
|
||||||
id: item.artistId || '',
|
id: item.artistId?.toString() || '',
|
||||||
imageUrl: null,
|
imageUrl: null,
|
||||||
name: item.artist || '',
|
name: item.artist || '',
|
||||||
},
|
},
|
||||||
@@ -95,7 +95,7 @@ const normalizeSong = (
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
: [],
|
: [],
|
||||||
id: item.id,
|
id: item.id.toString(),
|
||||||
imagePlaceholderUrl: null,
|
imagePlaceholderUrl: null,
|
||||||
imageUrl,
|
imageUrl,
|
||||||
itemType: LibraryItem.SONG,
|
itemType: LibraryItem.SONG,
|
||||||
@@ -135,7 +135,7 @@ const normalizeAlbumArtist = (
|
|||||||
const imageUrl =
|
const imageUrl =
|
||||||
getCoverArtUrl({
|
getCoverArtUrl({
|
||||||
baseUrl: server?.url,
|
baseUrl: server?.url,
|
||||||
coverArtId: item.coverArt,
|
coverArtId: item.coverArt?.toString(),
|
||||||
credential: server?.credential,
|
credential: server?.credential,
|
||||||
size: imageSize || 100,
|
size: imageSize || 100,
|
||||||
}) || null;
|
}) || null;
|
||||||
@@ -146,7 +146,7 @@ const normalizeAlbumArtist = (
|
|||||||
biography: null,
|
biography: null,
|
||||||
duration: null,
|
duration: null,
|
||||||
genres: [],
|
genres: [],
|
||||||
id: item.id,
|
id: item.id.toString(),
|
||||||
imageUrl,
|
imageUrl,
|
||||||
itemType: LibraryItem.ALBUM_ARTIST,
|
itemType: LibraryItem.ALBUM_ARTIST,
|
||||||
lastPlayedAt: null,
|
lastPlayedAt: null,
|
||||||
@@ -170,7 +170,7 @@ const normalizeAlbum = (
|
|||||||
const imageUrl =
|
const imageUrl =
|
||||||
getCoverArtUrl({
|
getCoverArtUrl({
|
||||||
baseUrl: server?.url,
|
baseUrl: server?.url,
|
||||||
coverArtId: item.coverArt,
|
coverArtId: item.coverArt?.toString(),
|
||||||
credential: server?.credential,
|
credential: server?.credential,
|
||||||
size: imageSize || 300,
|
size: imageSize || 300,
|
||||||
}) || null;
|
}) || null;
|
||||||
@@ -178,9 +178,11 @@ const normalizeAlbum = (
|
|||||||
return {
|
return {
|
||||||
albumArtist: item.artist,
|
albumArtist: item.artist,
|
||||||
albumArtists: item.artistId
|
albumArtists: item.artistId
|
||||||
? [{ id: item.artistId, imageUrl: null, name: item.artist }]
|
? [{ id: item.artistId.toString(), imageUrl: null, name: item.artist }]
|
||||||
|
: [],
|
||||||
|
artists: item.artistId
|
||||||
|
? [{ id: item.artistId.toString(), imageUrl: null, name: item.artist }]
|
||||||
: [],
|
: [],
|
||||||
artists: item.artistId ? [{ id: item.artistId, imageUrl: null, name: item.artist }] : [],
|
|
||||||
backdropImageUrl: null,
|
backdropImageUrl: null,
|
||||||
comment: null,
|
comment: null,
|
||||||
createdAt: item.created,
|
createdAt: item.created,
|
||||||
@@ -195,7 +197,7 @@ const normalizeAlbum = (
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
: [],
|
: [],
|
||||||
id: item.id,
|
id: item.id.toString(),
|
||||||
imagePlaceholderUrl: null,
|
imagePlaceholderUrl: null,
|
||||||
imageUrl,
|
imageUrl,
|
||||||
isCompilation: null,
|
isCompilation: null,
|
||||||
@@ -205,7 +207,7 @@ const normalizeAlbum = (
|
|||||||
name: item.name,
|
name: item.name,
|
||||||
originalDate: null,
|
originalDate: null,
|
||||||
playCount: null,
|
playCount: null,
|
||||||
releaseDate: item.year ? new Date(item.year, 0, 1).toISOString() : null,
|
releaseDate: item.year ? new Date(Date.UTC(item.year, 0, 1)).toISOString() : null,
|
||||||
releaseYear: item.year ? Number(item.year) : null,
|
releaseYear: item.year ? Number(item.year) : null,
|
||||||
serverId: server?.id || 'unknown',
|
serverId: server?.id || 'unknown',
|
||||||
serverType: ServerType.SUBSONIC,
|
serverType: ServerType.SUBSONIC,
|
||||||
@@ -232,11 +234,11 @@ const normalizePlaylist = (
|
|||||||
description: item.comment || null,
|
description: item.comment || null,
|
||||||
duration: item.duration,
|
duration: item.duration,
|
||||||
genres: [],
|
genres: [],
|
||||||
id: item.id,
|
id: item.id.toString(),
|
||||||
imagePlaceholderUrl: null,
|
imagePlaceholderUrl: null,
|
||||||
imageUrl: getCoverArtUrl({
|
imageUrl: getCoverArtUrl({
|
||||||
baseUrl: server?.url,
|
baseUrl: server?.url,
|
||||||
coverArtId: item.coverArt,
|
coverArtId: item.coverArt?.toString(),
|
||||||
credential: server?.credential,
|
credential: server?.credential,
|
||||||
size: 300,
|
size: 300,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ const authenticateParameters = z.object({
|
|||||||
v: z.string(),
|
v: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const id = z.number().or(z.string());
|
||||||
|
|
||||||
const createFavoriteParameters = z.object({
|
const createFavoriteParameters = z.object({
|
||||||
albumId: z.array(z.string()).optional(),
|
albumId: z.array(z.string()).optional(),
|
||||||
artistId: z.array(z.string()).optional(),
|
artistId: z.array(z.string()).optional(),
|
||||||
@@ -43,7 +45,7 @@ const setRatingParameters = z.object({
|
|||||||
const setRating = z.null();
|
const setRating = z.null();
|
||||||
|
|
||||||
const musicFolder = z.object({
|
const musicFolder = z.object({
|
||||||
id: z.string(),
|
id,
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -66,9 +68,9 @@ const genreItem = z.object({
|
|||||||
|
|
||||||
const song = z.object({
|
const song = z.object({
|
||||||
album: z.string().optional(),
|
album: z.string().optional(),
|
||||||
albumId: z.string().optional(),
|
albumId: id.optional(),
|
||||||
artist: z.string().optional(),
|
artist: z.string().optional(),
|
||||||
artistId: z.string().optional(),
|
artistId: id.optional(),
|
||||||
averageRating: z.number().optional(),
|
averageRating: z.number().optional(),
|
||||||
bitRate: z.number().optional(),
|
bitRate: z.number().optional(),
|
||||||
bpm: z.number().optional(),
|
bpm: z.number().optional(),
|
||||||
@@ -79,7 +81,7 @@ const song = z.object({
|
|||||||
duration: z.number().optional(),
|
duration: z.number().optional(),
|
||||||
genre: z.string().optional(),
|
genre: z.string().optional(),
|
||||||
genres: z.array(genreItem).optional(),
|
genres: z.array(genreItem).optional(),
|
||||||
id: z.string(),
|
id,
|
||||||
isDir: z.boolean(),
|
isDir: z.boolean(),
|
||||||
isVideo: z.boolean(),
|
isVideo: z.boolean(),
|
||||||
musicBrainzId: z.string().optional(),
|
musicBrainzId: z.string().optional(),
|
||||||
@@ -100,12 +102,12 @@ const song = z.object({
|
|||||||
const album = z.object({
|
const album = z.object({
|
||||||
album: z.string(),
|
album: z.string(),
|
||||||
artist: z.string(),
|
artist: z.string(),
|
||||||
artistId: z.string(),
|
artistId: id,
|
||||||
coverArt: z.string(),
|
coverArt: z.string(),
|
||||||
created: z.string(),
|
created: z.string(),
|
||||||
duration: z.number(),
|
duration: z.number(),
|
||||||
genre: z.string().optional(),
|
genre: z.string().optional(),
|
||||||
id: z.string(),
|
id,
|
||||||
isCompilation: z.boolean().optional(),
|
isCompilation: z.boolean().optional(),
|
||||||
isDir: z.boolean(),
|
isDir: z.boolean(),
|
||||||
isVideo: z.boolean(),
|
isVideo: z.boolean(),
|
||||||
@@ -140,7 +142,7 @@ const albumArtist = z.object({
|
|||||||
albumCount: z.string(),
|
albumCount: z.string(),
|
||||||
artistImageUrl: z.string().optional(),
|
artistImageUrl: z.string().optional(),
|
||||||
coverArt: z.string().optional(),
|
coverArt: z.string().optional(),
|
||||||
id: z.string(),
|
id,
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
starred: z.string().optional(),
|
starred: z.string().optional(),
|
||||||
});
|
});
|
||||||
@@ -398,7 +400,7 @@ const playlist = z.object({
|
|||||||
created: z.string(),
|
created: z.string(),
|
||||||
duration: z.number(),
|
duration: z.number(),
|
||||||
entry: z.array(song).optional(),
|
entry: z.array(song).optional(),
|
||||||
id: z.string(),
|
id,
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
owner: z.string(),
|
owner: z.string(),
|
||||||
public: z.boolean(),
|
public: z.boolean(),
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ export const App = () => {
|
|||||||
if (!isRunning) {
|
if (!isRunning) {
|
||||||
const extraParameters = useSettingsStore.getState().playback.mpvExtraParameters;
|
const extraParameters = useSettingsStore.getState().playback.mpvExtraParameters;
|
||||||
const properties: Record<string, any> = {
|
const properties: Record<string, any> = {
|
||||||
speed: usePlayerStore.getState().current.speed,
|
speed: usePlayerStore.getState().speed,
|
||||||
...getMpvProperties(useSettingsStore.getState().playback.mpvProperties),
|
...getMpvProperties(useSettingsStore.getState().playback.mpvProperties),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -341,7 +341,7 @@ export const AudioPlayer = forwardRef(
|
|||||||
// Set the current replaygain
|
// Set the current replaygain
|
||||||
if (current) {
|
if (current) {
|
||||||
const newVolume = calculateReplayGain(current) * volume;
|
const newVolume = calculateReplayGain(current) * volume;
|
||||||
webAudio.gain.gain.setValueAtTime(newVolume, 0);
|
webAudio.gain.gain.setValueAtTime(Math.max(0, newVolume), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the next track replaygain right before the end of this track
|
// Set the next track replaygain right before the end of this track
|
||||||
@@ -349,7 +349,10 @@ export const AudioPlayer = forwardRef(
|
|||||||
const next = sources[3 - currentPlayer];
|
const next = sources[3 - currentPlayer];
|
||||||
if (next && current) {
|
if (next && current) {
|
||||||
const newVolume = calculateReplayGain(next) * volume;
|
const newVolume = calculateReplayGain(next) * volume;
|
||||||
webAudio.gain.gain.setValueAtTime(newVolume, (current.duration - 1) / 1000);
|
webAudio.gain.gain.setValueAtTime(
|
||||||
|
Math.max(0, newVolume),
|
||||||
|
Math.max(0, (current.duration - 1) / 1000),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
calculateReplayGain,
|
calculateReplayGain,
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ export const AlbumCard = ({
|
|||||||
<ImageSection />
|
<ImageSection />
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
<DetailSection style={{ width: '100%' }}>
|
<DetailSection style={{ width: '100%' }}>
|
||||||
{cardRows.map((_row: CardRow<Album>, index: number) => (
|
{(cardRows || []).map((_row: CardRow<Album>, index: number) => (
|
||||||
<Skeleton
|
<Skeleton
|
||||||
visible
|
visible
|
||||||
height={15}
|
height={15}
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ export const PosterCard = ({
|
|||||||
</Skeleton>
|
</Skeleton>
|
||||||
<DetailContainer>
|
<DetailContainer>
|
||||||
<Stack spacing="sm">
|
<Stack spacing="sm">
|
||||||
{controls.cardRows.map((row, index) => (
|
{(controls?.cardRows || []).map((row, index) => (
|
||||||
<Skeleton
|
<Skeleton
|
||||||
key={`${index}-${row.arrayProperty}`}
|
key={`${index}-${row.arrayProperty}`}
|
||||||
visible
|
visible
|
||||||
|
|||||||
@@ -234,7 +234,7 @@ export const DefaultCard = ({
|
|||||||
</ImageContainer>
|
</ImageContainer>
|
||||||
<DetailContainer>
|
<DetailContainer>
|
||||||
<Stack spacing="sm">
|
<Stack spacing="sm">
|
||||||
{controls.cardRows.map((row, index) => (
|
{(controls?.cardRows || []).map((row, index) => (
|
||||||
<Skeleton
|
<Skeleton
|
||||||
key={`${index}-${columnIndex}-${row.arrayProperty}`}
|
key={`${index}-${columnIndex}-${row.arrayProperty}`}
|
||||||
visible
|
visible
|
||||||
|
|||||||
@@ -219,7 +219,7 @@ export const PosterCard = ({
|
|||||||
</Skeleton>
|
</Skeleton>
|
||||||
<DetailContainer>
|
<DetailContainer>
|
||||||
<Stack spacing="sm">
|
<Stack spacing="sm">
|
||||||
{controls.cardRows.map((row, index) => (
|
{(controls?.cardRows || []).map((row, index) => (
|
||||||
<Skeleton
|
<Skeleton
|
||||||
key={`${index}-${columnIndex}-${row.arrayProperty}`}
|
key={`${index}-${columnIndex}-${row.arrayProperty}`}
|
||||||
visible
|
visible
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
import React, { MouseEvent } from 'react';
|
||||||
|
import type { UnstyledButtonProps } from '@mantine/core';
|
||||||
|
import { RiPlayFill } from 'react-icons/ri';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { Play } from '/@/renderer/types';
|
||||||
|
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
|
||||||
|
import { LibraryItem } from '/@/renderer/api/types';
|
||||||
|
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||||
|
|
||||||
|
type PlayButtonType = UnstyledButtonProps & React.ComponentPropsWithoutRef<'button'>;
|
||||||
|
|
||||||
|
const PlayButton = styled.button<PlayButtonType>`
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
background-color: rgb(255 255 255);
|
||||||
|
border: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
opacity: 0.8;
|
||||||
|
transition: scale 0.1s ease-in-out;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
scale: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
opacity: 1;
|
||||||
|
scale: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
fill: rgb(0 0 0);
|
||||||
|
stroke: rgb(0 0 0);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ListConverControlsContainer = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
z-index: 100;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const ListCoverControls = ({
|
||||||
|
itemData,
|
||||||
|
itemType,
|
||||||
|
context,
|
||||||
|
uniqueId,
|
||||||
|
}: {
|
||||||
|
context: Record<string, any>;
|
||||||
|
itemData: any;
|
||||||
|
itemType: LibraryItem;
|
||||||
|
uniqueId?: string;
|
||||||
|
}) => {
|
||||||
|
const playButtonBehavior = usePlayButtonBehavior();
|
||||||
|
const handlePlayQueueAdd = usePlayQueueAdd();
|
||||||
|
const isQueue = Boolean(context?.isQueue);
|
||||||
|
|
||||||
|
const handlePlay = async (e: MouseEvent<HTMLButtonElement>, playType?: Play) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
handlePlayQueueAdd?.({
|
||||||
|
byItemType: {
|
||||||
|
id: [itemData.id],
|
||||||
|
type: itemType,
|
||||||
|
},
|
||||||
|
playType: playType || playButtonBehavior,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePlayFromQueue = () => {
|
||||||
|
context.handleDoubleClick({
|
||||||
|
data: {
|
||||||
|
uniqueId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ListConverControlsContainer className="card-controls">
|
||||||
|
<PlayButton onClick={isQueue ? handlePlayFromQueue : handlePlay}>
|
||||||
|
<RiPlayFill size={20} />
|
||||||
|
</PlayButton>
|
||||||
|
</ListConverControlsContainer>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -7,11 +7,12 @@ import { generatePath } from 'react-router';
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { SimpleImg } from 'react-simple-img';
|
import { SimpleImg } from 'react-simple-img';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import type { AlbumArtist, Artist } from '/@/renderer/api/types';
|
import { AlbumArtist, Artist } from '/@/renderer/api/types';
|
||||||
import { Text } from '/@/renderer/components/text';
|
import { Text } from '/@/renderer/components/text';
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
import { Skeleton } from '/@/renderer/components/skeleton';
|
import { Skeleton } from '/@/renderer/components/skeleton';
|
||||||
import { SEPARATOR_STRING } from '/@/renderer/api/utils';
|
import { SEPARATOR_STRING } from '/@/renderer/api/utils';
|
||||||
|
import { ListCoverControls } from '/@/renderer/components/virtual-table/cells/combined-title-cell-controls';
|
||||||
|
|
||||||
const CellContainer = styled(motion.div)<{ height: number }>`
|
const CellContainer = styled(motion.div)<{ height: number }>`
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -24,9 +25,20 @@ const CellContainer = styled(motion.div)<{ height: number }>`
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.5px;
|
||||||
|
|
||||||
|
.card-controls {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.card-controls {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ImageWrapper = styled.div`
|
const ImageWrapper = styled.div`
|
||||||
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
grid-area: image;
|
grid-area: image;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -48,7 +60,13 @@ const StyledImage = styled(SimpleImg)`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const CombinedTitleCell = ({ value, rowIndex, node }: ICellRendererParams) => {
|
export const CombinedTitleCell = ({
|
||||||
|
value,
|
||||||
|
rowIndex,
|
||||||
|
node,
|
||||||
|
context,
|
||||||
|
data,
|
||||||
|
}: ICellRendererParams) => {
|
||||||
const artists = useMemo(() => {
|
const artists = useMemo(() => {
|
||||||
if (!value) return null;
|
if (!value) return null;
|
||||||
return value.artists?.length ? value.artists : value.albumArtists;
|
return value.artists?.length ? value.artists : value.albumArtists;
|
||||||
@@ -102,6 +120,12 @@ export const CombinedTitleCell = ({ value, rowIndex, node }: ICellRendererParams
|
|||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
)}
|
)}
|
||||||
|
<ListCoverControls
|
||||||
|
context={context}
|
||||||
|
itemData={value}
|
||||||
|
itemType={context.itemType}
|
||||||
|
uniqueId={data?.uniqueId}
|
||||||
|
/>
|
||||||
</ImageWrapper>
|
</ImageWrapper>
|
||||||
<MetadataWrapper>
|
<MetadataWrapper>
|
||||||
<Text
|
<Text
|
||||||
|
|||||||
@@ -2,95 +2,95 @@ import type { ICellRendererParams } from '@ag-grid-community/core';
|
|||||||
import { Text } from '/@/renderer/components/text';
|
import { Text } from '/@/renderer/components/text';
|
||||||
import { CellContainer } from '/@/renderer/components/virtual-table/cells/generic-cell';
|
import { CellContainer } from '/@/renderer/components/virtual-table/cells/generic-cell';
|
||||||
|
|
||||||
const AnimatedSvg = () => {
|
// const AnimatedSvg = () => {
|
||||||
return (
|
// return (
|
||||||
<div style={{ height: '1rem', transform: 'rotate(180deg)', width: '1rem' }}>
|
// <div style={{ height: '1rem', transform: 'rotate(180deg)', width: '1rem' }}>
|
||||||
<svg
|
// <svg
|
||||||
viewBox="100 130 57 80"
|
// viewBox="100 130 57 80"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
// xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
// >
|
||||||
<g>
|
// <g>
|
||||||
<rect
|
// <rect
|
||||||
fill="var(--primary-color)"
|
// fill="var(--primary-color)"
|
||||||
height="80"
|
// height="80"
|
||||||
id="bar-1"
|
// id="bar-1"
|
||||||
width="12"
|
// width="12"
|
||||||
x="100"
|
// x="100"
|
||||||
y="130"
|
// y="130"
|
||||||
>
|
// >
|
||||||
<animate
|
// <animate
|
||||||
attributeName="height"
|
// attributeName="height"
|
||||||
begin="0.1s"
|
// begin="0.1s"
|
||||||
calcMode="spline"
|
// calcMode="spline"
|
||||||
dur="0.95s"
|
// dur="0.95s"
|
||||||
keySplines="0.42 0 0.58 1; 0.42 0 0.58 1"
|
// keySplines="0.42 0 0.58 1; 0.42 0 0.58 1"
|
||||||
keyTimes="0; 0.47368; 1"
|
// keyTimes="0; 0.47368; 1"
|
||||||
repeatCount="indefinite"
|
// repeatCount="indefinite"
|
||||||
values="80;15;80"
|
// values="80;15;80"
|
||||||
/>
|
// />
|
||||||
</rect>
|
// </rect>
|
||||||
<rect
|
// <rect
|
||||||
fill="var(--primary-color)"
|
// fill="var(--primary-color)"
|
||||||
height="80"
|
// height="80"
|
||||||
id="bar-2"
|
// id="bar-2"
|
||||||
width="12"
|
// width="12"
|
||||||
x="115"
|
// x="115"
|
||||||
y="130"
|
// y="130"
|
||||||
>
|
// >
|
||||||
<animate
|
// <animate
|
||||||
attributeName="height"
|
// attributeName="height"
|
||||||
begin="0.1s"
|
// begin="0.1s"
|
||||||
calcMode="spline"
|
// calcMode="spline"
|
||||||
dur="0.95s"
|
// dur="0.95s"
|
||||||
keySplines="0.45 0 0.55 1; 0.45 0 0.55 1"
|
// keySplines="0.45 0 0.55 1; 0.45 0 0.55 1"
|
||||||
keyTimes="0; 0.44444; 1"
|
// keyTimes="0; 0.44444; 1"
|
||||||
repeatCount="indefinite"
|
// repeatCount="indefinite"
|
||||||
values="25;80;25"
|
// values="25;80;25"
|
||||||
/>
|
// />
|
||||||
</rect>
|
// </rect>
|
||||||
<rect
|
// <rect
|
||||||
fill="var(--primary-color)"
|
// fill="var(--primary-color)"
|
||||||
height="80"
|
// height="80"
|
||||||
id="bar-3"
|
// id="bar-3"
|
||||||
width="12"
|
// width="12"
|
||||||
x="130"
|
// x="130"
|
||||||
y="130"
|
// y="130"
|
||||||
>
|
// >
|
||||||
<animate
|
// <animate
|
||||||
attributeName="height"
|
// attributeName="height"
|
||||||
begin="0.1s"
|
// begin="0.1s"
|
||||||
calcMode="spline"
|
// calcMode="spline"
|
||||||
dur="0.85s"
|
// dur="0.85s"
|
||||||
keySplines="0.65 0 0.35 1; 0.65 0 0.35 1"
|
// keySplines="0.65 0 0.35 1; 0.65 0 0.35 1"
|
||||||
keyTimes="0; 0.42105; 1"
|
// keyTimes="0; 0.42105; 1"
|
||||||
repeatCount="indefinite"
|
// repeatCount="indefinite"
|
||||||
values="80;10;80"
|
// values="80;10;80"
|
||||||
/>
|
// />
|
||||||
</rect>
|
// </rect>
|
||||||
<rect
|
// <rect
|
||||||
fill="var(--primary-color)"
|
// fill="var(--primary-color)"
|
||||||
height="80"
|
// height="80"
|
||||||
id="bar-4"
|
// id="bar-4"
|
||||||
width="12"
|
// width="12"
|
||||||
x="145"
|
// x="145"
|
||||||
y="130"
|
// y="130"
|
||||||
>
|
// >
|
||||||
<animate
|
// <animate
|
||||||
attributeName="height"
|
// attributeName="height"
|
||||||
begin="0.1s"
|
// begin="0.1s"
|
||||||
calcMode="spline"
|
// calcMode="spline"
|
||||||
dur="1.05s"
|
// dur="1.05s"
|
||||||
keySplines="0.42 0 0.58 1; 0.42 0 0.58 1"
|
// keySplines="0.42 0 0.58 1; 0.42 0 0.58 1"
|
||||||
keyTimes="0; 0.31579; 1"
|
// keyTimes="0; 0.31579; 1"
|
||||||
repeatCount="indefinite"
|
// repeatCount="indefinite"
|
||||||
values="30;80;30"
|
// values="30;80;30"
|
||||||
/>
|
// />
|
||||||
</rect>
|
// </rect>
|
||||||
</g>
|
// </g>
|
||||||
</svg>
|
// </svg>
|
||||||
</div>
|
// </div>
|
||||||
);
|
// );
|
||||||
};
|
// };
|
||||||
|
|
||||||
const StaticSvg = () => {
|
const StaticSvg = () => {
|
||||||
return (
|
return (
|
||||||
@@ -134,19 +134,14 @@ const StaticSvg = () => {
|
|||||||
|
|
||||||
export const RowIndexCell = ({ value, eGridCell }: ICellRendererParams) => {
|
export const RowIndexCell = ({ value, eGridCell }: ICellRendererParams) => {
|
||||||
const classList = eGridCell.classList;
|
const classList = eGridCell.classList;
|
||||||
const isFocused = classList.contains('focused');
|
// const isFocused = classList.contains('focused');
|
||||||
const isPlaying = classList.contains('playing');
|
const isPlaying = classList.contains('playing');
|
||||||
const isCurrentSong =
|
const isCurrentSong =
|
||||||
classList.contains('current-song-cell') || classList.contains('current-playlist-song-cell');
|
classList.contains('current-song-cell') || classList.contains('current-playlist-song-cell');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CellContainer $position="right">
|
<CellContainer $position="right">
|
||||||
{isPlaying &&
|
{isPlaying && (isCurrentSong ? <StaticSvg /> : null)}
|
||||||
(isFocused && isCurrentSong ? (
|
|
||||||
<AnimatedSvg />
|
|
||||||
) : isCurrentSong ? (
|
|
||||||
<StaticSvg />
|
|
||||||
) : null)}
|
|
||||||
<Text
|
<Text
|
||||||
$secondary
|
$secondary
|
||||||
align="right"
|
align="right"
|
||||||
|
|||||||
@@ -334,6 +334,7 @@ export const useVirtualTable = <TFilter extends BaseQuery<any>>({
|
|||||||
const onCellContextMenu = useHandleTableContextMenu(itemType, contextMenu);
|
const onCellContextMenu = useHandleTableContextMenu(itemType, contextMenu);
|
||||||
|
|
||||||
const context = {
|
const context = {
|
||||||
|
itemType,
|
||||||
onCellContextMenu,
|
onCellContextMenu,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -361,6 +361,7 @@ const tableColumns: { [key: string]: ColDef } = {
|
|||||||
? {
|
? {
|
||||||
albumArtists: params.data?.albumArtists,
|
albumArtists: params.data?.albumArtists,
|
||||||
artists: params.data?.artists,
|
artists: params.data?.artists,
|
||||||
|
id: params.data?.id,
|
||||||
imagePlaceholderUrl: params.data?.imagePlaceholderUrl,
|
imagePlaceholderUrl: params.data?.imagePlaceholderUrl,
|
||||||
imageUrl: params.data?.imageUrl,
|
imageUrl: params.data?.imageUrl,
|
||||||
name: params.data?.name,
|
name: params.data?.name,
|
||||||
|
|||||||
@@ -464,6 +464,7 @@ export const AlbumDetailContent = ({ tableRef, background }: AlbumDetailContentP
|
|||||||
context={{
|
context={{
|
||||||
currentSong,
|
currentSong,
|
||||||
isFocused,
|
isFocused,
|
||||||
|
itemType: LibraryItem.SONG,
|
||||||
onCellContextMenu,
|
onCellContextMenu,
|
||||||
status,
|
status,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -550,6 +550,9 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
|
|||||||
suppressLoadingOverlay
|
suppressLoadingOverlay
|
||||||
suppressRowDrag
|
suppressRowDrag
|
||||||
columnDefs={topSongsColumnDefs}
|
columnDefs={topSongsColumnDefs}
|
||||||
|
context={{
|
||||||
|
itemType: LibraryItem.SONG,
|
||||||
|
}}
|
||||||
enableCellChangeFlash={false}
|
enableCellChangeFlash={false}
|
||||||
getRowId={(data) => data.data.uniqueId}
|
getRowId={(data) => data.data.uniqueId}
|
||||||
rowData={topSongs}
|
rowData={topSongs}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { SetContextMenuItems } from '/@/renderer/features/context-menu/events';
|
|||||||
|
|
||||||
export const QUEUE_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
|
export const QUEUE_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
|
||||||
{ divider: true, id: 'removeFromQueue' },
|
{ divider: true, id: 'removeFromQueue' },
|
||||||
|
{ id: 'moveToNextOfQueue' },
|
||||||
{ id: 'moveToBottomOfQueue' },
|
{ id: 'moveToBottomOfQueue' },
|
||||||
{ divider: true, id: 'moveToTopOfQueue' },
|
{ divider: true, id: 'moveToTopOfQueue' },
|
||||||
{ divider: true, id: 'addToPlaylist' },
|
{ divider: true, id: 'addToPlaylist' },
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
RiAddBoxFill,
|
RiAddBoxFill,
|
||||||
RiAddCircleFill,
|
RiAddCircleFill,
|
||||||
RiArrowDownLine,
|
RiArrowDownLine,
|
||||||
|
RiArrowGoForwardLine,
|
||||||
RiArrowRightSFill,
|
RiArrowRightSFill,
|
||||||
RiArrowUpLine,
|
RiArrowUpLine,
|
||||||
RiDeleteBinFill,
|
RiDeleteBinFill,
|
||||||
@@ -609,7 +610,19 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const playbackType = usePlaybackType();
|
const playbackType = usePlaybackType();
|
||||||
const { moveToBottomOfQueue, moveToTopOfQueue, removeFromQueue } = useQueueControls();
|
const { moveToNextOfQueue, moveToBottomOfQueue, moveToTopOfQueue, removeFromQueue } =
|
||||||
|
useQueueControls();
|
||||||
|
|
||||||
|
const handleMoveToNext = useCallback(() => {
|
||||||
|
const uniqueIds = ctx.dataNodes?.map((row) => row.data.uniqueId);
|
||||||
|
if (!uniqueIds?.length) return;
|
||||||
|
|
||||||
|
const playerData = moveToNextOfQueue(uniqueIds);
|
||||||
|
|
||||||
|
if (playbackType === PlaybackType.LOCAL) {
|
||||||
|
setQueueNext(playerData);
|
||||||
|
}
|
||||||
|
}, [ctx.dataNodes, moveToNextOfQueue, playbackType]);
|
||||||
|
|
||||||
const handleMoveToBottom = useCallback(() => {
|
const handleMoveToBottom = useCallback(() => {
|
||||||
const uniqueIds = ctx.dataNodes?.map((row) => row.data.uniqueId);
|
const uniqueIds = ctx.dataNodes?.map((row) => row.data.uniqueId);
|
||||||
@@ -758,6 +771,12 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
|
|||||||
leftIcon: <RiArrowDownLine size="1.1rem" />,
|
leftIcon: <RiArrowDownLine size="1.1rem" />,
|
||||||
onClick: handleMoveToBottom,
|
onClick: handleMoveToBottom,
|
||||||
},
|
},
|
||||||
|
moveToNextOfQueue: {
|
||||||
|
id: 'moveToNext',
|
||||||
|
label: t('page.contextMenu.moveToNext', { postProcess: 'sentenceCase' }),
|
||||||
|
leftIcon: <RiArrowGoForwardLine size="1.1rem" />,
|
||||||
|
onClick: handleMoveToNext,
|
||||||
|
},
|
||||||
moveToTopOfQueue: {
|
moveToTopOfQueue: {
|
||||||
id: 'moveToTopOfQueue',
|
id: 'moveToTopOfQueue',
|
||||||
label: t('page.contextMenu.moveToTop', { postProcess: 'sentenceCase' }),
|
label: t('page.contextMenu.moveToTop', { postProcess: 'sentenceCase' }),
|
||||||
@@ -904,6 +923,7 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
|
|||||||
handleDeselectAll,
|
handleDeselectAll,
|
||||||
ctx.data,
|
ctx.data,
|
||||||
handleDownload,
|
handleDownload,
|
||||||
|
handleMoveToNext,
|
||||||
handleMoveToBottom,
|
handleMoveToBottom,
|
||||||
handleMoveToTop,
|
handleMoveToTop,
|
||||||
handleSimilar,
|
handleSimilar,
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export type ContextMenuItemType =
|
|||||||
| 'shareItem'
|
| 'shareItem'
|
||||||
| 'deletePlaylist'
|
| 'deletePlaylist'
|
||||||
| 'createPlaylist'
|
| 'createPlaylist'
|
||||||
|
| 'moveToNextOfQueue'
|
||||||
| 'moveToBottomOfQueue'
|
| 'moveToBottomOfQueue'
|
||||||
| 'moveToTopOfQueue'
|
| 'moveToTopOfQueue'
|
||||||
| 'removeFromQueue'
|
| 'removeFromQueue'
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
useCurrentSong,
|
useCurrentSong,
|
||||||
useCurrentStatus,
|
useCurrentStatus,
|
||||||
useDiscordSetttings,
|
useDiscordSetttings,
|
||||||
|
useGeneralSettings,
|
||||||
usePlayerStore,
|
usePlayerStore,
|
||||||
} from '/@/renderer/store';
|
} from '/@/renderer/store';
|
||||||
import { SetActivity } from '@xhayper/discord-rpc';
|
import { SetActivity } from '@xhayper/discord-rpc';
|
||||||
@@ -16,6 +17,7 @@ const discordRpc = isElectron() ? window.electron.discordRpc : null;
|
|||||||
export const useDiscordRpc = () => {
|
export const useDiscordRpc = () => {
|
||||||
const intervalRef = useRef(0);
|
const intervalRef = useRef(0);
|
||||||
const discordSettings = useDiscordSetttings();
|
const discordSettings = useDiscordSetttings();
|
||||||
|
const generalSettings = useGeneralSettings();
|
||||||
const currentSong = useCurrentSong();
|
const currentSong = useCurrentSong();
|
||||||
const currentStatus = useCurrentStatus();
|
const currentStatus = useCurrentStatus();
|
||||||
|
|
||||||
@@ -67,6 +69,19 @@ export const useDiscordRpc = () => {
|
|||||||
activity.largeImageKey = song?.imageUrl;
|
activity.largeImageKey = song?.imageUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (generalSettings.lastfmApiKey && song?.album && song?.artists.length) {
|
||||||
|
console.log('Fetching album info for', song.album, song.artists[0].name);
|
||||||
|
const albumInfo = await fetch(
|
||||||
|
`https://ws.audioscrobbler.com/2.0/?method=album.getinfo&api_key=${generalSettings.lastfmApiKey}&artist=${encodeURIComponent(song.artistName)}&album=${encodeURIComponent(song.album)}&format=json`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const albumInfoJson = await albumInfo.json();
|
||||||
|
|
||||||
|
if (albumInfoJson.album?.image?.[3]['#text']) {
|
||||||
|
activity.largeImageKey = albumInfoJson.album.image[3]['#text'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Fall back to default icon if not set
|
// Fall back to default icon if not set
|
||||||
if (!activity.largeImageKey) {
|
if (!activity.largeImageKey) {
|
||||||
activity.largeImageKey = 'icon';
|
activity.largeImageKey = 'icon';
|
||||||
@@ -79,6 +94,7 @@ export const useDiscordRpc = () => {
|
|||||||
discordSettings.enableIdle,
|
discordSettings.enableIdle,
|
||||||
discordSettings.showAsListening,
|
discordSettings.showAsListening,
|
||||||
discordSettings.showServerImage,
|
discordSettings.showServerImage,
|
||||||
|
generalSettings.lastfmApiKey,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import isElectron from 'is-electron';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
RiArrowDownLine,
|
RiArrowDownLine,
|
||||||
|
RiArrowGoForwardLine,
|
||||||
RiArrowUpLine,
|
RiArrowUpLine,
|
||||||
RiShuffleLine,
|
RiShuffleLine,
|
||||||
RiDeleteBinLine,
|
RiDeleteBinLine,
|
||||||
@@ -30,14 +31,32 @@ interface PlayQueueListOptionsProps {
|
|||||||
|
|
||||||
export const PlayQueueListControls = ({ type, tableRef }: PlayQueueListOptionsProps) => {
|
export const PlayQueueListControls = ({ type, tableRef }: PlayQueueListOptionsProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { clearQueue, moveToBottomOfQueue, moveToTopOfQueue, shuffleQueue, removeFromQueue } =
|
const {
|
||||||
useQueueControls();
|
clearQueue,
|
||||||
|
moveToBottomOfQueue,
|
||||||
|
moveToNextOfQueue,
|
||||||
|
moveToTopOfQueue,
|
||||||
|
shuffleQueue,
|
||||||
|
removeFromQueue,
|
||||||
|
} = useQueueControls();
|
||||||
|
|
||||||
const { pause } = usePlayerControls();
|
const { pause } = usePlayerControls();
|
||||||
|
|
||||||
const playbackType = usePlaybackType();
|
const playbackType = usePlaybackType();
|
||||||
const setCurrentTime = useSetCurrentTime();
|
const setCurrentTime = useSetCurrentTime();
|
||||||
|
|
||||||
|
const handleMoveToNext = () => {
|
||||||
|
const selectedRows = tableRef?.current?.grid.api.getSelectedRows();
|
||||||
|
const uniqueIds = selectedRows?.map((row) => row.uniqueId);
|
||||||
|
if (!uniqueIds?.length) return;
|
||||||
|
|
||||||
|
const playerData = moveToNextOfQueue(uniqueIds);
|
||||||
|
|
||||||
|
if (playbackType === PlaybackType.LOCAL) {
|
||||||
|
setQueueNext(playerData);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleMoveToBottom = () => {
|
const handleMoveToBottom = () => {
|
||||||
const selectedRows = tableRef?.current?.grid.api.getSelectedRows();
|
const selectedRows = tableRef?.current?.grid.api.getSelectedRows();
|
||||||
const uniqueIds = selectedRows?.map((row) => row.uniqueId);
|
const uniqueIds = selectedRows?.map((row) => row.uniqueId);
|
||||||
@@ -124,6 +143,15 @@ export const PlayQueueListControls = ({ type, tableRef }: PlayQueueListOptionsPr
|
|||||||
>
|
>
|
||||||
<RiShuffleLine size="1.1rem" />
|
<RiShuffleLine size="1.1rem" />
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
compact
|
||||||
|
size="md"
|
||||||
|
tooltip={{ label: t('action.moveToNext', { postProcess: 'sentenceCase' }) }}
|
||||||
|
variant="default"
|
||||||
|
onClick={handleMoveToNext}
|
||||||
|
>
|
||||||
|
<RiArrowGoForwardLine size="1.1rem" />
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
compact
|
compact
|
||||||
size="md"
|
size="md"
|
||||||
|
|||||||
@@ -256,7 +256,10 @@ export const PlayQueue = forwardRef(({ type }: QueueProps, ref: Ref<any>) => {
|
|||||||
columnDefs={columnDefs}
|
columnDefs={columnDefs}
|
||||||
context={{
|
context={{
|
||||||
currentSong,
|
currentSong,
|
||||||
|
handleDoubleClick,
|
||||||
isFocused,
|
isFocused,
|
||||||
|
isQueue: true,
|
||||||
|
itemType: LibraryItem.SONG,
|
||||||
onCellContextMenu,
|
onCellContextMenu,
|
||||||
status,
|
status,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ export const useRightControls = () => {
|
|||||||
const handleVolumeWheel = useCallback(
|
const handleVolumeWheel = useCallback(
|
||||||
(e: WheelEvent<HTMLDivElement | HTMLButtonElement>) => {
|
(e: WheelEvent<HTMLDivElement | HTMLButtonElement>) => {
|
||||||
let volumeToSet;
|
let volumeToSet;
|
||||||
if (e.deltaY > 0) {
|
if (e.deltaY > 0 || e.deltaX > 0) {
|
||||||
volumeToSet = calculateVolumeDown(volume, volumeWheelStep);
|
volumeToSet = calculateVolumeDown(volume, volumeWheelStep);
|
||||||
} else {
|
} else {
|
||||||
volumeToSet = calculateVolumeUp(volume, volumeWheelStep);
|
volumeToSet = calculateVolumeUp(volume, volumeWheelStep);
|
||||||
|
|||||||
@@ -301,6 +301,7 @@ export const PlaylistDetailSongListContent = ({ songs, tableRef }: PlaylistDetai
|
|||||||
context={{
|
context={{
|
||||||
currentSong,
|
currentSong,
|
||||||
isFocused,
|
isFocused,
|
||||||
|
itemType: LibraryItem.SONG,
|
||||||
onCellContextMenu: handleContextMenu,
|
onCellContextMenu: handleContextMenu,
|
||||||
status,
|
status,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -4,12 +4,17 @@ import {
|
|||||||
SettingOption,
|
SettingOption,
|
||||||
SettingsSection,
|
SettingsSection,
|
||||||
} from '/@/renderer/features/settings/components/settings-section';
|
} from '/@/renderer/features/settings/components/settings-section';
|
||||||
import { useDiscordSetttings, useSettingsStoreActions } from '/@/renderer/store';
|
import {
|
||||||
|
useDiscordSetttings,
|
||||||
|
useSettingsStoreActions,
|
||||||
|
useGeneralSettings,
|
||||||
|
} from '/@/renderer/store';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export const DiscordSettings = () => {
|
export const DiscordSettings = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const settings = useDiscordSetttings();
|
const settings = useDiscordSetttings();
|
||||||
|
const generalSettings = useGeneralSettings();
|
||||||
const { setSettings } = useSettingsStoreActions();
|
const { setSettings } = useSettingsStoreActions();
|
||||||
|
|
||||||
const discordOptions: SettingOption[] = [
|
const discordOptions: SettingOption[] = [
|
||||||
@@ -142,6 +147,31 @@ export const DiscordSettings = () => {
|
|||||||
postProcess: 'sentenceCase',
|
postProcess: 'sentenceCase',
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<TextInput
|
||||||
|
defaultValue={generalSettings.lastfmApiKey}
|
||||||
|
onBlur={(e) => {
|
||||||
|
setSettings({
|
||||||
|
general: {
|
||||||
|
...generalSettings,
|
||||||
|
lastfmApiKey: e.currentTarget.value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: t('setting.lastfmApiKey', {
|
||||||
|
context: 'description',
|
||||||
|
lastfm: 'Last.fm',
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
}),
|
||||||
|
isHidden: !isElectron(),
|
||||||
|
title: t('setting.lastfmApiKey', {
|
||||||
|
lastfm: 'Last.fm',
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
}),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return <SettingsSection options={discordOptions} />;
|
return <SettingsSection options={discordOptions} />;
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ export const SimilarSongsList = ({ count, fullScreen, song }: SimilarSongsListPr
|
|||||||
columnDefs={columnDefs}
|
columnDefs={columnDefs}
|
||||||
context={{
|
context={{
|
||||||
count,
|
count,
|
||||||
|
itemType: LibraryItem.SONG,
|
||||||
onCellContextMenu,
|
onCellContextMenu,
|
||||||
song,
|
song,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ export interface PlayerState {
|
|||||||
seek: boolean;
|
seek: boolean;
|
||||||
shuffledIndex: number;
|
shuffledIndex: number;
|
||||||
song?: QueueSong;
|
song?: QueueSong;
|
||||||
speed: number;
|
|
||||||
status: PlayerStatus;
|
status: PlayerStatus;
|
||||||
time: number;
|
time: number;
|
||||||
};
|
};
|
||||||
@@ -32,6 +31,7 @@ export interface PlayerState {
|
|||||||
};
|
};
|
||||||
repeat: PlayerRepeat;
|
repeat: PlayerRepeat;
|
||||||
shuffle: PlayerShuffle;
|
shuffle: PlayerShuffle;
|
||||||
|
speed: number;
|
||||||
volume: number;
|
volume: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,6 +72,7 @@ export interface PlayerSlice extends PlayerState {
|
|||||||
getQueueData: () => QueueData;
|
getQueueData: () => QueueData;
|
||||||
incrementPlayCount: (ids: string[]) => string[];
|
incrementPlayCount: (ids: string[]) => string[];
|
||||||
moveToBottomOfQueue: (uniqueIds: string[]) => PlayerData;
|
moveToBottomOfQueue: (uniqueIds: string[]) => PlayerData;
|
||||||
|
moveToNextOfQueue: (uniqueIds: string[]) => PlayerData;
|
||||||
moveToTopOfQueue: (uniqueIds: string[]) => PlayerData;
|
moveToTopOfQueue: (uniqueIds: string[]) => PlayerData;
|
||||||
next: () => PlayerData;
|
next: () => PlayerData;
|
||||||
pause: () => void;
|
pause: () => void;
|
||||||
@@ -536,6 +537,34 @@ export const usePlayerStore = create<PlayerSlice>()(
|
|||||||
|
|
||||||
return get().actions.getPlayerData();
|
return get().actions.getPlayerData();
|
||||||
},
|
},
|
||||||
|
moveToNextOfQueue: (uniqueIds) => {
|
||||||
|
const queue = get().queue.default;
|
||||||
|
const songsToMove = queue.filter((song) =>
|
||||||
|
uniqueIds.includes(song.uniqueId),
|
||||||
|
);
|
||||||
|
const currentSong = get().current.song;
|
||||||
|
const currentPosition =
|
||||||
|
get().current.index -
|
||||||
|
queue
|
||||||
|
.slice(0, get().current.index)
|
||||||
|
.filter((song) => uniqueIds.includes(song.uniqueId)).length;
|
||||||
|
const songsToStay = queue.filter(
|
||||||
|
(song) => !uniqueIds.includes(song.uniqueId),
|
||||||
|
);
|
||||||
|
const newQueue = [
|
||||||
|
...songsToStay.slice(0, currentPosition + 1),
|
||||||
|
...songsToMove,
|
||||||
|
...songsToStay.slice(currentPosition + 1),
|
||||||
|
];
|
||||||
|
const newCurrentSongIndex = newQueue.findIndex(
|
||||||
|
(song) => song.uniqueId === currentSong?.uniqueId,
|
||||||
|
);
|
||||||
|
set((state) => {
|
||||||
|
state.queue.default = newQueue;
|
||||||
|
state.current.index = newCurrentSongIndex;
|
||||||
|
});
|
||||||
|
return get().actions.getPlayerData();
|
||||||
|
},
|
||||||
moveToTopOfQueue: (uniqueIds) => {
|
moveToTopOfQueue: (uniqueIds) => {
|
||||||
const queue = get().queue.default;
|
const queue = get().queue.default;
|
||||||
|
|
||||||
@@ -805,7 +834,7 @@ export const usePlayerStore = create<PlayerSlice>()(
|
|||||||
},
|
},
|
||||||
setCurrentSpeed: (speed) => {
|
setCurrentSpeed: (speed) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
state.current.speed = speed;
|
state.speed = speed;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
setCurrentTime: (time, seek = false) => {
|
setCurrentTime: (time, seek = false) => {
|
||||||
@@ -1011,7 +1040,6 @@ export const usePlayerStore = create<PlayerSlice>()(
|
|||||||
seek: false,
|
seek: false,
|
||||||
shuffledIndex: 0,
|
shuffledIndex: 0,
|
||||||
song: {} as QueueSong,
|
song: {} as QueueSong,
|
||||||
speed: 1.0,
|
|
||||||
status: PlayerStatus.PAUSED,
|
status: PlayerStatus.PAUSED,
|
||||||
time: 0,
|
time: 0,
|
||||||
},
|
},
|
||||||
@@ -1026,6 +1054,7 @@ export const usePlayerStore = create<PlayerSlice>()(
|
|||||||
},
|
},
|
||||||
repeat: PlayerRepeat.NONE,
|
repeat: PlayerRepeat.NONE,
|
||||||
shuffle: PlayerShuffle.NONE,
|
shuffle: PlayerShuffle.NONE,
|
||||||
|
speed: 1.0,
|
||||||
transcode: {
|
transcode: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
@@ -1076,6 +1105,7 @@ export const useQueueControls = () =>
|
|||||||
addToQueue: state.actions.addToQueue,
|
addToQueue: state.actions.addToQueue,
|
||||||
clearQueue: state.actions.clearQueue,
|
clearQueue: state.actions.clearQueue,
|
||||||
moveToBottomOfQueue: state.actions.moveToBottomOfQueue,
|
moveToBottomOfQueue: state.actions.moveToBottomOfQueue,
|
||||||
|
moveToNextOfQueue: state.actions.moveToNextOfQueue,
|
||||||
moveToTopOfQueue: state.actions.moveToTopOfQueue,
|
moveToTopOfQueue: state.actions.moveToTopOfQueue,
|
||||||
removeFromQueue: state.actions.removeFromQueue,
|
removeFromQueue: state.actions.removeFromQueue,
|
||||||
reorderQueue: state.actions.reorderQueue,
|
reorderQueue: state.actions.reorderQueue,
|
||||||
@@ -1130,7 +1160,7 @@ export const useVolume = () => usePlayerStore((state) => state.volume);
|
|||||||
|
|
||||||
export const useMuted = () => usePlayerStore((state) => state.muted);
|
export const useMuted = () => usePlayerStore((state) => state.muted);
|
||||||
|
|
||||||
export const useSpeed = () => usePlayerStore((state) => state.current.speed);
|
export const useSpeed = () => usePlayerStore((state) => state.speed);
|
||||||
|
|
||||||
export const usePlayerFallback = () => usePlayerStore((state) => state.fallback);
|
export const usePlayerFallback = () => usePlayerStore((state) => state.fallback);
|
||||||
|
|
||||||
|
|||||||
@@ -232,6 +232,7 @@ export interface SettingsState {
|
|||||||
homeFeature: boolean;
|
homeFeature: boolean;
|
||||||
homeItems: SortableItem<HomeItem>[];
|
homeItems: SortableItem<HomeItem>[];
|
||||||
language: string;
|
language: string;
|
||||||
|
lastfmApiKey: string;
|
||||||
nativeAspectRatio: boolean;
|
nativeAspectRatio: boolean;
|
||||||
passwordStore?: string;
|
passwordStore?: string;
|
||||||
playButtonBehavior: Play;
|
playButtonBehavior: Play;
|
||||||
@@ -377,6 +378,7 @@ const initialState: SettingsState = {
|
|||||||
homeFeature: true,
|
homeFeature: true,
|
||||||
homeItems,
|
homeItems,
|
||||||
language: 'en',
|
language: 'en',
|
||||||
|
lastfmApiKey: '',
|
||||||
nativeAspectRatio: false,
|
nativeAspectRatio: false,
|
||||||
passwordStore: undefined,
|
passwordStore: undefined,
|
||||||
playButtonBehavior: Play.NOW,
|
playButtonBehavior: Play.NOW,
|
||||||
|
|||||||
@@ -69,6 +69,8 @@ export const toServerType = (value?: string): ServerType | null => {
|
|||||||
return ServerType.JELLYFIN;
|
return ServerType.JELLYFIN;
|
||||||
case ServerType.NAVIDROME:
|
case ServerType.NAVIDROME:
|
||||||
return ServerType.NAVIDROME;
|
return ServerType.NAVIDROME;
|
||||||
|
case ServerType.SUBSONIC:
|
||||||
|
return ServerType.SUBSONIC;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user