fix(import): prevent incorrect artist matches and missing tracks

This commit is contained in:
Samidy 2026-03-02 06:47:50 +03:00
parent 0fd5ad9c58
commit 23fdf10f86
2 changed files with 60 additions and 6 deletions

View file

@ -1134,6 +1134,9 @@ document.addEventListener('DOMContentLoaded', async () => {
const xspfFileInput = document.getElementById('xspf-file-input');
const xmlFileInput = document.getElementById('xml-file-input');
const m3uFileInput = document.getElementById('m3u-file-input');
const importOptions = { strictArtistMatch: true, albumMatch: true };
let tracks = [];
let importSource = 'manual';
let cover = document.getElementById('playlist-cover-input').value.trim();
@ -1214,7 +1217,7 @@ document.addEventListener('DOMContentLoaded', async () => {
currentTrackElement.textContent = progress.currentTrack;
if (currentArtistElement)
currentArtistElement.textContent = progress.currentArtist || '';
});
}, importOptions);
tracks = result.tracks;
const missingTracks = result.missingTracks;
@ -1361,7 +1364,7 @@ document.addEventListener('DOMContentLoaded', async () => {
? `Importing ${progress.type}...`
: '';
}
});
}, importOptions);
const hasMultipleTypes =
result.tracks.length > 0 && (result.albums.length > 0 || result.artists.length > 0);
@ -2559,6 +2562,7 @@ function escapeHtml(text) {
function showMissingTracksNotification(missingTracks) {
const modal = document.getElementById('missing-tracks-modal');
const listUl = document.getElementById('missing-tracks-list-ul');
const copyBtn = document.getElementById('copy-missing-tracks-btn');
listUl.innerHTML = missingTracks
.map((track) => {
@ -2568,6 +2572,25 @@ function showMissingTracksNotification(missingTracks) {
})
.join('');
if (copyBtn) {
const newCopyBtn = copyBtn.cloneNode(true);
copyBtn.parentNode.replaceChild(newCopyBtn, copyBtn);
newCopyBtn.addEventListener('click', () => {
const textToCopy = missingTracks
.map((track) => {
return typeof track === 'string' ? track : `${track.artist ? track.artist + ' - ' : ''}${track.title}`;
})
.join('\n');
navigator.clipboard.writeText(textToCopy).then(() => {
const originalText = newCopyBtn.textContent;
newCopyBtn.textContent = 'Copied!';
setTimeout(() => (newCopyBtn.textContent = originalText), 2000);
});
});
}
const closeModal = () => modal.classList.remove('active');
// Remove old listeners if any (though usually these functions are called once per instance,

View file

@ -1,3 +1,32 @@
function isFuzzyMatch(str1, str2) {
if (!str1 || !str2) return false;
const s1 = str1.toLowerCase().replace(/[^\p{L}\p{N}]/gu, '');
const s2 = str2.toLowerCase().replace(/[^\p{L}\p{N}]/gu, '');
return s1.includes(s2) || s2.includes(s1);
}
function findBestMatch(items, targetArtist, targetAlbum, options) {
if (!items || items.length === 0) return null;
if (!options?.strictArtistMatch && !options?.albumMatch) return items[0];
return items.find((item) => {
let artistOk = true;
let albumOk = true;
if (options.strictArtistMatch && targetArtist) {
const itemArtist = item.artist?.name || item.artists?.[0]?.name;
if (!isFuzzyMatch(itemArtist, targetArtist)) artistOk = false;
}
if (options.albumMatch && targetAlbum) {
const itemAlbum = item.album?.title;
if (itemAlbum && !isFuzzyMatch(itemAlbum, targetAlbum)) albumOk = false;
}
return artistOk && albumOk;
}) || null;
}
/**
* Helper function to get track artists string
*/
@ -172,7 +201,7 @@ function detectCSVFormat(mappedHeaders) {
};
}
export async function parseDynamicCSV(csvText, api, onProgress) {
export async function parseDynamicCSV(csvText, api, onProgress, options = {}) {
const lines = csvText.trim().split('\n');
if (lines.length < 2) {
return {
@ -277,7 +306,7 @@ export async function parseDynamicCSV(csvText, api, onProgress) {
const searchQuery = `"${trackName}" ${artistName}`.trim();
const searchResult = await api.searchTracks(searchQuery);
if (searchResult.items && searchResult.items.length > 0) {
foundTrack = searchResult.items[0];
foundTrack = findBestMatch(searchResult.items, artistName, albumName, options);
}
}
@ -431,7 +460,7 @@ export async function importToLibrary(csvResult, db, onProgress) {
return results;
}
export async function parseCSV(csvText, api, onProgress) {
export async function parseCSV(csvText, api, onProgress, options = {}) {
const lines = csvText.trim().split('\n');
if (lines.length < 2) return { tracks: [], missingTracks: [] };
@ -517,7 +546,9 @@ export async function parseCSV(csvText, api, onProgress) {
const searchResult = await api.searchTracks(searchQuery);
if (searchResult.items && searchResult.items.length > 0) {
tracks.push(searchResult.items[0]);
const match = findBestMatch(searchResult.items, artistNames, albumName, options);
if (match) tracks.push(match);
else missingTracks.push({ title: trackTitle, artist: artistNames, album: albumName });
} else {
missingTracks.push({ title: trackTitle, artist: artistNames, album: albumName });
}