Merge pull request #484 from kgarner7/fix-structured-lyrics

[bugfix/enhancement]: Support Navidrome structured lyrics
This commit is contained in:
Jeff 2024-02-13 16:17:24 -08:00 committed by GitHub
commit 83d5fee442
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 366 additions and 55 deletions

View file

@ -50,6 +50,21 @@ export const contract = c.router({
200: ssType._response.randomSongList,
},
},
getServerInfo: {
method: 'GET',
path: 'getOpenSubsonicExtensions.view',
responses: {
200: ssType._response.serverInfo,
},
},
getStructuredLyrics: {
method: 'GET',
path: 'getLyricsBySongId.view',
query: ssType._parameters.structuredLyrics,
responses: {
200: ssType._response.structuredLyrics,
},
},
getTopSongsList: {
method: 'GET',
path: 'getTopSongs.view',
@ -58,6 +73,13 @@ export const contract = c.router({
200: ssType._response.topSongsList,
},
},
ping: {
method: 'GET',
path: 'ping.view',
responses: {
200: ssType._response.ping,
},
},
removeFavorite: {
method: 'GET',
path: 'unstar.view',

View file

@ -21,6 +21,10 @@ import {
SearchResponse,
RandomSongListResponse,
RandomSongListArgs,
ServerInfo,
ServerInfoArgs,
StructuredLyricsArgs,
StructuredLyric,
} from '/@/renderer/api/types';
import { randomString } from '/@/renderer/utils';
@ -368,12 +372,86 @@ const getRandomSongList = async (args: RandomSongListArgs): Promise<RandomSongLi
};
};
const getServerInfo = async (args: ServerInfoArgs): Promise<ServerInfo> => {
const { apiClientProps } = args;
const ping = await ssApiClient(apiClientProps).ping();
if (ping.status !== 200) {
throw new Error('Failed to ping server');
}
if (!ping.body.openSubsonic || !ping.body.serverVersion) {
return { version: ping.body.version };
}
const res = await ssApiClient(apiClientProps).getServerInfo();
if (res.status !== 200) {
throw new Error('Failed to get server extensions');
}
const features: Record<string, number[]> = {};
for (const extension of res.body.openSubsonicExtensions) {
features[extension.name] = extension.versions;
}
return { features, id: apiClientProps.server?.id, version: ping.body.serverVersion };
};
export const getStructuredLyrics = async (
args: StructuredLyricsArgs,
): Promise<StructuredLyric[]> => {
const { query, apiClientProps } = args;
const res = await ssApiClient(apiClientProps).getStructuredLyrics({
query: {
id: query.songId,
},
});
if (res.status !== 200) {
throw new Error('Failed to get structured lyrics');
}
const lyrics = res.body.lyricsList?.structuredLyrics;
if (!lyrics) {
return [];
}
return lyrics.map((lyric) => {
const baseLyric = {
artist: lyric.displayArtist || '',
lang: lyric.lang,
name: lyric.displayTitle || '',
remote: false,
source: apiClientProps.server?.name || 'music server',
};
if (lyric.synced) {
return {
...baseLyric,
lyrics: lyric.line.map((line) => [line.start!, line.value]),
synced: true,
};
}
return {
...baseLyric,
lyrics: lyric.line.map((line) => [line.value]).join('\n'),
synced: false,
};
});
};
export const ssController = {
authenticate,
createFavorite,
getArtistInfo,
getMusicFolderList,
getRandomSongList,
getServerInfo,
getStructuredLyrics,
getTopSongList,
removeFavorite,
scrobble,

View file

@ -206,6 +206,47 @@ const randomSongList = z.object({
}),
});
const ping = z.object({
openSubsonic: z.boolean().optional(),
serverVersion: z.string().optional(),
version: z.string(),
});
const extension = z.object({
name: z.string(),
versions: z.number().array(),
});
const serverInfo = z.object({
openSubsonicExtensions: z.array(extension),
});
const structuredLyricsParameters = z.object({
id: z.string(),
});
const lyricLine = z.object({
start: z.number().optional(),
value: z.string(),
});
const structuredLyric = z.object({
displayArtist: z.string().optional(),
displayTitle: z.string().optional(),
lang: z.string(),
line: z.array(lyricLine),
offset: z.number().optional(),
synced: z.boolean(),
});
const structuredLyrics = z.object({
lyricsList: z
.object({
structuredLyrics: z.array(structuredLyric).optional(),
})
.optional(),
});
export const ssType = {
_parameters: {
albumList: albumListParameters,
@ -217,6 +258,7 @@ export const ssType = {
scrobble: scrobbleParameters,
search3: search3Parameters,
setRating: setRatingParameters,
structuredLyrics: structuredLyricsParameters,
topSongsList: topSongsListParameters,
},
_response: {
@ -229,12 +271,15 @@ export const ssType = {
baseResponse,
createFavorite,
musicFolderList,
ping,
randomSongList,
removeFavorite,
scrobble,
search3,
serverInfo,
setRating,
song,
structuredLyrics,
topSongsList,
},
};