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>
The library quality scanner judged quality by FILE EXTENSION only
(get_quality_tier_from_extension) and read the legacy v2 `qualities` dict —
so every FLAC was "lossless tier 1" regardless of bit depth / sample rate. It
could never flag a 16-bit FLAC as upgradeable under a 24-bit profile, and it
ignored the v3 ranked_targets entirely. Completely inconsistent with the
download guard.
Now both share one core:
- selection.targets_from_profile(profile) — single profile→targets conversion
(v2→v3 migration), reused by load_profile_targets.
- selection.quality_meets_profile(aq, targets) — strict: meets iff the real
measured quality satisfies a ranked target (fallback ignored — it's a
download concession, not a definition of "good enough").
- guards.check_quality_target refactored to use both.
- quality_scanner probes real quality (probe_audio_quality) and checks against
the v3 targets via quality_meets_profile. Extension tier kept only as a
fallback label when a file can't be probed.
Result: the scan flags exactly what the download gate would reject — 16-bit
when you want 24-bit, wrong sample rate, MP3 when you want FLAC.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replaces the Soulseek-only bit-depth heuristic with a source-agnostic
quality system that works across all download sources.
## core/quality/model.py (new)
- AudioQuality dataclass: format, bitrate, sample_rate, bit_depth
- QualityTarget: one ranked entry in the user's priority list
- filter_and_rank(): source-neutral candidate ranking
- rank_candidate(): scores any AudioQuality against ranked_targets
- v2_qualities_to_ranked_targets(): migration helper
## core/download_plugins/types.py
- SearchResult gains sample_rate + bit_depth fields
- audio_quality property returns unified AudioQuality
- AlbumResult gets audio_quality aggregated from tracks
## core/soulseek_client.py
- Parses slskd attributes array (type 4=sample_rate, type 5=bit_depth)
- Real values instead of kbps heuristic
- filter_results_by_quality_preference() replaced by filter_and_rank()
## database/music_database.py
- Quality profile v3 with ranked_targets list
- Auto-migration v2 → v3 on load
- Presets (audiophile/balanced/space_saver) updated to v3
## core/imports/file_ops.py
- probe_audio_quality(): reads actual downloaded file via mutagen
returns AudioQuality with ground-truth values
## core/imports/guards.py
- check_quality_target(): replaces check_flac_bit_depth
checks all formats/sources against ranked_targets
- check_flac_bit_depth() kept as backwards-compat wrapper
## core/imports/pipeline.py
- Uses check_quality_target() instead of check_flac_bit_depth()
- Quality mismatch triggers _requeue_quarantined_task_for_retry('quality')
so next-best candidate is tried before failing (same as AcoustID)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Closes#584. Quarantined files used to sit in ss_quarantine/ with a
thin sidecar — no UI, no recovery, no way to see what got dropped.
This adds the management surface the user needs without going to the
filesystem.
UI: new "Quarantine" button on the downloads page header opens a
modal with every quarantined file (filename, expected track/artist,
reason, when, size). Three actions per row:
- Approve (one-click): restores the file, re-runs the post-process
pipeline with ONLY the failing check skipped, lands in the library
with full tags + lyrics + scan
- Recover (legacy fallback): moves to Staging for thin-sidecar
entries that lack the embedded context Approve needs
- Delete: permanent removal of file + sidecar
Per-check bypass: context['_skip_quarantine_check'] = 'integrity' /
'acoustid' / 'bit_depth'. Skips ONLY the named check — other quality
gates stay live. No blanket bypass-all flag.
Sidecar expansion: move_to_quarantine now persists the full
json-serializable context via serialize_quarantine_context (drops
non-JSON-safe values, walks nested dicts/lists/sets, str-coerces
unknown objects) plus the trigger name. Existing thin sidecars are
detected and routed to Recover instead of Approve.
Pure helpers in core/imports/quarantine.py: list_quarantine_entries
/ delete_quarantine_entry / approve_quarantine_entry /
recover_to_staging / serialize_quarantine_context. 27 tests pin
every shape: orphan files / orphan sidecars / corrupt sidecars /
collision-safe filename restoration / full-context vs thin-sidecar
dispatch / json round-trip safety.
Four new endpoints in web_server.py — thin glue around the helpers:
GET /api/quarantine/list, DELETE /api/quarantine/<id>,
POST /api/quarantine/<id>/approve, POST /api/quarantine/<id>/recover.
Download modal status differentiates "🛡️ Quarantined" from
"❌ Failed" so recoverable files are visible at a glance — checked
against the error_message text, no schema change needed.
Pipeline changes are three minimal per-check conditionals at the
existing quarantine sites in core/imports/pipeline.py. Each
move_to_quarantine call now passes its trigger name so the sidecar
records which check fired.
Full suite: 2992 passed.
- Normalize album import track display handling so queue labels and match rows stay consistent
- Bound MusicBrainz caches and avoid caching transient lookup failures
- Stop swallowing programmer errors in source enrichment helpers
- Restore import config test seams without reintroducing lazy imports
- Guard task completion calls and fix the Windows path test expectation
- Keep file lock tracking from growing without bound
- Move import flow modules into a dedicated package
- Update app and test imports to the new namespace
- Group the import-focused tests under tests/imports