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>
This commit is contained in:
parent
1f14fb4d5e
commit
94637cbe6f
1 changed files with 30 additions and 12 deletions
|
|
@ -43,6 +43,7 @@ from core.library.path_resolver import resolve_library_file_path
|
||||||
# the SAME definition the download import guard uses. Module-level so they're
|
# the SAME definition the download import guard uses. Module-level so they're
|
||||||
# monkeypatchable in tests.
|
# monkeypatchable in tests.
|
||||||
from core.imports.file_ops import probe_audio_quality
|
from core.imports.file_ops import probe_audio_quality
|
||||||
|
from core.quality.model import rank_candidate
|
||||||
from core.quality.selection import targets_from_profile, quality_meets_profile
|
from core.quality.selection import targets_from_profile, quality_meets_profile
|
||||||
from utils.logging_config import get_logger
|
from utils.logging_config import get_logger
|
||||||
|
|
||||||
|
|
@ -566,11 +567,11 @@ class QualityUpgradeJob(RepairJob):
|
||||||
logger.info("[Quality Upgrade] No quality targets in profile — nothing to flag")
|
logger.info("[Quality Upgrade] No quality targets in profile — nothing to flag")
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# require_top_target: only the highest-priority target (targets[0]) counts
|
logger.info(
|
||||||
# as "good enough". A 16-bit FLAC is flagged even when 16-bit is an accepted
|
"[Quality Upgrade] scope=%s require_top=%s · all targets: %s",
|
||||||
# fallback, because the user's preferred quality is 24-bit. Default off so
|
scope, require_top,
|
||||||
# existing behaviour (any target satisfied = skip) is unchanged.
|
[t.label for t in targets] if targets else '(none — all pass)',
|
||||||
check_targets = targets[:1] if require_top and len(targets) > 1 else targets
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tracks = self._load_tracks(db, scope)
|
tracks = self._load_tracks(db, scope)
|
||||||
|
|
@ -648,11 +649,21 @@ class QualityUpgradeJob(RepairJob):
|
||||||
context.update_progress(i + 1, total)
|
context.update_progress(i + 1, total)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not broken_reason and measured_aq is not None and quality_meets_profile(measured_aq, check_targets):
|
if not broken_reason and measured_aq is not None:
|
||||||
result.skipped += 1
|
if require_top:
|
||||||
if context.update_progress and (i + 1) % 25 == 0:
|
# ranking-based: skip only if the file already sits at rank 0
|
||||||
context.update_progress(i + 1, total)
|
# (the top configured target). Any lower rank → flag for upgrade.
|
||||||
continue
|
idx, _ = rank_candidate(measured_aq, targets)
|
||||||
|
already_best = (idx == 0)
|
||||||
|
else:
|
||||||
|
# default: skip if the file meets ANY configured target (i.e.
|
||||||
|
# it's not below the acceptable floor).
|
||||||
|
already_best = quality_meets_profile(measured_aq, targets)
|
||||||
|
if already_best:
|
||||||
|
result.skipped += 1
|
||||||
|
if context.update_progress and (i + 1) % 25 == 0:
|
||||||
|
context.update_progress(i + 1, total)
|
||||||
|
continue
|
||||||
|
|
||||||
# Below profile (or broken) — find a better version to propose.
|
# Below profile (or broken) — find a better version to propose.
|
||||||
if engine is None:
|
if engine is None:
|
||||||
|
|
@ -754,7 +765,7 @@ class QualityUpgradeJob(RepairJob):
|
||||||
title=f'Upgrade: {artist_name} - {title} ({current_label})',
|
title=f'Upgrade: {artist_name} - {title} ({current_label})',
|
||||||
description=(
|
description=(
|
||||||
f'"{title}" by {artist_name} is {current_label}'
|
f'"{title}" by {artist_name} is {current_label}'
|
||||||
+ (f', below your preferred quality ({targets[0].label})' if require_top and len(targets) > 1 else ', below your preferred quality')
|
+ (f', below your preferred quality ({targets[0].label})' if require_top and targets else ', below your preferred quality')
|
||||||
+ f'. Best match: "{_track_name(best)}" via {source} '
|
+ f'. Best match: "{_track_name(best)}" via {source} '
|
||||||
f'({_MATCH_NOTE.get(matched_via, "matched") if matched_via != "search" else f"confidence {conf:.0%}"}). '
|
f'({_MATCH_NOTE.get(matched_via, "matched") if matched_via != "search" else f"confidence {conf:.0%}"}). '
|
||||||
'Apply to add it to the wishlist.'),
|
'Apply to add it to the wishlist.'),
|
||||||
|
|
@ -784,6 +795,13 @@ class QualityUpgradeJob(RepairJob):
|
||||||
|
|
||||||
if context.update_progress:
|
if context.update_progress:
|
||||||
context.update_progress(total, total)
|
context.update_progress(total, total)
|
||||||
logger.info("[Quality Upgrade] %d scanned, %d upgrades found, %d met/skip",
|
logger.info("[Quality Upgrade] %d scanned, %d upgrades found, %d already met profile / skipped",
|
||||||
result.scanned, result.findings_created, result.skipped)
|
result.scanned, result.findings_created, result.skipped)
|
||||||
|
if result.scanned > 0 and result.findings_created == 0 and result.errors == 0:
|
||||||
|
logger.info(
|
||||||
|
"[Quality Upgrade] All tracks already satisfy the configured targets. "
|
||||||
|
"If you expected upgrades, check your quality profile — the current "
|
||||||
|
"top target is: %s",
|
||||||
|
targets[0].label if targets else '(none)',
|
||||||
|
)
|
||||||
return result
|
return result
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue