the frontend keeps its own copy of the lossless set (settings.js RT_LOSSLESS_FORMATS + the
index.html quality-profile dropdown) — runtime-fetching a yearly-changing list from the backend
isn't worth the coupling. but that duplication IS the exact root cause of #941 (a format added
to one place, not another). so instead of unifying, pin it: two tests parse the frontend lists
and assert they match the backend LOSSLESS_FORMATS. adding a new lossless format now fails CI
until it's added everywhere, instead of silently shipping a half-wired feature.
verified the guard catches drift (not a tautology): a simulated backend-only 'ape' addition
makes the equality fail. 18 lossless tests green, ruff clean.
radoslav-orlov: "create lossy copies of lossless tracks" only recognized FLAC, even though ALAC/
WAV/AIFF/DSD are now quality-profile formats. the FLAC knowledge was hardcoded in 3 separate
places (the import path, the Lossy Converter scan, and the fix executor) — exactly how a format
gets added in one spot but not another.
kettui-style fix — one canonical seam both sites route through, instead of 3 more string edits:
- new core/quality/lossless.py: is_lossless_format / is_lossless_audio_path (pure; injects a
codec probe for the ambiguous .m4a/.mp4 — ALAC vs AAC — so the decision stays testable with no
I/O), LOSSLESS_FORMATS (single source of truth, derived-consistent with model.tier_score), and
the lossy_output_would_overwrite_source safety invariant.
- create_lossy_copy + the Lossy Converter scan + repair_worker._fix_missing_lossy_copy all route
through it. SQL pre-filters by candidate extensions, then each file is confirmed (probing .m4a).
- SAFETY: a lossy copy must never be written over its own source — an .m4a ALAC source + AAC
target lands on the same .m4a path, and ffmpeg runs with -y. all three sites now bail on the
overwrite case BEFORE ffmpeg (the existing delete-original guard was too late — the source was
already clobbered). dropped a vestigial mutagen FLAC import; updated FLAC-only UI strings.
19 tests: full seam coverage (formats, the .m4a ALAC/AAC probe branch, candidate extensions, the
overwrite guard), a tier-model consistency test that fails if the lossless set drifts, and import-
site wiring tests — WAV now converts (was rejected), and the .m4a-ALAC+AAC overwrite case proves
ffmpeg NEVER runs. 286 quality/import/repair tests green, ruff clean.
diegocade1: DSD files (.dsf, ~500MB DSD64) were labeled "Low Quality" and nagged to upgrade.
two independent causes, both fixed (additive — no existing format/behaviour changed):
1) DSF was an unrecognized format -> bottom 'unknown' tier -> "Low Quality":
- source_map: map .dsf/.dff -> 'dsf' (also lights it up in AUDIO_EXTENSIONS, so Soulseek can
match a DSF if one exists)
- model.tier_score: 'dsf' base 102 (just above FLAC) — lands in the lossless range
- probe_audio_quality: add a DSD branch returning format='dsf' (mutagen.dsf for .dsf detail;
.dff classifies lossless without measured detail) instead of None
- settings UI: DSD in RT_LOSSLESS_FORMATS + a "DSD (DSF / DFF)" option in the profile dropdown
2) the actual cause of the screenshot's findings — the truncation guard falsely called DSF
"broken (only ~12% decodes)": ffmpeg decodes DSD to PCM at a different rate than the DSD
container's 2.8 MHz, so astats samples ÷ container-rate massively under-counts. now
detect_broken_audio skips the truncation check for DSD (silence detection still applies).
8 seam tests: dsf/dff -> 'dsf'; dsf tier in lossless range (with + without measured bitrate);
is_dsd_path; and a contrast pair proving the same 12%-decode numbers flag a .flac but skip a
.dsf. 230 quality/import/silence tests green, ruff + JS integrity clean.
Adds an opt-in `rank_candidates_by_quality` profile flag. When on, the
priority-mode download walk orders candidates by the ranked-target quality
(confidence/speed only break ties) instead of confidence-first. Default off
keeps the byte-for-byte old behaviour, so existing installs are unaffected.
Best-quality search mode is always quality-first regardless of the flag; the
toggle only affects priority mode. Search-time source selection is unchanged —
nothing is skipped, so a track can never go missing, only the order in which
copies are tried changes.
The version-mismatch force-import follows automatically: it accepts the
first-tried (= best-ordered) quarantined candidate, which is the highest-quality
one once the walk is quality-first. No change to its selection logic needed.
- core/quality/selection.py: load_rank_candidates_by_quality() (fail-closed).
- core/downloads/task_worker.py: _best_quality_ordering -> _candidate_ordering;
quality-first when best_quality mode OR the toggle is on.
- database/music_database.py: default profile carries the flag (False).
- web_server.py: flag is preserved globally across preset apply/reset, like
search_mode.
- core/imports/version_mismatch_fallback.py: comment clarified (no behaviour
change).
Tests (TDD): load_rank_candidates_by_quality default/enabled/disabled/error;
_candidate_ordering across all mode+toggle combinations + fail-closed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The ranked-target list is now the single source of truth for which formats
download, in the user's exact priority order, for ALL sources — no hardcoded
format hierarchy decides anything. A candidate passes only if it matches a
ranked target; if nothing matches, the existing Use-Fallback toggle decides.
- source_map: new shared format_from_extension() + AUDIO_EXTENSIONS — one
source of truth for extension→format used by every extension-based source, so
adding a format lights it up everywhere. Soulseek now classifies through it
(opus/wav/aiff were previously dropped as 'unknown').
- file_ops.probe_audio_quality (generic import-time guard, all sources): add
WMA; detect ALAC from the real codec (an .m4a is AAC or ALAC).
- soulseek: drop the AAC-specific opt-in gate — AAC now follows the same
universal rule as every format.
- model.tier_score: documented as ONLY a same-format tiebreak + fallback order,
never cross-format priority (the list owns that); add opus/alac bases.
- UI: ranked-target editor offers all formats (FLAC/ALAC/WAV·AIFF lossless with
bit-depth+sample-rate; MP3/AAC/OGG/Opus/WMA lossy with min-bitrate).
- tests: AAC retargeted to the universal model; new coverage for
format_from_extension and matches_target across all formats.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PR #896 removed the per-source quality dropdowns; streaming sources now derive
their request tier from the global profile via quality_tier_for_source. Without
a migration, a user who had tidal_download/qobuz/hifi_download.quality on
'hires'/'hires_max' silently dropped to lossless (their migrated v2 'flac (any)'
top target resolves to the lossless tier).
_migrate_v2_to_v3 now seeds the 24-bit FLAC ladder at the top of ranked_targets
when such a Hi-Res source preference is detected — but only if the profile
doesn't already express 24-bit, so it never duplicates the ladder.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
#3 priority mode is quality-agnostic again: search_with_fallback (the
priority/hybrid path — best_quality has its own search_all_sources) returned
the first source whose results met a target, so an mp3 with bitrate=None (slskd
omits it often) was deemed unsatisfied and deprioritised, changing which source
wins for users who never opted in. Restore "first source with tracks wins",
byte-for-byte; cross-source quality pooling stays in best_quality mode.
#4 metadata-less FLAC no longer over-claims a hi-res target: matches_target let
a FLAC with no sample_rate/bit_depth satisfy a 24-bit/192k target while a real
16/44 FLAC failed it, so unknown-spec files outranked and discarded genuine CD
FLAC under audiophile/hi-res profiles. An unconfirmable spec now fails the strict
tier and falls to the plain-flac bucket.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Switching presets now restores the user's prior edits to that preset
instead of factory defaults. Edits are stashed per preset name under
the quality_profile_presets preference; 'custom'/unknown names are not
stashed. Adds a /reset endpoint + "Reset to defaults" UI link to drop a
preset's saved edits.
- DB: set_quality_profile stashes per-preset; get_quality_preset returns
the customized form by default, _factory_quality_preset for the raw
defaults; reset_quality_preset forgets a preset's edits.
- web_server: apply-preset carries the global search_mode across switches;
new preset/<name>/reset endpoint.
- UI: target edits now save via debouncedSaveQualityProfile (profile-only,
no full settings re-init/flicker); preset switch suppresses the global
auto-save listener; help text + reset link.
Co-Authored-By: Claude Opus 4.8 <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>
The audiophile preset (fallback_enabled=False) still shipped a "FLAC 16-bit"
target in its ladder, so 16-bit FLAC matched and imported even though the name
implies hi-res-only. Split the ladder: audiophile now uses a strict 24-bit FLAC
list; balanced keeps 24-bit + 16-bit + MP3. Gives users a one-click strict
"24-bit only" profile that actually rejects 16-bit/lossy.
Not a matches_target bug — that correctly rejects 16-bit vs a 24-bit target;
the leak was the preset's target LIST including 16-bit (+ fallback accepting
off-list lossy like MP3-128).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three fixes from on-device testing of best-quality mode:
1. clear_completed_local no longer prunes terminal tasks that belong to a
STILL-ACTIVE batch (one with non-terminal work remaining). The 5-min
"Clean Completed Downloads" automation was yanking completed/failed/
unverified rows out of download_tasks mid-run — and failed/cancelled
aren't in library_history — so they only reappeared after the batch
ended. Now the whole active batch stays intact until it finishes.
2. search_all_sources runs every source CONCURRENTLY (asyncio.gather)
instead of sequentially, so the pool waits only for the slowest source
(e.g. usenet/Prowlarr) in parallel rather than summing all latencies.
3. The pool log now reports per-source contribution counts
(e.g. "usenet=0, hifi=11, soulseek=1") instead of just echoing the
chain, so a release-level source that returns nothing for a track-title
query is visible rather than appearing to have been searched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds an opt-in search strategy toggle in the Quality Profile:
- priority (default): unchanged — first source in the hybrid chain that
meets a quality target wins.
- best_quality: pool candidates from EVERY source per query and download
them best→worst by actual audio quality; source order only breaks ties.
Implementation reuses existing plumbing so the retry system is untouched:
- engine.search_all_sources pools raw tracks across all configured,
non-exhausted sources (no first-source short-circuit).
- candidates.order_candidates: new quality_first sort path — profile
quality rank dominates, confidence/peer signals break ties. Priority
path is byte-for-byte unchanged (regression-locked by tests).
- task_worker passes quality_first + targets through; skips the redundant
hybrid-fallback block in best-quality mode (pool already covered it).
- Per-source retry budgets unchanged: a source that spends its budget is
added to exhausted_download_sources and thus dropped from the whole
pool. Independent of post_processing.retry_exhaustive.
- Query generator NOT touched.
Also clarifies the "Allow fallback" setting wording: it accepts OFF-LIST
quality as a last resort (not "walk down my list"), and notes that
lossy_copy.downsample_hires also bypasses the quality gate — the cause of
16-bit/MP3 files slipping through a 24-bit-only profile.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remove the per-source download-quality dropdowns (Tidal/HiFi/Qobuz/Deezer/
Amazon) — with the global ranked-targets system they were redundant and
conflicting. Add quality_tier_for_source(): picks the LOWEST source tier
that satisfies the user's top target (respects the quality ceiling, saves
bandwidth) or the source's max as best effort. Every source's search +
download + retry path now derives its tier from the global profile instead
of config_manager.get('<source>_download.quality').
Settings keep the per-source allow_fallback toggles; the quality selects are
replaced with a note pointing at Quality Profile.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Quality guard: rejects with a 'file is X, wanted Y' reason (the string the
track-detail modal surfaces), accepts when a target is met or fallback is on,
skips when unprobeable.
force_import isolation: the 'quality' bypass must not skip the AcoustID check
and vice-versa; a quality reject persists trigger='quality' (not 'acoustid')
in the sidecar — so a quality mismatch never routes through the force_import
path (reserved for AcoustID version-mismatch).
Model: lossy matches a MINIMUM bitrate (>=, a range); lossless matches on bit
depth + sample rate, never exact bitrate, so a FLAC's varying bitrate (mono /
compression) can't falsely reject it. v2->v3 migration preserves order.
47 passing across the quality + guard suites.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add core/quality/selection.py: rank_with_targets() returns (ranked,
satisfied) where satisfied = a candidate meets a real target (strict).
load_profile_targets()/rank_for_profile() are the DB-backed wrappers.
search_with_fallback now skips a source that can deliver no target-meeting
quality and escalates to the next (source priority still wins among
satisfying sources; first source's results kept as fallback unless the
profile disables it). Returns RAW tracks — the satisfied check is a coarse
source gate; match-filtering + final ranking stay in the orchestrator so
the correct track is never pruned. Ranking is fail-open: a ranking error
never drops a source's real results.
Tested: rank_with_targets satisfied/fallback matrix + engine escalation,
stop-on-first, raw-not-pruned, fallback on/off. Amazon field test updated
for the corrected format token.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add core/quality/source_map.py centralising each source's tier->AudioQuality
mapping (Tidal/HiFi tiers, Qobuz real kHz/bit-depth, Deezer codes, Amazon
codec/tier). Add TrackResult.set_quality() to merge a mapped AudioQuality
onto a result. Wire HiFi, Qobuz, Deezer, Tidal, Amazon search results to
stamp real sample_rate/bit_depth so the global ranker no longer relies on
crude kbps heuristics for streaming sources. Fixes Qobuz/Amazon display
labels ('FLAC 24-bit/192kHz', 'Lossless') breaking format derivation.
Tested: 22 passing (mappers + set_quality merge semantics).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>