enable quality pref.

pull/2/head
Broque Thomas 7 months ago
parent 2ad0dddba4
commit ed91099a1c

@ -95,5 +95,9 @@ class ConfigManager:
'plex': bool(self.get('plex.base_url')) and bool(self.get('plex.token')),
'soulseek': bool(self.get('soulseek.slskd_url'))
}
def get_quality_preference(self) -> str:
"""Get the user's preferred audio quality setting"""
return self.get('settings.audio_quality', 'flac')
config_manager = ConfigManager()

@ -1137,15 +1137,19 @@ class SoulseekClient:
logger.warning(f"No results found for: {query}")
return None
preferred_results = [r for r in results if r.quality.lower() == preferred_quality.lower()]
# Use the new quality filtering
filtered_results = self.filter_results_by_quality_preference(results, preferred_quality)
if preferred_results:
best_result = preferred_results[0]
else:
best_result = results[0]
logger.info(f"Preferred quality {preferred_quality} not found, using {best_result.quality}")
logger.info(f"Downloading: {best_result.filename} ({best_result.quality}) from {best_result.username}")
if not filtered_results:
logger.warning(f"No suitable quality results found for: {query}")
return None
best_result = filtered_results[0]
quality_info = f"{best_result.quality.upper()}"
if best_result.bitrate:
quality_info += f" {best_result.bitrate}kbps"
logger.info(f"Downloading: {best_result.filename} ({quality_info}) from {best_result.username}")
return await self.download(best_result.username, best_result.filename, best_result.size)
async def check_connection(self) -> bool:
@ -1160,6 +1164,74 @@ class SoulseekClient:
logger.debug(f"Connection check failed: {e}")
return False
def filter_results_by_quality_preference(self, results: List[TrackResult], preferred_quality: str) -> List[TrackResult]:
"""
Filter and sort results by quality preference with smart fallback.
Prefers exact match, then higher quality, then lower quality.
"""
if not results:
return []
# Normalize preference to match our quality strings
quality_map = {
'flac': 'flac',
'mp3_320': ('mp3', 320),
'mp3_256': ('mp3', 256),
'mp3_192': ('mp3', 192),
'any': 'any'
}
if preferred_quality not in quality_map:
return results # Return all if unknown preference
if preferred_quality == 'any':
# Sort by quality score for "any" preference
return sorted(results, key=lambda x: x.quality_score, reverse=True)
# Separate results by quality categories
exact_matches = []
higher_quality = []
lower_quality = []
if preferred_quality == 'flac':
for result in results:
if result.quality.lower() == 'flac':
exact_matches.append(result)
elif result.quality.lower() == 'mp3' and result.bitrate and result.bitrate >= 320:
higher_quality.append(result) # High-quality MP3 as fallback
else:
lower_quality.append(result)
else:
# MP3 preference with specific bitrate
pref_format, pref_bitrate = quality_map[preferred_quality]
for result in results:
if result.quality.lower() == 'flac':
higher_quality.append(result) # FLAC is always higher quality
elif result.quality.lower() == pref_format:
if result.bitrate:
if result.bitrate == pref_bitrate:
exact_matches.append(result)
elif result.bitrate > pref_bitrate:
higher_quality.append(result)
else:
lower_quality.append(result)
else:
exact_matches.append(result) # Unknown bitrate, assume match
else:
lower_quality.append(result)
# Sort each category by quality score and upload speed
def sort_key(result):
return (result.quality_score, result.upload_speed)
exact_matches.sort(key=sort_key, reverse=True)
higher_quality.sort(key=sort_key, reverse=True)
lower_quality.sort(key=sort_key, reverse=True)
# Return in preference order: exact > higher > lower
return exact_matches + higher_quality + lower_quality
async def get_session_info(self) -> Optional[Dict[str, Any]]:
"""Get slskd session information including version"""
if not self.base_url:

