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 from urllib.parse import urlparse, parse_qs # 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" # 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 - ORIGINAL CODE # ============================================ def get_task_info(task_id): """Manually check any Suno task status - ORIGINAL CODE""" if not task_id: return "ā Please enter a Task ID ā" try: resp = requests.get( "https://api.sunoapi.org/api/v1/generate/record-info", headers={"Authorization": f"Bearer {SUNO_API_KEY}"}, params={"taskId": task_id}, timeout=30 ) if resp.status_code != 200: return f"ā HTTP Error {resp.status_code}\n\n{resp.text}" data = resp.json() # Format the response for display output = f"## š Task Status: `{task_id}`\n\n" if data.get("code") == 200: task_data = data.get("data", {}) status = task_data.get("status", "UNKNOWN") output += f"**Status:** {status}\n" output += f"**Task ID:** `{task_data.get('taskId', 'N/A')}`\n" output += f"**Music ID:** `{task_data.get('musicId', 'N/A')}`\n" output += f"**Created:** {task_data.get('createTime', 'N/A')}\n" if status == "SUCCESS": response_data = task_data.get("response", {}) # Try to parse response (could be string or dict) if isinstance(response_data, str): try: response_data = json.loads(response_data) except: output += f"\n**Raw Response:**\n```\n{response_data}\n```\n" response_data = {} # Check for song data songs = [] if isinstance(response_data, dict): songs = response_data.get("sunoData", []) if not songs: songs = response_data.get("data", []) elif isinstance(response_data, list): songs = response_data if songs: output += f"\n## šµ Generated Songs ({len(songs)})\n\n" for i, song in enumerate(songs, 1): if isinstance(song, dict): output += f"### Song {i}\n" output += f"**Title:** {song.get('title', 'Untitled')}\n" output += f"**ID:** `{song.get('id', 'N/A')}`\n" # Audio URLs audio_url = song.get('audioUrl') or song.get('audio_url') stream_url = song.get('streamUrl') or song.get('stream_url') download_url = song.get('downloadUrl') or song.get('download_url') if audio_url: output += f"**Audio:** [Play]({audio_url}) | [Download]({audio_url})\n" elif stream_url: output += f"**Stream:** [Play]({stream_url})\n" if download_url: output += f"**Download:** [MP3]({download_url})\n" # Audio player play_url = audio_url or stream_url if play_url: output += f"""\n\n""" output += f"**Prompt:** {song.get('prompt', 'N/A')[:100]}...\n" output += f"**Duration:** {song.get('duration', 'N/A')}s\n" output += f"**Created:** {song.get('createTime', 'N/A')}\n\n" output += "---\n\n" else: output += "\n**No song data found in response.**\n" elif status == "FAILED": error_msg = task_data.get("errorMessage", "Unknown error") output += f"\n**Error:** {error_msg}\n" elif status in ["PENDING", "PROCESSING", "RUNNING"]: output += f"\n**Task is still processing...**\n" output += f"Check again in 30 seconds.\n" else: output += f"\n**Unknown status:** {status}\n" else: output += f"**API Error:** {data.get('msg', 'Unknown')}\n" # Show raw JSON for debugging output += "\n## š Raw Response\n" output += f"```json\n{json.dumps(data, indent=2)}\n```" return output except Exception as e: return f"ā Error checking task: {str(e)}" # ============================================ # URL PARAMETER HANDLING # ============================================ def parse_url_params(request: gr.Request): """Parse taskid from URL parameters""" task_id = None if request: try: query_params = parse_qs(urlparse(request.request.url).query) if 'taskid' in query_params: task_id = query_params['taskid'][0] # Remove any whitespace task_id = task_id.strip() except Exception as e: print(f"Error parsing URL params: {e}") return task_id def on_page_load(request: gr.Request): """Handle URL parameters when page loads""" task_id = parse_url_params(request) if task_id: # We have a task ID from URL, return it and fetch results task_result = get_task_info(task_id) return ( task_id, # For check_task_id task_result, # For check_output gr.Tabs(selected="tab_check"), # Switch to check tab True # Mark as loaded ) else: # No task ID in URL, stay on first tab return ( "", # Empty check_task_id "### Enter a Task ID above\n\nPaste any Suno Task ID to check its current status and results.", # Default message gr.Tabs(selected="tab_upload"), # Stay on upload tab True # Mark as loaded ) # ============================================ # 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; } audio { width: 100%; margin: 10px 0; } """ # Build interface with gr.Blocks(title="Suno Cover Creator", theme=gr.themes.Soft(), css=css) as app: gr.Markdown("""
Upload ā Create Cover ā Check Status
š Quick URL Access: Add ?taskid=YOUR_TASK_ID to auto-load task