feat(import): master toggle for quality-filtering on import + collapsible tile
Answers "does import respect quality?": yes — the pipeline already runs the quality gate (check_quality_target) BEFORE AcoustID and quarantines files that don't meet the profile (unless fallback/downsample is on). This adds an explicit user switch over that behaviour. - New config import.quality_filter_enabled (default True). When False, check_quality_target returns None early so EVERY file imports regardless of quality; the file is still probed and the library Quality Upgrade Scanner still flags below-profile tracks. Default preserves current behaviour. - Settings → Library: the Import Settings group is now a collapsible tile (same pattern as Post-Processing) and gains the "Only import tracks that meet your quality profile" toggle at the top, alongside replace-lower-quality and folder-artist-override. - settings.js populate/collect the new key; config schema default added. - Tests: key-aware config stub (a blanket-False mock would wrongly disable the filter) + a new test pinning toggle-OFF = accept below-target file. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
973d28f61d
commit
8bb749de9c
6 changed files with 75 additions and 4 deletions
|
|
@ -700,6 +700,12 @@ class ConfigManager:
|
|||
},
|
||||
"import": {
|
||||
"staging_path": "./Staging",
|
||||
# Master toggle for quality-filtering on import. On by default:
|
||||
# downloaded files that don't meet the quality profile are
|
||||
# quarantined instead of imported (same gate the download
|
||||
# pipeline uses). Off → import everything regardless of quality;
|
||||
# the library Quality Upgrade Scanner still flags them.
|
||||
"quality_filter_enabled": True,
|
||||
"replace_lower_quality": False,
|
||||
# Use the top Staging folder as the artist (Artist/Album layouts,
|
||||
# mixtapes). On by default to preserve the long-standing import
|
||||
|
|
|
|||
|
|
@ -127,6 +127,17 @@ def check_quality_target(file_path: str, context: dict) -> Optional[str]:
|
|||
from core.imports.file_ops import probe_audio_quality
|
||||
from core.quality.selection import targets_from_profile, quality_meets_profile
|
||||
|
||||
# Master toggle (Settings → Import). When OFF, the quality check is skipped
|
||||
# entirely and files import regardless of quality — the user opted out of
|
||||
# quality-filtering on import. Default ON preserves existing behaviour. The
|
||||
# library Quality Upgrade scanner still flags below-profile files either way.
|
||||
if _get_config_manager().get("import.quality_filter_enabled", True) is False:
|
||||
logger.debug(
|
||||
"[QualityGuard] import.quality_filter_enabled=False — skipping quality "
|
||||
"filter for %s", os.path.basename(file_path),
|
||||
)
|
||||
return None
|
||||
|
||||
aq = probe_audio_quality(file_path)
|
||||
if aq is None:
|
||||
logger.debug("[QualityGuard] Could not probe %s — skipping check", os.path.basename(file_path))
|
||||
|
|
|
|||
|
|
@ -35,9 +35,18 @@ _WANT_FLAC24 = {
|
|||
def _patch(monkeypatch, aq, profile):
|
||||
monkeypatch.setattr(file_ops, "probe_audio_quality", lambda fp: aq)
|
||||
monkeypatch.setattr(guards, "MusicDatabase", lambda: _FakeDB(profile))
|
||||
|
||||
# Key-aware config stub: the import quality filter is ON (its default), so
|
||||
# the guard runs; everything else (downsample, etc.) is OFF. A blanket False
|
||||
# would wrongly disable the filter itself via import.quality_filter_enabled.
|
||||
def _cfg_get(key, default=None):
|
||||
if key == "import.quality_filter_enabled":
|
||||
return True
|
||||
return False
|
||||
|
||||
monkeypatch.setattr(
|
||||
guards, "_get_config_manager",
|
||||
lambda: SimpleNamespace(get=lambda _key, default=None: False),
|
||||
lambda: SimpleNamespace(get=_cfg_get),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -30,12 +30,20 @@ class _FakeDB:
|
|||
return self._p
|
||||
|
||||
|
||||
def _patch_guard(monkeypatch, probe_aq, profile, downsample=False):
|
||||
def _patch_guard(monkeypatch, probe_aq, profile, downsample=False, quality_filter=True):
|
||||
monkeypatch.setattr(file_ops, 'probe_audio_quality', lambda fp: probe_aq)
|
||||
monkeypatch.setattr(guards, 'MusicDatabase', lambda: _FakeDB(profile))
|
||||
|
||||
def _cfg_get(k, d=None):
|
||||
if 'downsample' in k:
|
||||
return downsample
|
||||
if k == 'import.quality_filter_enabled':
|
||||
return quality_filter
|
||||
return d
|
||||
|
||||
monkeypatch.setattr(
|
||||
guards, '_get_config_manager',
|
||||
lambda: types.SimpleNamespace(get=lambda k, d=None: downsample if 'downsample' in k else d),
|
||||
lambda: types.SimpleNamespace(get=_cfg_get),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -68,6 +76,16 @@ def test_accepts_via_fallback(monkeypatch):
|
|||
assert guards.check_quality_target('/x/song.flac', {}) is None
|
||||
|
||||
|
||||
def test_master_toggle_off_skips_filter(monkeypatch):
|
||||
# import.quality_filter_enabled = False → a below-target file is accepted
|
||||
# (imported) regardless of quality, even with fallback off.
|
||||
_patch_guard(
|
||||
monkeypatch, AudioQuality('flac', sample_rate=44100, bit_depth=16),
|
||||
_WANT_FLAC24, quality_filter=False,
|
||||
)
|
||||
assert guards.check_quality_target('/x/song.flac', {}) is None
|
||||
|
||||
|
||||
def test_skips_when_unprobeable(monkeypatch):
|
||||
_patch_guard(monkeypatch, None, _WANT_FLAC24)
|
||||
assert guards.check_quality_target('/x/song.flac', {}) is None
|
||||
|
|
|
|||
|
|
@ -6261,10 +6261,32 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Import Quality Settings -->
|
||||
<!-- Import Settings (collapsible tile) -->
|
||||
<div class="settings-section-header collapsed" data-stg="library" onclick="this.classList.toggle('collapsed'); const b=this.nextElementSibling; b.classList.toggle('collapsed'); b.style.display=b.classList.contains('collapsed')?'none':''">
|
||||
<span class="settings-section-arrow">▼</span>
|
||||
<h3>Import</h3>
|
||||
<span class="settings-section-hint">Quality filter, replace rules, folder-artist</span>
|
||||
</div>
|
||||
<div class="settings-section-body collapsed" data-stg="library">
|
||||
|
||||
<div class="settings-group" data-stg="library">
|
||||
<h3>📥 Import Settings</h3>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" id="import-quality-filter-enabled" checked>
|
||||
Only import tracks that meet your quality profile
|
||||
</label>
|
||||
</div>
|
||||
<div class="help-text">
|
||||
On by default. SoulSync always probes each downloaded file's real audio quality; this
|
||||
toggle decides what to do with the result. <strong>On</strong> — files that don't meet your
|
||||
quality profile are quarantined instead of imported (the same gate the download pipeline
|
||||
uses; fallback/downsample settings still apply). <strong>Off</strong> — everything is
|
||||
imported regardless of quality. Either way, the library <em>Quality Upgrade Scanner</em>
|
||||
still flags below-profile tracks so you can act on them later.
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" id="import-replace-lower-quality">
|
||||
|
|
@ -6294,6 +6316,8 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- end Import body -->
|
||||
|
||||
|
||||
<!-- M3U Export Settings -->
|
||||
<div class="settings-group" data-stg="library">
|
||||
|
|
|
|||
|
|
@ -1299,6 +1299,8 @@ async function loadSettingsData() {
|
|||
}
|
||||
|
||||
// Populate Import settings
|
||||
const _qualFilterEl = document.getElementById('import-quality-filter-enabled');
|
||||
if (_qualFilterEl) _qualFilterEl.checked = settings.import?.quality_filter_enabled !== false; // default ON
|
||||
document.getElementById('import-replace-lower-quality').checked = settings.import?.replace_lower_quality === true;
|
||||
const _folderArtistEl = document.getElementById('import-folder-artist-override');
|
||||
if (_folderArtistEl) _folderArtistEl.checked = settings.import?.folder_artist_override === true;
|
||||
|
|
@ -3071,6 +3073,7 @@ async function saveSettings(quiet = false) {
|
|||
music_videos_path: document.getElementById('music-videos-path').value || './MusicVideos'
|
||||
},
|
||||
import: {
|
||||
quality_filter_enabled: document.getElementById('import-quality-filter-enabled')?.checked !== false,
|
||||
replace_lower_quality: document.getElementById('import-replace-lower-quality').checked,
|
||||
folder_artist_override: document.getElementById('import-folder-artist-override')?.checked === true,
|
||||
staging_path: document.getElementById('staging-path').value || './Staging'
|
||||
|
|
|
|||
Loading…
Reference in a new issue