Spaces:
Running
Running
| import os | |
| import requests | |
| import gradio as gr | |
| from dotenv import load_dotenv | |
| import json | |
| import uuid | |
| import tempfile | |
| from datetime import datetime | |
| import logging | |
| import sys | |
| from typing import Dict, Any, Optional, Union, List | |
| from dataclasses import dataclass | |
| import time | |
| # Load environment variables from .env file | |
| load_dotenv() | |
| # Configure logging | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', | |
| handlers=[ | |
| logging.StreamHandler(sys.stdout), | |
| logging.FileHandler('suno_generator.log') | |
| ] | |
| ) | |
| logger = logging.getLogger(__name__) | |
| # Configuration | |
| class Config: | |
| """Configuration manager""" | |
| def __init__(self): | |
| self.suno_api_key = os.environ.get("SUNO_API_KEY") or os.environ.get("SunoKey") | |
| self.suno_api_url = "https://api.sunoapi.org/api/v1/generate/upload-cover" | |
| self.fixed_callback_url = "https://1hit.no/cover/cb.php" # Your PHP callback URL | |
| self.default_model = "V4_5ALL" | |
| self.default_vocal_gender = "m" | |
| self.max_retries = 3 | |
| self.request_timeout = 30 | |
| # Default values for UI | |
| self.defaults = { | |
| "prompt": "A calm and relaxing piano track with soft melodies", | |
| "title": "Peaceful Piano Meditation", | |
| "style": "Classical", | |
| "style_weight": 0.65, | |
| "weirdness_constraint": 0.65, | |
| "audio_weight": 0.65, | |
| "instrumental": True, | |
| "custom_mode": True | |
| } | |
| config = Config() | |
| # Data models | |
| class ApiResponse: | |
| """Structured API response""" | |
| success: bool | |
| message: str | |
| task_id: Optional[str] = None | |
| upload_url: Optional[str] = None | |
| callback_url: Optional[str] = None | |
| raw_response: Optional[Dict] = None | |
| error_type: Optional[str] = None | |
| class SunoAPIError(Exception): | |
| """Custom exception for Suno API errors""" | |
| pass | |
| # Session management for requests | |
| session = requests.Session() | |
| session.headers.update({ | |
| "User-Agent": "SunoMusicGenerator/1.0", | |
| "Accept": "application/json" | |
| }) | |
| def validate_inputs( | |
| prompt: str, | |
| title: str, | |
| style: str, | |
| upload_url_type: str, | |
| custom_upload_url: str | |
| ) -> List[str]: | |
| """Validate user inputs and return list of errors""" | |
| errors = [] | |
| # Trim inputs | |
| prompt = prompt.strip() | |
| title = title.strip() | |
| style = style.strip() | |
| custom_upload_url = custom_upload_url.strip() | |
| # Check required fields | |
| if len(prompt) < 5: | |
| errors.append("Prompt should be at least 5 characters") | |
| if not title: | |
| errors.append("Title is required") | |
| if not style: | |
| errors.append("Style is required") | |
| # Check custom URL if selected | |
| if upload_url_type == "custom": | |
| if not custom_upload_url: | |
| errors.append("Please provide a custom upload URL") | |
| elif not custom_upload_url.startswith(("http://", "https://")): | |
| errors.append("Custom URL must start with http:// or https://") | |
| return errors | |
| def generate_temp_upload_url() -> str: | |
| """Generate a simulated temporary upload URL""" | |
| timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") | |
| unique_id = str(uuid.uuid4())[:8] | |
| return f"https://storage.temp.example.com/uploads/{timestamp}_{unique_id}.mp3" | |
| def make_api_request_with_retry( | |
| payload: Dict[str, Any], | |
| headers: Dict[str, str], | |
| max_retries: int = 3 | |
| ) -> requests.Response: | |
| """Make API request with retry logic""" | |
| last_exception = None | |
| for attempt in range(max_retries): | |
| try: | |
| logger.info(f"API Request Attempt {attempt + 1}/{max_retries}") | |
| response = session.post( | |
| config.suno_api_url, | |
| json=payload, | |
| headers=headers, | |
| timeout=config.request_timeout | |
| ) | |
| response.raise_for_status() | |
| return response | |
| except requests.exceptions.Timeout: | |
| logger.warning(f"Request timeout (attempt {attempt + 1})") | |
| last_exception = "Timeout" | |
| if attempt < max_retries - 1: | |
| time.sleep(2 ** attempt) # Exponential backoff | |
| except requests.exceptions.ConnectionError: | |
| logger.warning(f"Connection error (attempt {attempt + 1})") | |
| last_exception = "Connection error" | |
| if attempt < max_retries - 1: | |
| time.sleep(2 ** attempt) | |
| except requests.exceptions.HTTPError as e: | |
| logger.error(f"HTTP error: {e}") | |
| last_exception = f"HTTP {e.response.status_code}" | |
| break # Don't retry HTTP errors | |
| except Exception as e: | |
| logger.error(f"Request failed: {e}") | |
| last_exception = str(e) | |
| break # Don't retry other errors | |
| raise SunoAPIError(f"API request failed after {max_retries} attempts: {last_exception}") | |
| def generate_suno_music( | |
| prompt: str, | |
| title: str, | |
| style: str, | |
| upload_url_type: str, | |
| custom_upload_url: str, | |
| instrumental: bool = True, | |
| model: str = "V4_5ALL", | |
| persona_id: Optional[str] = None, | |
| negative_tags: Optional[str] = None, | |
| vocal_gender: str = "m", | |
| style_weight: float = 0.65, | |
| weirdness_constraint: float = 0.65, | |
| audio_weight: float = 0.65, | |
| custom_mode: bool = True | |
| ) -> ApiResponse: | |
| """ | |
| Generate music using Suno API with fixed callback URL | |
| """ | |
| logger.info(f"Starting music generation: {title}") | |
| # Check if API key is available | |
| if not config.suno_api_key: | |
| error_msg = "Suno API key not found. Please set the 'SUNO_API_KEY' or 'SunoKey' environment variable." | |
| logger.error(error_msg) | |
| return ApiResponse( | |
| success=False, | |
| message=f"❌ {error_msg}", | |
| error_type="ConfigurationError" | |
| ) | |
| # Validate inputs | |
| validation_errors = validate_inputs(prompt, title, style, upload_url_type, custom_upload_url) | |
| if validation_errors: | |
| error_msg = "Input validation failed:\n" + "\n".join([f"• {err}" for err in validation_errors]) | |
| logger.warning(f"Validation errors: {validation_errors}") | |
| return ApiResponse( | |
| success=False, | |
| message=f"❌ {error_msg}", | |
| error_type="ValidationError" | |
| ) | |
| # Determine upload URL | |
| if upload_url_type == "auto": | |
| upload_url = generate_temp_upload_url() | |
| logger.info(f"Using auto-generated upload URL: {upload_url}") | |
| else: | |
| upload_url = custom_upload_url.strip() | |
| logger.info(f"Using custom upload URL: {upload_url}") | |
| # Prepare payload | |
| payload = { | |
| "uploadUrl": upload_url, | |
| "customMode": custom_mode, | |
| "instrumental": instrumental, | |
| "model": model, | |
| "callBackUrl": config.fixed_callback_url, # Always use fixed callback URL | |
| "prompt": prompt, | |
| "style": style, | |
| "title": title, | |
| "personaId": persona_id or "", | |
| "negativeTags": negative_tags or "", | |
| "vocalGender": vocal_gender, | |
| "styleWeight": style_weight, | |
| "weirdnessConstraint": weirdness_constraint, | |
| "audioWeight": audio_weight | |
| } | |
| # Remove empty fields | |
| payload = {k: v for k, v in payload.items() if v not in ["", None]} | |
| # Prepare headers | |
| headers = { | |
| "Authorization": f"Bearer {config.suno_api_key}", | |
| "Content-Type": "application/json" | |
| } | |
| try: | |
| # Make API request | |
| response = make_api_request_with_retry(payload, headers, config.max_retries) | |
| result = response.json() | |
| # Check API response | |
| if response.status_code == 200 and result.get("code") == 200: | |
| task_id = result.get("data", {}).get("taskId", "Unknown") | |
| logger.info(f"Success! Task ID: {task_id}") | |
| return ApiResponse( | |
| success=True, | |
| message="✅ Music generation started successfully!", | |
| task_id=task_id, | |
| upload_url=upload_url, | |
| callback_url=config.fixed_callback_url, | |
| raw_response=result | |
| ) | |
| else: | |
| error_msg = result.get('msg', 'Unknown API error') | |
| logger.error(f"API error: {error_msg}") | |
| return ApiResponse( | |
| success=False, | |
| message=f"❌ API Error: {error_msg}", | |
| raw_response=result, | |
| error_type="APIError" | |
| ) | |
| except SunoAPIError as e: | |
| logger.error(f"API request failed: {e}") | |
| return ApiResponse( | |
| success=False, | |
| message=f"❌ Request failed after retries: {str(e)}", | |
| error_type="RequestError" | |
| ) | |
| except requests.exceptions.HTTPError as e: | |
| logger.error(f"HTTP error: {e}") | |
| return ApiResponse( | |
| success=False, | |
| message=f"❌ HTTP Error {e.response.status_code}: {e.response.text}", | |
| error_type="HTTPError" | |
| ) | |
| except json.JSONDecodeError as e: | |
| logger.error(f"JSON decode error: {e}") | |
| return ApiResponse( | |
| success=False, | |
| message=f"❌ Failed to parse API response: {str(e)}", | |
| error_type="ParseError" | |
| ) | |
| except Exception as e: | |
| logger.error(f"Unexpected error: {e}") | |
| return ApiResponse( | |
| success=False, | |
| message=f"❌ Unexpected error: {str(e)}", | |
| error_type="UnexpectedError" | |
| ) | |
| def format_api_response(response: ApiResponse) -> str: | |
| """Format API response for display""" | |
| if response.success: | |
| return f"""{response.message} | |
| 📋 Task Details: | |
| • Task ID: {response.task_id} | |
| • Upload URL: {response.upload_url} | |
| • Callback URL: {response.callback_url} | |
| ✅ Your music is being generated! | |
| The callback will be sent to: {config.fixed_callback_url} | |
| 📱 You can check the playlist at: https://1hit.no/cover/playlist.php | |
| (Newest songs appear first) | |
| 🔍 Full API Response: | |
| {json.dumps(response.raw_response, indent=2) if response.raw_response else 'No response data'}""" | |
| else: | |
| error_details = f"\n\n🔍 Error Type: {response.error_type}" if response.error_type else "" | |
| raw_response = f"\n\n📋 Full Response:\n{json.dumps(response.raw_response, indent=2)}" if response.raw_response else "" | |
| return f"{response.message}{error_details}{raw_response}" | |
| def test_api_connection() -> str: | |
| """Test API connection and key validity""" | |
| if not config.suno_api_key: | |
| return "❌ API Key: Not found" | |
| try: | |
| # Simple test endpoint if available, or use a minimal payload | |
| test_payload = { | |
| "uploadUrl": "https://example.com/test.mp3", | |
| "callBackUrl": config.fixed_callback_url, | |
| "prompt": "test", | |
| "title": "test", | |
| "style": "test", | |
| "customMode": False, | |
| "instrumental": True | |
| } | |
| headers = { | |
| "Authorization": f"Bearer {config.suno_api_key}", | |
| "Content-Type": "application/json" | |
| } | |
| # Use HEAD request or minimal endpoint if available | |
| response = session.head( | |
| config.suno_api_url.replace("/upload-cover", ""), | |
| headers={"Authorization": f"Bearer {config.suno_api_key}"}, | |
| timeout=10 | |
| ) | |
| if response.status_code < 500: | |
| return f"✅ API Connection: OK (Key is configured)" | |
| else: | |
| return f"⚠️ API Connection: May require valid payload (Status: {response.status_code})" | |
| except Exception as e: | |
| return f"❌ API Connection: Failed - {str(e)}" | |
| def load_example() -> Dict: | |
| """Load example values""" | |
| return { | |
| "prompt": config.defaults["prompt"], | |
| "title": config.defaults["title"], | |
| "style": config.defaults["style"], | |
| "upload_url_type": "auto", | |
| "custom_upload_url": "https://storage.example.com/upload", | |
| "instrumental": config.defaults["instrumental"], | |
| "model": config.default_model, | |
| "persona_id": "", | |
| "negative_tags": "Heavy Metal, Upbeat Drums", | |
| "vocal_gender": config.default_vocal_gender, | |
| "style_weight": config.defaults["style_weight"], | |
| "weirdness_constraint": config.defaults["weirdness_constraint"], | |
| "audio_weight": config.defaults["audio_weight"], | |
| "custom_mode": config.defaults["custom_mode"] | |
| } | |
| # Create Gradio interface | |
| with gr.Blocks( | |
| title="Suno Music Generator", | |
| theme=gr.themes.Soft(), | |
| css=""" | |
| .success-box { background: #d4edda; border: 1px solid #c3e6cb; border-radius: 5px; padding: 10px; margin: 10px 0; } | |
| .error-box { background: #f8d7da; border: 1px solid #f5c6cb; border-radius: 5px; padding: 10px; margin: 10px 0; } | |
| .info-box { background: #d1ecf1; border: 1px solid #bee5eb; border-radius: 5px; padding: 10px; margin: 10px 0; } | |
| """ | |
| ) as app: | |
| # Custom CSS for better UI | |
| gr.HTML(""" | |
| <style> | |
| .gradio-container { max-width: 1200px !important; margin: 0 auto !important; } | |
| .success-message { color: #155724; background: #d4edda; padding: 15px; border-radius: 5px; border-left: 4px solid #28a745; margin: 10px 0; } | |
| .error-message { color: #721c24; background: #f8d7da; padding: 15px; border-radius: 5px; border-left: 4px solid #dc3545; margin: 10px 0; } | |
| .warning-message { color: #856404; background: #fff3cd; padding: 15px; border-radius: 5px; border-left: 4px solid #ffc107; margin: 10px 0; } | |
| .info-box { background: #e7f3ff; border: 1px solid #b3d7ff; border-radius: 8px; padding: 15px; margin: 15px 0; } | |
| .playlist-link { background: #007bff; color: white; padding: 8px 16px; border-radius: 4px; text-decoration: none; display: inline-block; margin: 5px; } | |
| .playlist-link:hover { background: #0056b3; color: white; text-decoration: none; } | |
| </style> | |
| """) | |
| gr.Markdown("# 🎵 Suno Music Generator") | |
| gr.Markdown(f"Generate music using Suno API. Callbacks sent to your PHP endpoint at `{config.fixed_callback_url}`") | |
| # Status Bar | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| api_status = gr.HTML( | |
| value=f""" | |
| <div class="info-box"> | |
| <h4 style="margin-top: 0;">🔧 API Status</h4> | |
| <p><strong>API Key:</strong> {'<span style="color: green;">✅ Loaded</span>' if config.suno_api_key else '<span style="color: red;">❌ Not found</span>'}</p> | |
| <p><strong>Callback URL:</strong> <code>{config.fixed_callback_url}</code></p> | |
| <a href="https://1hit.no/cover/playlist.php" target="_blank" class="playlist-link">🎧 View Playlist</a> | |
| </div> | |
| """ | |
| ) | |
| # Test API button | |
| test_result = gr.Textbox(label="Connection Test", interactive=False, visible=False) | |
| def run_test(): | |
| return test_api_connection() | |
| test_btn = gr.Button("🔍 Test API Connection", variant="secondary") | |
| test_btn.click(run_test, outputs=test_result) | |
| with gr.Column(scale=2): | |
| gr.Markdown(f""" | |
| ### 📋 About This App | |
| This app generates music using the Suno API and sends callbacks to your PHP endpoint. | |
| **How it works:** | |
| 1. Configure your settings below | |
| 2. Click "Generate Music" | |
| 3. Suno processes your request | |
| 4. Callback is sent to: `{config.fixed_callback_url}` | |
| 5. Music appears in your playlist | |
| **Playlist:** [https://1hit.no/cover/playlist.php](https://1hit.no/cover/playlist.php) | |
| **Required setup:** | |
| - Set your Suno API key as environment variable: `SUNO_API_KEY` | |
| - For Hugging Face Spaces: Add as Repository Secret | |
| - For local: Create `.env` file with `SUNO_API_KEY=your_key_here` | |
| """) | |
| # Main Tabs | |
| with gr.Tabs(): | |
| # Basic Settings Tab | |
| with gr.TabItem("🎯 Basic Settings"): | |
| with gr.Row(): | |
| with gr.Column(): | |
| prompt = gr.Textbox( | |
| label="Music Prompt", | |
| value=config.defaults["prompt"], | |
| placeholder="Describe the music you want to generate...", | |
| lines=3, | |
| info="Be descriptive for better results" | |
| ) | |
| title = gr.Textbox( | |
| label="Title", | |
| value=config.defaults["title"], | |
| placeholder="Title for your music track" | |
| ) | |
| style = gr.Textbox( | |
| label="Style", | |
| value=config.defaults["style"], | |
| placeholder="Music style (e.g., Classical, Pop, Rock, Jazz)", | |
| info="Examples: Lo-fi, Synthwave, Orchestral, Hip Hop" | |
| ) | |
| with gr.Column(): | |
| gr.Markdown("### 📤 Upload Settings") | |
| upload_url_type = gr.Radio( | |
| label="Upload URL Type", | |
| choices=[ | |
| ("Auto-generate temporary URL", "auto"), | |
| ("Use custom URL", "custom") | |
| ], | |
| value="auto", | |
| info="Auto-generate creates a temporary URL, or provide your own storage URL" | |
| ) | |
| custom_upload_url = gr.Textbox( | |
| label="Custom Upload URL", | |
| value="https://storage.example.com/upload", | |
| placeholder="Enter your custom upload URL here", | |
| visible=False, | |
| info="Must be a publicly accessible URL where audio can be uploaded" | |
| ) | |
| # Show/hide custom URL field | |
| upload_url_type.change( | |
| lambda x: gr.update(visible=x == "custom"), | |
| inputs=upload_url_type, | |
| outputs=custom_upload_url | |
| ) | |
| gr.Markdown("### 🔗 Callback URL (Fixed)") | |
| gr.Textbox( | |
| value=config.fixed_callback_url, | |
| interactive=False, | |
| label="" | |
| ) | |
| gr.HTML(f""" | |
| <div class="info-box"> | |
| <strong>🎧 Your Playlist:</strong> | |
| <p>Generated songs will appear here:</p> | |
| <a href="https://1hit.no/cover/playlist.php" target="_blank" class="playlist-link"> | |
| 🎵 Open Playlist | |
| </a> | |
| </div> | |
| """) | |
| # Advanced Settings Tab | |
| with gr.TabItem("⚙️ Advanced Settings"): | |
| with gr.Row(): | |
| with gr.Column(): | |
| model = gr.Dropdown( | |
| label="Model", | |
| choices=["V5", "V4_5ALL", "V4", "V3", "V2"], | |
| value=config.default_model, | |
| info="Latest models generally produce better results" | |
| ) | |
| instrumental = gr.Checkbox( | |
| label="Instrumental (No Vocals)", | |
| value=config.defaults["instrumental"], | |
| info="Check for instrumental music only" | |
| ) | |
| custom_mode = gr.Checkbox( | |
| label="Custom Mode", | |
| value=config.defaults["custom_mode"], | |
| info="Enable for more control over generation" | |
| ) | |
| with gr.Column(): | |
| persona_id = gr.Textbox( | |
| label="Persona ID (Optional)", | |
| placeholder="Leave empty for no persona", | |
| info="Specific vocal persona ID if needed" | |
| ) | |
| negative_tags = gr.Textbox( | |
| label="Negative Tags (Optional)", | |
| placeholder="Heavy Metal, Upbeat Drums, Distortion", | |
| info="Tags to avoid in the music, comma-separated" | |
| ) | |
| vocal_gender = gr.Dropdown( | |
| label="Vocal Gender", | |
| choices=["m", "f", "none"], | |
| value=config.default_vocal_gender, | |
| info="Gender of vocalist, or 'none' for instrumental" | |
| ) | |
| # Weight Settings Tab | |
| with gr.TabItem("🎛️ Weight Settings"): | |
| with gr.Row(): | |
| with gr.Column(): | |
| style_weight = gr.Slider( | |
| label="Style Weight", | |
| minimum=0.0, | |
| maximum=1.0, | |
| value=config.defaults["style_weight"], | |
| step=0.05, | |
| info="How much to follow the specified style" | |
| ) | |
| weirdness_constraint = gr.Slider( | |
| label="Weirdness Constraint", | |
| minimum=0.0, | |
| maximum=1.0, | |
| value=config.defaults["weirdness_constraint"], | |
| step=0.05, | |
| info="Lower values allow more experimental results" | |
| ) | |
| audio_weight = gr.Slider( | |
| label="Audio Weight", | |
| minimum=0.0, | |
| maximum=1.0, | |
| value=config.defaults["audio_weight"], | |
| step=0.05, | |
| info="Overall audio quality emphasis" | |
| ) | |
| with gr.Column(): | |
| gr.Markdown("### ℹ️ About Weights") | |
| gr.Markdown(""" | |
| **Style Weight:** Controls how closely the output follows your specified style. | |
| **Weirdness Constraint:** Lower values allow more creative/experimental outputs. | |
| **Audio Weight:** Affects the overall audio quality and coherence. | |
| **Default values (0.65)** are usually a good starting point. | |
| """) | |
| # Action Buttons | |
| with gr.Row(): | |
| submit_btn = gr.Button("🎶 Generate Music", variant="primary", size="lg", scale=2) | |
| example_btn = gr.Button("📋 Load Example", variant="secondary", scale=1) | |
| clear_btn = gr.Button("🗑️ Clear", variant="secondary", scale=1) | |
| # Output Section | |
| with gr.Row(): | |
| with gr.Column(): | |
| output = gr.Textbox( | |
| label="Generation Result", | |
| lines=15, | |
| interactive=False, | |
| show_copy_button=True | |
| ) | |
| # Progress indicator (hidden initially) | |
| progress = gr.HTML("", visible=False) | |
| # Success message area | |
| success_html = gr.HTML("", visible=False) | |
| # Footer | |
| gr.Markdown("---") | |
| gr.Markdown(""" | |
| ### 📝 Notes: | |
| - **Callback URL is fixed** to your PHP endpoint | |
| - **Processing time** varies based on length and complexity (usually 1-5 minutes) | |
| - **Check your playlist** for new songs: [1hit.no/cover/playlist.php](https://1hit.no/cover/playlist.php) | |
| - **Make sure** you have a valid Suno API key configured | |
| - **Logs** are saved to `suno_generator.log` | |
| """) | |
| # Callback functions | |
| def on_generate_click( | |
| prompt, title, style, upload_url_type, custom_upload_url, | |
| instrumental, model, persona_id, negative_tags, | |
| vocal_gender, style_weight, weirdness_constraint, | |
| audio_weight, custom_mode | |
| ): | |
| """Handle generate button click""" | |
| # Show progress | |
| progress_html = """ | |
| <div class="info-box"> | |
| <h4>⏳ Processing Request...</h4> | |
| <p>Sending request to Suno API...</p> | |
| <div style="background: #e9ecef; height: 4px; border-radius: 2px; margin: 10px 0;"> | |
| <div style="background: #007bff; width: 50%; height: 100%; border-radius: 2px; animation: pulse 1.5s infinite;"></div> | |
| </div> | |
| <p><small>This may take a moment. Do not close the window.</small></p> | |
| </div> | |
| """ | |
| yield progress_html, "", "" | |
| # Make API call | |
| response = generate_suno_music( | |
| prompt, title, style, upload_url_type, custom_upload_url, | |
| instrumental, model, persona_id, negative_tags, | |
| vocal_gender, style_weight, weirdness_constraint, | |
| audio_weight, custom_mode | |
| ) | |
| # Format output | |
| output_text = format_api_response(response) | |
| # Prepare success message | |
| if response.success: | |
| success_message = f""" | |
| <div class="success-message"> | |
| <h4>✅ Generation Started Successfully!</h4> | |
| <p><strong>Task ID:</strong> {response.task_id}</p> | |
| <p><strong>Check your playlist:</strong></p> | |
| <a href="https://1hit.no/cover/playlist.php" target="_blank" class="playlist-link"> | |
| 🎵 Open Playlist (Newest songs first) | |
| </a> | |
| <p><small>Your music will appear here when processing is complete.</small></p> | |
| </div> | |
| """ | |
| else: | |
| success_message = f""" | |
| <div class="error-message"> | |
| <h4>❌ Generation Failed</h4> | |
| <p>Please check the error details below and try again.</p> | |
| </div> | |
| """ | |
| yield "", output_text, success_message if response.success else "" | |
| def clear_all(): | |
| """Clear all inputs and outputs""" | |
| defaults = load_example() | |
| return { | |
| prompt: defaults["prompt"], | |
| title: defaults["title"], | |
| style: defaults["style"], | |
| upload_url_type: "auto", | |
| custom_upload_url: defaults["custom_upload_url"], | |
| instrumental: defaults["instrumental"], | |
| model: defaults["model"], | |
| persona_id: "", | |
| negative_tags: defaults["negative_tags"], | |
| vocal_gender: defaults["vocal_gender"], | |
| style_weight: defaults["style_weight"], | |
| weirdness_constraint: defaults["weirdness_constraint"], | |
| audio_weight: defaults["audio_weight"], | |
| custom_mode: defaults["custom_mode"], | |
| output: "", | |
| progress: "", | |
| success_html: "" | |
| } | |
| # Connect buttons | |
| submit_btn.click( | |
| fn=on_generate_click, | |
| inputs=[ | |
| prompt, title, style, upload_url_type, custom_upload_url, | |
| instrumental, model, persona_id, negative_tags, | |
| vocal_gender, style_weight, weirdness_constraint, | |
| audio_weight, custom_mode | |
| ], | |
| outputs=[progress, output, success_html] | |
| ) | |
| example_btn.click( | |
| fn=load_example, | |
| outputs=[ | |
| prompt, title, style, upload_url_type, custom_upload_url, | |
| instrumental, model, persona_id, negative_tags, | |
| vocal_gender, style_weight, weirdness_constraint, | |
| audio_weight, custom_mode | |
| ] | |
| ) | |
| clear_btn.click( | |
| fn=clear_all, | |
| outputs=[ | |
| prompt, title, style, upload_url_type, custom_upload_url, | |
| instrumental, model, persona_id, negative_tags, | |
| vocal_gender, style_weight, weirdness_constraint, | |
| audio_weight, custom_mode, output, progress, success_html | |
| ] | |
| ) | |
| # Initialize with example | |
| app.load(load_example, outputs=[ | |
| prompt, title, style, upload_url_type, custom_upload_url, | |
| instrumental, model, persona_id, negative_tags, | |
| vocal_gender, style_weight, weirdness_constraint, | |
| audio_weight, custom_mode | |
| ]) | |
| # Launch the app | |
| if __name__ == "__main__": | |
| # Check if API key is available | |
| if not config.suno_api_key: | |
| print("⚠️ Warning: Suno API key not found.") | |
| print("Please set the 'SUNO_API_KEY' environment variable:") | |
| print(" - For Hugging Face Spaces: Add as Repository Secret") | |
| print(" - For local development: Create a .env file with SUNO_API_KEY=your_key") | |
| print(" - Or set it directly: export SUNO_API_KEY=your_key") | |
| print("\nCurrent environment variables:") | |
| for key, value in os.environ.items(): | |
| if 'SUNO' in key.upper() or 'API' in key.upper(): | |
| print(f" {key}: {'*' * len(value) if 'KEY' in key.upper() else value}") | |
| # Launch settings | |
| launch_config = { | |
| "server_name": os.environ.get("GRADIO_SERVER_NAME", "0.0.0.0"), | |
| "server_port": int(os.environ.get("GRADIO_SERVER_PORT", 7860)), | |
| "share": os.environ.get("GRADIO_SHARE", "False").lower() == "true", | |
| } | |
| # Add analytics if needed | |
| if os.environ.get("GRADIO_ANALYTICS_ENABLED", "").lower() == "true": | |
| launch_config["analytics_enabled"] = True | |
| print(f"\n🚀 Starting Suno Music Generator...") | |
| print(f"🌐 Local URL: http://localhost:{launch_config['server_port']}") | |
| print(f"🎯 Callback URL: {config.fixed_callback_url}") | |
| print(f"🎧 Playlist: https://1hit.no/cover/playlist.php") | |
| print(f"📝 Logs: suno_generator.log") | |
| app.launch(**launch_config) |