refactor song path replacement

- path replacement during runtime instead of during API normalization
- fix Navidrome API path not appending libraryPath which caused inconsistency between ND and Subsonic paths
This commit is contained in:
jeffvli
2026-06-19 22:02:12 -07:00
parent 36624350f6
commit 61cc87e0b7
15 changed files with 119 additions and 398 deletions
@@ -531,12 +531,7 @@ export const JellyfinController: InternalControllerEndpoint = {
const albumIdSet = new Set([query.id]); const albumIdSet = new Set([query.id]);
const songs = songsRes.body.Items.filter((item) => albumIdSet.has(item.AlbumId!)); const songs = songsRes.body.Items.filter((item) => albumIdSet.has(item.AlbumId!));
return jfNormalize.album( return jfNormalize.album({ ...res.body, Songs: songs }, apiClientProps.server);
{ ...res.body, Songs: songs },
apiClientProps.server,
args.context?.pathReplace,
args.context?.pathReplaceWith,
);
}, },
getAlbumList: async (args) => { getAlbumList: async (args) => {
const { apiClientProps, query } = args; const { apiClientProps, query } = args;
@@ -630,14 +625,7 @@ export const JellyfinController: InternalControllerEndpoint = {
throw new Error('Failed to get album radio songs'); throw new Error('Failed to get album radio songs');
} }
return res.body.Items.map((song) => return res.body.Items.map((song) => jfNormalize.song(song, apiClientProps.server));
jfNormalize.song(
song,
apiClientProps.server,
args.context?.pathReplace,
args.context?.pathReplaceWith,
),
);
}, },
getArtistList: async (args) => { getArtistList: async (args) => {
const { apiClientProps, query } = args; const { apiClientProps, query } = args;
@@ -693,14 +681,7 @@ export const JellyfinController: InternalControllerEndpoint = {
throw new Error('Failed to get artist radio songs'); throw new Error('Failed to get artist radio songs');
} }
return res.body.Items.map((song) => return res.body.Items.map((song) => jfNormalize.song(song, apiClientProps.server));
jfNormalize.song(
song,
apiClientProps.server,
args.context?.pathReplace,
args.context?.pathReplaceWith,
),
);
}, },
getDownloadUrl: (args) => { getDownloadUrl: (args) => {
const { apiClientProps, query } = args; const { apiClientProps, query } = args;
@@ -870,8 +851,6 @@ export const JellyfinController: InternalControllerEndpoint = {
jfNormalize.song( jfNormalize.song(
item as unknown as z.infer<typeof jfType._response.song>, item as unknown as z.infer<typeof jfType._response.song>,
apiClientProps.server, apiClientProps.server,
args.context?.pathReplace,
args.context?.pathReplaceWith,
), ),
); );
@@ -1100,14 +1079,7 @@ export const JellyfinController: InternalControllerEndpoint = {
} }
return { return {
items: res.body.Items.map((item) => items: res.body.Items.map((item) => jfNormalize.song(item, apiClientProps.server)),
jfNormalize.song(
item,
apiClientProps.server,
args.context?.pathReplace,
args.context?.pathReplaceWith,
),
),
startIndex: 0, startIndex: 0,
totalRecordCount: res.body.TotalRecordCount, totalRecordCount: res.body.TotalRecordCount,
}; };
@@ -1160,14 +1132,7 @@ export const JellyfinController: InternalControllerEndpoint = {
} }
return { return {
items: res.body.Items.map((item) => items: res.body.Items.map((item) => jfNormalize.song(item, apiClientProps.server)),
jfNormalize.song(
item,
apiClientProps.server,
args.context?.pathReplace,
args.context?.pathReplaceWith,
),
),
startIndex: 0, startIndex: 0,
totalRecordCount: res.body.Items.length || 0, totalRecordCount: res.body.Items.length || 0,
}; };
@@ -1219,14 +1184,7 @@ export const JellyfinController: InternalControllerEndpoint = {
if (res.status === 200 && res.body.Items.length) { if (res.status === 200 && res.body.Items.length) {
const results = res.body.Items.reduce<Song[]>((acc, song) => { const results = res.body.Items.reduce<Song[]>((acc, song) => {
if (song.Id !== query.songId) { if (song.Id !== query.songId) {
acc.push( acc.push(jfNormalize.song(song, apiClientProps.server));
jfNormalize.song(
song,
apiClientProps.server,
args.context?.pathReplace,
args.context?.pathReplaceWith,
),
);
} }
return acc; return acc;
@@ -1255,14 +1213,7 @@ export const JellyfinController: InternalControllerEndpoint = {
return mix.body.Items.reduce<Song[]>((acc, song) => { return mix.body.Items.reduce<Song[]>((acc, song) => {
if (song.Id !== query.songId) { if (song.Id !== query.songId) {
acc.push( acc.push(jfNormalize.song(song, apiClientProps.server));
jfNormalize.song(
song,
apiClientProps.server,
args.context?.pathReplace,
args.context?.pathReplaceWith,
),
);
} }
return acc; return acc;
@@ -1282,12 +1233,7 @@ export const JellyfinController: InternalControllerEndpoint = {
throw new Error('Failed to get song detail'); throw new Error('Failed to get song detail');
} }
return jfNormalize.song( return jfNormalize.song(res.body, apiClientProps.server);
res.body,
apiClientProps.server,
args.context?.pathReplace,
args.context?.pathReplaceWith,
);
}, },
getSongList: async (args) => { getSongList: async (args) => {
const { apiClientProps, query } = args; const { apiClientProps, query } = args;
@@ -1399,14 +1345,7 @@ export const JellyfinController: InternalControllerEndpoint = {
} }
return { return {
items: items.map((item) => items: items.map((item) => jfNormalize.song(item, apiClientProps.server)),
jfNormalize.song(
item,
apiClientProps.server,
args.context?.pathReplace,
args.context?.pathReplaceWith,
),
),
startIndex: query.startIndex, startIndex: query.startIndex,
totalRecordCount, totalRecordCount,
}; };
@@ -1538,14 +1477,7 @@ export const JellyfinController: InternalControllerEndpoint = {
throw new Error('Failed to get top song list'); throw new Error('Failed to get top song list');
} }
const items = res.body.Items.map((item) => const items = res.body.Items.map((item) => jfNormalize.song(item, apiClientProps.server));
jfNormalize.song(
item,
apiClientProps.server,
args.context?.pathReplace,
args.context?.pathReplaceWith,
),
);
if (type === 'personal') { if (type === 'personal') {
const sorted = orderBy( const sorted = orderBy(
@@ -1647,12 +1579,7 @@ export const JellyfinController: InternalControllerEndpoint = {
} }
const existingSongs = existingSongsRes.body.Items.map((item) => const existingSongs = existingSongsRes.body.Items.map((item) =>
jfNormalize.song( jfNormalize.song(item, apiClientProps.server),
item,
apiClientProps.server,
args.context?.pathReplace,
args.context?.pathReplaceWith,
),
); );
// 2. Get playlist detail to get the name // 2. Get playlist detail to get the name
@@ -1903,14 +1830,7 @@ export const JellyfinController: InternalControllerEndpoint = {
jfNormalize.albumArtist(item, apiClientProps.server), jfNormalize.albumArtist(item, apiClientProps.server),
), ),
albums: albums.map((item) => jfNormalize.album(item, apiClientProps.server)), albums: albums.map((item) => jfNormalize.album(item, apiClientProps.server)),
songs: songs.map((item) => songs: songs.map((item) => jfNormalize.song(item, apiClientProps.server)),
jfNormalize.song(
item,
apiClientProps.server,
args.context?.pathReplace,
args.context?.pathReplaceWith,
),
),
}; };
}, },
setPlaylistSongs: async (args) => { setPlaylistSongs: async (args) => {
@@ -367,7 +367,7 @@ export const NavidromeController: InternalControllerEndpoint = {
query: { ...query, limit: 1, startIndex: 0 }, query: { ...query, limit: 1, startIndex: 0 },
}).then((result) => result!.totalRecordCount!), }).then((result) => result!.totalRecordCount!),
getAlbumDetail: async (args) => { getAlbumDetail: async (args) => {
const { apiClientProps, context, query } = args; const { apiClientProps, query } = args;
const albumRes = await ndApiClient(apiClientProps).getAlbumDetail({ const albumRes = await ndApiClient(apiClientProps).getAlbumDetail({
params: { params: {
@@ -393,8 +393,6 @@ export const NavidromeController: InternalControllerEndpoint = {
return ndNormalize.album( return ndNormalize.album(
{ ...albumRes.body.data, songs: songsData.body.data }, { ...albumRes.body.data, songs: songsData.body.data },
apiClientProps.server, apiClientProps.server,
context?.pathReplace,
context?.pathReplaceWith,
); );
}, },
getAlbumInfo: async (args) => { getAlbumInfo: async (args) => {
@@ -418,7 +416,7 @@ export const NavidromeController: InternalControllerEndpoint = {
}; };
}, },
getAlbumList: async (args) => { getAlbumList: async (args) => {
const { apiClientProps, context, query } = args; const { apiClientProps, query } = args;
const genres = hasFeature(apiClientProps.server, ServerFeature.BFR) const genres = hasFeature(apiClientProps.server, ServerFeature.BFR)
? query.genreIds ? query.genreIds
@@ -453,14 +451,7 @@ export const NavidromeController: InternalControllerEndpoint = {
} }
return { return {
items: res.body.data.map((album) => items: res.body.data.map((album) => ndNormalize.album(album, apiClientProps.server)),
ndNormalize.album(
album,
apiClientProps.server,
context?.pathReplace,
context?.pathReplaceWith,
),
),
startIndex: query?.startIndex || 0, startIndex: query?.startIndex || 0,
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0), totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
}; };
@@ -493,12 +484,7 @@ export const NavidromeController: InternalControllerEndpoint = {
} }
return res.body.similarSongs.song.map((song) => return res.body.similarSongs.song.map((song) =>
ssNormalize.song( ssNormalize.song(song, apiClientProps.server),
song,
apiClientProps.server,
args.context?.pathReplace,
args.context?.pathReplaceWith,
),
); );
}, },
getArtistList: async (args) => { getArtistList: async (args) => {
@@ -568,12 +554,7 @@ export const NavidromeController: InternalControllerEndpoint = {
} }
return res.body.similarSongs2.song.map((song) => return res.body.similarSongs2.song.map((song) =>
ssNormalize.song( ssNormalize.song(song, apiClientProps.server),
song,
apiClientProps.server,
args.context?.pathReplace,
args.context?.pathReplaceWith,
),
); );
}, },
getDownloadUrl: SubsonicController.getDownloadUrl, getDownloadUrl: SubsonicController.getDownloadUrl,
@@ -723,14 +704,7 @@ export const NavidromeController: InternalControllerEndpoint = {
} }
return { return {
items: res.body.data.map((item) => items: res.body.data.map((item) => ndNormalize.song(item, apiClientProps.server)),
ndNormalize.song(
item,
apiClientProps.server,
args.context?.pathReplace,
args.context?.pathReplaceWith,
),
),
startIndex: 0, startIndex: 0,
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0), totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
}; };
@@ -747,14 +721,7 @@ export const NavidromeController: InternalControllerEndpoint = {
const { changedBy, current, items = [], position, updatedAt } = res.body.data; // if there is no queue saved, items is undefined const { changedBy, current, items = [], position, updatedAt } = res.body.data; // if there is no queue saved, items is undefined
const entries = items.map((song) => const entries = items.map((song) => ndNormalize.song(song, apiClientProps.server));
ndNormalize.song(
song,
apiClientProps.server,
args.context?.pathReplace,
args.context?.pathReplaceWith,
),
);
return { return {
changed: updatedAt, changed: updatedAt,
@@ -830,14 +797,7 @@ export const NavidromeController: InternalControllerEndpoint = {
return ( return (
(res.body.similarSongs?.song || []) (res.body.similarSongs?.song || [])
.filter((song) => song.id !== query.songId) .filter((song) => song.id !== query.songId)
.map((song) => .map((song) => ssNormalize.song(song, apiClientProps.server)) || []
ssNormalize.song(
song,
apiClientProps.server,
args.context?.pathReplace,
args.context?.pathReplaceWith,
),
) || []
); );
}, },
getSongDetail: async (args) => { getSongDetail: async (args) => {
@@ -853,12 +813,7 @@ export const NavidromeController: InternalControllerEndpoint = {
throw new Error('Failed to get song detail'); throw new Error('Failed to get song detail');
} }
return ndNormalize.song( return ndNormalize.song(res.body.data, apiClientProps.server);
res.body.data,
apiClientProps.server,
args.context?.pathReplace,
args.context?.pathReplaceWith,
);
}, },
getSongList: async (args) => { getSongList: async (args) => {
const { apiClientProps, query } = args; const { apiClientProps, query } = args;
@@ -898,14 +853,7 @@ export const NavidromeController: InternalControllerEndpoint = {
} }
return { return {
items: res.body.data.map((song) => items: res.body.data.map((song) => ndNormalize.song(song, apiClientProps.server)),
ndNormalize.song(
song,
apiClientProps.server,
args.context?.pathReplace,
args.context?.pathReplaceWith,
),
),
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0), totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
}; };
}; };
@@ -1022,12 +970,7 @@ export const NavidromeController: InternalControllerEndpoint = {
return { return {
items: (res.body.topSongs?.song || []).map((song) => items: (res.body.topSongs?.song || []).map((song) =>
ssNormalize.song( ssNormalize.song(song, apiClientProps.server),
song,
apiClientProps.server,
args.context?.pathReplace,
args.context?.pathReplaceWith,
),
), ),
startIndex: 0, startIndex: 0,
totalRecordCount: res.body.topSongs?.song?.length || 0, totalRecordCount: res.body.topSongs?.song?.length || 0,
@@ -1036,7 +979,6 @@ export const NavidromeController: InternalControllerEndpoint = {
const res = await NavidromeController.getSongList({ const res = await NavidromeController.getSongList({
apiClientProps, apiClientProps,
context: args.context,
query: { query: {
artistIds: [query.artistId], artistIds: [query.artistId],
sortBy: SongListSort.PLAY_COUNT, sortBy: SongListSort.PLAY_COUNT,
@@ -1138,12 +1080,7 @@ export const NavidromeController: InternalControllerEndpoint = {
} }
const existingSongs = existingSongsRes.body.data.map((item) => const existingSongs = existingSongsRes.body.data.map((item) =>
ndNormalize.song( ndNormalize.song(item, apiClientProps.server),
item,
apiClientProps.server,
args.context?.pathReplace,
args.context?.pathReplaceWith,
),
); );
// 2. Get playlist detail to get the name // 2. Get playlist detail to get the name
+37 -186
View File
@@ -482,14 +482,7 @@ export const SubsonicController: InternalControllerEndpoint = {
return { return {
...ssNormalize.albumArtist(artist, apiClientProps.server), ...ssNormalize.albumArtist(artist, apiClientProps.server),
albums: artist.album?.map((album) => albums: artist.album?.map((album) => ssNormalize.album(album, apiClientProps.server)),
ssNormalize.album(
album,
apiClientProps.server,
args.context?.pathReplace,
args.context?.pathReplaceWith,
),
),
similarArtists: null, similarArtists: null,
}; };
}, },
@@ -564,7 +557,6 @@ export const SubsonicController: InternalControllerEndpoint = {
getAlbumArtistListCount: (args) => getAlbumArtistListCount: (args) =>
SubsonicController.getAlbumArtistList({ SubsonicController.getAlbumArtistList({
...args, ...args,
context: args.context,
query: { ...args.query, startIndex: 0 }, query: { ...args.query, startIndex: 0 },
}).then((res) => res!.totalRecordCount!), }).then((res) => res!.totalRecordCount!),
getAlbumDetail: async (args) => { getAlbumDetail: async (args) => {
@@ -580,12 +572,7 @@ export const SubsonicController: InternalControllerEndpoint = {
throw new Error('Failed to get album detail'); throw new Error('Failed to get album detail');
} }
return ssNormalize.album( return ssNormalize.album(res.body.album, apiClientProps.server);
res.body.album,
apiClientProps.server,
args.context?.pathReplace,
args.context?.pathReplaceWith,
);
}, },
getAlbumList: async (args) => { getAlbumList: async (args) => {
const { apiClientProps, query } = args; const { apiClientProps, query } = args;
@@ -610,12 +597,7 @@ export const SubsonicController: InternalControllerEndpoint = {
const results = const results =
res.body.searchResult3?.album?.map((album) => res.body.searchResult3?.album?.map((album) =>
ssNormalize.album( ssNormalize.album(album, apiClientProps.server),
album,
apiClientProps.server,
args.context?.pathReplace,
args.context?.pathReplaceWith,
),
) || []; ) || [];
return { return {
@@ -650,14 +632,7 @@ export const SubsonicController: InternalControllerEndpoint = {
return artist.body.artist.album ?? []; return artist.body.artist.album ?? [];
}); });
const items = albums.map((album) => const items = albums.map((album) => ssNormalize.album(album, apiClientProps.server));
ssNormalize.album(
album,
apiClientProps.server,
args.context?.pathReplace,
args.context?.pathReplaceWith,
),
);
return { return {
items: sortAlbumList(items, query.sortBy, query.sortOrder), items: sortAlbumList(items, query.sortBy, query.sortOrder),
@@ -679,12 +654,7 @@ export const SubsonicController: InternalControllerEndpoint = {
const allResults = const allResults =
res.body.starred?.album?.map((album) => res.body.starred?.album?.map((album) =>
ssNormalize.album( ssNormalize.album(album, apiClientProps.server),
album,
apiClientProps.server,
args.context?.pathReplace,
args.context?.pathReplaceWith,
),
) || []; ) || [];
return sortAndPaginate(allResults, { return sortAndPaginate(allResults, {
@@ -749,12 +719,7 @@ export const SubsonicController: InternalControllerEndpoint = {
return { return {
items: items:
res.body.albumList2.album?.map((album) => res.body.albumList2.album?.map((album) =>
ssNormalize.album( ssNormalize.album(album, apiClientProps.server),
album,
apiClientProps.server,
args.context?.pathReplace,
args.context?.pathReplaceWith,
),
) || [], ) || [],
startIndex: query.startIndex, startIndex: query.startIndex,
totalRecordCount: null, totalRecordCount: null,
@@ -905,7 +870,7 @@ export const SubsonicController: InternalControllerEndpoint = {
return totalRecordCount; return totalRecordCount;
}, },
getAlbumRadio: async (args) => { getAlbumRadio: async (args) => {
const { apiClientProps, context, query } = args; const { apiClientProps, query } = args;
const res = await ssApiClient(apiClientProps).getSimilarSongs({ const res = await ssApiClient(apiClientProps).getSimilarSongs({
query: { query: {
@@ -923,12 +888,7 @@ export const SubsonicController: InternalControllerEndpoint = {
} }
return res.body.similarSongs.song.map((song) => return res.body.similarSongs.song.map((song) =>
ssNormalize.song( ssNormalize.song(song, apiClientProps.server),
song,
apiClientProps.server,
context?.pathReplace,
context?.pathReplaceWith,
),
); );
}, },
getArtistList: async (args) => { getArtistList: async (args) => {
@@ -974,11 +934,10 @@ export const SubsonicController: InternalControllerEndpoint = {
getArtistListCount: async (args) => getArtistListCount: async (args) =>
SubsonicController.getArtistList({ SubsonicController.getArtistList({
...args, ...args,
context: args.context,
query: { ...args.query, startIndex: 0 }, query: { ...args.query, startIndex: 0 },
}).then((res) => res!.totalRecordCount!), }).then((res) => res!.totalRecordCount!),
getArtistRadio: async (args) => { getArtistRadio: async (args) => {
const { apiClientProps, context, query } = args; const { apiClientProps, query } = args;
const res = await ssApiClient(apiClientProps).getSimilarSongs2({ const res = await ssApiClient(apiClientProps).getSimilarSongs2({
query: { query: {
@@ -996,12 +955,7 @@ export const SubsonicController: InternalControllerEndpoint = {
} }
return res.body.similarSongs2.song.map((song) => return res.body.similarSongs2.song.map((song) =>
ssNormalize.song( ssNormalize.song(song, apiClientProps.server),
song,
apiClientProps.server,
context?.pathReplace,
context?.pathReplaceWith,
),
); );
}, },
getDownloadUrl: (args) => { getDownloadUrl: (args) => {
@@ -1015,7 +969,7 @@ export const SubsonicController: InternalControllerEndpoint = {
'&c=Feishin' '&c=Feishin'
); );
}, },
getFolder: async ({ apiClientProps, context, query }) => { getFolder: async ({ apiClientProps, query }) => {
const sortOrder = (query.sortOrder?.toLowerCase() ?? 'asc') as 'asc' | 'desc'; const sortOrder = (query.sortOrder?.toLowerCase() ?? 'asc') as 'asc' | 'desc';
const isRootFolderId = query.id === '0'; const isRootFolderId = query.id === '0';
@@ -1048,14 +1002,7 @@ export const SubsonicController: InternalControllerEndpoint = {
}); });
} }
let folders = items.map((item) => let folders = items.map((item) => ssNormalize.folder(item, apiClientProps.server));
ssNormalize.folder(
item,
apiClientProps.server,
context?.pathReplace,
context?.pathReplaceWith,
),
);
folders = orderBy(folders, [(v) => v.name.toLowerCase()], [sortOrder]); folders = orderBy(folders, [(v) => v.name.toLowerCase()], [sortOrder]);
@@ -1083,12 +1030,7 @@ export const SubsonicController: InternalControllerEndpoint = {
throw new Error('Failed to get folder'); throw new Error('Failed to get folder');
} }
const folder = ssNormalize.folder( const folder = ssNormalize.folder(directoryRes.body.directory, apiClientProps.server);
directoryRes.body.directory,
apiClientProps.server,
context?.pathReplace,
context?.pathReplaceWith,
);
let filteredFolders = folder.children?.folders || []; let filteredFolders = folder.children?.folders || [];
let filteredSongs = folder.children?.songs || []; let filteredSongs = folder.children?.songs || [];
@@ -1281,7 +1223,7 @@ export const SubsonicController: InternalControllerEndpoint = {
return results.length; return results.length;
}, },
getPlaylistSongList: async ({ apiClientProps, context, query }) => { getPlaylistSongList: async ({ apiClientProps, query }) => {
const res = await ssApiClient(apiClientProps).getPlaylist({ const res = await ssApiClient(apiClientProps).getPlaylist({
query: { query: {
id: query.id, id: query.id,
@@ -1294,13 +1236,7 @@ export const SubsonicController: InternalControllerEndpoint = {
const items = const items =
res.body.playlist.entry?.map((song, index) => res.body.playlist.entry?.map((song, index) =>
ssNormalize.song( ssNormalize.song(song, apiClientProps.server, index),
song,
apiClientProps.server,
context?.pathReplace,
context?.pathReplaceWith,
index,
),
) || []; ) || [];
return { return {
@@ -1309,7 +1245,7 @@ export const SubsonicController: InternalControllerEndpoint = {
totalRecordCount: items.length, totalRecordCount: items.length,
}; };
}, },
getPlayQueue: async ({ apiClientProps, context }) => { getPlayQueue: async ({ apiClientProps }) => {
if (hasFeature(apiClientProps.server, ServerFeature.SERVER_PLAY_QUEUE)) { if (hasFeature(apiClientProps.server, ServerFeature.SERVER_PLAY_QUEUE)) {
const res = await ssApiClient(apiClientProps).getPlayQueueByIndex(); const res = await ssApiClient(apiClientProps).getPlayQueueByIndex();
@@ -1324,15 +1260,7 @@ export const SubsonicController: InternalControllerEndpoint = {
changed: changed ?? '', changed: changed ?? '',
changedBy: changedBy ?? '', changedBy: changedBy ?? '',
currentIndex: currentIndex ?? 0, currentIndex: currentIndex ?? 0,
entry: entry: entry?.map((song) => ssNormalize.song(song, apiClientProps.server)) || [],
entry?.map((song) =>
ssNormalize.song(
song,
apiClientProps.server,
context?.pathReplace,
context?.pathReplaceWith,
),
) || [],
positionMs: position ?? 0, positionMs: position ?? 0,
username: username ?? '', username: username ?? '',
}; };
@@ -1349,22 +1277,14 @@ export const SubsonicController: InternalControllerEndpoint = {
changed, changed,
changedBy, changedBy,
currentIndex: current ? entry.findIndex((item) => item.id === current) : 0, currentIndex: current ? entry.findIndex((item) => item.id === current) : 0,
entry: entry: entry?.map((song) => ssNormalize.song(song, apiClientProps.server)) || [],
entry?.map((song) =>
ssNormalize.song(
song,
apiClientProps.server,
context?.pathReplace,
context?.pathReplaceWith,
),
) || [],
positionMs: position ?? 0, positionMs: position ?? 0,
username, username,
}; };
} }
}, },
getRandomSongList: async (args) => { getRandomSongList: async (args) => {
const { apiClientProps, context, query } = args; const { apiClientProps, query } = args;
const res = await ssApiClient(apiClientProps).getRandomSongList({ const res = await ssApiClient(apiClientProps).getRandomSongList({
query: { query: {
@@ -1382,12 +1302,7 @@ export const SubsonicController: InternalControllerEndpoint = {
const results = res.body.randomSongs?.song || []; const results = res.body.randomSongs?.song || [];
const normalizedResults = results.map((song) => const normalizedResults = results.map((song) =>
ssNormalize.song( ssNormalize.song(song, apiClientProps.server),
song,
apiClientProps.server,
context?.pathReplace,
context?.pathReplaceWith,
),
); );
return { return {
@@ -1473,7 +1388,7 @@ export const SubsonicController: InternalControllerEndpoint = {
return { features, id: apiClientProps.server?.id, version: ping.body.serverVersion }; return { features, id: apiClientProps.server?.id, version: ping.body.serverVersion };
}, },
getSimilarSongs: async (args) => { getSimilarSongs: async (args) => {
const { apiClientProps, context, query } = args; const { apiClientProps, query } = args;
const res = await ssApiClient(apiClientProps).getSimilarSongs({ const res = await ssApiClient(apiClientProps).getSimilarSongs({
query: { query: {
@@ -1492,21 +1407,14 @@ export const SubsonicController: InternalControllerEndpoint = {
return res.body.similarSongs.song.reduce<Song[]>((acc, song) => { return res.body.similarSongs.song.reduce<Song[]>((acc, song) => {
if (song.id !== query.songId) { if (song.id !== query.songId) {
acc.push( acc.push(ssNormalize.song(song, apiClientProps.server));
ssNormalize.song(
song,
apiClientProps.server,
context?.pathReplace,
context?.pathReplaceWith,
),
);
} }
return acc; return acc;
}, []); }, []);
}, },
getSongDetail: async (args) => { getSongDetail: async (args) => {
const { apiClientProps, context, query } = args; const { apiClientProps, query } = args;
const res = await ssApiClient(apiClientProps).getSong({ const res = await ssApiClient(apiClientProps).getSong({
query: { query: {
@@ -1518,14 +1426,9 @@ export const SubsonicController: InternalControllerEndpoint = {
throw new Error('Failed to get song detail'); throw new Error('Failed to get song detail');
} }
return ssNormalize.song( return ssNormalize.song(res.body.song, apiClientProps.server);
res.body.song,
apiClientProps.server,
context?.pathReplace,
context?.pathReplaceWith,
);
}, },
getSongList: async ({ apiClientProps, context, query }) => { getSongList: async ({ apiClientProps, query }) => {
const fromAlbumPromises: Promise<ServerInferResponses<typeof contract.getAlbum>>[] = []; const fromAlbumPromises: Promise<ServerInferResponses<typeof contract.getAlbum>>[] = [];
const artistDetailPromises: Promise<ServerInferResponses<typeof contract.getArtist>>[] = []; const artistDetailPromises: Promise<ServerInferResponses<typeof contract.getArtist>>[] = [];
@@ -1550,12 +1453,7 @@ export const SubsonicController: InternalControllerEndpoint = {
return { return {
items: items:
res.body.searchResult3?.song?.map((song) => res.body.searchResult3?.song?.map((song) =>
ssNormalize.song( ssNormalize.song(song, apiClientProps.server),
song,
apiClientProps.server,
context?.pathReplace,
context?.pathReplaceWith,
),
) || [], ) || [],
startIndex: query.startIndex, startIndex: query.startIndex,
totalRecordCount: null, totalRecordCount: null,
@@ -1579,15 +1477,7 @@ export const SubsonicController: InternalControllerEndpoint = {
const results = res.body.songsByGenre?.song || []; const results = res.body.songsByGenre?.song || [];
return { return {
items: items: results.map((song) => ssNormalize.song(song, apiClientProps.server)) || [],
results.map((song) =>
ssNormalize.song(
song,
apiClientProps.server,
context?.pathReplace,
context?.pathReplaceWith,
),
) || [],
startIndex: 0, startIndex: 0,
totalRecordCount: null, totalRecordCount: null,
}; };
@@ -1606,12 +1496,7 @@ export const SubsonicController: InternalControllerEndpoint = {
let allResults = let allResults =
(res.body.starred?.song || []).map((song) => (res.body.starred?.song || []).map((song) =>
ssNormalize.song( ssNormalize.song(song, apiClientProps.server),
song,
apiClientProps.server,
context?.pathReplace,
context?.pathReplaceWith,
),
) || []; ) || [];
const filterArtistIds = query.albumArtistIds || query.artistIds; const filterArtistIds = query.albumArtistIds || query.artistIds;
@@ -1696,15 +1581,7 @@ export const SubsonicController: InternalControllerEndpoint = {
} }
return { return {
items: items: results.map((song) => ssNormalize.song(song, apiClientProps.server)) || [],
results.map((song) =>
ssNormalize.song(
song,
apiClientProps.server,
context?.pathReplace,
context?.pathReplaceWith,
),
) || [],
startIndex: 0, startIndex: 0,
totalRecordCount: results.length, totalRecordCount: results.length,
}; };
@@ -1730,12 +1607,7 @@ export const SubsonicController: InternalControllerEndpoint = {
return { return {
items: items:
res.body.searchResult3?.song?.map((song) => res.body.searchResult3?.song?.map((song) =>
ssNormalize.song( ssNormalize.song(song, apiClientProps.server),
song,
apiClientProps.server,
context?.pathReplace,
context?.pathReplaceWith,
),
) || [], ) || [],
startIndex: 0, startIndex: 0,
totalRecordCount: null, totalRecordCount: null,
@@ -2103,7 +1975,7 @@ export const SubsonicController: InternalControllerEndpoint = {
}); });
}, },
getTopSongs: async (args) => { getTopSongs: async (args) => {
const { apiClientProps, context, query } = args; const { apiClientProps, query } = args;
const type = query.type === 'personal' ? 'personal' : 'community'; const type = query.type === 'personal' ? 'personal' : 'community';
@@ -2121,12 +1993,7 @@ export const SubsonicController: InternalControllerEndpoint = {
return { return {
items: (res.body.topSongs?.song || []).map((song) => items: (res.body.topSongs?.song || []).map((song) =>
ssNormalize.song( ssNormalize.song(song, apiClientProps.server),
song,
apiClientProps.server,
context?.pathReplace,
context?.pathReplaceWith,
),
), ),
startIndex: 0, startIndex: 0,
totalRecordCount: res.body.topSongs?.song?.length || 0, totalRecordCount: res.body.topSongs?.song?.length || 0,
@@ -2135,7 +2002,6 @@ export const SubsonicController: InternalControllerEndpoint = {
const res = await SubsonicController.getSongList({ const res = await SubsonicController.getSongList({
apiClientProps, apiClientProps,
context,
query: { query: {
artistIds: [query.artistId], artistIds: [query.artistId],
sortBy: SongListSort.PLAY_COUNT, sortBy: SongListSort.PLAY_COUNT,
@@ -2190,7 +2056,7 @@ export const SubsonicController: InternalControllerEndpoint = {
return null; return null;
}, },
replacePlaylist: async (args) => { replacePlaylist: async (args) => {
const { apiClientProps, body, context, query } = args; const { apiClientProps, body, query } = args;
// 1. Fetch existing songs from the playlist // 1. Fetch existing songs from the playlist
const existingSongsRes = await ssApiClient(apiClientProps).getPlaylist({ const existingSongsRes = await ssApiClient(apiClientProps).getPlaylist({
@@ -2205,12 +2071,7 @@ export const SubsonicController: InternalControllerEndpoint = {
const existingSongs = const existingSongs =
existingSongsRes.body.playlist.entry?.map((song) => existingSongsRes.body.playlist.entry?.map((song) =>
ssNormalize.song( ssNormalize.song(song, apiClientProps.server),
song,
apiClientProps.server,
context?.pathReplace,
context?.pathReplaceWith,
),
) || []; ) || [];
// 2. Get playlist detail to get the name // 2. Get playlist detail to get the name
@@ -2388,7 +2249,7 @@ export const SubsonicController: InternalControllerEndpoint = {
return null; return null;
}, },
search: async (args) => { search: async (args) => {
const { apiClientProps, context, query } = args; const { apiClientProps, query } = args;
const res = await ssApiClient(apiClientProps).search3({ const res = await ssApiClient(apiClientProps).search3({
query: { query: {
@@ -2412,20 +2273,10 @@ export const SubsonicController: InternalControllerEndpoint = {
ssNormalize.albumArtist(artist, apiClientProps.server), ssNormalize.albumArtist(artist, apiClientProps.server),
), ),
albums: (res.body.searchResult3?.album || []).map((album) => albums: (res.body.searchResult3?.album || []).map((album) =>
ssNormalize.album( ssNormalize.album(album, apiClientProps.server),
album,
apiClientProps.server,
args.context?.pathReplace,
args.context?.pathReplaceWith,
),
), ),
songs: (res.body.searchResult3?.song || []).map((song) => songs: (res.body.searchResult3?.song || []).map((song) =>
ssNormalize.song( ssNormalize.song(song, apiClientProps.server),
song,
apiClientProps.server,
context?.pathReplace,
context?.pathReplaceWith,
),
), ),
}; };
}, },
@@ -1,3 +1,5 @@
import { ItemDetailListCellProps } from './types'; import { ItemDetailListCellProps } from './types';
import { resolveSongPath } from '/@/renderer/utils/resolve-song-path';
export const PathColumn = ({ song }: ItemDetailListCellProps) => song.path ?? <>&nbsp;</>; export const PathColumn = ({ song }: ItemDetailListCellProps) =>
resolveSongPath(song.path) ?? <>&nbsp;</>;
@@ -4,15 +4,17 @@ import {
ItemTableListInnerColumn, ItemTableListInnerColumn,
TableColumnTextContainer, TableColumnTextContainer,
} from '/@/renderer/components/item-list/item-table-list/item-table-list-column'; } from '/@/renderer/components/item-list/item-table-list/item-table-list-column';
import { resolveSongPath } from '/@/renderer/utils/resolve-song-path';
export const PathColumn = (props: ItemTableListInnerColumn) => { export const PathColumn = (props: ItemTableListInnerColumn) => {
const rowItem = props.getRowItem?.(props.rowIndex) ?? (props.data as any[])[props.rowIndex]; const rowItem = props.getRowItem?.(props.rowIndex) ?? (props.data as any[])[props.rowIndex];
const row: string | undefined = (rowItem as any)?.[props.columns[props.columnIndex].id]; const row: string | undefined = (rowItem as any)?.[props.columns[props.columnIndex].id];
const resolvedPath = typeof row === 'string' ? resolveSongPath(row) : null;
if (typeof row === 'string' && row) { if (resolvedPath) {
return ( return (
<TableColumnTextContainer {...props}> <TableColumnTextContainer {...props}>
<span>{row}</span> <span>{resolvedPath}</span>
</TableColumnTextContainer> </TableColumnTextContainer>
); );
} }
@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
import { ContextMenu } from '/@/shared/components/context-menu/context-menu'; import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
import { toast } from '/@/shared/components/toast/toast'; import { toast } from '/@/shared/components/toast/toast';
import { resolveSongPath } from '/@/renderer/utils/resolve-song-path';
import { QueueSong, Song } from '/@/shared/types/domain-types'; import { QueueSong, Song } from '/@/shared/types/domain-types';
interface ShowInFileExplorerActionProps { interface ShowInFileExplorerActionProps {
@@ -21,12 +22,13 @@ export const ShowInFileExplorerAction = ({ items }: ShowInFileExplorerActionProp
} }
const firstItem = items[0]; const firstItem = items[0];
if (!firstItem?.path) { const resolvedPath = resolveSongPath(firstItem?.path);
if (!resolvedPath) {
return; return;
} }
try { try {
await utils.openItem(firstItem.path); await utils.openItem(resolvedPath);
} catch (error) { } catch (error) {
toast.error({ toast.error({
message: (error as Error).message, message: (error as Error).message,
@@ -1,6 +1,7 @@
import isElectron from 'is-electron'; import isElectron from 'is-electron';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useResolvedSongPath } from '/@/renderer/utils/resolve-song-path';
import { ActionIcon } from '/@/shared/components/action-icon/action-icon'; import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
import { CopyButton } from '/@/shared/components/copy-button/copy-button'; import { CopyButton } from '/@/shared/components/copy-button/copy-button';
import { Group } from '/@/shared/components/group/group'; import { Group } from '/@/shared/components/group/group';
@@ -17,12 +18,13 @@ export type SongPathProps = {
export const SongPath = ({ path }: SongPathProps) => { export const SongPath = ({ path }: SongPathProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const resolvedPath = useResolvedSongPath(path);
if (!path) return null; if (!resolvedPath) return null;
return ( return (
<Group> <Group>
<CopyButton timeout={2000} value={path}> <CopyButton timeout={2000} value={resolvedPath}>
{({ copied, copy }) => ( {({ copied, copy }) => (
<Tooltip <Tooltip
label={t( label={t(
@@ -42,7 +44,7 @@ export const SongPath = ({ path }: SongPathProps) => {
<ActionIcon <ActionIcon
icon="externalLink" icon="externalLink"
onClick={() => { onClick={() => {
util.openItem(path).catch((error) => { util.openItem(resolvedPath).catch((error) => {
toast.error({ toast.error({
message: (error as Error).message, message: (error as Error).message,
title: t('error.openError'), title: t('error.openError'),
@@ -53,7 +55,7 @@ export const SongPath = ({ path }: SongPathProps) => {
/> />
</Tooltip> </Tooltip>
)} )}
<Text style={{ userSelect: 'all' }}>{path}</Text> <Text style={{ userSelect: 'all' }}>{resolvedPath}</Text>
</Group> </Group>
); );
}; };
+2 -1
View File
@@ -6,6 +6,7 @@ import { folderQueries } from '/@/renderer/features/folders/api/folder-api';
import { PlayerFilter, useSettingsStore } from '/@/renderer/store'; import { PlayerFilter, useSettingsStore } from '/@/renderer/store';
import { LogCategory, logFn } from '/@/renderer/utils/logger'; import { LogCategory, logFn } from '/@/renderer/utils/logger';
import { logMsg } from '/@/renderer/utils/logger-message'; import { logMsg } from '/@/renderer/utils/logger-message';
import { resolveSongPath } from '/@/renderer/utils/resolve-song-path';
import { sortSongList } from '/@/shared/api/utils'; import { sortSongList } from '/@/shared/api/utils';
import { import {
PlaylistSongListQuery, PlaylistSongListQuery,
@@ -351,7 +352,7 @@ const getSongFieldValue = (song: Song, field: string): boolean | null | number |
case 'note': case 'note':
return song.comment || ''; return song.comment || '';
case 'path': case 'path':
return song.path || ''; return resolveSongPath(song.path) || '';
case 'playCount': case 'playCount':
return song.playCount; return song.playCount;
case 'rating': case 'rating':
@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
import { songsQueries } from '/@/renderer/features/songs/api/songs-api'; import { songsQueries } from '/@/renderer/features/songs/api/songs-api';
import { useCurrentServerId, useGeneralSettings, useSettingsStoreActions } from '/@/renderer/store'; import { useCurrentServerId, useGeneralSettings, useSettingsStoreActions } from '/@/renderer/store';
import { useResolvedSongPath } from '/@/renderer/utils/resolve-song-path';
import { ActionIcon } from '/@/shared/components/action-icon/action-icon'; import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
import { Code } from '/@/shared/components/code/code'; import { Code } from '/@/shared/components/code/code';
import { Group } from '/@/shared/components/group/group'; import { Group } from '/@/shared/components/group/group';
@@ -27,6 +28,7 @@ export const PathSettings = memo(() => {
const { pathReplace, pathReplaceWith } = useGeneralSettings(); const { pathReplace, pathReplaceWith } = useGeneralSettings();
const { setSettings } = useSettingsStoreActions(); const { setSettings } = useSettingsStoreActions();
const resolvedPreviewPath = useResolvedSongPath(randomSong.data?.items[0]?.path);
const [localPathReplace, setLocalPathReplace] = useState(pathReplace); const [localPathReplace, setLocalPathReplace] = useState(pathReplace);
const [localPathReplaceWith, setLocalPathReplaceWith] = useState(pathReplaceWith); const [localPathReplaceWith, setLocalPathReplaceWith] = useState(pathReplaceWith);
@@ -45,8 +47,6 @@ export const PathSettings = memo(() => {
pathReplace: value, pathReplace: value,
}, },
}); });
randomSong.refetch();
}, 500); }, 500);
const debouncedSetPathReplaceWith = useDebouncedCallback((value: string) => { const debouncedSetPathReplaceWith = useDebouncedCallback((value: string) => {
@@ -55,8 +55,6 @@ export const PathSettings = memo(() => {
pathReplaceWith: value, pathReplaceWith: value,
}, },
}); });
randomSong.refetch();
}, 500); }, 500);
return ( return (
@@ -73,7 +71,7 @@ export const PathSettings = memo(() => {
</Group> </Group>
<Code> <Code>
<Text isMuted size="md"> <Text isMuted size="md">
{randomSong.data?.items[0]?.path || ''} {resolvedPreviewPath || ''}
</Text> </Text>
</Code> </Code>
<Group grow> <Group grow>
+1
View File
@@ -4,6 +4,7 @@ export * from './get-header-color';
export * from './normalize-server-url'; export * from './normalize-server-url';
export * from './parse-search-params'; export * from './parse-search-params';
export * from './random-string'; export * from './random-string';
export * from './resolve-song-path';
export * from './rgb-to-rgba'; export * from './rgb-to-rgba';
export * from './sentence-case'; export * from './sentence-case';
export * from './set-local-storage-setttings'; export * from './set-local-storage-setttings';
+26
View File
@@ -0,0 +1,26 @@
import { useMemo } from 'react';
import { usePathReplace, useSettingsStore } from '/@/renderer/store/settings.store';
import { replacePathPrefix } from '/@/shared/api/utils';
export const resolveSongPath = (path: null | string | undefined): null | string => {
if (!path) {
return null;
}
const { pathReplace, pathReplaceWith } = useSettingsStore.getState().general;
return replacePathPrefix(path, pathReplace, pathReplaceWith);
};
export const useResolvedSongPath = (path: null | string | undefined): null | string => {
const { pathReplace, pathReplaceWith } = usePathReplace();
return useMemo(() => {
if (!path) {
return null;
}
return replacePathPrefix(path, pathReplace, pathReplaceWith);
}, [path, pathReplace, pathReplaceWith]);
};
@@ -2,7 +2,6 @@ import { z } from 'zod';
import { jfType } from '/@/shared/api/jellyfin/jellyfin-types'; import { jfType } from '/@/shared/api/jellyfin/jellyfin-types';
import { coerceYear, parsePartialIsoDateFromApi } from '/@/shared/api/partial-iso-date'; import { coerceYear, parsePartialIsoDateFromApi } from '/@/shared/api/partial-iso-date';
import { replacePathPrefix } from '/@/shared/api/utils';
import { import {
Album, Album,
AlbumArtist, AlbumArtist,
@@ -156,8 +155,6 @@ const jellyfinPremiereFields = (item: {
const normalizeSong = ( const normalizeSong = (
item: z.infer<typeof jfType._response.song>, item: z.infer<typeof jfType._response.song>,
server: null | ServerListItem, server: null | ServerListItem,
pathReplace?: string,
pathReplaceWith?: string,
): Song => { ): Song => {
let bitDepth: null | number = null; let bitDepth: null | number = null;
let bitRate = 0; let bitRate = 0;
@@ -257,7 +254,7 @@ const normalizeSong = (
mbzTrackId: item.ProviderIds?.MusicBrainzTrack || null, mbzTrackId: item.ProviderIds?.MusicBrainzTrack || null,
name: item.Name, name: item.Name,
participants, participants,
path: replacePathPrefix(path || '', pathReplace, pathReplaceWith), path: path || '',
peak: null, peak: null,
playCount: (item.UserData && item.UserData.PlayCount) || 0, playCount: (item.UserData && item.UserData.PlayCount) || 0,
playlistItemId: item.PlaylistItemId, playlistItemId: item.PlaylistItemId,
@@ -278,8 +275,6 @@ const normalizeSong = (
const normalizeAlbum = ( const normalizeAlbum = (
item: z.infer<typeof jfType._response.album>, item: z.infer<typeof jfType._response.album>,
server: null | ServerListItem, server: null | ServerListItem,
pathReplace?: string,
pathReplaceWith?: string,
): Album => { ): Album => {
const { originalYear, releaseDate, releaseYear } = jellyfinPremiereFields(item); const { originalYear, releaseDate, releaseYear } = jellyfinPremiereFields(item);
@@ -342,7 +337,7 @@ const normalizeAlbum = (
releaseYear, releaseYear,
size: null, size: null,
songCount: item?.ChildCount || null, songCount: item?.ChildCount || null,
songs: item.Songs?.map((song) => normalizeSong(song, server, pathReplace, pathReplaceWith)), songs: item.Songs?.map((song) => normalizeSong(song, server)),
sortName: item.SortName || item.Name, sortName: item.SortName || item.Name,
tags: getTags(item), tags: getTags(item),
updatedAt: item?.DateLastMediaAdded || item.DateCreated, updatedAt: item?.DateLastMediaAdded || item.DateCreated,
@@ -3,7 +3,6 @@ import z from 'zod';
import { ndType } from '/@/shared/api/navidrome/navidrome-types'; import { ndType } from '/@/shared/api/navidrome/navidrome-types';
import { coerceYear, parsePartialIsoDate } from '/@/shared/api/partial-iso-date'; import { coerceYear, parsePartialIsoDate } from '/@/shared/api/partial-iso-date';
import { ssType } from '/@/shared/api/subsonic/subsonic-types'; import { ssType } from '/@/shared/api/subsonic/subsonic-types';
import { replacePathPrefix } from '/@/shared/api/utils';
import { import {
Album, Album,
AlbumArtist, AlbumArtist,
@@ -199,8 +198,6 @@ const getArtists = (
const normalizeSong = ( const normalizeSong = (
item: z.infer<typeof ndType._response.playlistSong> | z.infer<typeof ndType._response.song>, item: z.infer<typeof ndType._response.playlistSong> | z.infer<typeof ndType._response.song>,
server?: null | ServerListItem, server?: null | ServerListItem,
pathReplace?: string,
pathReplaceWith?: string,
): Song => { ): Song => {
let id; let id;
let playlistItemId; let playlistItemId;
@@ -270,7 +267,7 @@ const normalizeSong = (
name: item.title, name: item.title,
// Thankfully, Windows is merciful and allows a mix of separators. So, we can use the // Thankfully, Windows is merciful and allows a mix of separators. So, we can use the
// POSIX separator here instead // POSIX separator here instead
path: item.path ? replacePathPrefix(item.path, pathReplace, pathReplaceWith) : null, path: item.path ? `${item.libraryPath}/${item.path}` : null,
peak: peak:
item.rgAlbumPeak || item.rgTrackPeak item.rgAlbumPeak || item.rgTrackPeak
? { album: item.rgAlbumPeak, track: item.rgTrackPeak } ? { album: item.rgAlbumPeak, track: item.rgTrackPeak }
@@ -337,8 +334,6 @@ const normalizeAlbum = (
songs?: z.infer<typeof ndType._response.songList>; songs?: z.infer<typeof ndType._response.songList>;
}, },
server?: null | ServerListItem, server?: null | ServerListItem,
pathReplace?: string,
pathReplaceWith?: string,
): Album => { ): Album => {
const releaseDate = normalizeNavidromeReleaseDate(item); const releaseDate = normalizeNavidromeReleaseDate(item);
const originalDate = normalizeNavidromeOriginalDate(item); const originalDate = normalizeNavidromeOriginalDate(item);
@@ -386,9 +381,7 @@ const normalizeAlbum = (
releaseYear: releaseDate.year > 0 ? releaseDate.year : null, releaseYear: releaseDate.year > 0 ? releaseDate.year : null,
size: item.size, size: item.size,
songCount: item.songCount, songCount: item.songCount,
songs: item.songs songs: item.songs ? item.songs.map((song) => normalizeSong(song, server)) : undefined,
? item.songs.map((song) => normalizeSong(song, server, pathReplace, pathReplaceWith))
: undefined,
sortName: item.orderAlbumName, sortName: item.orderAlbumName,
tags: item.tags || null, tags: item.tags || null,
updatedAt: item.updatedAt, updatedAt: item.updatedAt,
+3 -10
View File
@@ -2,7 +2,6 @@ import { z } from 'zod';
import { coerceYear, parsePartialIsoDate } from '/@/shared/api/partial-iso-date'; import { coerceYear, parsePartialIsoDate } from '/@/shared/api/partial-iso-date';
import { ssType } from '/@/shared/api/subsonic/subsonic-types'; import { ssType } from '/@/shared/api/subsonic/subsonic-types';
import { replacePathPrefix } from '/@/shared/api/utils';
import { import {
Album, Album,
AlbumArtist, AlbumArtist,
@@ -163,8 +162,6 @@ const subsonicReleaseFields = (item: {
const normalizeSong = ( const normalizeSong = (
item: z.infer<typeof ssType._response.song>, item: z.infer<typeof ssType._response.song>,
server?: null | ServerListItemWithCredential, server?: null | ServerListItemWithCredential,
pathReplace?: string,
pathReplaceWith?: string,
playlistIndex?: number, playlistIndex?: number,
discTitleMap?: Map<number, string>, discTitleMap?: Map<number, string>,
): Song => { ): Song => {
@@ -221,7 +218,7 @@ const normalizeSong = (
mbzTrackId: null, mbzTrackId: null,
name: item.title, name: item.title,
participants, participants,
path: replacePathPrefix(item.path || '', pathReplace, pathReplaceWith), path: item.path || '',
peak: peak:
item.replayGain && (item.replayGain.albumPeak || item.replayGain.trackPeak) item.replayGain && (item.replayGain.albumPeak || item.replayGain.trackPeak)
? { ? {
@@ -305,8 +302,6 @@ const getReleaseType = (
const normalizeAlbum = ( const normalizeAlbum = (
item: z.infer<typeof ssType._response.album> | z.infer<typeof ssType._response.albumListEntry>, item: z.infer<typeof ssType._response.album> | z.infer<typeof ssType._response.albumListEntry>,
server?: null | ServerListItemWithCredential, server?: null | ServerListItemWithCredential,
pathReplace?: string,
pathReplaceWith?: string,
): Album => { ): Album => {
const discTitleMap = new Map<number, string>(); const discTitleMap = new Map<number, string>();
@@ -354,7 +349,7 @@ const normalizeAlbum = (
songCount: item.songCount, songCount: item.songCount,
songs: songs:
(item as z.infer<typeof ssType._response.album>).song?.map((song) => (item as z.infer<typeof ssType._response.album>).song?.map((song) =>
normalizeSong(song, server, pathReplace, pathReplaceWith, undefined, discTitleMap), normalizeSong(song, server, undefined, discTitleMap),
) || [], ) || [],
sortName: item.title, sortName: item.title,
tags: null, tags: null,
@@ -410,8 +405,6 @@ const normalizeGenre = (
const normalizeFolder = ( const normalizeFolder = (
item: z.infer<typeof ssType._response.directory>, item: z.infer<typeof ssType._response.directory>,
server?: null | ServerListItemWithCredential, server?: null | ServerListItemWithCredential,
pathReplace?: string,
pathReplaceWith?: string,
): Folder => { ): Folder => {
const results = item.child?.reduce( const results = item.child?.reduce(
(acc: { folders: Folder[]; songs: Song[] }, item) => { (acc: { folders: Folder[]; songs: Song[] }, item) => {
@@ -421,7 +414,7 @@ const normalizeFolder = (
const folder = normalizeFolder(item, server); const folder = normalizeFolder(item, server);
acc.folders.push(folder); acc.folders.push(folder);
} else { } else {
const song = normalizeSong(item, server, pathReplace, pathReplaceWith); const song = normalizeSong(item, server);
acc.songs.push(song); acc.songs.push(song);
} }
+2 -4
View File
@@ -414,10 +414,8 @@ export type Song = {
userRating: null | number; userRating: null | number;
}; };
type ApiContext = { // eslint-disable-next-line @typescript-eslint/no-empty-object-type
pathReplace?: string; type ApiContext = {};
pathReplaceWith?: string;
};
type BaseEndpointArgs = { type BaseEndpointArgs = {
apiClientProps: { apiClientProps: {