mirror of
https://github.com/antebudimir/feishin.git
synced 2026-01-02 02:43:33 +00:00
prepare bfr changes (#882)
* prepare bfr changes * contributors to subsonic/navidrome * show performer roles * Add BFR smart playlist fields * Fix upload-artifact action to handle v4 --------- Co-authored-by: jeffvli <jeffvictorli@gmail.com>
This commit is contained in:
parent
571aacbaa0
commit
c6d7dc0b32
13 changed files with 378 additions and 88 deletions
|
|
@ -15,6 +15,7 @@ import {
|
|||
genreListSortMap,
|
||||
Song,
|
||||
ControllerEndpoint,
|
||||
ServerListItem,
|
||||
} from '../types';
|
||||
import { VersionInfo, getFeatures, hasFeature } from '/@/renderer/api/utils';
|
||||
import { ServerFeature, ServerFeatures } from '/@/renderer/api/features-types';
|
||||
|
|
@ -24,10 +25,19 @@ import { ssNormalize } from '/@/renderer/api/subsonic/subsonic-normalize';
|
|||
import { SubsonicController } from '/@/renderer/api/subsonic/subsonic-controller';
|
||||
|
||||
const VERSION_INFO: VersionInfo = [
|
||||
['0.55.0', { [ServerFeature.BFR]: [1] }],
|
||||
['0.49.3', { [ServerFeature.SHARING_ALBUM_SONG]: [1] }],
|
||||
['0.48.0', { [ServerFeature.PLAYLISTS_SMART]: [1] }],
|
||||
];
|
||||
|
||||
const excludeMissing = (server: ServerListItem | null) => {
|
||||
if (hasFeature(server, ServerFeature.BFR)) {
|
||||
return { missing: false };
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const NavidromeController: ControllerEndpoint = {
|
||||
addToPlaylist: async (args) => {
|
||||
const { body, query, apiClientProps } = args;
|
||||
|
|
@ -159,6 +169,7 @@ export const NavidromeController: ControllerEndpoint = {
|
|||
_start: query.startIndex,
|
||||
name: query.searchTerm,
|
||||
...query._custom?.navidrome,
|
||||
role: hasFeature(apiClientProps.server, ServerFeature.BFR) ? 'albumartist' : '',
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -231,6 +242,7 @@ export const NavidromeController: ControllerEndpoint = {
|
|||
name: query.searchTerm,
|
||||
...query._custom?.navidrome,
|
||||
starred: query.favorite,
|
||||
...excludeMissing(apiClientProps.server),
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -367,6 +379,10 @@ export const NavidromeController: ControllerEndpoint = {
|
|||
throw new Error('Failed to ping server');
|
||||
}
|
||||
|
||||
if (ping.body.serverVersion?.includes('pr-2709')) {
|
||||
ping.body.serverVersion = '0.55.0';
|
||||
}
|
||||
|
||||
const navidromeFeatures: Record<string, number[]> = getFeatures(
|
||||
VERSION_INFO,
|
||||
ping.body.serverVersion!,
|
||||
|
|
@ -390,6 +406,7 @@ export const NavidromeController: ControllerEndpoint = {
|
|||
}
|
||||
|
||||
const features: ServerFeatures = {
|
||||
bfr: !!navidromeFeatures[ServerFeature.BFR],
|
||||
lyricsMultipleStructured: !!navidromeFeatures[SubsonicExtensions.SONG_LYRICS],
|
||||
playlistsSmart: !!navidromeFeatures[ServerFeature.PLAYLISTS_SMART],
|
||||
publicPlaylist: true,
|
||||
|
|
@ -479,6 +496,7 @@ export const NavidromeController: ControllerEndpoint = {
|
|||
starred: query.favorite,
|
||||
title: query.searchTerm,
|
||||
...query._custom?.navidrome,
|
||||
...excludeMissing(apiClientProps.server),
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
Genre,
|
||||
ServerListItem,
|
||||
ServerType,
|
||||
RelatedArtist,
|
||||
} from '/@/renderer/api/types';
|
||||
import z from 'zod';
|
||||
import { ndType } from './navidrome-types';
|
||||
|
|
@ -54,6 +55,70 @@ const normalizePlayDate = (item: WithDate): string | null => {
|
|||
return !item.playDate || item.playDate.includes('0001-') ? null : item.playDate;
|
||||
};
|
||||
|
||||
const getArtists = (
|
||||
item:
|
||||
| z.infer<typeof ndType._response.song>
|
||||
| z.infer<typeof ndType._response.playlistSong>
|
||||
| z.infer<typeof ndType._response.album>,
|
||||
) => {
|
||||
let albumArtists: RelatedArtist[] | undefined;
|
||||
let artists: RelatedArtist[] | undefined;
|
||||
let participants: Record<string, RelatedArtist[]> | null = null;
|
||||
|
||||
if (item.participants) {
|
||||
participants = {};
|
||||
for (const [role, list] of Object.entries(item.participants)) {
|
||||
if (role === 'albumartist' || role === 'artist') {
|
||||
const roleList = list.map((item) => ({
|
||||
id: item.id,
|
||||
imageUrl: null,
|
||||
name: item.name,
|
||||
}));
|
||||
|
||||
if (role === 'albumartist') {
|
||||
albumArtists = roleList;
|
||||
} else {
|
||||
artists = roleList;
|
||||
}
|
||||
} else {
|
||||
const subRoles = new Map<string | undefined, RelatedArtist[]>();
|
||||
|
||||
for (const artist of list) {
|
||||
const item: RelatedArtist = {
|
||||
id: artist.id,
|
||||
imageUrl: null,
|
||||
name: artist.name,
|
||||
};
|
||||
|
||||
if (subRoles.has(artist.subRole)) {
|
||||
subRoles.get(artist.subRole)!.push(item);
|
||||
} else {
|
||||
subRoles.set(artist.subRole, [item]);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [subRole, items] of subRoles.entries()) {
|
||||
if (subRole) {
|
||||
participants[`${role} (${subRole})`] = items;
|
||||
} else {
|
||||
participants[role] = items;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (albumArtists === undefined) {
|
||||
albumArtists = [{ id: item.albumArtistId, imageUrl: null, name: item.albumArtist }];
|
||||
}
|
||||
|
||||
if (artists === undefined) {
|
||||
artists = [{ id: item.artistId, imageUrl: null, name: item.artist }];
|
||||
}
|
||||
|
||||
return { albumArtists, artists, participants };
|
||||
};
|
||||
|
||||
const normalizeSong = (
|
||||
item: z.infer<typeof ndType._response.song> | z.infer<typeof ndType._response.playlistSong>,
|
||||
server: ServerListItem | null,
|
||||
|
|
@ -80,10 +145,9 @@ const normalizeSong = (
|
|||
const imagePlaceholderUrl = null;
|
||||
return {
|
||||
album: item.album,
|
||||
albumArtists: [{ id: item.albumArtistId, imageUrl: null, name: item.albumArtist }],
|
||||
albumId: item.albumId,
|
||||
...getArtists(item),
|
||||
artistName: item.artist,
|
||||
artists: [{ id: item.artistId, imageUrl: null, name: item.artist }],
|
||||
bitRate: item.bitRate,
|
||||
bpm: item.bpm ? item.bpm : null,
|
||||
channels: item.channels ? item.channels : null,
|
||||
|
|
@ -116,7 +180,7 @@ const normalizeSong = (
|
|||
item.rgAlbumPeak || item.rgTrackPeak
|
||||
? { album: item.rgAlbumPeak, track: item.rgTrackPeak }
|
||||
: null,
|
||||
playCount: item.playCount,
|
||||
playCount: item.playCount || 0,
|
||||
playlistItemId,
|
||||
releaseDate: (item.releaseDate
|
||||
? new Date(item.releaseDate)
|
||||
|
|
@ -155,12 +219,11 @@ const normalizeAlbum = (
|
|||
|
||||
return {
|
||||
albumArtist: item.albumArtist,
|
||||
albumArtists: [{ id: item.albumArtistId, imageUrl: null, name: item.albumArtist }],
|
||||
artists: [{ id: item.artistId, imageUrl: null, name: item.artist }],
|
||||
...getArtists(item),
|
||||
backdropImageUrl: imageBackdropUrl,
|
||||
comment: item.comment || null,
|
||||
createdAt: item.createdAt.split('T')[0],
|
||||
duration: item.duration * 1000 || null,
|
||||
duration: item.duration !== undefined ? item.duration * 1000 : null,
|
||||
genres: (item.genres || []).map((genre) => ({
|
||||
id: genre.id,
|
||||
imageUrl: null,
|
||||
|
|
@ -180,7 +243,7 @@ const normalizeAlbum = (
|
|||
: item.originalYear
|
||||
? new Date(item.originalYear, 0, 1).toISOString()
|
||||
: null,
|
||||
playCount: item.playCount,
|
||||
playCount: item.playCount || 0,
|
||||
releaseDate: (item.releaseDate
|
||||
? new Date(item.releaseDate)
|
||||
: new Date(item.minYear, 0, 1)
|
||||
|
|
@ -232,7 +295,7 @@ const normalizeAlbumArtist = (
|
|||
lastPlayedAt: normalizePlayDate(item),
|
||||
mbz: item.mbzArtistId || null,
|
||||
name: item.name,
|
||||
playCount: item.playCount,
|
||||
playCount: item.playCount || 0,
|
||||
serverId: server?.id || 'unknown',
|
||||
serverType: ServerType.NAVIDROME,
|
||||
similarArtists:
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ const albumArtist = z.object({
|
|||
mediumImageUrl: z.string().optional(),
|
||||
name: z.string(),
|
||||
orderArtistName: z.string(),
|
||||
playCount: z.number(),
|
||||
playCount: z.number().optional(),
|
||||
playDate: z.string().optional(),
|
||||
rating: z.number(),
|
||||
size: z.number(),
|
||||
|
|
@ -98,10 +98,20 @@ const albumArtistList = z.array(albumArtist);
|
|||
const albumArtistListParameters = paginationParameters.extend({
|
||||
_sort: z.nativeEnum(NDAlbumArtistListSort).optional(),
|
||||
genre_id: z.string().optional(),
|
||||
missing: z.boolean().optional(),
|
||||
name: z.string().optional(),
|
||||
role: z.string().optional(),
|
||||
starred: z.boolean().optional(),
|
||||
});
|
||||
|
||||
const participant = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
subRole: z.string().optional(),
|
||||
});
|
||||
|
||||
const participants = z.record(z.string(), z.array(participant));
|
||||
|
||||
const album = z.object({
|
||||
albumArtist: z.string(),
|
||||
albumArtistId: z.string(),
|
||||
|
|
@ -113,7 +123,7 @@ const album = z.object({
|
|||
coverArtId: z.string().optional(), // Removed after v0.48.0
|
||||
coverArtPath: z.string().optional(), // Removed after v0.48.0
|
||||
createdAt: z.string(),
|
||||
duration: z.number(),
|
||||
duration: z.number().optional(),
|
||||
fullText: z.string(),
|
||||
genre: z.string(),
|
||||
genres: z.array(genre).nullable(),
|
||||
|
|
@ -127,7 +137,8 @@ const album = z.object({
|
|||
orderAlbumName: z.string(),
|
||||
originalDate: z.string().optional(),
|
||||
originalYear: z.number().optional(),
|
||||
playCount: z.number(),
|
||||
participants: z.optional(participants),
|
||||
playCount: z.number().optional(),
|
||||
playDate: z.string().optional(),
|
||||
rating: z.number().optional(),
|
||||
releaseDate: z.string().optional(),
|
||||
|
|
@ -195,8 +206,9 @@ const song = z.object({
|
|||
orderAlbumName: z.string(),
|
||||
orderArtistName: z.string(),
|
||||
orderTitle: z.string(),
|
||||
participants: z.optional(participants),
|
||||
path: z.string(),
|
||||
playCount: z.number(),
|
||||
playCount: z.number().optional(),
|
||||
playDate: z.string().optional(),
|
||||
rating: z.number().optional(),
|
||||
releaseDate: z.string().optional(),
|
||||
|
|
@ -211,6 +223,7 @@ const song = z.object({
|
|||
starred: z.boolean(),
|
||||
starredAt: z.string().optional(),
|
||||
suffix: z.string(),
|
||||
tags: z.record(z.string(), z.array(z.string())).optional(),
|
||||
title: z.string(),
|
||||
trackNumber: z.number(),
|
||||
updatedAt: z.string(),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue