Commit graph

11 commits

Author SHA1 Message Date
dev
1005c7e306 refactor(quality-upgrade): remove legacy hardcoded rank constants and dead functions
RANK_LOSSLESS/320/256/192/BELOW, _PROFILE_KEY_RANK, classify_track_quality,
preferred_quality_floor, meets_preferred_quality, _rank_label — none of these
were called from scan(). The job already uses rank_candidate() + targets_from_profile
from the quality model, which is fully profile-driven (no hardcoded thresholds).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 16:02:30 +02:00
dev
ff12d8bbf2 fix(repair-jobs): boolean settings saved as string 'true'/'false' by UI dropdown
HTML <select> options can only store string values, so setting_options booleans
([True, False]) were serialised as 'true'/'false' strings and sent to the API.
Python's `x is True` check returned False for the string, making require_top_target
and deep_audio_verify permanently read as False regardless of what the user saved.

Fix JS: convert 'true'/'false' strings to real booleans before POSTing.
Fix Python: _to_bool() in quality_upgrade + inline coercion in scanner to handle
both existing string values in config and correct future booleans.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 15:59:25 +02:00
dev
94637cbe6f fix(quality-upgrade): ranking-based top-target check via rank_candidate
With require_top_target=True the old code used check_targets = targets[:1]
plus quality_meets_profile — which fell back to ALL targets when only one
target was configured, so single-target profiles (FLAC (any) or FLAC 24-bit
alone) never flagged anything.

Replace with a direct rank_candidate(measured_aq, targets) call: skip only
when idx==0 (file already at rank 0, the top tier). Any lower rank (or no
matching rank) is flagged for upgrade search. This is fully profile-driven:
a library of 16-bit FLACs against a [24-bit/192, 24-bit/96, 24-bit/44, 16-bit]
ranking will flag the 16-bit files; a library already at 24-bit/192 won't.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 15:45:39 +02:00
dev
ef0b68a973 fix(quarantine+quality): persistent group toggle, better alt-row UI, quality upgrade default scope
Quarantine grouping:
- First candidate shown as normal row; others hidden under a "▾ N more"
  button inline in the actions bar — no separate header row
- Group open state tracked in _verifQuarOpenGroups (Set), survives
  periodic re-renders so the list no longer auto-collapses

Quality Upgrade Finder:
- Default scope changed from 'watchlist' to 'all' so it scans the whole
  library when no scope is explicitly configured
- _get_settings rewritten to read the full settings dict at once
  (same pattern as QualityUpgradeScannerJob) to fix silent read failures

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 15:26:41 +02:00
dev
e4ba27d8b3 fix(quality-upgrade): pass config_manager to path resolver so relative DB paths resolve
resolve_library_file_path() was called without config_manager, so
_collect_base_dirs() returned an empty list and every track stored
with a relative path (Artist/Album/track.flac) resolved to None.
probe_audio_quality was never called → 0 files checked.

Also threads resolved_path through _read_file_ids to avoid a second
redundant resolution pass on the same file.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 15:16:42 +02:00
dev
e32b4ec727 feat(repair): require_top_target option — flag files upgradeable to preferred quality
Adds an opt-in setting (default off) to both Quality Upgrade jobs.

When enabled, a file only counts as "good enough" if it meets the
highest-priority target in the ranked profile — not just any target.
Example: with [FLAC 24-bit, FLAC 16-bit], a 16-bit file is flagged as
a candidate for upgrade even though it satisfies the profile's fallback
target. Finding titles say "Upgradeable" (not "Below quality") and the
description names the preferred target explicitly.

- quality_upgrade.py (Finder): reads require_top_target from settings,
  builds check_targets = targets[:1] when on, improves finding description
- quality_upgrade_scanner.py (flag-only): same option + matching finding
  title/description change

Pairs naturally with the best_quality search mode on this branch: the
scanner surfaces the candidates, the wishlist download picks the best
available version across all sources.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 00:44:24 +02:00
nick2000713
d169818043 feat(quality): upgrade Finder to v3 quality + clarify the two quality jobs
The two library quality jobs overlapped confusingly. Keep both, but make
each one's role obvious and put them on the same v3 quality definition.

Quality Upgrade Finder (quality_upgrade) — the ACTIVE job:
- Quality decision moved from v2 (extension + DB bitrate) to v3: probes the
  REAL file with mutagen (measured bit depth / sample rate / bitrate) and
  checks it against the profile's ranked targets — same as the import guard.
- New optional `deep_audio_verify` setting (default OFF): also run the ffmpeg
  decode guard (truncation + silence); a broken file is proposed for replacement.
- Renamed to "Quality Upgrade Finder (active — proposes a replacement)" + help
  text spells out it actively searches a better version and queues it.
- v3 helpers imported at module level so they stay monkeypatchable in tests.

