utilize quality profile over preferred.

This commit is contained in:
Broque Thomas 2025-11-17 14:11:14 -08:00
parent 14cfbd01f4
commit a3f98f29d3
10 changed files with 166 additions and 345 deletions

View file

@ -36,9 +36,6 @@
"path": "logs/app.log", "path": "logs/app.log",
"level": "INFO" "level": "INFO"
}, },
"settings": {
"audio_quality": "flac"
},
"database": { "database": {
"path": "database/music_library.db", "path": "database/music_library.db",
"max_workers": 5 "max_workers": 5

View file

@ -143,8 +143,4 @@ class ConfigManager:
return validation return validation
def get_quality_preference(self) -> str:
"""Get the user's preferred audio quality setting"""
return self.get('settings.audio_quality', 'flac')
config_manager = ConfigManager() config_manager = ConfigManager()

View file

@ -1246,15 +1246,15 @@ class SoulseekClient:
logger.error(f"Error during search history buffer maintenance: {e}") logger.error(f"Error during search history buffer maintenance: {e}")
return False return False
async def search_and_download_best(self, query: str, preferred_quality: str = 'flac') -> Optional[str]: async def search_and_download_best(self, query: str) -> Optional[str]:
results = await self.search(query) results = await self.search(query)
if not results: if not results:
logger.warning(f"No results found for: {query}") logger.warning(f"No results found for: {query}")
return None return None
# Use the new quality filtering # Use quality profile filtering
filtered_results = self.filter_results_by_quality_preference(results, preferred_quality) filtered_results = self.filter_results_by_quality_preference(results)
if not filtered_results: if not filtered_results:
logger.warning(f"No suitable quality results found for: {query}") logger.warning(f"No suitable quality results found for: {query}")
@ -1280,73 +1280,131 @@ class SoulseekClient:
logger.debug(f"Connection check failed: {e}") logger.debug(f"Connection check failed: {e}")
return False return False
def filter_results_by_quality_preference(self, results: List[TrackResult], preferred_quality: str) -> List[TrackResult]: def filter_results_by_quality_preference(self, results: List[TrackResult]) -> List[TrackResult]:
""" """
Filter and sort results by quality preference with smart fallback. Filter candidates based on user's quality profile with file size constraints.
Prefers exact match, then higher quality, then lower quality. Uses priority waterfall logic: tries highest priority quality first, falls back to lower priorities.
Returns candidates matching quality profile constraints, sorted by confidence and size.
""" """
from database.music_database import MusicDatabase
if not results: if not results:
return [] return []
# Normalize preference to match our quality strings # Get quality profile from database
quality_map = { db = MusicDatabase()
'flac': 'flac', profile = db.get_quality_profile()
'mp3_320': ('mp3', 320),
'mp3_256': ('mp3', 256), logger.debug(f"Quality Filter: Using profile preset '{profile.get('preset', 'custom')}', filtering {len(results)} candidates")
'mp3_192': ('mp3', 192),
'any': 'any' # Categorize candidates by quality with file size constraints
quality_buckets = {
'flac': [],
'mp3_320': [],
'mp3_256': [],
'mp3_192': [],
'other': []
} }
if preferred_quality not in quality_map: # Track all candidates that pass size checks (for fallback)
return results # Return all if unknown preference size_filtered_all = []
if preferred_quality == 'any': for candidate in results:
# Sort by quality score for "any" preference if not candidate.quality:
return sorted(results, key=lambda x: x.quality_score, reverse=True) quality_buckets['other'].append(candidate)
continue
# Separate results by quality categories track_format = candidate.quality.lower()
exact_matches = [] track_bitrate = candidate.bitrate or 0
higher_quality = [] file_size_mb = candidate.size / (1024 * 1024) # Convert bytes to MB
lower_quality = []
if preferred_quality == 'flac': # Categorize and apply file size constraints
for result in results: if track_format == 'flac':
if result.quality.lower() == 'flac': quality_config = profile['qualities'].get('flac', {})
exact_matches.append(result) min_mb = quality_config.get('min_mb', 0)
elif result.quality.lower() == 'mp3' and result.bitrate and result.bitrate >= 320: max_mb = quality_config.get('max_mb', 999)
higher_quality.append(result) # High-quality MP3 as fallback
# Check if within size range
if min_mb <= file_size_mb <= max_mb:
# Add to bucket if enabled
if quality_config.get('enabled', False):
quality_buckets['flac'].append(candidate)
# Always track for fallback
size_filtered_all.append(candidate)
else: else:
lower_quality.append(result) logger.debug(f"Quality Filter: FLAC file rejected - {file_size_mb:.1f}MB outside range {min_mb}-{max_mb}MB")
elif track_format == 'mp3':
# Determine MP3 quality tier based on bitrate
if track_bitrate >= 320:
quality_key = 'mp3_320'
elif track_bitrate >= 256:
quality_key = 'mp3_256'
elif track_bitrate >= 192:
quality_key = 'mp3_192'
else:
quality_buckets['other'].append(candidate)
continue
quality_config = profile['qualities'].get(quality_key, {})
min_mb = quality_config.get('min_mb', 0)
max_mb = quality_config.get('max_mb', 999)
# Check if within size range
if min_mb <= file_size_mb <= max_mb:
# Add to bucket if enabled
if quality_config.get('enabled', False):
quality_buckets[quality_key].append(candidate)
# Always track for fallback
size_filtered_all.append(candidate)
else:
logger.debug(f"Quality Filter: {quality_key.upper()} file rejected - {file_size_mb:.1f}MB outside range {min_mb}-{max_mb}MB")
else:
quality_buckets['other'].append(candidate)
# Sort each bucket by quality score and size
for bucket in quality_buckets.values():
bucket.sort(key=lambda x: (x.quality_score, x.size), reverse=True)
# Debug logging
for quality, bucket in quality_buckets.items():
if bucket:
logger.debug(f"Quality Filter: Found {len(bucket)} '{quality}' candidates (after size filtering)")
# Waterfall priority logic: try qualities in priority order
# Build priority list from enabled qualities
quality_priorities = []
for quality_name, quality_config in profile['qualities'].items():
if quality_config.get('enabled', False):
priority = quality_config.get('priority', 999)
quality_priorities.append((priority, quality_name))
# Sort by priority (lower number = higher priority)
quality_priorities.sort()
# Try each quality in priority order
for priority, quality_name in quality_priorities:
candidates_for_quality = quality_buckets.get(quality_name, [])
if candidates_for_quality:
logger.info(f"Quality Filter: Returning {len(candidates_for_quality)} '{quality_name}' candidates (priority {priority})")
return candidates_for_quality
# If no enabled qualities matched, check if fallback is enabled
if profile.get('fallback_enabled', True):
logger.warning(f"Quality Filter: No enabled qualities matched, falling back to size-filtered candidates")
# Return candidates that passed size checks (even if quality disabled)
# This respects file size constraints while allowing any quality
if size_filtered_all:
size_filtered_all.sort(key=lambda x: (x.quality_score, x.size), reverse=True)
logger.info(f"Quality Filter: Returning {len(size_filtered_all)} fallback candidates (size-filtered, any quality)")
return size_filtered_all
else:
# All candidates failed size checks - respect user's constraints and fail
logger.warning(f"Quality Filter: All candidates failed size checks, returning empty (respecting size constraints)")
return []
else: else:
# MP3 preference with specific bitrate logger.warning(f"Quality Filter: No enabled qualities matched and fallback is disabled, returning empty")
pref_format, pref_bitrate = quality_map[preferred_quality] return []
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]]: async def get_session_info(self) -> Optional[Dict[str, Any]]:
"""Get slskd session information including version""" """Get slskd session information including version"""

