AcoustID Fingerprint Verification — If AcoustID is configured, the downloaded file is fingerprinted and compared against the expected track. Title and artist are fuzzy-matched (title ≥ 70% similarity, artist ≥ 60%). Files that fail verification are quarantined instead of added to your library. Note: AcoustID is skipped for streaming sources (Tidal, Qobuz, Deezer, HiFi) since files are downloaded by exact track ID. However, streaming search results are still verified by artist and title matching before download to prevent wrong-track matches (e.g. same title, different artist).
Metadata Tagging — The file is tagged with official metadata: title, artist, album artist, album, track number, disc number, year, genre, and composer. Tags are written using Mutagen (supports MP3, FLAC, OGG, M4A).
Cover Art Embedding — Album artwork is downloaded from the metadata source and embedded directly into the audio file.
-
File Organization — The file is renamed and moved to your transfer path following customizable templates. Separate templates for albums, singles, and playlists are configured in Settings. Available variables include $artist, $album, $title, $track, $year, $quality, and $albumtype (resolves to Album, Single, EP, or Compilation). For multi-disc albums, a Disc N/ subfolder is automatically created when the album has more than one disc (or use $disc in your template for manual control).
+
File Organization — The file is renamed and moved to your transfer path following customizable templates. Separate templates for albums, singles, and playlists are configured in Settings. Available variables include $artist, $album, $title, $track, $year, $quality, and $albumtype (resolves to Album, Single, EP, or Compilation). For multi-disc albums, a Disc N/ subfolder is automatically created when the album has more than one disc (or use $disc for zero-padded "01" or $discnum for unpadded "1" in your template for manual control).
Lyrics (LRC) — Synced lyrics are fetched from the LRClib API and saved as .lrc sidecar files alongside the audio file. Compatible media players (foobar2000, MusicBee, Plex, etc.) will display time-synced lyrics automatically. Falls back to plain-text lyrics if synced versions aren't available.
Lossy Copy — If enabled in settings, a lower-bitrate copy is created alongside the original (useful for mobile device syncing).
Media Server Scan — Your media server (Plex/Jellyfin) is notified to scan for the new file. Navidrome auto-detects changes.
diff --git a/webui/static/script.js b/webui/static/script.js
index 8e31ac67..2e557267 100644
--- a/webui/static/script.js
+++ b/webui/static/script.js
@@ -3362,15 +3362,19 @@ function updateMediaScanFromData(data) {
}
}
+let _wishlistAutoProcessingNotified = false;
function updateWishlistStatsFromData(data) {
- // Auto-processing detection: close modal and notify
+ // Auto-processing detection: close modal and notify (once only)
if (data.is_auto_processing) {
- if (typeof closeWishlistOverviewModal === 'function') {
+ if (!_wishlistAutoProcessingNotified && typeof closeWishlistOverviewModal === 'function') {
closeWishlistOverviewModal();
showToast('Wishlist auto-processing started. View progress in Download Manager.', 'info');
+ _wishlistAutoProcessingNotified = true;
}
return;
}
+ // Reset flag when auto-processing ends
+ _wishlistAutoProcessingNotified = false;
// Store latest stats for countdown timer refresh
_lastWishlistStats = data;
}
@@ -5361,7 +5365,7 @@ function validateFileOrganizationTemplates() {
// Valid variables for each template type
const validVars = {
- album: ['$artist', '$albumartist', '$artistletter', '$album', '$albumtype', '$title', '$track', '$disc', '$year', '$quality'],
+ album: ['$artist', '$albumartist', '$artistletter', '$album', '$albumtype', '$title', '$track', '$disc', '$discnum', '$year', '$quality'],
single: ['$artist', '$albumartist', '$artistletter', '$album', '$albumtype', '$title', '$year', '$quality'],
playlist: ['$artist', '$artistletter', '$playlist', '$title', '$year', '$quality']
};
@@ -12891,8 +12895,11 @@ function startWishlistCountdownTimer(currentCycle, initialSeconds) {
if (socketConnected && _lastWishlistStats) {
const data = _lastWishlistStats;
if (data.is_auto_processing) {
- closeWishlistOverviewModal();
- showToast('Wishlist auto-processing started. View progress in Download Manager.', 'info');
+ if (!_wishlistAutoProcessingNotified) {
+ closeWishlistOverviewModal();
+ showToast('Wishlist auto-processing started. View progress in Download Manager.', 'info');
+ _wishlistAutoProcessingNotified = true;
+ }
return;
}
if (remainingSeconds <= 0) {
@@ -12908,11 +12915,14 @@ function startWishlistCountdownTimer(currentCycle, initialSeconds) {
const response = await fetch('/api/wishlist/stats');
const data = await response.json();
- // AUTO-CLOSE DETECTION: If auto-processing started, close modal and notify user
+ // AUTO-CLOSE DETECTION: If auto-processing started, close modal and notify user (once)
if (data.is_auto_processing) {
- console.log('🤖 [Wishlist] Auto-processing detected, closing overview modal');
- closeWishlistOverviewModal();
- showToast('Wishlist auto-processing started. View progress in Download Manager.', 'info');
+ if (!_wishlistAutoProcessingNotified) {
+ console.log('🤖 [Wishlist] Auto-processing detected, closing overview modal');
+ closeWishlistOverviewModal();
+ showToast('Wishlist auto-processing started. View progress in Download Manager.', 'info');
+ _wishlistAutoProcessingNotified = true;
+ }
return; // Exit interval
}