Merge pull request #488 from Nezreka/chore/drop-redundant-spotify-library-section
Drop redundant standalone "Your Spotify Library" section on Discover
This commit is contained in:
commit
8b41670717
3 changed files with 1 additions and 390 deletions
|
|
@ -2913,48 +2913,6 @@
|
|||
<div class="spotify-library-pagination" id="your-albums-pagination" style="display: none;"></div>
|
||||
</div>
|
||||
|
||||
<!-- Spotify Library Section -->
|
||||
<div class="discover-section" id="spotify-library-section" style="display: none;">
|
||||
<div class="discover-section-header">
|
||||
<div>
|
||||
<h2 class="discover-section-title">Your Spotify Library</h2>
|
||||
<p class="discover-section-subtitle" id="spotify-library-subtitle">Your saved albums on Spotify</p>
|
||||
</div>
|
||||
<div class="spotify-library-header-actions">
|
||||
<button class="spotify-library-action-btn spotify-library-refresh-btn" onclick="refreshSpotifyLibraryCache()" title="Refresh from Spotify">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M23 4v6h-6M1 20v-6h6"/><path d="M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15"/></svg>
|
||||
Refresh
|
||||
</button>
|
||||
<button class="spotify-library-action-btn spotify-library-download-btn" id="spotify-library-download-missing-btn" onclick="downloadMissingSpotifyLibraryAlbums()" style="display: none;">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg>
|
||||
Download Missing
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="spotify-library-filters" id="spotify-library-filters" style="display: none;">
|
||||
<input type="text" class="spotify-library-search" id="spotify-library-search"
|
||||
placeholder="Search by artist or album..." oninput="debouncedSpotifyLibrarySearch()">
|
||||
<select id="spotify-library-status-filter" class="spotify-library-select" onchange="loadSpotifyLibraryAlbums()">
|
||||
<option value="all">All Albums</option>
|
||||
<option value="missing">Missing</option>
|
||||
<option value="owned">Owned</option>
|
||||
</select>
|
||||
<select id="spotify-library-sort" class="spotify-library-select" onchange="loadSpotifyLibraryAlbums()">
|
||||
<option value="date_saved">Date Saved</option>
|
||||
<option value="artist_name">Artist</option>
|
||||
<option value="album_name">Album</option>
|
||||
<option value="release_date">Release Date</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="spotify-library-grid" id="spotify-library-grid">
|
||||
<div class="discover-loading">
|
||||
<div class="loading-spinner"></div>
|
||||
<p>Loading your Spotify library...</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="spotify-library-pagination" id="spotify-library-pagination" style="display: none;"></div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Releases Section -->
|
||||
<div class="discover-section">
|
||||
<div class="discover-section-header">
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ async function loadDiscoverPage() {
|
|||
loadDiscoverHero(),
|
||||
loadYourArtists(),
|
||||
loadYourAlbums(),
|
||||
loadSpotifyLibrarySection(),
|
||||
loadDiscoverRecentReleases(),
|
||||
loadSeasonalContent(), // Seasonal discovery
|
||||
loadPersonalizedRecentlyAdded(), // NEW: Recently added from library
|
||||
|
|
@ -1292,341 +1291,6 @@ async function downloadMissingYourAlbums() {
|
|||
}
|
||||
}
|
||||
|
||||
// ===============================
|
||||
// SPOTIFY LIBRARY SECTION
|
||||
// ===============================
|
||||
|
||||
let spotifyLibraryAlbums = [];
|
||||
let spotifyLibraryPage = 0;
|
||||
let spotifyLibraryTotal = 0;
|
||||
const SPOTIFY_LIBRARY_PAGE_SIZE = 48;
|
||||
let _spotifyLibrarySearchTimeout = null;
|
||||
|
||||
function debouncedSpotifyLibrarySearch() {
|
||||
clearTimeout(_spotifyLibrarySearchTimeout);
|
||||
_spotifyLibrarySearchTimeout = setTimeout(() => {
|
||||
spotifyLibraryPage = 0;
|
||||
loadSpotifyLibraryAlbums();
|
||||
}, 400);
|
||||
}
|
||||
|
||||
async function loadSpotifyLibrarySection() {
|
||||
try {
|
||||
const section = document.getElementById('spotify-library-section');
|
||||
if (!section) return;
|
||||
|
||||
const response = await fetch(`/api/discover/spotify-library?offset=0&limit=${SPOTIFY_LIBRARY_PAGE_SIZE}`);
|
||||
if (!response.ok) throw new Error('Failed to fetch');
|
||||
|
||||
const data = await response.json();
|
||||
if (!data.success || !data.albums || data.albums.length === 0) {
|
||||
section.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
section.style.display = '';
|
||||
spotifyLibraryAlbums = data.albums;
|
||||
spotifyLibraryTotal = data.total;
|
||||
spotifyLibraryPage = 0;
|
||||
|
||||
// Update subtitle with stats
|
||||
const subtitle = document.getElementById('spotify-library-subtitle');
|
||||
if (subtitle && data.stats) {
|
||||
const s = data.stats;
|
||||
subtitle.textContent = `${s.total} albums \u00B7 ${s.owned} owned \u00B7 ${s.missing} missing`;
|
||||
}
|
||||
|
||||
// Show download missing button if there are missing albums
|
||||
const dlBtn = document.getElementById('spotify-library-download-missing-btn');
|
||||
if (dlBtn && data.stats && data.stats.missing > 0) {
|
||||
dlBtn.style.display = '';
|
||||
}
|
||||
|
||||
// Show filters
|
||||
const filters = document.getElementById('spotify-library-filters');
|
||||
if (filters) filters.style.display = '';
|
||||
|
||||
renderSpotifyLibraryGrid(data.albums);
|
||||
renderSpotifyLibraryPagination(data.total, 0);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading Spotify library section:', error);
|
||||
const section = document.getElementById('spotify-library-section');
|
||||
if (section) section.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
async function loadSpotifyLibraryAlbums() {
|
||||
const grid = document.getElementById('spotify-library-grid');
|
||||
if (!grid) return;
|
||||
|
||||
grid.innerHTML = '<div class="discover-loading"><div class="loading-spinner"></div><p>Loading...</p></div>';
|
||||
|
||||
try {
|
||||
const search = (document.getElementById('spotify-library-search')?.value || '').trim();
|
||||
const status = document.getElementById('spotify-library-status-filter')?.value || 'all';
|
||||
const sort = document.getElementById('spotify-library-sort')?.value || 'date_saved';
|
||||
const offset = spotifyLibraryPage * SPOTIFY_LIBRARY_PAGE_SIZE;
|
||||
|
||||
const params = new URLSearchParams({
|
||||
offset, limit: SPOTIFY_LIBRARY_PAGE_SIZE, sort, sort_dir: 'desc', status
|
||||
});
|
||||
if (search) params.set('search', search);
|
||||
|
||||
const response = await fetch(`/api/discover/spotify-library?${params}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (!data.success) throw new Error(data.error);
|
||||
|
||||
spotifyLibraryAlbums = data.albums;
|
||||
spotifyLibraryTotal = data.total;
|
||||
|
||||
// Update subtitle
|
||||
const subtitle = document.getElementById('spotify-library-subtitle');
|
||||
if (subtitle && data.stats) {
|
||||
const s = data.stats;
|
||||
subtitle.textContent = `${s.total} albums \u00B7 ${s.owned} owned \u00B7 ${s.missing} missing`;
|
||||
}
|
||||
|
||||
renderSpotifyLibraryGrid(data.albums);
|
||||
renderSpotifyLibraryPagination(data.total, offset);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading Spotify library albums:', error);
|
||||
grid.innerHTML = '<div class="spotify-library-empty"><p>Failed to load albums</p></div>';
|
||||
}
|
||||
}
|
||||
|
||||
function renderSpotifyLibraryGrid(albums) {
|
||||
const grid = document.getElementById('spotify-library-grid');
|
||||
if (!grid) return;
|
||||
|
||||
if (!albums || albums.length === 0) {
|
||||
grid.innerHTML = '<div class="spotify-library-empty"><p>No albums found</p></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
albums.forEach((album, index) => {
|
||||
const coverUrl = album.image_url || '/static/placeholder-album.png';
|
||||
const year = album.release_date ? album.release_date.substring(0, 4) : '';
|
||||
const badgeClass = album.in_library ? 'owned' : 'missing';
|
||||
const badgeIcon = album.in_library ? '\u2713' : '\u2193';
|
||||
const trackInfo = album.total_tracks ? `${album.total_tracks} tracks` : '';
|
||||
const meta = [year, trackInfo].filter(Boolean).join(' \u00B7 ');
|
||||
|
||||
html += `
|
||||
<div class="spotify-library-card" onclick="openSpotifyLibraryAlbumDownload(${index})" title="${album.album_name} — ${album.artist_name}">
|
||||
<div class="spotify-library-card-img">
|
||||
<img src="${coverUrl}" alt="${album.album_name}" loading="lazy">
|
||||
<div class="spotify-library-card-badge ${badgeClass}">${badgeIcon}</div>
|
||||
</div>
|
||||
<div class="spotify-library-card-info">
|
||||
<p class="spotify-library-card-title">${album.album_name}</p>
|
||||
<p class="spotify-library-card-artist">${album.artist_name}</p>
|
||||
<p class="spotify-library-card-meta">${meta}</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
grid.innerHTML = html;
|
||||
}
|
||||
|
||||
function renderSpotifyLibraryPagination(total, offset) {
|
||||
const container = document.getElementById('spotify-library-pagination');
|
||||
if (!container) return;
|
||||
|
||||
if (total <= SPOTIFY_LIBRARY_PAGE_SIZE) {
|
||||
container.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
container.style.display = '';
|
||||
const totalPages = Math.ceil(total / SPOTIFY_LIBRARY_PAGE_SIZE);
|
||||
const currentPage = Math.floor(offset / SPOTIFY_LIBRARY_PAGE_SIZE) + 1;
|
||||
const showEnd = Math.min(offset + SPOTIFY_LIBRARY_PAGE_SIZE, total);
|
||||
|
||||
container.innerHTML = `
|
||||
<button class="spotify-library-page-btn" onclick="spotifyLibraryPrevPage()" ${currentPage <= 1 ? 'disabled' : ''}>← Previous</button>
|
||||
<span class="spotify-library-page-info">${offset + 1}\u2013${showEnd} of ${total}</span>
|
||||
<button class="spotify-library-page-btn" onclick="spotifyLibraryNextPage()" ${currentPage >= totalPages ? 'disabled' : ''}>Next →</button>
|
||||
`;
|
||||
}
|
||||
|
||||
function spotifyLibraryPrevPage() {
|
||||
if (spotifyLibraryPage > 0) {
|
||||
spotifyLibraryPage--;
|
||||
loadSpotifyLibraryAlbums();
|
||||
}
|
||||
}
|
||||
|
||||
function spotifyLibraryNextPage() {
|
||||
const totalPages = Math.ceil(spotifyLibraryTotal / SPOTIFY_LIBRARY_PAGE_SIZE);
|
||||
if (spotifyLibraryPage < totalPages - 1) {
|
||||
spotifyLibraryPage++;
|
||||
loadSpotifyLibraryAlbums();
|
||||
}
|
||||
}
|
||||
|
||||
async function openSpotifyLibraryAlbumDownload(index) {
|
||||
const album = spotifyLibraryAlbums[index];
|
||||
if (!album) {
|
||||
showToast('Album data not found', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`\u{1F4E5} Opening download modal for Spotify library album: ${album.album_name}`);
|
||||
showLoadingOverlay(`Loading tracks for ${album.album_name}...`);
|
||||
|
||||
try {
|
||||
const _params = new URLSearchParams({ name: album.album_name || '', artist: album.artist_name || '' });
|
||||
const response = await fetch(`/api/discover/album/spotify/${album.spotify_album_id}?${_params}`);
|
||||
if (!response.ok) throw new Error('Failed to fetch album tracks');
|
||||
|
||||
const albumData = await response.json();
|
||||
if (!albumData.tracks || albumData.tracks.length === 0) {
|
||||
throw new Error('No tracks found in album');
|
||||
}
|
||||
|
||||
const spotifyTracks = albumData.tracks.map(track => {
|
||||
let artists = track.artists || albumData.artists || [{ name: album.artist_name }];
|
||||
if (Array.isArray(artists)) {
|
||||
artists = artists.map(a => a.name || a);
|
||||
}
|
||||
return {
|
||||
id: track.id,
|
||||
name: track.name,
|
||||
artists: artists,
|
||||
album: {
|
||||
id: albumData.id,
|
||||
name: albumData.name,
|
||||
album_type: albumData.album_type || 'album',
|
||||
total_tracks: albumData.total_tracks || 0,
|
||||
release_date: albumData.release_date || '',
|
||||
images: albumData.images || []
|
||||
},
|
||||
duration_ms: track.duration_ms || 0,
|
||||
track_number: track.track_number || 0
|
||||
};
|
||||
});
|
||||
|
||||
const virtualPlaylistId = `spotify_library_${album.spotify_album_id}`;
|
||||
const artistContext = {
|
||||
id: album.artist_id,
|
||||
name: album.artist_name,
|
||||
source: 'spotify'
|
||||
};
|
||||
const albumContext = {
|
||||
id: albumData.id,
|
||||
name: albumData.name,
|
||||
album_type: albumData.album_type || 'album',
|
||||
total_tracks: albumData.total_tracks || 0,
|
||||
release_date: albumData.release_date || '',
|
||||
images: albumData.images || []
|
||||
};
|
||||
|
||||
await openDownloadMissingModalForYouTube(virtualPlaylistId, albumData.name, spotifyTracks, artistContext, albumContext);
|
||||
hideLoadingOverlay();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error opening Spotify library album download:', error);
|
||||
showToast(`Failed to load album: ${error.message}`, 'error');
|
||||
hideLoadingOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshSpotifyLibraryCache() {
|
||||
try {
|
||||
showToast('Refreshing Spotify library...', 'info');
|
||||
const response = await fetch('/api/discover/spotify-library/refresh', { method: 'POST' });
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showToast('Spotify library refresh started — will update shortly', 'success');
|
||||
// Reload after a delay to let the sync run
|
||||
setTimeout(() => loadSpotifyLibrarySection(), 10000);
|
||||
} else {
|
||||
showToast(`Error: ${data.error}`, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showToast(`Error: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadMissingSpotifyLibraryAlbums() {
|
||||
// Fetch all missing albums (no pagination limit)
|
||||
try {
|
||||
const response = await fetch('/api/discover/spotify-library?status=missing&limit=500&offset=0');
|
||||
const data = await response.json();
|
||||
if (!data.success || !data.albums || data.albums.length === 0) {
|
||||
showToast('No missing albums to download', 'info');
|
||||
return;
|
||||
}
|
||||
|
||||
const missing = data.albums.filter(a => !a.in_library);
|
||||
if (missing.length === 0) {
|
||||
showToast('All albums are already in your library!', 'success');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm(`Download ${missing.length} missing album${missing.length > 1 ? 's' : ''} from your Spotify library?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
showToast(`Starting download for ${missing.length} albums...`, 'info');
|
||||
|
||||
// Download one at a time to avoid overwhelming the system
|
||||
for (let i = 0; i < missing.length; i++) {
|
||||
const album = missing[i];
|
||||
try {
|
||||
showToast(`Queuing ${i + 1}/${missing.length}: ${album.album_name}`, 'info');
|
||||
|
||||
const _params = new URLSearchParams({ name: album.album_name || '', artist: album.artist_name || '' });
|
||||
const response = await fetch(`/api/discover/album/spotify/${album.spotify_album_id}?${_params}`);
|
||||
if (!response.ok) continue;
|
||||
|
||||
const albumData = await response.json();
|
||||
if (!albumData.tracks || albumData.tracks.length === 0) continue;
|
||||
|
||||
const spotifyTracks = albumData.tracks.map(track => {
|
||||
let artists = track.artists || albumData.artists || [{ name: album.artist_name }];
|
||||
if (Array.isArray(artists)) artists = artists.map(a => a.name || a);
|
||||
return {
|
||||
id: track.id,
|
||||
name: track.name,
|
||||
artists: artists,
|
||||
album: {
|
||||
id: albumData.id,
|
||||
name: albumData.name,
|
||||
album_type: albumData.album_type || 'album',
|
||||
total_tracks: albumData.total_tracks || 0,
|
||||
release_date: albumData.release_date || '',
|
||||
images: albumData.images || []
|
||||
},
|
||||
duration_ms: track.duration_ms || 0,
|
||||
track_number: track.track_number || 0
|
||||
};
|
||||
});
|
||||
|
||||
const virtualPlaylistId = `spotify_library_${album.spotify_album_id}`;
|
||||
await openDownloadMissingModalForYouTube(virtualPlaylistId, albumData.name, spotifyTracks, {
|
||||
id: album.artist_id, name: album.artist_name, source: 'spotify'
|
||||
}, {
|
||||
id: albumData.id, name: albumData.name, album_type: albumData.album_type || 'album',
|
||||
total_tracks: albumData.total_tracks || 0, release_date: albumData.release_date || '',
|
||||
images: albumData.images || []
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
console.error(`Error downloading album ${album.album_name}:`, err);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error downloading missing Spotify library albums:', error);
|
||||
showToast(`Error: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function loadDiscoverReleaseRadar() {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -1230,17 +1230,6 @@ const HELPER_CONTENT = {
|
|||
description: 'Click to browse tracks in this genre from your discovery pool.',
|
||||
},
|
||||
|
||||
// Spotify Library
|
||||
'#spotify-library-section': {
|
||||
title: 'Your Spotify Library',
|
||||
description: 'Albums saved in your Spotify account. Browse, search, and download albums you\'ve saved on Spotify but don\'t have locally.',
|
||||
tips: [
|
||||
'Search and filter by status (All/Missing/Owned)',
|
||||
'Sort by date saved, artist, album name, or release date',
|
||||
'"Download Missing" downloads all albums not in your library'
|
||||
],
|
||||
},
|
||||
|
||||
// Playlist Sync/Download buttons (generic — matches all discover playlist sections)
|
||||
'.discover-section-actions .action-button.primary': {
|
||||
title: 'Sync to Media Server',
|
||||
|
|
@ -2485,7 +2474,6 @@ const HELPER_TOURS = {
|
|||
{ page: 'discover', selector: '#discover-hero-view-all', title: 'View All Recommendations', description: 'Opens a modal with all recommended artists at once. "Watch All" adds every recommended artist to your watchlist in one click.' },
|
||||
|
||||
// Content sections (top to bottom)
|
||||
{ page: 'discover', selector: '#spotify-library-section', title: 'Your Spotify Library', description: 'If Spotify is connected, this shows all your saved albums. Filter by Missing/Owned, sort by date, and click "Download Missing" to grab everything you don\'t have yet. Only visible with Spotify connected.' },
|
||||
{ page: 'discover', selector: '#recent-releases-carousel', title: 'Recent Releases', description: 'New music from artists in your watchlist. Album cards show cover art — click any to open the download modal. Updates automatically when watchlist scans find new releases.' },
|
||||
{ page: 'discover', selector: '#seasonal-albums-section', title: 'Seasonal Content', description: 'Season-aware sections that appear automatically — Christmas albums in December, summer vibes in July. Includes curated albums and a Seasonal Mix playlist you can sync to your server.' },
|
||||
|
||||
|
|
@ -3444,6 +3432,7 @@ const WHATS_NEW = {
|
|||
'2.4.2': [
|
||||
// --- post-2.4.1 dev work — entries hidden by _getLatestWhatsNewVersion until the build version bumps ---
|
||||
{ date: 'Unreleased — 2.4.2 dev cycle' },
|
||||
{ title: 'Drop Redundant "Your Spotify Library" Section on Discover', desc: 'discover page used to show two near-identical sections: "Your Albums" (cross-source aggregator across spotify/deezer/etc) AND "Your Spotify Library" (spotify-only). same UI, same grid, same filter / sort / download-missing controls — the spotify-only one was a strict subset of what your albums already covers. removed it. spotify saved albums still surface via the your albums section with spotify as one of its configured sources (gear button → configure sources). backend collection / storage is unchanged — the watchlist scanner still populates the spotify_library_albums cache for your albums to read.', page: 'discover' },
|
||||
{ title: 'Library Disk Usage on Stats Page', desc: 'discord request (samuel [KC]): show how much disk space the library takes. new card on stats → system statistics shows total bytes + per-format breakdown (FLAC vs MP3 vs M4A bars). data comes from `tracks.file_size` populated during deep scan from whatever the media server already returns (plex MediaPart.size, jellyfin MediaSources[].Size, navidrome song.size, soulsync standalone os.path.getsize) — zero filesystem walk overhead. existing libraries see "Run a Deep Scan to populate" until the next deep scan fills in sizes; partial coverage shown as "X tracks measured (+Y pending)". migration is additive (NULL on legacy rows) so upgrading users have nothing to do.', page: 'stats' },
|
||||
{ title: 'Fix: ReplayGain Wrote Same +52 dB Gain to Every Track', desc: 'noticed every downloaded track came out with `replaygain_track_gain: +52.00 dB` regardless of actual loudness. cause: parser used `re.search` which returned the FIRST `I:` (integrated loudness) reading from ffmpeg\'s ebur128 output. that\'s the per-window measurement at t=0.5s — almost always ~-70 LUFS because tracks start with silence/encoder padding. -18 (RG2 reference) - (-70) = +52 dB on every track. fix: parser now anchors to the `Summary:` block at the end of ffmpeg\'s output and reads the actual integrated loudness from there, not the silent-intro partial. defensive fallback uses the LAST per-window reading if Summary is missing (still better than the first). gains now reflect real per-track loudness.', page: 'downloads' },
|
||||
{ title: 'Fix: Tracks Showed Completed When File Was Quarantined', desc: 'caught downloading kendrick mr morale: three tracks (rich interlude, savior interlude, savior) showed ✅ completed in the modal but were missing on disk. two layered bugs. (1) the post-process verification wrapper had a fallback that assumed success when no `_final_processed_path` was in context — but integrity-rejected files (which get quarantined instead of moved) leave that path unset, so the wrapper marked them complete. now wrapper explicitly checks `_integrity_failure_msg` and `_race_guard_failed` markers before the assume-success fallback. failed integrity = task marked failed, batch tracker notified with success=false. (2) acoustid skip-logic was too lenient — when fingerprint confidence was very high and either title OR artist matched a bit, it skipped verification with reason "likely same song in different language/script." that fired for english-vs-english by the same artist with the word "interlude" in both — same artist + 0.55 title sim = skip = wrong file accepted. tightened: skip now requires non-ASCII chars present (real language/script case) AND artist match, OR very high title similarity (≥0.80) AND artist match. english-vs-english with very different titles by same artist no longer skipped — verification correctly returns FAIL and the wrong file gets quarantined.', page: 'downloads' },
|
||||
|
|
|
|||
Loading…
Reference in a new issue