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
@@ -2,7 +2,6 @@ import { z } from 'zod';
import { jfType } from '/@/shared/api/jellyfin/jellyfin-types';
import { coerceYear, parsePartialIsoDateFromApi } from '/@/shared/api/partial-iso-date';
import { replacePathPrefix } from '/@/shared/api/utils';
import {
Album,
AlbumArtist,
@@ -156,8 +155,6 @@ const jellyfinPremiereFields = (item: {
const normalizeSong = (
item: z.infer<typeof jfType._response.song>,
server: null | ServerListItem,
pathReplace?: string,
pathReplaceWith?: string,
): Song => {
let bitDepth: null | number = null;
let bitRate = 0;
@@ -257,7 +254,7 @@ const normalizeSong = (
mbzTrackId: item.ProviderIds?.MusicBrainzTrack || null,
name: item.Name,
participants,
path: replacePathPrefix(path || '', pathReplace, pathReplaceWith),
path: path || '',
peak: null,
playCount: (item.UserData && item.UserData.PlayCount) || 0,
playlistItemId: item.PlaylistItemId,
@@ -278,8 +275,6 @@ const normalizeSong = (
const normalizeAlbum = (
item: z.infer<typeof jfType._response.album>,
server: null | ServerListItem,
pathReplace?: string,
pathReplaceWith?: string,
): Album => {
const { originalYear, releaseDate, releaseYear } = jellyfinPremiereFields(item);
@@ -342,7 +337,7 @@ const normalizeAlbum = (
releaseYear,
size: 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,
tags: getTags(item),
updatedAt: item?.DateLastMediaAdded || item.DateCreated,
@@ -3,7 +3,6 @@ import z from 'zod';
import { ndType } from '/@/shared/api/navidrome/navidrome-types';
import { coerceYear, parsePartialIsoDate } from '/@/shared/api/partial-iso-date';
import { ssType } from '/@/shared/api/subsonic/subsonic-types';
import { replacePathPrefix } from '/@/shared/api/utils';
import {
Album,
AlbumArtist,
@@ -199,8 +198,6 @@ const getArtists = (
const normalizeSong = (
item: z.infer<typeof ndType._response.playlistSong> | z.infer<typeof ndType._response.song>,
server?: null | ServerListItem,
pathReplace?: string,
pathReplaceWith?: string,
): Song => {
let id;
let playlistItemId;
@@ -270,7 +267,7 @@ const normalizeSong = (
name: item.title,
// Thankfully, Windows is merciful and allows a mix of separators. So, we can use the
// POSIX separator here instead
path: item.path ? replacePathPrefix(item.path, pathReplace, pathReplaceWith) : null,
path: item.path ? `${item.libraryPath}/${item.path}` : null,
peak:
item.rgAlbumPeak || item.rgTrackPeak
? { album: item.rgAlbumPeak, track: item.rgTrackPeak }
@@ -337,8 +334,6 @@ const normalizeAlbum = (
songs?: z.infer<typeof ndType._response.songList>;
},
server?: null | ServerListItem,
pathReplace?: string,
pathReplaceWith?: string,
): Album => {
const releaseDate = normalizeNavidromeReleaseDate(item);
const originalDate = normalizeNavidromeOriginalDate(item);
@@ -386,9 +381,7 @@ const normalizeAlbum = (
releaseYear: releaseDate.year > 0 ? releaseDate.year : null,
size: item.size,
songCount: item.songCount,
songs: item.songs
? item.songs.map((song) => normalizeSong(song, server, pathReplace, pathReplaceWith))
: undefined,
songs: item.songs ? item.songs.map((song) => normalizeSong(song, server)) : undefined,
sortName: item.orderAlbumName,
tags: item.tags || null,
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 { ssType } from '/@/shared/api/subsonic/subsonic-types';
import { replacePathPrefix } from '/@/shared/api/utils';
import {
Album,
AlbumArtist,
@@ -163,8 +162,6 @@ const subsonicReleaseFields = (item: {
const normalizeSong = (
item: z.infer<typeof ssType._response.song>,
server?: null | ServerListItemWithCredential,
pathReplace?: string,
pathReplaceWith?: string,
playlistIndex?: number,
discTitleMap?: Map<number, string>,
): Song => {
@@ -221,7 +218,7 @@ const normalizeSong = (
mbzTrackId: null,
name: item.title,
participants,
path: replacePathPrefix(item.path || '', pathReplace, pathReplaceWith),
path: item.path || '',
peak:
item.replayGain && (item.replayGain.albumPeak || item.replayGain.trackPeak)
? {
@@ -305,8 +302,6 @@ const getReleaseType = (
const normalizeAlbum = (
item: z.infer<typeof ssType._response.album> | z.infer<typeof ssType._response.albumListEntry>,
server?: null | ServerListItemWithCredential,
pathReplace?: string,
pathReplaceWith?: string,
): Album => {
const discTitleMap = new Map<number, string>();
@@ -354,7 +349,7 @@ const normalizeAlbum = (
songCount: item.songCount,
songs:
(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,
tags: null,
@@ -410,8 +405,6 @@ const normalizeGenre = (
const normalizeFolder = (
item: z.infer<typeof ssType._response.directory>,
server?: null | ServerListItemWithCredential,
pathReplace?: string,
pathReplaceWith?: string,
): Folder => {
const results = item.child?.reduce(
(acc: { folders: Folder[]; songs: Song[] }, item) => {
@@ -421,7 +414,7 @@ const normalizeFolder = (
const folder = normalizeFolder(item, server);
acc.folders.push(folder);
} else {
const song = normalizeSong(item, server, pathReplace, pathReplaceWith);
const song = normalizeSong(item, server);
acc.songs.push(song);
}
+2 -4
View File
@@ -414,10 +414,8 @@ export type Song = {
userRating: null | number;
};
type ApiContext = {
pathReplace?: string;
pathReplaceWith?: string;
};
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
type ApiContext = {};
type BaseEndpointArgs = {
apiClientProps: {