@ -2714,9 +2714,26 @@ class DownloadMissingAlbumTracksModal(QDialog):
print(f"❌ Artist '{spotify_artist_name}' NOT found in path: '{slskd_full_path}'. Discarding candidate.")
if verified_candidates:
# Apply quality preference filtering before returning
from config.settings import config_manager
quality_preference = config_manager.get_quality_preference()
# Filter candidates by quality preference with smart fallback
if hasattr(self.parent_artists_page, 'soulseek_client'):
quality_filtered = self.parent_artists_page.soulseek_client.filter_results_by_quality_preference(
verified_candidates, quality_preference
)
if quality_filtered:
verified_candidates = quality_filtered
print(f"🎯 Applied quality filtering ({quality_preference}): {len(verified_candidates)} candidates remain")
else:
print(f"⚠️ Quality filtering ({quality_preference}) removed all candidates, keeping originals")
best_confidence = verified_candidates[0].confidence
best_version = getattr(verified_candidates[0], 'version_type', 'unknown')
print(f"✅ Found {len(verified_candidates)} VERIFIED matches for '{spotify_track.name}'. Best: {best_confidence:.2f} ({best_version})")
best_quality = getattr(verified_candidates[0], 'quality', 'unknown')
print(f"✅ Found {len(verified_candidates)} VERIFIED matches for '{spotify_track.name}'. Best: {best_confidence:.2f} ({best_version}, {best_quality.upper()})")
# Log version breakdown for debugging
version_counts = {}
@ -2724,7 +2741,9 @@ class DownloadMissingAlbumTracksModal(QDialog):
version = getattr(candidate, 'version_type', 'unknown')
version_counts[version] = version_counts.get(version, 0) + 1
penalty = getattr(candidate, 'version_penalty', 0.0)
print(f" 🎵 {candidate.confidence:.2f} - {version} (penalty: {penalty:.2f}) - {candidate.filename[:100]}...")
quality = getattr(candidate, 'quality', 'unknown')
bitrate_info = f" {candidate.bitrate}kbps" if hasattr(candidate, 'bitrate') and candidate.bitrate else ""
print(f" 🎵 {candidate.confidence:.2f} - {version} ({quality.upper()}{bitrate_info}) (penalty: {penalty:.2f}) - {candidate.filename[:100]}...")
else:
print(f"⚠️ No verified matches found for '{spotify_track.name}' after checking file paths.")

@ -579,6 +579,22 @@ class SettingsPage(QWidget):
if hasattr(self, 'log_path_display'):
self.log_path_display.setText(logging_config.get('path', 'logs/app.log'))
# Load quality preference
if hasattr(self, 'quality_combo'):
audio_quality = config_manager.get('settings.audio_quality', 'FLAC')
# Map config values to combo box text
quality_mapping = {
'flac': 'FLAC',
'mp3_320': '320 kbps MP3',
'mp3_256': '256 kbps MP3',
'mp3_192': '192 kbps MP3',
'any': 'Any'
}
display_quality = quality_mapping.get(audio_quality.lower(), audio_quality)
index = self.quality_combo.findText(display_quality)
if index >= 0:
self.quality_combo.setCurrentIndex(index)
except Exception as e:
QMessageBox.warning(self, "Error", f"Failed to load configuration: {e}")
@ -604,6 +620,20 @@ class SettingsPage(QWidget):
max_workers = int(self.max_workers_combo.currentText())
config_manager.set('database.max_workers', max_workers)
# Save Quality preference
if hasattr(self, 'quality_combo'):
quality_text = self.quality_combo.currentText()
# Map combo box text to config values
config_mapping = {
'FLAC': 'flac',
'320 kbps MP3': 'mp3_320',
'256 kbps MP3': 'mp3_256',
'192 kbps MP3': 'mp3_192',
'Any': 'any'
}
config_value = config_mapping.get(quality_text, 'flac')
config_manager.set('settings.audio_quality', config_value)
# Emit signals for path changes to update other pages immediately
self.settings_changed.emit('soulseek.download_path', self.download_path_input.text())
self.settings_changed.emit('soulseek.transfer_path', self.transfer_path_input.text())
@ -1218,14 +1248,15 @@ class SettingsPage(QWidget):
quality_label = QLabel("Preferred Quality:")
quality_label.setStyleSheet("color: #ffffff; font-size: 12px;")
quality_combo = QComboBox()
quality_combo.addItems(["FLAC", "320 kbps MP3", "256 kbps MP3", "192 kbps MP3", "Any"])
quality_combo.setCurrentText("FLAC")
quality_combo.setStyleSheet(self.get_combo_style())
quality_combo.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
self.quality_combo = QComboBox()
self.quality_combo.addItems(["FLAC", "320 kbps MP3", "256 kbps MP3", "192 kbps MP3", "Any"])
self.quality_combo.setCurrentText("FLAC")
self.quality_combo.setStyleSheet(self.get_combo_style())
self.quality_combo.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
self.form_inputs['settings.audio_quality'] = self.quality_combo
quality_layout.addWidget(quality_label)
quality_layout.addWidget(quality_combo)
quality_layout.addWidget(self.quality_combo)
# Download path
path_container = QVBoxLayout()

