Add FLAC bit depth filter to post-download quality gate

This commit is contained in:
Broque Thomas 2026-02-20 15:05:45 -08:00
parent d1109d9fda
commit 3644422ab8
5 changed files with 186 additions and 4 deletions

View file

@ -2894,7 +2894,8 @@ class MusicDatabase:
"enabled": True,
"min_kbps": 500,
"max_kbps": 10000,
"priority": 1
"priority": 1,
"bit_depth": "any"
},
"mp3_320": {
"enabled": True,
@ -2942,7 +2943,8 @@ class MusicDatabase:
"enabled": True,
"min_kbps": 500,
"max_kbps": 10000,
"priority": 1
"priority": 1,
"bit_depth": "any"
},
"mp3_320": {
"enabled": False,
@ -2973,7 +2975,8 @@ class MusicDatabase:
"enabled": True,
"min_kbps": 500,
"max_kbps": 10000,
"priority": 1
"priority": 1,
"bit_depth": "any"
},
"mp3_320": {
"enabled": True,
@ -3004,7 +3007,8 @@ class MusicDatabase:
"enabled": False,
"min_kbps": 500,
"max_kbps": 10000,
"priority": 4
"priority": 4,
"bit_depth": "any"
},
"mp3_320": {
"enabled": True,

View file

@ -9221,6 +9221,56 @@ def _post_process_matched_download_with_verification(context_key, context, file_
_on_download_completed(batch_id, task_id, success=False)
def _check_flac_bit_depth(file_path, context, context_key):
"""
Check if a FLAC file matches the user's preferred bit depth.
Returns True if the file was rejected (caller should return), False if OK.
"""
if not context.get('_audio_quality', '').startswith('FLAC'):
return False
from database.music_database import MusicDatabase
_qp_db = MusicDatabase()
_quality_profile = _qp_db.get_quality_profile()
_flac_pref = _quality_profile.get('qualities', {}).get('flac', {}).get('bit_depth', 'any')
if _flac_pref == 'any':
return False
# Parse actual bit depth from quality string like "FLAC 16bit"
_actual_bits = context['_audio_quality'].replace('FLAC ', '').replace('bit', '')
if _actual_bits == _flac_pref:
return False
# Reject — same pattern as AcoustID verification failure
rejection_msg = f"FLAC bit depth mismatch: file is {_actual_bits}-bit, preference is {_flac_pref}-bit"
try:
quarantine_path = _move_to_quarantine(file_path, context, rejection_msg)
print(f"🚫 File quarantined due to bit depth filter: {quarantine_path}")
except Exception as quarantine_error:
logger.error(f"Quarantine failed ({quarantine_error}), deleting file: {file_path}")
try:
os.remove(file_path)
except Exception:
pass
context['_bitdepth_rejected'] = True
with matched_context_lock:
if context_key in matched_downloads_context:
del matched_downloads_context[context_key]
task_id = context.get('task_id')
batch_id = context.get('batch_id')
if task_id:
with tasks_lock:
if task_id in download_tasks:
download_tasks[task_id]['status'] = 'failed'
download_tasks[task_id]['error_message'] = f"Bit depth filter: {rejection_msg}"
if task_id and batch_id:
_on_download_completed(batch_id, task_id, success=False)
return True
def _move_to_quarantine(file_path: str, context: dict, reason: str) -> str:
"""
Move a file to quarantine folder when AcoustID verification fails.
@ -9584,6 +9634,11 @@ def _post_process_matched_download(context_key, context, file_path):
context['_audio_quality'] = _get_audio_quality_string(file_path)
if context['_audio_quality']:
print(f"🎧 Audio quality detected: {context['_audio_quality']}")
# FLAC bit depth filter
if _check_flac_bit_depth(file_path, context, context_key):
return
final_path, _ = _build_final_path_for_track(context, spotify_artist, None, file_ext)
print(f"📁 Playlist mode final path: '{final_path}'")
@ -9712,6 +9767,10 @@ def _post_process_matched_download(context_key, context, file_path):
if context['_audio_quality']:
print(f"🎧 Audio quality detected: {context['_audio_quality']}")
# FLAC bit depth filter
if _check_flac_bit_depth(file_path, context, context_key):
return
# 2. Build the final path using GUI-style track naming with multiple fallback sources
if album_info and album_info['is_album']:
album_name_sanitized = _sanitize_filename(album_info['album_name'])

View file

@ -3018,6 +3018,17 @@
</div>
</div>
</div>
<div class="flac-bit-depth-selector" id="flac-bit-depth-selector">
<label>Bit Depth:</label>
<div class="bit-depth-buttons">
<button class="bit-depth-btn active" data-value="any" onclick="setFlacBitDepth('any')">Any</button>
<button class="bit-depth-btn" data-value="16" onclick="setFlacBitDepth('16')">16-bit</button>
<button class="bit-depth-btn" data-value="24" onclick="setFlacBitDepth('24')">24-bit</button>
</div>
<div style="color: #888; font-size: 0.75em; margin-top: 4px;">
Rejects FLAC files that don't match the selected bit depth
</div>
</div>
</div>
<!-- MP3 320 Quality -->

View file

@ -1993,6 +1993,22 @@ function populateQualityProfileUI(profile) {
sliders.classList.add('disabled');
}
}
// FLAC-specific: restore bit depth selector
if (quality === 'flac') {
const bitDepthValue = config.bit_depth || 'any';
document.querySelectorAll('.bit-depth-btn').forEach(btn => {
btn.classList.toggle('active', btn.getAttribute('data-value') === bitDepthValue);
});
const bitDepthSelector = document.getElementById('flac-bit-depth-selector');
if (bitDepthSelector) {
if (config.enabled) {
bitDepthSelector.classList.remove('disabled');
} else {
bitDepthSelector.classList.add('disabled');
}
}
}
}
});
@ -2042,6 +2058,18 @@ function toggleQuality(quality) {
}
}
// Also toggle FLAC bit depth selector
if (quality === 'flac') {
const bitDepthSelector = document.getElementById('flac-bit-depth-selector');
if (bitDepthSelector && checkbox) {
if (checkbox.checked) {
bitDepthSelector.classList.remove('disabled');
} else {
bitDepthSelector.classList.add('disabled');
}
}
}
// Mark preset as custom when manually changing
if (currentQualityProfile) {
currentQualityProfile.preset = 'custom';
@ -2051,6 +2079,22 @@ function toggleQuality(quality) {
}
}
function setFlacBitDepth(value) {
document.querySelectorAll('.bit-depth-btn').forEach(btn => {
btn.classList.toggle('active', btn.getAttribute('data-value') === value);
});
// Mark preset as custom when manually changing
if (currentQualityProfile) {
currentQualityProfile.preset = 'custom';
document.querySelectorAll('.preset-button').forEach(btn => {
btn.classList.remove('active');
});
}
debouncedAutoSaveSettings();
}
async function applyQualityPreset(presetName) {
try {
showLoadingOverlay(`Applying ${presetName} preset...`);
@ -2100,6 +2144,12 @@ function collectQualityProfileFromUI() {
max_kbps: parseInt(maxSlider?.value || 99999),
priority: existingPriority
};
// Add FLAC-specific bit_depth setting
if (quality === 'flac') {
const activeBtn = document.querySelector('.bit-depth-btn.active');
profile.qualities[quality].bit_depth = activeBtn ? activeBtn.getAttribute('data-value') : 'any';
}
});
// Check if current profile matches a preset

View file

@ -1499,6 +1499,64 @@ body {
pointer-events: none;
}
.flac-bit-depth-selector {
padding: 8px 12px;
transition: opacity 0.3s ease;
}
.flac-bit-depth-selector.disabled {
opacity: 0.35;
pointer-events: none;
}
.flac-bit-depth-selector label {
display: block;
margin-bottom: 6px;
color: rgba(255, 255, 255, 0.7);
font-size: 11px;
}
.bit-depth-buttons {
display: flex;
gap: 8px;
}
.bit-depth-btn {
flex: 1;
padding: 7px 12px;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0.02));
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 10px;
color: rgba(255, 255, 255, 0.7);
font-size: 11px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.bit-depth-btn.active {
background: linear-gradient(135deg,
rgba(29, 185, 84, 0.2) 0%,
rgba(30, 215, 96, 0.12) 100%);
border-color: rgba(29, 185, 84, 0.4);
color: #1ed760;
font-weight: 600;
box-shadow: 0 4px 16px rgba(29, 185, 84, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.05);
}
.bit-depth-btn:hover {
background: rgba(255, 255, 255, 0.08);
border-color: rgba(255, 255, 255, 0.15);
color: #ffffff;
transform: translateY(-1px);
}
.bit-depth-btn.active:hover {
background: linear-gradient(135deg,
rgba(29, 185, 84, 0.28) 0%,
rgba(30, 215, 96, 0.18) 100%);
}
.slider-group {
margin-top: 8px;
}