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>
This commit is contained in:
parent
13bfdaeda2
commit
d310b090e2
2 changed files with 87 additions and 2 deletions
|
|
@ -8760,15 +8760,52 @@ class MusicDatabase:
|
|||
|
||||
return self._get_default_quality_profile()
|
||||
|
||||
# 24-bit FLAC ladder seeded on migration for users who had a streaming
|
||||
# source on Hi-Res under the old (now removed) per-source quality dropdowns.
|
||||
_HIRES_24BIT_TARGETS = [
|
||||
{"label": "FLAC 24-bit/192kHz", "format": "flac", "bit_depth": 24, "min_sample_rate": 192000},
|
||||
{"label": "FLAC 24-bit/96kHz", "format": "flac", "bit_depth": 24, "min_sample_rate": 96000},
|
||||
{"label": "FLAC 24-bit/48kHz", "format": "flac", "bit_depth": 24, "min_sample_rate": 48000},
|
||||
{"label": "FLAC 24-bit/44.1kHz","format": "flac", "bit_depth": 24, "min_sample_rate": 44100},
|
||||
]
|
||||
|
||||
def _had_hires_source_preference(self) -> bool:
|
||||
"""True if the user had any streaming source set to a Hi-Res tier under
|
||||
the old per-source quality dropdowns (tidal_download/qobuz/hifi_download
|
||||
.quality = 'hires'|'hires_max'), which #896 removed in favour of the
|
||||
global profile. Used to preserve their intent on migration."""
|
||||
try:
|
||||
from config.settings import config_manager
|
||||
except Exception:
|
||||
return False
|
||||
hires = {'hires', 'hires_max'}
|
||||
for key in ('tidal_download.quality', 'qobuz.quality', 'hifi_download.quality'):
|
||||
try:
|
||||
if str(config_manager.get(key) or '').strip().lower() in hires:
|
||||
return True
|
||||
except Exception:
|
||||
continue
|
||||
return False
|
||||
|
||||
def _migrate_v2_to_v3(self, profile: dict) -> dict:
|
||||
"""Add ranked_targets to a v2 profile without losing its qualities dict."""
|
||||
from core.quality.model import v2_qualities_to_ranked_targets
|
||||
profile = dict(profile)
|
||||
profile['version'] = 3
|
||||
if 'ranked_targets' not in profile:
|
||||
profile['ranked_targets'] = v2_qualities_to_ranked_targets(
|
||||
profile.get('qualities', {})
|
||||
ranked = v2_qualities_to_ranked_targets(profile.get('qualities', {}))
|
||||
# #896 review #5: the per-source quality dropdowns are gone — sources
|
||||
# now derive their tier from this profile. If the user had a source on
|
||||
# Hi-Res, seed 24-bit FLAC targets at the top so they keep Hi-Res
|
||||
# instead of silently dropping to lossless. Skip when the profile
|
||||
# already expresses 24-bit (don't duplicate the ladder).
|
||||
already_24bit = any(
|
||||
t.get('format') == 'flac' and (t.get('bit_depth') or 0) >= 24
|
||||
for t in ranked
|
||||
)
|
||||
if not already_24bit and self._had_hires_source_preference():
|
||||
ranked = [dict(t) for t in self._HIRES_24BIT_TARGETS] + ranked
|
||||
profile['ranked_targets'] = ranked
|
||||
return profile
|
||||
|
||||
def _get_default_quality_profile(self) -> dict:
|
||||
|
|
|
|||
|
|
@ -31,3 +31,51 @@ def test_balanced_still_includes_16bit_and_mp3():
|
|||
assert 'FLAC 16-bit' in labels
|
||||
assert any('MP3' in l for l in labels)
|
||||
assert p['fallback_enabled'] is True
|
||||
|
||||
|
||||
# ── v2 → v3 migration seeds Hi-Res from the old per-source dropdowns (#896 #5) ──
|
||||
|
||||
class _FakeCfg:
|
||||
def __init__(self, values):
|
||||
self._v = values
|
||||
|
||||
def get(self, key, default=None):
|
||||
return self._v.get(key, default)
|
||||
|
||||
|
||||
_V2_LOSSLESS = {
|
||||
'version': 2, 'preset': 'balanced',
|
||||
'qualities': {
|
||||
'flac': {'enabled': True, 'priority': 1, 'bit_depth': 'any'},
|
||||
'mp3_320': {'enabled': True, 'priority': 2},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _migrate(profile, cfg_values, monkeypatch):
|
||||
monkeypatch.setattr('config.settings.config_manager', _FakeCfg(cfg_values), raising=False)
|
||||
db = MusicDatabase.__new__(MusicDatabase)
|
||||
return db._migrate_v2_to_v3(profile)
|
||||
|
||||
|
||||
def test_v2_to_v3_seeds_hires_when_a_source_was_hires(monkeypatch):
|
||||
"""A user who had Tidal on Hi-Res keeps it: the migrated profile gains 24-bit
|
||||
targets at the top so quality_tier_for_source resolves to 'hires', not a
|
||||
silent drop to lossless."""
|
||||
out = _migrate(dict(_V2_LOSSLESS), {'tidal_download.quality': 'hires'}, monkeypatch)
|
||||
top = out['ranked_targets'][0]
|
||||
assert top['format'] == 'flac' and top['bit_depth'] == 24
|
||||
# the user's existing lossy fallback is preserved below the seeded ladder
|
||||
assert any(t.get('format') == 'mp3' for t in out['ranked_targets'])
|
||||
|
||||
|
||||
def test_v2_to_v3_no_seed_without_hires_preference(monkeypatch):
|
||||
out = _migrate(dict(_V2_LOSSLESS), {'tidal_download.quality': 'lossless'}, monkeypatch)
|
||||
assert not any(t.get('bit_depth') == 24 for t in out['ranked_targets'])
|
||||
|
||||
|
||||
def test_v2_to_v3_no_duplicate_when_profile_already_24bit(monkeypatch):
|
||||
v2 = dict(_V2_LOSSLESS)
|
||||
v2['qualities'] = {'flac': {'enabled': True, 'priority': 1, 'bit_depth': '24'}}
|
||||
out = _migrate(v2, {'qobuz.quality': 'hires_max'}, monkeypatch)
|
||||
assert sum(1 for t in out['ranked_targets'] if t.get('bit_depth') == 24) == 1
|
||||
|
|
|
|||
Loading…
Reference in a new issue