Add FLAC bit depth filter to post-download quality gate
This commit is contained in:
parent
d1109d9fda
commit
3644422ab8
5 changed files with 186 additions and 4 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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'])
|
||||
|
|
|
|||
|
|
@ -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 -->
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue