Merge pull request #326 from lsmda/feature/add-strict-album-match-toggle-on-playlist-creation
feat(playlists): add strict album match toggle on CSV tab import
This commit is contained in:
commit
7b70e55895
3 changed files with 25 additions and 10 deletions
17
index.html
17
index.html
|
|
@ -528,7 +528,6 @@
|
||||||
color: var(--foreground);
|
color: var(--foreground);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 0.25rem 0.5rem;
|
padding: 0.25rem 0.5rem;
|
||||||
opacity: 0.7;
|
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
CSV
|
CSV
|
||||||
|
|
@ -669,6 +668,22 @@
|
||||||
Make sure its headers are in English.
|
Make sure its headers are in English.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="setting-item"
|
||||||
|
style="margin-top: 1rem; margin-bottom: 1rem; background: transparent"
|
||||||
|
>
|
||||||
|
<div class="info">
|
||||||
|
<span style="font-size: 0.9rem; font-weight: 600">Strict Album Matching</span>
|
||||||
|
<span style="font-size: 0.8rem; max-width: 14rem">
|
||||||
|
Album name should strictly match CSV metadata. Disable for better discovery.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<label class="toggle-switch">
|
||||||
|
<input type="checkbox" id="strict-album-match-toggle" />
|
||||||
|
<span class="slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="jspf-import-panel" class="import-panel" style="display: none">
|
<div id="jspf-import-panel" class="import-panel" style="display: none">
|
||||||
|
|
|
||||||
|
|
@ -1265,6 +1265,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
let name = document.getElementById('playlist-name-input').value.trim();
|
let name = document.getElementById('playlist-name-input').value.trim();
|
||||||
let description = document.getElementById('playlist-description-input').value.trim();
|
let description = document.getElementById('playlist-description-input').value.trim();
|
||||||
const isPublic = document.getElementById('playlist-public-toggle')?.checked;
|
const isPublic = document.getElementById('playlist-public-toggle')?.checked;
|
||||||
|
const isStrictAlbumMatch = document.getElementById('strict-album-match-toggle')?.checked;
|
||||||
|
|
||||||
if (name) {
|
if (name) {
|
||||||
const modal = document.getElementById('playlist-modal');
|
const modal = document.getElementById('playlist-modal');
|
||||||
|
|
@ -1317,7 +1318,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
const xmlFileInput = document.getElementById('xml-file-input');
|
const xmlFileInput = document.getElementById('xml-file-input');
|
||||||
const m3uFileInput = document.getElementById('m3u-file-input');
|
const m3uFileInput = document.getElementById('m3u-file-input');
|
||||||
|
|
||||||
const importOptions = { strictArtistMatch: true, albumMatch: true };
|
const importOptions = { strictArtistMatch: true, strictAlbumMatch: isStrictAlbumMatch };
|
||||||
|
|
||||||
let tracks = [];
|
let tracks = [];
|
||||||
let importSource = 'manual';
|
let importSource = 'manual';
|
||||||
|
|
|
||||||
|
|
@ -5,21 +5,21 @@ function isFuzzyMatch(str1, str2) {
|
||||||
return s1.includes(s2) || s2.includes(s1);
|
return s1.includes(s2) || s2.includes(s1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function findBestMatch(items, targetArtist, targetAlbum, options) {
|
function findBestMatch(items, targetArtist, targetAlbum, importOptions) {
|
||||||
if (!items || items.length === 0) return null;
|
if (!items || items.length === 0) return null;
|
||||||
if (!options?.strictArtistMatch && !options?.albumMatch) return items[0];
|
if (!importOptions?.strictArtistMatch && !importOptions?.strictAlbumMatch) return items[0];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
items.find((item) => {
|
items.find((item) => {
|
||||||
let artistOk = true;
|
let artistOk = true;
|
||||||
let albumOk = true;
|
let albumOk = true;
|
||||||
|
|
||||||
if (options.strictArtistMatch && targetArtist) {
|
if (importOptions.strictArtistMatch && targetArtist) {
|
||||||
const itemArtist = item.artist?.name || item.artists?.[0]?.name;
|
const itemArtist = item.artist?.name || item.artists?.[0]?.name;
|
||||||
if (!isFuzzyMatch(itemArtist, targetArtist)) artistOk = false;
|
if (!isFuzzyMatch(itemArtist, targetArtist)) artistOk = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.albumMatch && targetAlbum) {
|
if (importOptions.strictAlbumMatch && targetAlbum) {
|
||||||
const itemAlbum = item.album?.title;
|
const itemAlbum = item.album?.title;
|
||||||
if (itemAlbum && !isFuzzyMatch(itemAlbum, targetAlbum)) albumOk = false;
|
if (itemAlbum && !isFuzzyMatch(itemAlbum, targetAlbum)) albumOk = false;
|
||||||
}
|
}
|
||||||
|
|
@ -294,8 +294,7 @@ export async function parseDynamicCSV(csvText, api, onProgress, options = {}) {
|
||||||
const albumName = mappedHeaders.album !== undefined ? values[mappedHeaders.album] : '';
|
const albumName = mappedHeaders.album !== undefined ? values[mappedHeaders.album] : '';
|
||||||
const isrc = mappedHeaders.isrc !== undefined ? values[mappedHeaders.isrc] : '';
|
const isrc = mappedHeaders.isrc !== undefined ? values[mappedHeaders.isrc] : '';
|
||||||
const playlistName = mappedHeaders.playlistName !== undefined ? values[mappedHeaders.playlistName] : '';
|
const playlistName = mappedHeaders.playlistName !== undefined ? values[mappedHeaders.playlistName] : '';
|
||||||
const typeValue =
|
const typeValue = mappedHeaders.type !== undefined ? values[mappedHeaders.type]?.toLowerCase().trim() : '';
|
||||||
mappedHeaders.type !== undefined ? values[mappedHeaders.type]?.toLowerCase().trim() : '';
|
|
||||||
const isFavorite = typeValue.includes('favorite');
|
const isFavorite = typeValue.includes('favorite');
|
||||||
|
|
||||||
if (onProgress) {
|
if (onProgress) {
|
||||||
|
|
@ -497,7 +496,7 @@ export async function importToLibrary(csvResult, db, onProgress, options = {}) {
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function parseCSV(csvText, api, onProgress, options = {}) {
|
export async function parseCSV(csvText, api, onProgress, importOptions = {}) {
|
||||||
const lines = csvText.trim().split('\n');
|
const lines = csvText.trim().split('\n');
|
||||||
if (lines.length < 2) return { tracks: [], missingTracks: [] };
|
if (lines.length < 2) return { tracks: [], missingTracks: [] };
|
||||||
|
|
||||||
|
|
@ -583,7 +582,7 @@ export async function parseCSV(csvText, api, onProgress, options = {}) {
|
||||||
const searchResult = await api.searchTracks(searchQuery);
|
const searchResult = await api.searchTracks(searchQuery);
|
||||||
|
|
||||||
if (searchResult.items && searchResult.items.length > 0) {
|
if (searchResult.items && searchResult.items.length > 0) {
|
||||||
const match = findBestMatch(searchResult.items, artistNames, albumName, options);
|
const match = findBestMatch(searchResult.items, artistNames, albumName, importOptions);
|
||||||
if (match) tracks.push(match);
|
if (match) tracks.push(match);
|
||||||
else missingTracks.push({ title: trackTitle, artist: artistNames, album: albumName });
|
else missingTracks.push({ title: trackTitle, artist: artistNames, album: albumName });
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue