enable quality pref.
This commit is contained in:
parent
2ad0dddba4
commit
ed91099a1c
11 changed files with 166 additions and 20 deletions
BIN
config/__pycache__/settings.cpython-310.pyc
Normal file
BIN
config/__pycache__/settings.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
|
|
@ -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()
|
||||
Binary file not shown.
|
|
@ -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:
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -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…
Reference in a new issue