mirror of
https://github.com/Dvorinka/swingmusic-extended.git
synced 2026-06-04 20:43:04 +00:00
Add comprehensive backend services and API enhancements
- Complete Spotify integration with downloader and settings - Advanced UX features and audio quality management - Enhanced search capabilities and mobile offline support - Music catalog browser and recap features - Universal downloader and upload functionality - Update tracking system with database models and migrations - Comprehensive service layer architecture - Enhanced lyrics API and streaming capabilities - Extended application builder and startup configuration - New logging infrastructure and services directory
This commit is contained in:
@@ -0,0 +1,372 @@
|
||||
"""
|
||||
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',
|
||||
__name__,
|
||||
url_prefix='/api/settings/spotify',
|
||||
abp_tag=Tag(name='Spotify Settings', description='Spotify downloader settings operations')
|
||||
)
|
||||
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user