View file

@ -3039,21 +3039,17 @@ class DownloadMissingAlbumTracksModal(QDialog):
print(f"❌ Artist '{spotify_artist_name}' NOT found in path: '{slskd_full_path}'. Discarding candidate.") print(f"❌ Artist '{spotify_artist_name}' NOT found in path: '{slskd_full_path}'. Discarding candidate.")
if verified_candidates: if verified_candidates:
# Apply quality preference filtering before returning # Apply quality profile 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'): if hasattr(self.parent_artists_page, 'soulseek_client'):
quality_filtered = self.parent_artists_page.soulseek_client.filter_results_by_quality_preference( quality_filtered = self.parent_artists_page.soulseek_client.filter_results_by_quality_preference(
verified_candidates, quality_preference verified_candidates
) )
if quality_filtered: if quality_filtered:
verified_candidates = quality_filtered verified_candidates = quality_filtered
print(f"🎯 Applied quality filtering ({quality_preference}): {len(verified_candidates)} candidates remain") print(f"🎯 Applied quality profile filtering: {len(verified_candidates)} candidates remain")
else: else:
print(f"⚠️ Quality filtering ({quality_preference}) removed all candidates, keeping originals") print(f"⚠️ Quality profile filtering removed all candidates, keeping originals")
best_confidence = verified_candidates[0].confidence best_confidence = verified_candidates[0].confidence
best_version = getattr(verified_candidates[0], 'version_type', 'unknown') best_version = getattr(verified_candidates[0], 'version_type', 'unknown')

