Use strict album-title-artist existence matching and add exists/missing result filter
This commit is contained in:
parent
a42e5e2542
commit
b021df31b4
3 changed files with 114 additions and 11 deletions
83
server.js
83
server.js
|
|
@ -30,6 +30,7 @@ let spotifyTokenCache = {
|
|||
let libraryIndexCache = {
|
||||
byTitle: new Map(),
|
||||
byAlbum: new Map(),
|
||||
byTrackTriplet: new Map(),
|
||||
trackById: new Map(),
|
||||
updatedAt: 0
|
||||
};
|
||||
|
|
@ -91,6 +92,13 @@ function normalizeTitle(title) {
|
|||
.trim();
|
||||
}
|
||||
|
||||
function normalizeStrict(value) {
|
||||
return String(value || '')
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
}
|
||||
|
||||
function buildTrackTitleKey(title) {
|
||||
return normalizeTitle(title);
|
||||
}
|
||||
|
|
@ -99,6 +107,10 @@ function buildAlbumKey(albumName, artistName) {
|
|||
return `${normalize(albumName)}::${normalize(artistName)}`;
|
||||
}
|
||||
|
||||
function buildTrackTripletKey(trackName, albumName, artistName) {
|
||||
return `${normalizeStrict(trackName)}::${normalizeStrict(albumName)}::${normalizeStrict(artistName)}`;
|
||||
}
|
||||
|
||||
function hasFileForTrack(track) {
|
||||
return Boolean(track?.hasFile || track?.trackFileId || track?.fileId);
|
||||
}
|
||||
|
|
@ -107,7 +119,7 @@ function extractTrackName(track) {
|
|||
return track?.title || track?.trackTitle || '';
|
||||
}
|
||||
|
||||
function addTrackToLibraryIndex(track) {
|
||||
function addTrackToLibraryIndex(track, albumMeta = null) {
|
||||
const titleKey = buildTrackTitleKey(extractTrackName(track));
|
||||
if (!titleKey) return;
|
||||
|
||||
|
|
@ -119,22 +131,42 @@ function addTrackToLibraryIndex(track) {
|
|||
id: track.id,
|
||||
albumId: track.albumId,
|
||||
title: extractTrackName(track),
|
||||
albumName: albumMeta?.albumName || '',
|
||||
artistName: albumMeta?.artistName || '',
|
||||
hasFile: hasFileForTrack(track)
|
||||
};
|
||||
libraryIndexCache.byTitle.get(titleKey).push(entry);
|
||||
if (track.id) {
|
||||
libraryIndexCache.trackById.set(track.id, entry);
|
||||
}
|
||||
|
||||
const tripletKey = buildTrackTripletKey(entry.title, entry.albumName, entry.artistName);
|
||||
if (tripletKey) {
|
||||
if (!libraryIndexCache.byTrackTriplet.has(tripletKey)) {
|
||||
libraryIndexCache.byTrackTriplet.set(tripletKey, []);
|
||||
}
|
||||
libraryIndexCache.byTrackTriplet.get(tripletKey).push(entry);
|
||||
}
|
||||
}
|
||||
|
||||
async function rebuildLibraryIndex() {
|
||||
const albums = await lidarrRequest('get', '/api/v1/album');
|
||||
const byTitle = new Map();
|
||||
const byAlbum = new Map();
|
||||
const byTrackTriplet = new Map();
|
||||
const trackById = new Map();
|
||||
|
||||
const prev = libraryIndexCache;
|
||||
libraryIndexCache = { byTitle, byAlbum, trackById, updatedAt: Date.now() };
|
||||
libraryIndexCache = { byTitle, byAlbum, byTrackTriplet, trackById, updatedAt: Date.now() };
|
||||
|
||||
const albumMetaById = new Map();
|
||||
for (const album of albums || []) {
|
||||
if (!album?.id) continue;
|
||||
albumMetaById.set(album.id, {
|
||||
albumName: album.title || album.albumTitle || '',
|
||||
artistName: album.artistName || album.artist?.artistName || ''
|
||||
});
|
||||
}
|
||||
|
||||
let trackCount = 0;
|
||||
for (const album of albums || []) {
|
||||
|
|
@ -146,7 +178,7 @@ async function rebuildLibraryIndex() {
|
|||
tracks = [];
|
||||
}
|
||||
for (const track of tracks || []) {
|
||||
addTrackToLibraryIndex(track);
|
||||
addTrackToLibraryIndex(track, albumMetaById.get(album.id));
|
||||
trackCount += 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -177,7 +209,36 @@ async function ensureLibraryIndexFresh(maxAgeMs = 5 * 60 * 1000) {
|
|||
}
|
||||
}
|
||||
|
||||
function getTrackLibraryState(trackName) {
|
||||
function getTrackLibraryState(trackName, albumName, artistName) {
|
||||
const tripletKey = buildTrackTripletKey(trackName, albumName, artistName);
|
||||
const tripletMatches = libraryIndexCache.byTrackTriplet.get(tripletKey) || [];
|
||||
if (tripletMatches.length > 0) {
|
||||
const existing = tripletMatches.some((m) => m.hasFile);
|
||||
return {
|
||||
exists: existing,
|
||||
matchCount: tripletMatches.length
|
||||
};
|
||||
}
|
||||
|
||||
// Fallback only when no album/artist context exists.
|
||||
if (!albumName && !artistName) {
|
||||
const key = buildTrackTitleKey(trackName);
|
||||
const matches = libraryIndexCache.byTitle.get(key) || [];
|
||||
const existing = matches.some((m) => m.hasFile);
|
||||
return {
|
||||
exists: existing,
|
||||
matchCount: matches.length
|
||||
};
|
||||
}
|
||||
|
||||
// Strict mode with context: no full triplet match means "not existing".
|
||||
return {
|
||||
exists: false,
|
||||
matchCount: 0
|
||||
};
|
||||
}
|
||||
|
||||
function getTrackLibraryStateLoose(trackName) {
|
||||
const key = buildTrackTitleKey(trackName);
|
||||
const matches = libraryIndexCache.byTitle.get(key) || [];
|
||||
const existing = matches.some((m) => m.hasFile);
|
||||
|
|
@ -631,7 +692,11 @@ app.get('/api/spotify/search', async (req, res) => {
|
|||
}
|
||||
if (type === 'track') {
|
||||
items = (data.tracks?.items || []).map((track) => ({
|
||||
...getTrackLibraryState(track.name),
|
||||
...getTrackLibraryState(
|
||||
track.name,
|
||||
track.album?.name || '',
|
||||
track.album?.artists?.[0]?.name || track.artists?.[0]?.name || ''
|
||||
),
|
||||
id: track.id,
|
||||
trackName: track.name,
|
||||
artist: track.artists?.map((a) => a.name).join(', ') || 'Unbekannt',
|
||||
|
|
@ -698,7 +763,11 @@ app.get('/api/spotify/album/:id', async (req, res) => {
|
|||
name: track.name,
|
||||
durationMs: track.duration_ms,
|
||||
trackNumber: track.track_number,
|
||||
exists: getTrackLibraryState(track.name).exists
|
||||
exists: getTrackLibraryState(
|
||||
track.name,
|
||||
album.name,
|
||||
album.artists?.[0]?.name || 'Unbekannt'
|
||||
).exists
|
||||
}))
|
||||
});
|
||||
} catch (err) {
|
||||
|
|
@ -734,7 +803,7 @@ app.post('/api/import/playlist', async (req, res) => {
|
|||
|
||||
const enriched = tracks.map((t) => ({
|
||||
...t,
|
||||
exists: getTrackLibraryState(t.trackName).exists
|
||||
exists: getTrackLibraryState(t.trackName, t.albumName, t.albumArtist || t.artist).exists
|
||||
}));
|
||||
|
||||
res.json({ success: true, tracks: enriched });
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue