feat(acoustid): opt-in fail-closed mode — only import verified tracks
New setting acoustid.require_verified (default off), shown under Settings → Quality Profile only when AcoustID is enabled. When on, an AcoustID SKIP (ran but couldn't confirm — no fingerprint match or cross-script metadata, the ⚠ "unverified" case) is treated like a FAIL: the file is quarantined and the next-best candidate is tried, instead of importing an unverified file. Only a clean AcoustID PASS is kept. Transient ERROR results (rate-limit / outage) are deliberately NOT blocked — that would stall the whole pipeline during an AcoustID outage. Those still import with their existing flag. - pipeline.py: SKIP routes through the existing FAIL quarantine + retry path (trigger 'acoustid') when require_verified is on. - UI: checkbox under Quality Profile, visibility tied to acoustid-enabled via syncAcoustidRequireVerifiedVisibility(); load/save wired in settings.js. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
d169818043
commit
674b80972a
3 changed files with 53 additions and 3 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -4224,7 +4224,8 @@
|
|||
<label class="checkbox-label"
|
||||
style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
|
||||
<input type="checkbox" id="acoustid-enabled"
|
||||
style="width: 16px; height: 16px;">
|
||||
style="width: 16px; height: 16px;"
|
||||
onchange="if (typeof syncAcoustidRequireVerifiedVisibility === 'function') syncAcoustidRequireVerifiedVisibility();">
|
||||
<span>Enable Download Verification</span>
|
||||
</label>
|
||||
<div style="color: #888; font-size: 0.8em; margin-top: 4px; margin-left: 24px;">
|
||||
|
|
@ -5118,6 +5119,22 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Require hard AcoustID verification — only shown when AcoustID is enabled -->
|
||||
<div class="form-group" id="acoustid-require-verified-group" style="display:none;">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" id="acoustid-require-verified">
|
||||
Only import AcoustID-verified tracks (reject "could not confirm")
|
||||
</label>
|
||||
<div class="help-text" style="margin-top:4px">
|
||||
When <strong>on</strong>, a download that AcoustID runs but <em>cannot confirm</em>
|
||||
(no fingerprint match / cross-script metadata — the ⚠ "unverified" case) is
|
||||
<strong>quarantined and the next candidate is tried</strong>, instead of being
|
||||
imported as unverified. Only a clean AcoustID <strong>pass</strong> is kept.
|
||||
Transient lookup errors (rate-limit / outage) still import, so an AcoustID outage
|
||||
never stalls your downloads. Requires AcoustID to be enabled.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="help-text">
|
||||
<strong>How it works:</strong> Each download source is checked against this list
|
||||
top-down. The first target a source can satisfy wins; a source that meets no target
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue