Add per-source quality fallback toggle for streaming downloads (#187)
Each streaming source (Tidal, Qobuz, HiFi, Deezer) now has an "Allow quality fallback" checkbox in Settings. When disabled, the source only tries the exact quality selected — if unavailable, it skips and lets the orchestrator try the next source. Default is ON (current behavior).
This commit is contained in:
parent
e28aeccfd1
commit
89cfea0fe7
6 changed files with 56 additions and 13 deletions
|
|
@ -451,14 +451,18 @@ class DeezerDownloadClient:
|
||||||
# Determine quality and get media URL with fallback
|
# Determine quality and get media URL with fallback
|
||||||
media_url = None
|
media_url = None
|
||||||
actual_quality = None
|
actual_quality = None
|
||||||
quality_order = _QUALITY_ORDER.copy()
|
allow_fallback = config_manager.get('deezer_download.allow_fallback', True)
|
||||||
|
|
||||||
# Start from user's preferred quality
|
if allow_fallback:
|
||||||
try:
|
quality_order = _QUALITY_ORDER.copy()
|
||||||
pref_idx = quality_order.index(self._quality)
|
# Start from user's preferred quality
|
||||||
quality_order = quality_order[pref_idx:] + quality_order[:pref_idx]
|
try:
|
||||||
except ValueError:
|
pref_idx = quality_order.index(self._quality)
|
||||||
pass
|
quality_order = quality_order[pref_idx:] + quality_order[:pref_idx]
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
quality_order = [self._quality]
|
||||||
|
|
||||||
for q in quality_order:
|
for q in quality_order:
|
||||||
url = self._get_media_url(track_token, q)
|
url = self._get_media_url(track_token, q)
|
||||||
|
|
|
||||||
|
|
@ -568,7 +568,8 @@ class HiFiClient:
|
||||||
quality_key = config_manager.get('hifi_download.quality', 'lossless')
|
quality_key = config_manager.get('hifi_download.quality', 'lossless')
|
||||||
chain = ['hires', 'lossless', 'high', 'low']
|
chain = ['hires', 'lossless', 'high', 'low']
|
||||||
start = chain.index(quality_key) if quality_key in chain else 1
|
start = chain.index(quality_key) if quality_key in chain else 1
|
||||||
chain = chain[start:]
|
allow_fallback = config_manager.get('hifi_download.allow_fallback', True)
|
||||||
|
chain = chain[start:] if allow_fallback else [quality_key]
|
||||||
|
|
||||||
MIN_AUDIO_SIZE = 100 * 1024 # 100KB
|
MIN_AUDIO_SIZE = 100 * 1024 # 100KB
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -894,7 +894,8 @@ class QobuzClient:
|
||||||
# Quality fallback chain: hires_max → hires → lossless → mp3
|
# Quality fallback chain: hires_max → hires → lossless → mp3
|
||||||
quality_chain = ['hires_max', 'hires', 'lossless', 'mp3']
|
quality_chain = ['hires_max', 'hires', 'lossless', 'mp3']
|
||||||
start_idx = quality_chain.index(quality_key) if quality_key in quality_chain else 2
|
start_idx = quality_chain.index(quality_key) if quality_key in quality_chain else 2
|
||||||
chain = quality_chain[start_idx:]
|
allow_fallback = config_manager.get('qobuz.allow_fallback', True)
|
||||||
|
chain = quality_chain[start_idx:] if allow_fallback else [quality_key]
|
||||||
|
|
||||||
stream_data = None
|
stream_data = None
|
||||||
actual_quality = None
|
actual_quality = None
|
||||||
|
|
|
||||||
|
|
@ -443,7 +443,8 @@ class TidalDownloadClient:
|
||||||
# files (stubs, empty HiRes responses) trigger a retry at the next tier.
|
# files (stubs, empty HiRes responses) trigger a retry at the next tier.
|
||||||
quality_chain = ['hires', 'lossless', 'high', 'low']
|
quality_chain = ['hires', 'lossless', 'high', 'low']
|
||||||
start_idx = quality_chain.index(quality_key) if quality_key in quality_chain else 1
|
start_idx = quality_chain.index(quality_key) if quality_key in quality_chain else 1
|
||||||
chain = quality_chain[start_idx:]
|
allow_fallback = config_manager.get('tidal_download.allow_fallback', True)
|
||||||
|
chain = quality_chain[start_idx:] if allow_fallback else [quality_key]
|
||||||
|
|
||||||
MIN_AUDIO_SIZE = 100 * 1024 # 100KB
|
MIN_AUDIO_SIZE = 100 * 1024 # 100KB
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4047,6 +4047,13 @@
|
||||||
<div class="setting-help-text">
|
<div class="setting-help-text">
|
||||||
Audio quality for Tidal downloads. HiRes requires a Tidal HiFi Plus subscription.
|
Audio quality for Tidal downloads. HiRes requires a Tidal HiFi Plus subscription.
|
||||||
</div>
|
</div>
|
||||||
|
<label class="checkbox-inline" style="margin-top: 8px;">
|
||||||
|
<input type="checkbox" id="tidal-allow-fallback" checked>
|
||||||
|
Allow quality fallback
|
||||||
|
</label>
|
||||||
|
<div class="setting-help-text">
|
||||||
|
When disabled, only downloads at the exact quality selected. If unavailable, skips to the next source.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Tidal Download Auth:</label>
|
<label>Tidal Download Auth:</label>
|
||||||
|
|
@ -4074,6 +4081,13 @@
|
||||||
<div class="setting-help-text">
|
<div class="setting-help-text">
|
||||||
Audio quality for Qobuz downloads. Hi-Res requires a Qobuz Studio or Sublime subscription.
|
Audio quality for Qobuz downloads. Hi-Res requires a Qobuz Studio or Sublime subscription.
|
||||||
</div>
|
</div>
|
||||||
|
<label class="checkbox-inline" style="margin-top: 8px;">
|
||||||
|
<input type="checkbox" id="qobuz-allow-fallback" checked>
|
||||||
|
Allow quality fallback
|
||||||
|
</label>
|
||||||
|
<div class="setting-help-text">
|
||||||
|
When disabled, only downloads at the exact quality selected. If unavailable, skips to the next source.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Qobuz Account:</label>
|
<label>Qobuz Account:</label>
|
||||||
|
|
@ -4116,6 +4130,13 @@
|
||||||
<div class="setting-help-text">
|
<div class="setting-help-text">
|
||||||
Audio quality for HiFi downloads. Uses public API instances — no account required.
|
Audio quality for HiFi downloads. Uses public API instances — no account required.
|
||||||
</div>
|
</div>
|
||||||
|
<label class="checkbox-inline" style="margin-top: 8px;">
|
||||||
|
<input type="checkbox" id="hifi-allow-fallback" checked>
|
||||||
|
Allow quality fallback
|
||||||
|
</label>
|
||||||
|
<div class="setting-help-text">
|
||||||
|
When disabled, only downloads at the exact quality selected. If unavailable, skips to the next source.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>HiFi Status:</label>
|
<label>HiFi Status:</label>
|
||||||
|
|
@ -4144,6 +4165,13 @@
|
||||||
Audio quality for Deezer downloads. FLAC requires a Deezer HiFi subscription.
|
Audio quality for Deezer downloads. FLAC requires a Deezer HiFi subscription.
|
||||||
MP3 320 requires Premium or higher.
|
MP3 320 requires Premium or higher.
|
||||||
</div>
|
</div>
|
||||||
|
<label class="checkbox-inline" style="margin-top: 8px;">
|
||||||
|
<input type="checkbox" id="deezer-allow-fallback" checked>
|
||||||
|
Allow quality fallback
|
||||||
|
</label>
|
||||||
|
<div class="setting-help-text">
|
||||||
|
When disabled, only downloads at the exact quality selected. If unavailable, skips to the next source.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Deezer ARL Token:</label>
|
<label>Deezer ARL Token:</label>
|
||||||
|
|
|
||||||
|
|
@ -5549,9 +5549,13 @@ async function loadSettingsData() {
|
||||||
document.getElementById('download-source-mode').value = settings.download_source?.mode || 'soulseek';
|
document.getElementById('download-source-mode').value = settings.download_source?.mode || 'soulseek';
|
||||||
loadHybridSourceOrder(settings);
|
loadHybridSourceOrder(settings);
|
||||||
document.getElementById('tidal-download-quality').value = settings.tidal_download?.quality || 'lossless';
|
document.getElementById('tidal-download-quality').value = settings.tidal_download?.quality || 'lossless';
|
||||||
|
document.getElementById('tidal-allow-fallback').checked = settings.tidal_download?.allow_fallback !== false;
|
||||||
document.getElementById('qobuz-quality').value = settings.qobuz?.quality || 'lossless';
|
document.getElementById('qobuz-quality').value = settings.qobuz?.quality || 'lossless';
|
||||||
|
document.getElementById('qobuz-allow-fallback').checked = settings.qobuz?.allow_fallback !== false;
|
||||||
document.getElementById('hifi-download-quality').value = settings.hifi_download?.quality || 'lossless';
|
document.getElementById('hifi-download-quality').value = settings.hifi_download?.quality || 'lossless';
|
||||||
|
document.getElementById('hifi-allow-fallback').checked = settings.hifi_download?.allow_fallback !== false;
|
||||||
document.getElementById('deezer-download-quality').value = settings.deezer_download?.quality || 'flac';
|
document.getElementById('deezer-download-quality').value = settings.deezer_download?.quality || 'flac';
|
||||||
|
document.getElementById('deezer-allow-fallback').checked = settings.deezer_download?.allow_fallback !== false;
|
||||||
document.getElementById('deezer-download-arl').value = settings.deezer_download?.arl || '';
|
document.getElementById('deezer-download-arl').value = settings.deezer_download?.arl || '';
|
||||||
|
|
||||||
// Populate YouTube settings
|
// Populate YouTube settings
|
||||||
|
|
@ -6549,19 +6553,23 @@ async function saveSettings(quiet = false) {
|
||||||
hybrid_order: getHybridOrder(),
|
hybrid_order: getHybridOrder(),
|
||||||
},
|
},
|
||||||
tidal_download: {
|
tidal_download: {
|
||||||
quality: document.getElementById('tidal-download-quality').value || 'lossless'
|
quality: document.getElementById('tidal-download-quality').value || 'lossless',
|
||||||
|
allow_fallback: document.getElementById('tidal-allow-fallback').checked,
|
||||||
},
|
},
|
||||||
hifi_download: {
|
hifi_download: {
|
||||||
quality: document.getElementById('hifi-download-quality').value || 'lossless'
|
quality: document.getElementById('hifi-download-quality').value || 'lossless',
|
||||||
|
allow_fallback: document.getElementById('hifi-allow-fallback').checked,
|
||||||
},
|
},
|
||||||
deezer_download: {
|
deezer_download: {
|
||||||
quality: document.getElementById('deezer-download-quality').value || 'flac',
|
quality: document.getElementById('deezer-download-quality').value || 'flac',
|
||||||
arl: document.getElementById('deezer-download-arl').value || '',
|
arl: document.getElementById('deezer-download-arl').value || '',
|
||||||
|
allow_fallback: document.getElementById('deezer-allow-fallback').checked,
|
||||||
},
|
},
|
||||||
qobuz: {
|
qobuz: {
|
||||||
quality: document.getElementById('qobuz-quality').value || 'lossless',
|
quality: document.getElementById('qobuz-quality').value || 'lossless',
|
||||||
embed_tags: document.getElementById('embed-qobuz').checked,
|
embed_tags: document.getElementById('embed-qobuz').checked,
|
||||||
tags: _collectServiceTags('qobuz')
|
tags: _collectServiceTags('qobuz'),
|
||||||
|
allow_fallback: document.getElementById('qobuz-allow-fallback').checked,
|
||||||
},
|
},
|
||||||
database: {
|
database: {
|
||||||
max_workers: parseInt(document.getElementById('max-workers').value)
|
max_workers: parseInt(document.getElementById('max-workers').value)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue