Files
SpotifyRecAlg/swingmusic/api/spotify_settings.py
T
Tomas Dvorak 6e8fedf534 first commit
2026-04-13 17:46:58 +02:00

356 lines
11 KiB
Python

"""
Spotify Downloader Settings API endpoints
"""
from typing import Any
from flask import jsonify
from flask_jwt_extended import get_jwt_identity
from flask_openapi3 import APIBlueprint
from pydantic import BaseModel, Field
from swingmusic import logger
from swingmusic.config import UserConfig
from swingmusic.services.download_jobs import download_job_manager
from swingmusic.utils.auth import get_current_userid
spotify_settings_bp = APIBlueprint(
"spotify_settings",
import_name="spotify_settings",
url_prefix="/api/settings/spotify",
)
def _current_userid() -> int:
try:
identity = get_jwt_identity()
if isinstance(identity, dict) and identity.get("id") is not None:
return int(identity["id"])
except Exception:
pass
return get_current_userid()
class SpotifySettingsRequest(BaseModel):
defaultQuality: str = Field("flac", description="Default download quality")
downloadFolder: str | None = 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: list | None = 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: dict[str, Any] | None = None
message: str | None = 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 pending/active download jobs for current user.
"""
try:
userid = _current_userid()
cancelled = download_job_manager.clear_queue(userid)
return jsonify(
{
"success": True,
"cancelled_jobs": cancelled,
"message": f"Cleared queue ({cancelled} job(s) cancelled)",
}
)
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 completed/failed/cancelled download history for current user.
"""
try:
userid = _current_userid()
deleted = download_job_manager.clear_history(userid)
return jsonify(
{
"success": True,
"deleted_jobs": deleted,
"message": f"Download history cleared ({deleted} job(s) removed)",
}
)
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