Quality Check (quality_upgrade_scanner) — the FLAG-ONLY job:
- `deep_audio_verify` default flipped ON->OFF (the ffmpeg decode is the
  CPU-heavy step; matches the download pipeline's default).
- Renamed to "Quality Check (flag only — you decide per finding)" + help text
  contrasts it with the active Finder.

UI: deep_audio_verify setting label now shows "(ffmpeg decode — CPU heavy)".

Tests: scan() tests stub the v3 probe path (probe_audio_quality /
quality_meets_profile / resolve_library_file_path) since they use fake paths.
The v2 pure-function helpers stay (still unit-tested).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 12:43:02 +02:00
BoulderBadgeDad
030d9bf9ff Quality Upgrade: best-in-class matching (direct track-ID tier, dedup-skip, duration guard)
Four refinements on top of the tiered matcher:

1. Direct source track-ID tier (new top tier): enrichment writes each source's own
   track ID into the file tags (spotify_track_id/deezer_track_id/itunes_track_id/...).
   If we have the active source's track ID, fetch that exact track by ID via
   get_track_details — zero search. Tiers are now: track-ID -> ISRC -> album->track
   -> artist+title. _read_file_ids reads ISRC + all per-source IDs in one tag read.

2. Skip already-proposed tracks: a re-run loads existing finding entity_ids for the
   job and skips those tracks before any API call (pending stays deduped, dismissed
   stays dismissed) — re-runs are cheap.

3. Wrong-version guard: the fuzzy tiers (album-search + track search) reject a
   candidate whose length differs from ours by >5s (live/edit/remix with same title).
   _load_tracks now selects t.duration; exact tiers (track-ID/ISRC/stored-album-ID)
   skip the guard.

4. Tighter album matching: same-title cuts in an album are disambiguated by closest
   duration when track_number doesn't decide it.

Findings record matched_via = track_id | isrc | album | search. 30 repair tests pass
(added track-ID tier, duration guard, dedup-skip, and unit coverage).
2026-06-13 13:34:48 -07:00
BoulderBadgeDad
777781db6a Quality Upgrade: tiered structured matching (ISRC -> album->track -> artist+title)
Replaces the blind fuzzy search with a smart hierarchy that uses the data we
already have, best identity first:

1. ISRC embedded in the file tags (enriched track) -> exact track.
2. Album -> track: use the album's stored source ID (albums.spotify_album_id /
   itunes_album_id / deezer_id / musicbrainz_release_id / audiodb_id) when the
   ALBUM is enriched (even if the track isn't); else find the album by searching
   'artist album', then locate our track in that album's tracklist by normalized
   title (track_number breaks ties). Pins the exact album context. (artist->album->track)
3. Plain artist+title search with similarity scoring. (artist->track) — loosest.

_load_tracks now returns dict rows (adds track_number + the album source-id
columns). Findings record matched_via = isrc | album | search. All clients
(spotify/deezer/itunes/discogs) expose search_albums + get_album_tracks with a
uniform {'items': [...]} shape, so the album tier is source-agnostic.

26 repair tests pass (added album-tier + _find_track_in_album coverage).
2026-06-13 13:00:16 -07:00
BoulderBadgeDad
3ea5b5181f Quality Upgrade: ISRC-first exact matching using the IDs enrichment already embedded
The job was doing a blind fuzzy search for every low-quality track, ignoring that
enrichment writes each track's ISRC + per-source IDs into the file tags. Now it
reads the file's embedded ISRC and resolves the EXACT track via each source's
'isrc:' search (universal cross-source key), guarded by an ISRC-equality check so
a source that ignores the syntax can't produce a false match — exact track, exact
album context, one call. Falls back to the name/artist fuzzy search only for
un-enriched tracks with no usable ISRC. Findings record matched_via=isrc|search.

4 new seam tests (guard accept/reject, ISRC-preferred-over-fuzzy, fuzzy fallback).
2026-06-13 12:43:46 -07:00
BoulderBadgeDad
69dd4e1792 Quality Upgrade Finder: new findings-based repair job (replaces auto-acting Quality Scanner)
The old Quality Scanner tool judged quality by file EXTENSION only (a 128k and a
320k MP3 looked identical), ignored the bitrate-based quality profile, used min()
of enabled tiers so the default profile flagged the ENTIRE non-lossless library,
and auto-dumped every match into the wishlist with no review.

This new repair job does it properly:
- meets_preferred_quality(): pure, bitrate-AWARE decision honoring every enabled
  quality bucket (320 MP3 passes a FLAC+320+256 profile; 128 MP3 doesn't). Floor
  is the worst enabled bucket, not the best.
- scans watchlist artists or whole library, finds below-quality tracks, matches a
  better version at scan time (reusing the existing tested match helpers), emits a
  FINDING showing the match + confidence. Off by default; nothing auto-queued.
- _fix_quality_upgrade apply handler adds the matched track WITH album context to
  the wishlist — the user-approved version of what the old tool did silently.
- Transcode/fake-lossless detection intentionally left to the existing Fake
  Lossless Detector job.

12 seam tests incl. a regression pinning the default-profile flooding bug. The old
tool is still in place; removing it + rewiring its automation action is the next step.
2026-06-13 11:51:43 -07:00