View file

@ -1324,10 +1324,6 @@ class SimpleWishlistDownloadWorker(QRunnable):
# Update status: Starting search # Update status: Starting search
self.signals.status_updated.emit(self.download_index, "🔍 Searching...") self.signals.status_updated.emit(self.download_index, "🔍 Searching...")
# Get quality preference
from config.settings import config_manager
quality_preference = config_manager.get_quality_preference()
# Use async method in sync context # Use async method in sync context
loop = asyncio.new_event_loop() loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop) asyncio.set_event_loop(loop)
@ -1338,7 +1334,7 @@ class SimpleWishlistDownloadWorker(QRunnable):
# Use the enhanced search method that provides more feedback # Use the enhanced search method that provides more feedback
results = loop.run_until_complete( results = loop.run_until_complete(
self._search_with_progress(self.query, quality_preference) self._search_with_progress(self.query)
) )
if results and len(results) > 0: if results and len(results) > 0:
@ -1369,7 +1365,7 @@ class SimpleWishlistDownloadWorker(QRunnable):
except Exception as e: except Exception as e:
self.signals.download_failed.emit(self.download_index, str(e)) self.signals.download_failed.emit(self.download_index, str(e))
async def _search_with_progress(self, query, quality_preference): async def _search_with_progress(self, query):
"""Search for tracks with progress updates""" """Search for tracks with progress updates"""
try: try:
# Emit search progress # Emit search progress
@ -1381,7 +1377,7 @@ class SimpleWishlistDownloadWorker(QRunnable):
# This is a simplified version - in practice you'd want to: # This is a simplified version - in practice you'd want to:
# 1. Search for candidates # 1. Search for candidates
# 2. Filter by quality # 2. Filter by quality profile
# 3. Return the results for manual download # 3. Return the results for manual download
# For now, let's use a direct approach # For now, let's use a direct approach
@ -1390,10 +1386,8 @@ class SimpleWishlistDownloadWorker(QRunnable):
results = await self.soulseek_client.search_tracks(query) results = await self.soulseek_client.search_tracks(query)
if results: if results:
# Filter by quality preference if needed # Filter by quality profile
filtered_results = self.soulseek_client.filter_results_by_quality_preference( filtered_results = self.soulseek_client.filter_results_by_quality_preference(results)
results, quality_preference
)
return filtered_results return filtered_results
return [] return []
@ -4149,10 +4143,6 @@ class AutoWishlistProcessorWorker(QRunnable):
def run(self): def run(self):
"""Run automatic wishlist processing""" """Run automatic wishlist processing"""
try: try:
# Get quality preference
from config.settings import config_manager
quality_preference = config_manager.get_quality_preference()
# Get all wishlist tracks (no limit - process everything) # Get all wishlist tracks (no limit - process everything)
wishlist_tracks = self.wishlist_service.get_wishlist_tracks_for_download() wishlist_tracks = self.wishlist_service.get_wishlist_tracks_for_download()
@ -4188,7 +4178,7 @@ class AutoWishlistProcessorWorker(QRunnable):
try: try:
download_id = loop.run_until_complete( download_id = loop.run_until_complete(
self.soulseek_client.search_and_download_best(query, quality_preference) self.soulseek_client.search_and_download_best(query)
) )
track_id = track_data.get('spotify_track_id') track_id = track_data.get('spotify_track_id')

View file

