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>
69 lines
2.6 KiB
Python
69 lines
2.6 KiB
Python
"""order_candidates — the candidate sort used by attempt_download_with_candidates.
|
|
|
|
Default (priority mode) sorts confidence-first, then peer quality — today's
|
|
behaviour, locked here as a regression guard. quality_first=True (best-quality
|
|
mode) makes the user's profile quality rank dominate, with confidence as the
|
|
tiebreaker. Both keep correctly-matched candidates; ordering only changes which
|
|
is tried first.
|
|
"""
|
|
|
|
from core.downloads.candidates import order_candidates
|
|
from core.quality.model import AudioQuality, QualityTarget
|
|
|
|
|
|
class _Cand:
|
|
def __init__(self, name, aq, confidence, quality_score=0,
|
|
upload_speed=0, queue_length=0, free_upload_slots=0, size=0):
|
|
self.name = name
|
|
self.audio_quality = aq
|
|
self.confidence = confidence
|
|
self.quality_score = quality_score
|
|
self.upload_speed = upload_speed
|
|
self.queue_length = queue_length
|
|
self.free_upload_slots = free_upload_slots
|
|
self.size = size
|
|
|
|
|
|
FLAC_HI = AudioQuality('flac', sample_rate=96000, bit_depth=24)
|
|
FLAC_CD = AudioQuality('flac', sample_rate=44100, bit_depth=16)
|
|
|
|
TARGETS = [
|
|
QualityTarget(label='FLAC 24', format='flac', bit_depth=24, min_sample_rate=96000),
|
|
QualityTarget(label='FLAC 16', format='flac', bit_depth=16),
|
|
]
|
|
|
|
|
|
def test_priority_mode_is_confidence_first():
|
|
hi = _Cand('hi-flac', FLAC_HI, confidence=0.80)
|
|
lo = _Cand('cd-flac', FLAC_CD, confidence=0.95)
|
|
|
|
ordered = order_candidates([hi, lo], quality_first=False, targets=TARGETS)
|
|
|
|
assert [c.name for c in ordered] == ['cd-flac', 'hi-flac'] # higher confidence wins
|
|
|
|
|
|
def test_quality_first_lets_better_quality_win_over_confidence():
|
|
hi = _Cand('hi-flac', FLAC_HI, confidence=0.80)
|
|
lo = _Cand('cd-flac', FLAC_CD, confidence=0.95)
|
|
|
|
ordered = order_candidates([hi, lo], quality_first=True, targets=TARGETS)
|
|
|
|
assert [c.name for c in ordered] == ['hi-flac', 'cd-flac'] # 24-bit beats higher-confidence 16-bit
|
|
|
|
|
|
def test_quality_first_uses_confidence_as_tiebreak_within_same_quality():
|
|
a = _Cand('a', FLAC_HI, confidence=0.70)
|
|
b = _Cand('b', FLAC_HI, confidence=0.90)
|
|
|
|
ordered = order_candidates([a, b], quality_first=True, targets=TARGETS)
|
|
|
|
assert [c.name for c in ordered] == ['b', 'a'] # same quality → confidence breaks tie
|
|
|
|
|
|
def test_quality_first_ranks_unmatched_quality_last():
|
|
matched = _Cand('matched', FLAC_CD, confidence=0.50)
|
|
off_list = _Cand('off', AudioQuality('mp3', bitrate=320), confidence=0.99)
|
|
|
|
ordered = order_candidates([off_list, matched], quality_first=True, targets=TARGETS)
|
|
|
|
assert [c.name for c in ordered] == ['matched', 'off'] # off-list sorts last despite high confidence
|