diff --git a/config/settings.py b/config/settings.py index f1482081..a31ed972 100644 --- a/config/settings.py +++ b/config/settings.py @@ -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 diff --git a/core/imports/guards.py b/core/imports/guards.py index 7f968daa..12bc9afb 100644 --- a/core/imports/guards.py +++ b/core/imports/guards.py @@ -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)) diff --git a/tests/imports/test_import_guards.py b/tests/imports/test_import_guards.py index 44cd5bde..22586d5a 100644 --- a/tests/imports/test_import_guards.py +++ b/tests/imports/test_import_guards.py @@ -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), ) diff --git a/tests/imports/test_quality_guard.py b/tests/imports/test_quality_guard.py index b180bf75..12ac06db 100644 --- a/tests/imports/test_quality_guard.py +++ b/tests/imports/test_quality_guard.py @@ -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 diff --git a/webui/index.html b/webui/index.html index 6e531ba2..236f83a9 100644 --- a/webui/index.html +++ b/webui/index.html @@ -6261,10 +6261,32 @@ - + +