import gradio as gr import os import tempfile import shutil import requests import json import uuid import time from datetime import datetime from dotenv import load_dotenv # Load environment variables load_dotenv() # Configuration SUNO_API_KEY = os.environ.get("SunoKey") FIXED_CALLBACK_URL = "https://1hit.no/cover/cb.php" SUNO_API_URL = "https://api.sunoapi.org/api/v1/generate/upload-cover" CHECK_TASK_URL = "https://api.sunoapi.org/api/v1/task" # Adjust if different # Create uploads directory UPLOADS_FOLDER = "uploads" os.makedirs(UPLOADS_FOLDER, exist_ok=True) # ============================================ # PAGE 1: FILE UPLOAD # ============================================ def upload_file(file_obj, custom_name): """Simple file upload - returns file for download""" if not file_obj: return None, "ā Please upload a file first" try: # Handle Gradio 6+ file object if isinstance(file_obj, dict): file_path = file_obj["path"] original_name = file_obj["name"] else: file_path = file_obj.name original_name = os.path.basename(file_path) # Create temp directory temp_dir = tempfile.mkdtemp() # Determine filename if custom_name and custom_name.strip(): ext = os.path.splitext(original_name)[1] base_name = custom_name.strip() if not base_name.endswith(ext): final_name = base_name + ext else: final_name = base_name else: final_name = original_name # Copy file final_path = os.path.join(temp_dir, final_name) shutil.copy2(file_path, final_path) return final_path, f"ā Ready: {final_name}" except Exception as e: return None, f"ā Error: {str(e)}" def get_file_info(file_obj): """Display file information""" if not file_obj: return "š No file selected" try: if isinstance(file_obj, dict): file_path = file_obj["path"] file_name = file_obj["name"] else: file_path = file_obj.name file_name = os.path.basename(file_path) size = os.path.getsize(file_path) return f"š **{file_name}**\n⢠Size: {size/1024:.1f} KB\n⢠Type: {os.path.splitext(file_name)[1]}" except: return "š File selected" # ============================================ # PAGE 2: COVER CREATION # ============================================ def generate_cover( prompt, title, style, upload_url_type, custom_upload_url, uploaded_file, instrumental=True, model="V4_5ALL", persona_id="", negative_tags="", vocal_gender="m", style_weight=0.65, weirdness_constraint=0.65, audio_weight=0.65, custom_mode=True ): """Generate cover using Suno API""" # Check API key if not SUNO_API_KEY: return "ā Suno API key not found. Please set 'SunoKey' environment variable." # Determine upload URL if upload_url_type == "uploaded": # Use uploaded file - in real implementation, you'd upload to storage and get URL if not uploaded_file: return "ā Please upload a file first or select another URL type" # For demo - simulate URL from uploaded file if isinstance(uploaded_file, dict): file_name = uploaded_file.get("name", "uploaded_file.mp3") else: file_name = getattr(uploaded_file, "name", "uploaded_file.mp3") upload_url = f"https://storage.temp.example.com/uploads/{int(time.time())}_{file_name}" elif upload_url_type == "custom": upload_url = custom_upload_url.strip() if not upload_url: return "ā Please provide a custom upload URL" else: # auto timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") unique_id = str(uuid.uuid4())[:8] upload_url = f"https://storage.temp.example.com/uploads/{timestamp}_{unique_id}.mp3" # Prepare payload payload = { "uploadUrl": upload_url, "customMode": custom_mode, "instrumental": instrumental, "model": model, "callBackUrl": FIXED_CALLBACK_URL, "prompt": prompt, "style": style, "title": title, "personaId": persona_id, "negativeTags": negative_tags, "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]} # Headers headers = { "Authorization": f"Bearer {SUNO_API_KEY}", "Content-Type": "application/json" } try: # Make API request response = requests.post(SUNO_API_URL, json=payload, headers=headers) if response.status_code == 200: result = response.json() if result.get("code") == 200: task_id = result.get("data", {}).get("taskId", "Unknown") return f"""ā **Generation Started!** **Task ID:** `{task_id}` **Upload URL:** {upload_url} **Callback URL:** {FIXED_CALLBACK_URL} š **Full Response:** ```json {json.dumps(result, indent=2)} ```""" else: return f"""ā **API Error:** {result.get('msg', 'Unknown error')} š **Response:** ```json {json.dumps(result, indent=2)} ```""" else: return f"""ā **HTTP Error {response.status_code}** {response.text}""" except Exception as e: return f"ā **Error:** {str(e)}" # ============================================ # PAGE 3: CHECK TASK # ============================================ def check_task_status(task_id): """Check status of a generation task""" if not task_id or not task_id.strip(): return "ā Please enter a Task ID" if not SUNO_API_KEY: return "ā Suno API key not found" headers = { "Authorization": f"Bearer {SUNO_API_KEY}", "Content-Type": "application/json" } try: # Try different endpoint patterns - adjust based on actual API endpoints = [ f"{CHECK_TASK_URL}/{task_id}", f"{CHECK_TASK_URL}?taskId={task_id}", f"https://api.sunoapi.org/api/v1/task/{task_id}" ] for url in endpoints: try: response = requests.get(url, headers=headers) if response.status_code == 200: result = response.json() return f"""ā **Task Status** š **Result:** ```json {json.dumps(result, indent=2)} ```""" except: continue return f"ā Could not find task with ID: {task_id}" except Exception as e: return f"ā **Error:** {str(e)}" def poll_task(task_id, interval=5, max_attempts=12): """Poll task until completion""" if not task_id or not task_id.strip(): return "ā Please enter a Task ID" status_msg = "" for attempt in range(max_attempts): status_msg += f"ā³ Polling attempt {attempt + 1}/{max_attempts}...\n" # Get status result = check_task_status(task_id) status_msg += f"{result}\n\n" # Check if completed (adjust based on actual response format) if "completed" in result.lower() or "success" in result.lower(): status_msg += "ā **Task completed!**" break if attempt < max_attempts - 1: time.sleep(interval) return status_msg # ============================================ # BUILD THE APP # ============================================ # Custom CSS css = """ .status-badge { padding: 5px 10px; border-radius: 20px; font-weight: bold; } .api-status-ok { background-color: #d4edda; color: #155724; } .api-status-missing { background-color: #f8d7da; color: #721c24; } """ # Build interface with gr.Blocks(title="Suno Cover Creator", theme=gr.themes.Soft(), css=css) as app: gr.Markdown("""
Upload ā Create Cover ā Check Status
{FIXED_CALLBACK_URL}