@ -1259,22 +1259,6 @@ class SettingsPage(QWidget):
if hasattr(self, 'log_path_display'): if hasattr(self, 'log_path_display'):
self.log_path_display.setText(logging_config.get('path', 'logs/app.log')) 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)
# Load metadata enhancement settings # Load metadata enhancement settings
metadata_config = config_manager.get('metadata_enhancement', {}) metadata_config = config_manager.get('metadata_enhancement', {})
if hasattr(self, 'metadata_enabled_checkbox'): if hasattr(self, 'metadata_enabled_checkbox'):
@ -1330,20 +1314,6 @@ class SettingsPage(QWidget):
max_workers = int(self.max_workers_combo.currentText()) max_workers = int(self.max_workers_combo.currentText())
config_manager.set('database.max_workers', max_workers) 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 # 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.download_path', self.download_path_input.text())
self.settings_changed.emit('soulseek.transfer_path', self.transfer_path_input.text()) self.settings_changed.emit('soulseek.transfer_path', self.transfer_path_input.text())
@ -2722,21 +2692,6 @@ class SettingsPage(QWidget):
download_layout.setContentsMargins(16, 20, 16, 16) download_layout.setContentsMargins(16, 20, 16, 16)
download_layout.setSpacing(12) download_layout.setSpacing(12)
# Quality preference
quality_layout = QHBoxLayout()
quality_label = QLabel("Preferred Quality:")
quality_label.setStyleSheet(self.get_label_style(12))
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(self.quality_combo)
# Download path # Download path
path_container = QVBoxLayout() path_container = QVBoxLayout()
path_label = QLabel("Slskd Download Dir:") path_label = QLabel("Slskd Download Dir:")
@ -2777,7 +2732,6 @@ class SettingsPage(QWidget):
transfer_input_layout.addWidget(transfer_browse_btn) transfer_input_layout.addWidget(transfer_browse_btn)
transfer_path_container.addLayout(transfer_input_layout) transfer_path_container.addLayout(transfer_input_layout)
download_layout.addLayout(quality_layout)
download_layout.addLayout(path_container) download_layout.addLayout(path_container)
download_layout.addLayout(transfer_path_container) download_layout.addLayout(transfer_path_container)

View file

@ -3069,39 +3069,8 @@ class SyncOptionsPanel(QFrame):
} }
""") """)
# Quality selection
quality_layout = QHBoxLayout()
quality_label = QLabel("Preferred Quality:")
quality_label.setStyleSheet("color: #b3b3b3; font-size: 11px;")
self.quality_combo = QComboBox()
self.quality_combo.addItems(["FLAC", "320 kbps MP3", "256 kbps MP3", "Any"])
self.quality_combo.setCurrentText("FLAC")
self.quality_combo.setStyleSheet("""
QComboBox {
background: #404040;
border: 1px solid #606060;
border-radius: 4px;
padding: 5px;
color: #ffffff;
font-size: 11px;
}
QComboBox::drop-down {
border: none;
}
QComboBox::down-arrow {
image: none;
border: none;
}
""")
quality_layout.addWidget(quality_label)
quality_layout.addWidget(self.quality_combo)
quality_layout.addStretch()
layout.addWidget(title_label) layout.addWidget(title_label)
layout.addWidget(self.download_missing) layout.addWidget(self.download_missing)
layout.addLayout(quality_layout)
class SyncPage(QWidget): class SyncPage(QWidget):
# Signals for dashboard activity tracking # Signals for dashboard activity tracking
@ -8935,21 +8904,17 @@ class DownloadMissingTracksModal(QDialog):
print(f"❌ Artist '{spotify_artist_name}' NOT found in path: '{slskd_full_path}'. Discarding candidate.") print(f"❌ Artist '{spotify_artist_name}' NOT found in path: '{slskd_full_path}'. Discarding candidate.")
if verified_candidates: if verified_candidates:
# Apply quality preference filtering before returning # Apply quality profile 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'): if hasattr(self.parent_page, 'soulseek_client'):
quality_filtered = self.parent_page.soulseek_client.filter_results_by_quality_preference( quality_filtered = self.parent_page.soulseek_client.filter_results_by_quality_preference(
verified_candidates, quality_preference verified_candidates
) )
if quality_filtered: if quality_filtered:
verified_candidates = quality_filtered verified_candidates = quality_filtered
print(f"🎯 Applied quality filtering ({quality_preference}): {len(verified_candidates)} candidates remain") print(f"🎯 Applied quality profile filtering: {len(verified_candidates)} candidates remain")
else: else:
print(f"⚠️ Quality filtering ({quality_preference}) removed all candidates, keeping originals") print(f"⚠️ Quality profile filtering removed all candidates, keeping originals")
best_confidence = verified_candidates[0].confidence best_confidence = verified_candidates[0].confidence
best_version = getattr(verified_candidates[0], 'version_type', 'unknown') best_version = getattr(verified_candidates[0], 'version_type', 'unknown')

