[enhancement]: use jellyfin 10.9.0 lyrics
This commit is contained in:
parent
cb2597d2c8
commit
087ea44737
4 changed files with 63 additions and 37 deletions
|
@ -204,7 +204,7 @@ export const contract = c.router({
|
|||
},
|
||||
getSongLyrics: {
|
||||
method: 'GET',
|
||||
path: 'users/:userId/Items/:id/Lyrics',
|
||||
path: 'audio/:id/Lyrics',
|
||||
responses: {
|
||||
200: jfType._response.lyrics,
|
||||
404: jfType._response.error,
|
||||
|
|
|
@ -61,7 +61,8 @@ import packageJson from '../../../../package.json';
|
|||
import { z } from 'zod';
|
||||
import { JFSongListSort, JFSortOrder } from '/@/renderer/api/jellyfin.types';
|
||||
import isElectron from 'is-electron';
|
||||
import { ServerFeatures } from '/@/renderer/api/features-types';
|
||||
import { ServerFeature } from '/@/renderer/api/features-types';
|
||||
import { VersionInfo, getFeatures } from '/@/renderer/api/utils';
|
||||
|
||||
const formatCommaDelimitedString = (value: string[]) => {
|
||||
return value.join(',');
|
||||
|
@ -937,7 +938,6 @@ const getLyrics = async (args: LyricsArgs): Promise<LyricsResponse> => {
|
|||
const res = await jfApiClient(apiClientProps).getSongLyrics({
|
||||
params: {
|
||||
id: query.songId,
|
||||
userId: apiClientProps.server?.userId,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -969,6 +969,8 @@ const getSongDetail = async (args: SongDetailArgs): Promise<SongDetailResponse>
|
|||
return jfNormalize.song(res.body, apiClientProps.server, '');
|
||||
};
|
||||
|
||||
const VERSION_INFO: VersionInfo = [['10.9.0', { [ServerFeature.LYRICS_SINGLE_STRUCTURED]: [1] }]];
|
||||
|
||||
const getServerInfo = async (args: ServerInfoArgs): Promise<ServerInfo> => {
|
||||
const { apiClientProps } = args;
|
||||
|
||||
|
@ -978,9 +980,7 @@ const getServerInfo = async (args: ServerInfoArgs): Promise<ServerInfo> => {
|
|||
throw new Error('Failed to get server info');
|
||||
}
|
||||
|
||||
const features: ServerFeatures = {
|
||||
lyricsSingleStructured: true,
|
||||
};
|
||||
const features = getFeatures(VERSION_INFO, res.body.Version);
|
||||
|
||||
return {
|
||||
features,
|
||||
|
|
|
@ -2,8 +2,6 @@ import { ndApiClient } from '/@/renderer/api/navidrome/navidrome-api';
|
|||
import { ndNormalize } from '/@/renderer/api/navidrome/navidrome-normalize';
|
||||
import { ndType } from '/@/renderer/api/navidrome/navidrome-types';
|
||||
import { ssApiClient } from '/@/renderer/api/subsonic/subsonic-api';
|
||||
import semverCoerce from 'semver/functions/coerce';
|
||||
import semverGte from 'semver/functions/gte';
|
||||
import {
|
||||
AlbumArtistDetailArgs,
|
||||
AlbumArtistDetailResponse,
|
||||
|
@ -52,7 +50,7 @@ import {
|
|||
SimilarSongsArgs,
|
||||
Song,
|
||||
} from '../types';
|
||||
import { hasFeature } from '/@/renderer/api/utils';
|
||||
import { VersionInfo, getFeatures, hasFeature } from '/@/renderer/api/utils';
|
||||
import { ServerFeature, ServerFeatures } from '/@/renderer/api/features-types';
|
||||
import { SubsonicExtensions } from '/@/renderer/api/subsonic/subsonic-types';
|
||||
import { NDSongListSort } from '/@/renderer/api/navidrome.types';
|
||||
|
@ -486,37 +484,11 @@ const removeFromPlaylist = async (
|
|||
return null;
|
||||
};
|
||||
|
||||
// The order should be in decreasing version, as the highest version match
|
||||
// will automatically consider all lower versions matched
|
||||
const VERSION_INFO: Array<[string, Record<string, number[]>]> = [
|
||||
const VERSION_INFO: VersionInfo = [
|
||||
['0.49.3', { [ServerFeature.SHARING_ALBUM_SONG]: [1] }],
|
||||
['0.48.0', { [ServerFeature.PLAYLISTS_SMART]: [1] }],
|
||||
];
|
||||
|
||||
const getFeatures = (version: string): Record<string, number[]> => {
|
||||
const cleanVersion = semverCoerce(version);
|
||||
const features: Record<string, number[]> = {};
|
||||
let matched = cleanVersion === null;
|
||||
|
||||
for (const [version, supportedFeatures] of VERSION_INFO) {
|
||||
if (!matched) {
|
||||
matched = semverGte(cleanVersion!, version);
|
||||
}
|
||||
|
||||
if (matched) {
|
||||
for (const [feature, feat] of Object.entries(supportedFeatures)) {
|
||||
if (feature in features) {
|
||||
features[feature].push(...feat);
|
||||
} else {
|
||||
features[feature] = feat;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return features;
|
||||
};
|
||||
|
||||
const getServerInfo = async (args: ServerInfoArgs): Promise<ServerInfo> => {
|
||||
const { apiClientProps } = args;
|
||||
|
||||
|
@ -527,7 +499,10 @@ const getServerInfo = async (args: ServerInfoArgs): Promise<ServerInfo> => {
|
|||
throw new Error('Failed to ping server');
|
||||
}
|
||||
|
||||
const navidromeFeatures: Record<string, number[]> = getFeatures(ping.body.serverVersion!);
|
||||
const navidromeFeatures: Record<string, number[]> = getFeatures(
|
||||
VERSION_INFO,
|
||||
ping.body.serverVersion!,
|
||||
);
|
||||
|
||||
if (ping.body.openSubsonic) {
|
||||
const res = await ssApiClient(apiClientProps).getServerInfo();
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { AxiosHeaders } from 'axios';
|
||||
import semverCoerce from 'semver/functions/coerce';
|
||||
import semverGte from 'semver/functions/gte';
|
||||
import { z } from 'zod';
|
||||
import { toast } from '/@/renderer/components';
|
||||
import { useAuthStore } from '/@/renderer/store';
|
||||
|
@ -48,4 +50,53 @@ export const hasFeature = (server: ServerListItem | null, feature: ServerFeature
|
|||
return server.features[feature] ?? false;
|
||||
};
|
||||
|
||||
export type VersionInfo = ReadonlyArray<[string, Record<string, readonly number[]>]>;
|
||||
|
||||
/**
|
||||
* Returns the available server features given the version string.
|
||||
* @param versionInfo a list, in DECREASING VERSION order, of the features supported by the server.
|
||||
* The first version match will automatically consider the rest matched.
|
||||
* @example
|
||||
* ```
|
||||
* // The CORRECT way to order
|
||||
* const VERSION_INFO: VersionInfo = [
|
||||
* ['0.49.3', { [ServerFeature.SHARING_ALBUM_SONG]: [1] }],
|
||||
* ['0.48.0', { [ServerFeature.PLAYLISTS_SMART]: [1] }],
|
||||
* ];
|
||||
* // INCORRECT way to order
|
||||
* const VERSION_INFO: VersionInfo = [
|
||||
* ['0.48.0', { [ServerFeature.PLAYLISTS_SMART]: [1] }],
|
||||
* ['0.49.3', { [ServerFeature.SHARING_ALBUM_SONG]: [1] }],
|
||||
* ];
|
||||
* ```
|
||||
* @param version the version string (SemVer)
|
||||
* @returns a Record containing the matched features (if any) and their versions
|
||||
*/
|
||||
export const getFeatures = (
|
||||
versionInfo: VersionInfo,
|
||||
version: string,
|
||||
): Record<string, number[]> => {
|
||||
const cleanVersion = semverCoerce(version);
|
||||
const features: Record<string, number[]> = {};
|
||||
let matched = cleanVersion === null;
|
||||
|
||||
for (const [version, supportedFeatures] of versionInfo) {
|
||||
if (!matched) {
|
||||
matched = semverGte(cleanVersion!, version);
|
||||
}
|
||||
|
||||
if (matched) {
|
||||
for (const [feature, feat] of Object.entries(supportedFeatures)) {
|
||||
if (feature in features) {
|
||||
features[feature].push(...feat);
|
||||
} else {
|
||||
features[feature] = [...feat];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return features;
|
||||
};
|
||||
|
||||
export const SEPARATOR_STRING = ' · ';
|
||||
|
|
Reference in a new issue