Files
swingmusic-extended/src/swingmusic/api/spotify_settings.py
T
Tomas Dvorak aad2f2d421 Add Spotify downloader and enhanced API features
- Add spotify_downloader service for track/album/playlist downloads
- Update Spotify API endpoints with enhanced functionality
- Fix pydub utils import issues
- Update GitHub workflows for improved CI/CD
2026-03-17 22:12:41 +01:00

372 lines
11 KiB
Python

"""
Spotify Downloader Settings API endpoints
"""
from flask import Blueprint, request, jsonify
from flask_openapi3 import APIBlueprint, Tag
from pydantic import BaseModel, Field
from typing import Optional, Dict, Any
from swingmusic import logger
from swingmusic.config import UserConfig
spotify_settings_bp = APIBlueprint(
'spotify_settings',
import_name='spotify_settings',
url_prefix='/api/settings/spotify'
)
class SpotifySettingsRequest(BaseModel):
defaultQuality: str = Field('flac', description='Default download quality')
downloadFolder: Optional[str] = Field(None, description='Download folder path')
autoAddToLibrary: bool = Field(True, description='Auto-add downloads to library')
maxConcurrentDownloads: int = Field(3, description='Max concurrent downloads')
sources: Optional[list] = Field(None, description='Download sources configuration')
maxRetryAttempts: int = Field(3, description='Max retry attempts')
cleanupHistoryDays: int = Field(30, description='Auto-cleanup history days')
showExplicitWarning: bool = Field(True, description='Show explicit content warning')
class SpotifySettingsResponse(BaseModel):
success: bool
settings: Optional[Dict[str, Any]] = None
message: Optional[str] = None
# Default settings
DEFAULT_SETTINGS = {
'defaultQuality': 'flac',
'downloadFolder': '',
'autoAddToLibrary': True,
'maxConcurrentDownloads': 3,
'sources': [
{
'name': 'tidal',
'display_name': 'Tidal',
'enabled': True,
'priority': 1,
'config': {
'quality_preference': ['lossless', 'high', 'normal'],
'formats': ['flac', 'mp3']
}
},
{
'name': 'qobuz',
'display_name': 'Qobuz',
'enabled': True,
'priority': 2,
'config': {
'quality_preference': ['lossless', 'high', 'normal'],
'formats': ['flac', 'mp3']
}
},
{
'name': 'amazon',
'display_name': 'Amazon Music',
'enabled': False,
'priority': 3,
'config': {
'quality_preference': ['high', 'normal'],
'formats': ['mp3', 'aac']
}
}
],
'maxRetryAttempts': 3,
'cleanupHistoryDays': 30,
'showExplicitWarning': True
}
def get_spotify_settings():
"""Get Spotify downloader settings from config"""
try:
config = UserConfig()
spotify_settings = config.spotify_downloads if hasattr(config, 'spotify_downloads') else {}
# Merge with defaults
settings = {**DEFAULT_SETTINGS}
settings.update(spotify_settings)
return settings
except Exception as e:
logger.error(f"Error loading Spotify settings: {e}")
return DEFAULT_SETTINGS
def save_spotify_settings(settings_data: dict):
"""Save Spotify downloader settings to config"""
try:
config = UserConfig()
# Update only provided settings
current_settings = get_spotify_settings()
current_settings.update(settings_data)
# Save to config
config.spotify_downloads = current_settings
config.save()
logger.info("Spotify settings saved successfully")
return True
except Exception as e:
logger.error(f"Error saving Spotify settings: {e}")
return False
@spotify_settings_bp.get('/', summary='Get Spotify downloader settings')
def get_settings():
"""
Get current Spotify downloader settings
Returns all Spotify downloader configuration including:
- Default quality settings
- Download folder configuration
- Source priorities and enablement
- Advanced options
"""
try:
settings = get_spotify_settings()
return jsonify({
'success': True,
'settings': settings
})
except Exception as e:
logger.error(f"Error getting Spotify settings: {e}")
return jsonify({
'success': False,
'message': str(e)
}), 500
@spotify_settings_bp.post('/', summary='Update Spotify downloader settings')
def update_settings(body: SpotifySettingsRequest):
"""
Update Spotify downloader settings
- **defaultQuality**: Default download quality (flac, mp3_320, mp3_128)
- **downloadFolder**: Custom download folder path
- **autoAddToLibrary**: Whether to auto-add downloads to library
- **maxConcurrentDownloads**: Maximum concurrent downloads (1-10)
- **sources**: Download sources configuration
- **maxRetryAttempts**: Maximum retry attempts for failed downloads
- **cleanupHistoryDays**: Days to keep download history (0 = disabled)
- **showExplicitWarning**: Show warning for explicit content
Updates the Spotify downloader configuration and saves to user settings.
"""
try:
# Validate inputs
if body.defaultQuality not in ['flac', 'mp3_320', 'mp3_128']:
return jsonify({
'success': False,
'message': 'Invalid quality setting'
}), 400
if not 1 <= body.maxConcurrentDownloads <= 10:
return jsonify({
'success': False,
'message': 'Max concurrent downloads must be between 1 and 10'
}), 400
if not 0 <= body.maxRetryAttempts <= 10:
return jsonify({
'success': False,
'message': 'Max retry attempts must be between 0 and 10'
}), 400
if not 0 <= body.cleanupHistoryDays <= 365:
return jsonify({
'success': False,
'message': 'Cleanup days must be between 0 and 365'
}), 400
# Prepare settings data
settings_data = {
'defaultQuality': body.defaultQuality,
'downloadFolder': body.downloadFolder,
'autoAddToLibrary': body.autoAddToLibrary,
'maxConcurrentDownloads': body.maxConcurrentDownloads,
'sources': body.sources,
'maxRetryAttempts': body.maxRetryAttempts,
'cleanupHistoryDays': body.cleanupHistoryDays,
'showExplicitWarning': body.showExplicitWarning
}
# Remove None values
settings_data = {k: v for k, v in settings_data.items() if v is not None}
# Save settings
if save_spotify_settings(settings_data):
return jsonify({
'success': True,
'message': 'Settings saved successfully'
})
else:
return jsonify({
'success': False,
'message': 'Failed to save settings'
}), 500
except Exception as e:
logger.error(f"Error updating Spotify settings: {e}")
return jsonify({
'success': False,
'message': str(e)
}), 500
@spotify_settings_bp.post('/reset', summary='Reset Spotify settings to defaults')
def reset_settings():
"""
Reset all Spotify downloader settings to default values
Resets all Spotify downloader configuration to factory defaults.
"""
try:
if save_spotify_settings(DEFAULT_SETTINGS):
return jsonify({
'success': True,
'message': 'Settings reset to defaults',
'settings': DEFAULT_SETTINGS
})
else:
return jsonify({
'success': False,
'message': 'Failed to reset settings'
}), 500
except Exception as e:
logger.error(f"Error resetting Spotify settings: {e}")
return jsonify({
'success': False,
'message': str(e)
}), 500
@spotify_settings_bp.delete('/queue', summary='Clear download queue')
def clear_queue():
"""
Clear the entire download queue
Removes all pending and active downloads from the queue.
"""
try:
from swingmusic.services.spotify_downloader import spotify_downloader
# Clear queue
spotify_downloader.download_queue.clear()
return jsonify({
'success': True,
'message': 'Download queue cleared'
})
except Exception as e:
logger.error(f"Error clearing download queue: {e}")
return jsonify({
'success': False,
'message': str(e)
}), 500
@spotify_settings_bp.delete('/history', summary='Clear download history')
def clear_history():
"""
Clear the download history
Removes all completed and failed downloads from history.
"""
try:
from swingmusic.services.spotify_downloader import spotify_downloader
# Clear history
spotify_downloader.download_history.clear()
return jsonify({
'success': True,
'message': 'Download history cleared'
})
except Exception as e:
logger.error(f"Error clearing download history: {e}")
return jsonify({
'success': False,
'message': str(e)
}), 500
@spotify_settings_bp.get('/sources', summary='Get available download sources')
def get_available_sources():
"""
Get list of available download sources
Returns information about supported download sources and their capabilities.
"""
try:
sources = [
{
'name': 'tidal',
'display_name': 'Tidal',
'description': 'High-quality FLAC downloads from Tidal',
'quality_options': ['lossless', 'high', 'normal'],
'formats': ['flac', 'mp3'],
'available': True,
'requires_auth': False,
'max_quality': 'lossless'
},
{
'name': 'qobuz',
'display_name': 'Qobuz',
'description': 'Alternative high-quality source with extensive catalog',
'quality_options': ['lossless', 'high', 'normal'],
'formats': ['flac', 'mp3'],
'available': True,
'requires_auth': True,
'max_quality': 'lossless'
},
{
'name': 'amazon',
'display_name': 'Amazon Music',
'description': 'Fallback source with wide availability',
'quality_options': ['high', 'normal'],
'formats': ['mp3', 'aac'],
'available': False, # Disabled by default
'requires_auth': True,
'max_quality': 'high'
}
]
return jsonify({
'success': True,
'sources': sources
})
except Exception as e:
logger.error(f"Error getting available sources: {e}")
return jsonify({
'success': False,
'message': str(e)
}), 500
# Error handlers
@spotify_settings_bp.errorhandler(400)
def bad_request(error):
return jsonify({
'error': 'Bad request',
'message': str(error),
'success': False
}), 400
@spotify_settings_bp.errorhandler(500)
def internal_error(error):
return jsonify({
'error': 'Internal server error',
'message': str(error),
'success': False
}), 500