View file

@ -8728,129 +8728,6 @@ def stop_duplicate_cleaner():
# == DOWNLOAD MISSING TRACKS == # == DOWNLOAD MISSING TRACKS ==
# =============================== # ===============================
def _filter_candidates_by_quality_preference(candidates):
"""
Filter candidates based on user's quality profile with file size constraints.
Uses priority waterfall logic: tries highest priority quality first, falls back to lower priorities.
Returns candidates matching quality profile constraints, sorted by confidence and size.
"""
from database.music_database import MusicDatabase
# Get quality profile from database
db = MusicDatabase()
profile = db.get_quality_profile()
print(f"🎵 [Quality Filter] Using profile preset: '{profile.get('preset', 'custom')}', filtering {len(candidates)} candidates")
# Categorize candidates by quality with file size constraints
quality_buckets = {
'flac': [],
'mp3_320': [],
'mp3_256': [],
'mp3_192': [],
'other': []
}
# Track all candidates that pass size checks (for fallback)
size_filtered_all = []
for candidate in candidates:
if not candidate.quality:
quality_buckets['other'].append(candidate)
continue
track_format = candidate.quality.lower()
track_bitrate = candidate.bitrate or 0
file_size_mb = candidate.size / (1024 * 1024) # Convert bytes to MB
# Categorize and apply file size constraints
if track_format == 'flac':
quality_config = profile['qualities'].get('flac', {})
min_mb = quality_config.get('min_mb', 0)
max_mb = quality_config.get('max_mb', 999)
# Check if within size range
if min_mb <= file_size_mb <= max_mb:
# Add to bucket if enabled
if quality_config.get('enabled', False):
quality_buckets['flac'].append(candidate)
# Always track for fallback
size_filtered_all.append(candidate)
else:
print(f"🎵 [Quality Filter] FLAC file rejected: {file_size_mb:.1f}MB outside range {min_mb}-{max_mb}MB")
elif track_format == 'mp3':
# Determine MP3 quality tier based on bitrate
if track_bitrate >= 320:
quality_key = 'mp3_320'
elif track_bitrate >= 256:
quality_key = 'mp3_256'
elif track_bitrate >= 192:
quality_key = 'mp3_192'
else:
quality_buckets['other'].append(candidate)
continue
quality_config = profile['qualities'].get(quality_key, {})
min_mb = quality_config.get('min_mb', 0)
max_mb = quality_config.get('max_mb', 999)
# Check if within size range
if min_mb <= file_size_mb <= max_mb:
# Add to bucket if enabled
if quality_config.get('enabled', False):
quality_buckets[quality_key].append(candidate)
# Always track for fallback
size_filtered_all.append(candidate)
else:
print(f"🎵 [Quality Filter] {quality_key.upper()} file rejected: {file_size_mb:.1f}MB outside range {min_mb}-{max_mb}MB")
else:
quality_buckets['other'].append(candidate)
# Sort each bucket by quality score and size
for bucket in quality_buckets.values():
bucket.sort(key=lambda x: (x.quality_score, x.size), reverse=True)
# Debug logging
for quality, bucket in quality_buckets.items():
if bucket:
print(f"🎵 [Quality Filter] Found {len(bucket)} '{quality}' candidates (after size filtering)")
# Waterfall priority logic: try qualities in priority order
# Build priority list from enabled qualities
quality_priorities = []
for quality_name, quality_config in profile['qualities'].items():
if quality_config.get('enabled', False):
priority = quality_config.get('priority', 999)
quality_priorities.append((priority, quality_name))
# Sort by priority (lower number = higher priority)
quality_priorities.sort()
# Try each quality in priority order
for priority, quality_name in quality_priorities:
candidates_for_quality = quality_buckets.get(quality_name, [])
if candidates_for_quality:
print(f"🎯 [Quality Filter] Returning {len(candidates_for_quality)} '{quality_name}' candidates (priority {priority})")
return candidates_for_quality
# If no enabled qualities matched, check if fallback is enabled
if profile.get('fallback_enabled', True):
print(f"⚠️ [Quality Filter] No enabled qualities matched, falling back to size-filtered candidates")
# Return candidates that passed size checks (even if quality disabled)
# This respects file size constraints while allowing any quality
if size_filtered_all:
size_filtered_all.sort(key=lambda x: (x.quality_score, x.size), reverse=True)
print(f"🎯 [Quality Filter] Returning {len(size_filtered_all)} fallback candidates (size-filtered, any quality)")
return size_filtered_all
else:
# All candidates failed size checks - respect user's constraints and fail
print(f"❌ [Quality Filter] All candidates failed size checks, returning empty (respecting size constraints)")
return []
else:
print(f"❌ [Quality Filter] No enabled qualities matched and fallback is disabled, returning empty")
return []
def get_valid_candidates(results, spotify_track, query): def get_valid_candidates(results, spotify_track, query):
""" """
This function is a direct port from sync.py. It scores and filters This function is a direct port from sync.py. It scores and filters
@ -8864,10 +8741,13 @@ def get_valid_candidates(results, spotify_track, query):
if not initial_candidates: if not initial_candidates:
return [] return []
# Filter by user's quality preference before artist verification # Filter by user's quality profile before artist verification
quality_filtered_candidates = _filter_candidates_by_quality_preference(initial_candidates) # Use shared soulseek_client method for consistency
from core.soulseek_client import SoulseekClient
temp_client = SoulseekClient()
quality_filtered_candidates = temp_client.filter_results_by_quality_preference(initial_candidates)
if not quality_filtered_candidates: if not quality_filtered_candidates:
# If no candidates match preference, fall back to all candidates # If no candidates match profile, fall back to all candidates
quality_filtered_candidates = initial_candidates quality_filtered_candidates = initial_candidates
verified_candidates = [] verified_candidates = []

