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>
104 lines
4.1 KiB
Python
104 lines
4.1 KiB
Python
"""Per-source quality mappers — turn each download source's tier string or
|
|
API values into a unified :class:`~core.quality.model.AudioQuality`.
|
|
|
|
Every streaming source describes quality differently (Tidal/HiFi use tier
|
|
strings, Qobuz reports real kHz + bit depth, Deezer uses config codes,
|
|
Amazon mixes real values with HD/UHD tiers). Centralising the knowledge
|
|
here keeps the per-client code to a single call and keeps the tier tables
|
|
in one auditable place.
|
|
|
|
Each value is a *claim*: the download client populates its ``TrackResult``
|
|
from it so the global ranker can choose a source, and the post-download
|
|
quality guard later verifies the real file. Over-claiming is the danger —
|
|
an unknown tier maps to ``format='unknown'`` rather than pretending to be
|
|
lossless.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Optional
|
|
|
|
from core.quality.model import AudioQuality
|
|
|
|
|
|
# ── Tidal / HiFi (Monochrome is Tidal-backed) ──────────────────────────────
|
|
#
|
|
# Tidal exposes UPPER_SNAKE tier strings (``HI_RES_LOSSLESS``); HiFi's config
|
|
# uses lowercase keys (``hires``/``lossless``). We normalise both into the
|
|
# same lookup so one mapper serves both sources.
|
|
|
|
_TIDAL_HIRES = AudioQuality(format='flac', sample_rate=96000, bit_depth=24)
|
|
_TIDAL_LOSSLESS = AudioQuality(format='flac', sample_rate=44100, bit_depth=16)
|
|
_TIDAL_HIGH = AudioQuality(format='aac', bitrate=320)
|
|
_TIDAL_LOW = AudioQuality(format='aac', bitrate=96)
|
|
|
|
TIDAL_TIER_MAP = {
|
|
'HI_RES_LOSSLESS': _TIDAL_HIRES,
|
|
'HI_RES': _TIDAL_HIRES,
|
|
'HIRES': _TIDAL_HIRES,
|
|
'LOSSLESS': _TIDAL_LOSSLESS,
|
|
'HIGH': _TIDAL_HIGH,
|
|
'LOW': _TIDAL_LOW,
|
|
}
|
|
|
|
|
|
def quality_from_tidal_tier(tier: str) -> AudioQuality:
|
|
"""Map a Tidal/HiFi quality tier string to an AudioQuality.
|
|
|
|
Case-insensitive; accepts both ``HI_RES`` and ``hires`` spellings.
|
|
Unrecognised tiers map to ``format='unknown'`` so they never
|
|
over-claim lossless quality.
|
|
"""
|
|
key = (tier or '').strip().upper()
|
|
return TIDAL_TIER_MAP.get(key, AudioQuality(format='unknown'))
|
|
|
|
|
|
# ── Qobuz (real API values) ────────────────────────────────────────────────
|
|
|
|
def quality_from_qobuz(sampling_rate_khz: float, bit_depth: int) -> AudioQuality:
|
|
"""Qobuz reports ``maximum_sampling_rate`` in kHz (e.g. 44.1, 96, 192)
|
|
and ``maximum_bit_depth``. These are real values from the API.
|
|
"""
|
|
sample_rate = int(round(sampling_rate_khz * 1000)) if sampling_rate_khz else None
|
|
return AudioQuality(format='flac', sample_rate=sample_rate, bit_depth=bit_depth)
|
|
|
|
|
|
# ── Deezer (config code) ───────────────────────────────────────────────────
|
|
|
|
DEEZER_CODE_MAP = {
|
|
'flac': AudioQuality(format='flac', sample_rate=44100, bit_depth=16),
|
|
'mp3_320': AudioQuality(format='mp3', bitrate=320),
|
|
'mp3_128': AudioQuality(format='mp3', bitrate=128),
|
|
}
|
|
|
|
|
|
def quality_from_deezer(code: str) -> AudioQuality:
|
|
"""Map a Deezer download quality code to AudioQuality.
|
|
|
|
Deezer FLAC is always CD-quality (16-bit/44.1 kHz).
|
|
"""
|
|
return DEEZER_CODE_MAP.get((code or '').lower(), AudioQuality(format='unknown'))
|
|
|
|
|
|
# ── Amazon Music (real sampleRate preferred, HD/UHD tier fallback) ─────────
|
|
|
|
_AMAZON_TIER_MAP = {
|
|
'UHD': AudioQuality(format='flac', sample_rate=96000, bit_depth=24),
|
|
'HD': AudioQuality(format='flac', sample_rate=44100, bit_depth=16),
|
|
}
|
|
|
|
|
|
def quality_from_amazon(
|
|
tier: str,
|
|
sample_rate: Optional[int] = None,
|
|
bit_depth: Optional[int] = None,
|
|
) -> AudioQuality:
|
|
"""Amazon Music is FLAC; prefer the real ``sampleRate``/``bitDepth`` from
|
|
the stream info when present, otherwise fall back to the HD/UHD tier.
|
|
"""
|
|
base = _AMAZON_TIER_MAP.get((tier or '').strip().upper(), AudioQuality(format='flac'))
|
|
return AudioQuality(
|
|
format='flac',
|
|
sample_rate=sample_rate if sample_rate is not None else base.sample_rate,
|
|
bit_depth=bit_depth if bit_depth is not None else base.bit_depth,
|
|
)
|