@ -291,12 +291,13 @@ class TrackDownloadWorkerSignals(QObject):
class TrackDownloadWorker(QRunnable):
"""Background worker to download individual tracks via Soulseek"""
def __init__(self, spotify_track, soulseek_client, download_index, track_index):
def __init__(self, spotify_track, soulseek_client, download_index, track_index, quality_preference=None):
super().__init__()
self.spotify_track = spotify_track
self.soulseek_client = soulseek_client
self.download_index = download_index
self.track_index = track_index
self.quality_preference = quality_preference or 'flac'
self.signals = TrackDownloadWorkerSignals()
self._cancelled = False
@ -337,7 +338,7 @@ class TrackDownloadWorker(QRunnable):
try:
download_id = loop.run_until_complete(
self.soulseek_client.search_and_download_best(query)
self.soulseek_client.search_and_download_best(query, self.quality_preference)
)
if download_id:
break # Success - stop trying other queries
@ -4628,15 +4629,34 @@ class DownloadMissingTracksModal(QDialog):
print(f"❌ Artist '{spotify_artist_name}' NOT found in path: '{slskd_full_path}'. Discarding candidate.")
if verified_candidates:
# Apply quality preference filtering before returning
from config.settings import config_manager
quality_preference = config_manager.get_quality_preference()
# Filter candidates by quality preference with smart fallback
if hasattr(self.parent_page, 'soulseek_client'):
quality_filtered = self.parent_page.soulseek_client.filter_results_by_quality_preference(
verified_candidates, quality_preference
)
if quality_filtered:
verified_candidates = quality_filtered
print(f"🎯 Applied quality filtering ({quality_preference}): {len(verified_candidates)} candidates remain")
else:
print(f"⚠️ Quality filtering ({quality_preference}) removed all candidates, keeping originals")
best_confidence = verified_candidates[0].confidence
best_version = getattr(verified_candidates[0], 'version_type', 'unknown')
print(f"✅ Found {len(verified_candidates)} VERIFIED matches for '{spotify_track.name}'. Best: {best_confidence:.2f} ({best_version})")
best_quality = getattr(verified_candidates[0], 'quality', 'unknown')
print(f"✅ Found {len(verified_candidates)} VERIFIED matches for '{spotify_track.name}'. Best: {best_confidence:.2f} ({best_version}, {best_quality.upper()})")
# Log version breakdown for debugging
for candidate in verified_candidates[:3]: # Show top 3
version = getattr(candidate, 'version_type', 'unknown')
penalty = getattr(candidate, 'version_penalty', 0.0)
print(f" 🎵 {candidate.confidence:.2f} - {version} (penalty: {penalty:.2f}) - {candidate.filename[:80]}...")
quality = getattr(candidate, 'quality', 'unknown')
bitrate_info = f" {candidate.bitrate}kbps" if hasattr(candidate, 'bitrate') and candidate.bitrate else ""
print(f" 🎵 {candidate.confidence:.2f} - {version} ({quality.upper()}{bitrate_info}) (penalty: {penalty:.2f}) - {candidate.filename[:80]}...")
else:
print(f"⚠️ No verified matches found for '{spotify_track.name}' after checking file paths.")

Loading…
Cancel
Save