[enhancement]: better version checks for lyrics, Navidrome (#529)

- Actually make serverfeatures partial
- Navidrome: only set multiple structured lyrics if extension exists
- Navidrome/Subsonic: minor type checking of OS extension (Navidrome implementation detail)
- Jellyfin: add separate knob for lyrics. Note, this should also probably be behind some version check...
This commit is contained in:
Kendall Garner 2024-03-05 08:31:51 +00:00 committed by GitHub
parent d52d9136b8
commit 73845a9432
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 34 additions and 34 deletions

View File

@ -1,6 +1,7 @@
export enum ServerFeature { export enum ServerFeature {
MULTIPLE_STRUCTURED_LYRICS = 'multipleStructuredLyrics',
SINGLE_STRUCTURED_LYRIC = 'singleStructuredLyric',
SMART_PLAYLISTS = 'smartPlaylists', SMART_PLAYLISTS = 'smartPlaylists',
SONG_LYRICS = 'songLyrics',
} }
export type ServerFeatures = Record<Partial<ServerFeature>, boolean>; export type ServerFeatures = Partial<Record<ServerFeature, boolean>>;

View File

@ -961,8 +961,7 @@ const getServerInfo = async (args: ServerInfoArgs): Promise<ServerInfo> => {
} }
const features: ServerFeatures = { const features: ServerFeatures = {
smartPlaylists: false, singleStructuredLyric: true,
songLyrics: true,
}; };
return { return {

View File

@ -50,6 +50,7 @@ import {
} from '../types'; } from '../types';
import { hasFeature } from '/@/renderer/api/utils'; import { hasFeature } from '/@/renderer/api/utils';
import { ServerFeature, ServerFeatures } from '/@/renderer/api/features.types'; import { ServerFeature, ServerFeatures } from '/@/renderer/api/features.types';
import { SubsonicExtensions } from '/@/renderer/api/subsonic/subsonic-types';
const authenticate = async ( const authenticate = async (
url: string, url: string,
@ -528,20 +529,21 @@ const getServerInfo = async (args: ServerInfoArgs): Promise<ServerInfo> => {
throw new Error('Failed to get server extensions'); throw new Error('Failed to get server extensions');
} }
for (const extension of res.body.openSubsonicExtensions) { // The type here isn't necessarily an array (even though it's supposed to be). This is
navidromeFeatures[extension.name] = extension.versions; // an implementation detail of Navidrome 0.50. Do a type check to make sure it's actually
// an array, and not an empty object.
if (Array.isArray(res.body.openSubsonicExtensions)) {
for (const extension of res.body.openSubsonicExtensions) {
navidromeFeatures[extension.name] = extension.versions;
}
} }
} }
const features: ServerFeatures = { const features: ServerFeatures = {
smartPlaylists: false, multipleStructuredLyrics: !!navidromeFeatures[SubsonicExtensions.SONG_LYRICS],
songLyrics: true, smartPlaylists: !!navidromeFeatures[NavidromeExtensions.SMART_PLAYLISTS],
}; };
if (navidromeFeatures[NavidromeExtensions.SMART_PLAYLISTS]) {
features[ServerFeature.SMART_PLAYLISTS] = true;
}
return { features, id: apiClientProps.server?.id, version: ping.body.serverVersion! }; return { features, id: apiClientProps.server?.id, version: ping.body.serverVersion! };
}; };

View File

@ -384,10 +384,7 @@ const getServerInfo = async (args: ServerInfoArgs): Promise<ServerInfo> => {
throw new Error('Failed to ping server'); throw new Error('Failed to ping server');
} }
const features: ServerFeatures = { const features: ServerFeatures = {};
smartPlaylists: false,
songLyrics: false,
};
if (!ping.body.openSubsonic || !ping.body.serverVersion) { if (!ping.body.openSubsonic || !ping.body.serverVersion) {
return { features, version: ping.body.version }; return { features, version: ping.body.version };
@ -400,12 +397,14 @@ const getServerInfo = async (args: ServerInfoArgs): Promise<ServerInfo> => {
} }
const subsonicFeatures: Record<string, number[]> = {}; const subsonicFeatures: Record<string, number[]> = {};
for (const extension of res.body.openSubsonicExtensions) { if (Array.isArray(res.body.openSubsonicExtensions)) {
subsonicFeatures[extension.name] = extension.versions; for (const extension of res.body.openSubsonicExtensions) {
subsonicFeatures[extension.name] = extension.versions;
}
} }
if (subsonicFeatures[SubsonicExtensions.SONG_LYRICS]) { if (subsonicFeatures[SubsonicExtensions.SONG_LYRICS]) {
features.songLyrics = true; features.multipleStructuredLyrics = true;
} }
return { features, id: apiClientProps.server?.id, version: ping.body.serverVersion }; return { features, id: apiClientProps.server?.id, version: ping.body.serverVersion };

View File

@ -218,7 +218,7 @@ const extension = z.object({
}); });
const serverInfo = z.object({ const serverInfo = z.object({
openSubsonicExtensions: z.array(extension), openSubsonicExtensions: z.array(extension).optional(),
}); });
const structuredLyricsParameters = z.object({ const structuredLyricsParameters = z.object({
@ -266,7 +266,6 @@ export enum SubsonicExtensions {
TRANSCODE_OFFSET = 'transcodeOffset', TRANSCODE_OFFSET = 'transcodeOffset',
} }
export const ssType = { export const ssType = {
_parameters: { _parameters: {
albumList: albumListParameters, albumList: albumListParameters,

View File

@ -45,5 +45,5 @@ export const hasFeature = (server: ServerListItem | null, feature: ServerFeature
return false; return false;
} }
return server.features[feature]; return server.features[feature] ?? false;
}; };

View File

@ -96,7 +96,18 @@ export const useSongLyricsBySong = (
if (!server) throw new Error('Server not found'); if (!server) throw new Error('Server not found');
if (!song) return null; if (!song) return null;
if (server.type === ServerType.JELLYFIN) { if (hasFeature(server, ServerFeature.MULTIPLE_STRUCTURED_LYRICS)) {
const subsonicLyrics = await api.controller
.getStructuredLyrics({
apiClientProps: { server, signal },
query: { songId: song.id },
})
.catch(console.error);
if (subsonicLyrics) {
return subsonicLyrics;
}
} else if (hasFeature(server, ServerFeature.SINGLE_STRUCTURED_LYRIC)) {
const jfLyrics = await api.controller const jfLyrics = await api.controller
.getLyrics({ .getLyrics({
apiClientProps: { server, signal }, apiClientProps: { server, signal },
@ -113,17 +124,6 @@ export const useSongLyricsBySong = (
source: server?.name ?? 'music server', source: server?.name ?? 'music server',
}; };
} }
} else if (hasFeature(server, ServerFeature.SONG_LYRICS)) {
const subsonicLyrics = await api.controller
.getStructuredLyrics({
apiClientProps: { server, signal },
query: { songId: song.id },
})
.catch(console.error);
if (subsonicLyrics) {
return subsonicLyrics;
}
} else if (song.lyrics) { } else if (song.lyrics) {
return { return {
artist: song.artists?.[0]?.name, artist: song.artists?.[0]?.name,