Commit graph

5 commits

Author SHA1 Message Date
BoulderBadgeDad
b62d9b5b08 quality: recognize DSD (.dsf/.dff) as lossless + stop the false "truncated" flag (#939)
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.
2026-06-28 11:45:16 -07:00
dev
774d0f6c65 feat(quality): make every audio format controllable via ranked targets
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>
2026-06-24 23:01:20 +02:00
dev
13bfdaeda2 feat(quality): re-port AAC opt-in tier (#886) onto ranked-targets
The 2.7.4 merge replaced the bucket-based quality filter with core.quality.model
and silently dropped #886's AAC handling. Restore it in the new model:

- v2_qualities_to_ranked_targets maps an enabled 'aac' tier to a format-only
  target (slskd AAC rarely carries a bitrate, so no min_bitrate gate); priority
  order (above MP3, below FLAC) comes from the caller's sort.
- soulseek filter_results_by_quality_preference drops AAC/.m4a candidates when
  the profile has no AAC target, so AAC stays OFF-by-default instead of slipping
  through via fallback.
- the three factory presets carry the legacy qualities dict again with the
  disabled aac tier (used by the settings UI + the #886 opt-in toggle).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 21:33:53 +02:00
dev
8934c6418c fix(quality): correct ranking behaviour flagged in PR #896 (#3, #4)
#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>
2026-06-24 21:25:36 +02:00
nick2000713
2c91af0062 feat: global AudioQuality model with post-download quarantine + retry
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>
2026-06-13 22:22:08 +02:00