diff --git a/core/imports/pipeline.py b/core/imports/pipeline.py
index 69ed30b1..50c774b2 100644
--- a/core/imports/pipeline.py
+++ b/core/imports/pipeline.py
@@ -503,7 +503,26 @@ def post_process_matched_download(context_key, context, file_path, runtime, meta
logger.info(f"AcoustID verification result: {verification_result.value} - {verification_msg}")
context['_acoustid_result'] = verification_result.value
- if verification_result == VerificationResult.FAIL:
+ # Fail-closed mode: when the user requires a hard AcoustID
+ # PASS, a SKIP (ran but couldn't confirm — no fingerprint
+ # match / cross-script metadata) is treated like a FAIL:
+ # quarantine + try the next candidate, instead of importing
+ # an unverified file. ERROR (rate-limit / infra) is NOT
+ # blocked — that would stall the whole pipeline during an
+ # outage; those still import with their existing flag.
+ require_verified = config_manager.get('acoustid.require_verified', False)
+ _skip_as_fail = (
+ require_verified
+ and verification_result == VerificationResult.SKIP
+ )
+ if _skip_as_fail:
+ verification_msg = (
+ f"AcoustID could not confirm the track and 'require verified' "
+ f"is on — rejecting unverified file ({verification_msg})"
+ )
+ logger.warning("[AcoustID] Require-verified: SKIP treated as FAIL — %s", verification_msg)
+
+ if verification_result == VerificationResult.FAIL or _skip_as_fail:
try:
quarantine_path = move_to_quarantine(
file_path,
diff --git a/webui/index.html b/webui/index.html
index ccc75be9..9023a52e 100644
--- a/webui/index.html
+++ b/webui/index.html
@@ -4224,7 +4224,8 @@
@@ -5118,6 +5119,22 @@
+
+
+
+
+ When on, a download that AcoustID runs but cannot confirm
+ (no fingerprint match / cross-script metadata — the ⚠ "unverified" case) is
+ quarantined and the next candidate is tried, instead of being
+ imported as unverified. Only a clean AcoustID pass is kept.
+ Transient lookup errors (rate-limit / outage) still import, so an AcoustID outage
+ never stalls your downloads. Requires AcoustID to be enabled.
+
+
+
How it works: Each download source is checked against this list
top-down. The first target a source can satisfy wins; a source that meets no target
diff --git a/webui/static/settings.js b/webui/static/settings.js
index 272e5f01..3da92eb4 100644
--- a/webui/static/settings.js
+++ b/webui/static/settings.js
@@ -40,6 +40,15 @@ async function copyAddress(address, cryptoName) {
let settingsAutoSaveTimer = null;
+// The "Only import AcoustID-verified tracks" toggle (under Quality Profile) is
+// meaningless when AcoustID itself is off — only show it when verification is on.
+function syncAcoustidRequireVerifiedVisibility() {
+ const group = document.getElementById('acoustid-require-verified-group');
+ const enabled = document.getElementById('acoustid-enabled');
+ if (group) group.style.display = (enabled && enabled.checked) ? '' : 'none';
+}
+window.syncAcoustidRequireVerifiedVisibility = syncAcoustidRequireVerifiedVisibility;
+
function debouncedAutoSaveSettings() {
// Ignore changes made while the page is programmatically populating its
// fields on load — those aren't user edits and must not trigger a full
@@ -1078,6 +1087,10 @@ async function loadSettingsData() {
// Populate AcoustID settings
document.getElementById('acoustid-api-key').value = settings.acoustid?.api_key || '';
document.getElementById('acoustid-enabled').checked = settings.acoustid?.enabled || false;
+ const _acoustidRequireVerified = document.getElementById('acoustid-require-verified');
+ if (_acoustidRequireVerified) _acoustidRequireVerified.checked = settings.acoustid?.require_verified === true;
+ // Show the "require verified" toggle (under Quality Profile) only when AcoustID is on.
+ if (typeof syncAcoustidRequireVerifiedVisibility === 'function') syncAcoustidRequireVerifiedVisibility();
// Populate Last.fm settings
document.getElementById('lastfm-api-key').value = settings.lastfm?.api_key || '';
@@ -3001,7 +3014,8 @@ async function saveSettings(quiet = false) {
},
acoustid: {
api_key: document.getElementById('acoustid-api-key').value,
- enabled: document.getElementById('acoustid-enabled').checked
+ enabled: document.getElementById('acoustid-enabled').checked,
+ require_verified: document.getElementById('acoustid-require-verified')?.checked === true
},
lastfm: {
api_key: document.getElementById('lastfm-api-key').value,