View file

@ -2270,17 +2270,6 @@
<div class="settings-group"> <div class="settings-group">
<h3>Download Settings</h3> <h3>Download Settings</h3>
<div class="form-group">
<label>Preferred Quality:</label>
<select id="preferred-quality">
<option value="flac">FLAC</option>
<option value="mp3_320">320 kbps MP3</option>
<option value="mp3_256">256 kbps MP3</option>
<option value="mp3_192">192 kbps MP3</option>
<option value="any">Any</option>
</select>
</div>
<div class="form-group"> <div class="form-group">
<label>Slskd Download Dir:</label> <label>Slskd Download Dir:</label>
<div class="path-input-group"> <div class="path-input-group">

View file

@ -1468,7 +1468,6 @@ async function loadSettingsData() {
document.getElementById('soulseek-api-key').value = settings.soulseek?.api_key || ''; document.getElementById('soulseek-api-key').value = settings.soulseek?.api_key || '';
// Populate Download settings (right column) // Populate Download settings (right column)
document.getElementById('preferred-quality').value = settings.settings?.audio_quality || 'flac';
document.getElementById('download-path').value = settings.soulseek?.download_path || './downloads'; document.getElementById('download-path').value = settings.soulseek?.download_path || './downloads';
document.getElementById('transfer-path').value = settings.soulseek?.transfer_path || './Transfer'; document.getElementById('transfer-path').value = settings.soulseek?.transfer_path || './Transfer';
@ -1776,9 +1775,6 @@ async function saveSettings() {
download_path: document.getElementById('download-path').value, download_path: document.getElementById('download-path').value,
transfer_path: document.getElementById('transfer-path').value transfer_path: document.getElementById('transfer-path').value
}, },
settings: {
audio_quality: document.getElementById('preferred-quality').value
},
database: { database: {
max_workers: parseInt(document.getElementById('max-workers').value) max_workers: parseInt(document.getElementById('max-workers').value)
}, },