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("""

šŸŽµ Suno Cover Creator

Upload → Create Cover → Check Status

""") # API Status Banner api_status_color = "āœ… API Key: Loaded" if SUNO_API_KEY else "āŒ API Key: Not Found" api_status_class = "api-status-ok" if SUNO_API_KEY else "api-status-missing" with gr.Row(): gr.Markdown(f"""
{api_status_color}
""") # State for initial load tracking initial_load_done = gr.State(value=False) # Main tabs with gr.Tabs() as tabs: # ========== PAGE 1: FILE UPLOAD ========== with gr.TabItem("šŸ“¤ 1. Upload File", id="tab_upload"): with gr.Row(): with gr.Column(): file_input = gr.File( label="Upload Your Audio File", file_count="single", file_types=["audio", ".mp3", ".wav", ".m4a", ".flac", ".ogg"] ) file_info = gr.Markdown("šŸ“ No file selected") custom_filename = gr.Textbox( label="Custom Filename (optional)", placeholder="my-audio-file", info="Extension preserved automatically" ) upload_btn = gr.Button("⚔ Prepare File", variant="primary", size="lg") upload_status = gr.Textbox(label="Status", interactive=False) upload_download = gr.File(label="Download File", interactive=False) # ========== PAGE 2: COVER CREATION ========== with gr.TabItem("šŸŽØ 2. Create Cover", id="tab_create"): with gr.Row(): with gr.Column(scale=1): gr.Markdown("### šŸŽµ Cover Details") cover_prompt = gr.Textbox( label="Prompt", value="A dramatic orchestral cover with dark undertones", lines=2 ) cover_title = gr.Textbox( label="Title", value="The Fool's Ascension" ) cover_style = gr.Textbox( label="Style", value="Epic Orchestral, Dark Cinematic" ) # URL Selection gr.Markdown("### šŸ”— Upload URL") url_type = gr.Radio( label="Source", choices=[ ("Use uploaded file", "uploaded"), ("Use custom URL", "custom"), ("Auto-generate", "auto") ], value="uploaded", info="Choose where the audio comes from" ) custom_url = gr.Textbox( label="Custom URL", placeholder="https://example.com/audio.mp3", visible=False ) # Reference to uploaded file from Page 1 uploaded_file_ref = gr.State() with gr.Column(scale=1): gr.Markdown("### āš™ļø Advanced Settings") cover_model = gr.Dropdown( label="Model", choices=["V4_5ALL", "V5", "V4"], value="V4_5ALL" ) cover_instrumental = gr.Checkbox( label="Instrumental", value=True ) cover_vocal = gr.Dropdown( label="Vocal Gender", choices=["m", "f", "none"], value="m" ) cover_negative = gr.Textbox( label="Negative Tags", value="Distorted, Low Quality", placeholder="What to avoid" ) # Show/hide custom URL def toggle_custom_url(choice): return gr.update(visible=choice == "custom") url_type.change( toggle_custom_url, inputs=url_type, outputs=custom_url ) # Generate button generate_btn = gr.Button("šŸŽ¬ Generate Cover", variant="primary", size="lg") generate_output = gr.Markdown("Ready to generate...") # ========== PAGE 3: CHECK TASK - ORIGINAL ========== with gr.TabItem("šŸ” 3. Check Any Task", id="tab_check"): with gr.Row(): with gr.Column(scale=1): gr.Markdown("### Check Task Status") gr.Markdown("Enter any Suno Task ID to check its status") check_task_id = gr.Textbox( label="Task ID", placeholder="Enter Task ID from generation", info="From Cover Creator or any Suno API request" ) check_btn = gr.Button("šŸ” Check Status", variant="primary") check_clear_btn = gr.Button("šŸ—‘ļø Clear", variant="secondary") # URL parameter info gr.Markdown(""" **Quick access via URL:** Add `?taskid=YOUR_TASK_ID` to the URL Example: `https://www.1hit.no/cover/logs.php` """) with gr.Column(scale=2): check_output = gr.Markdown( value="### Enter a Task ID above\n\nPaste any Suno Task ID to check its current status and results." ) # ============================================ # EVENT HANDLERS # ============================================ # Page 1: File Upload file_input.change( fn=get_file_info, inputs=[file_input], outputs=file_info ) upload_btn.click( fn=upload_file, inputs=[file_input, custom_filename], outputs=[upload_download, upload_status] ) # Store uploaded file reference for Page 2 def store_file_ref(file_obj): return file_obj file_input.change( fn=store_file_ref, inputs=[file_input], outputs=uploaded_file_ref ) # Page 2: Generate Cover generate_btn.click( fn=generate_cover, inputs=[ cover_prompt, cover_title, cover_style, url_type, custom_url, uploaded_file_ref, cover_instrumental, cover_model, gr.State(""), # persona_id placeholder cover_negative, cover_vocal, gr.State(0.65), gr.State(0.65), gr.State(0.65), # weights gr.State(True) # custom_mode ], outputs=generate_output ) # Page 3: Check Task - Original handlers def clear_check(): return "", "### Enter a Task ID above\n\nPaste any Suno Task ID to check its current status and results." check_clear_btn.click( clear_check, outputs=[check_task_id, check_output] ) check_btn.click( get_task_info, inputs=[check_task_id], outputs=check_output ) # Load URL parameters when the app starts app.load( fn=on_page_load, inputs=[], outputs=[check_task_id, check_output, tabs, initial_load_done], queue=False ) # Footer gr.Markdown("---") gr.Markdown("""

šŸ”— Quick URL Access: Add ?taskid=YOUR_TASK_ID to auto-load task

""") # Launch if __name__ == "__main__": if not SUNO_API_KEY: print("āš ļø Warning: Suno API key not found") print("Set 'SunoKey' environment variable or add to .env file") print("šŸš€ Starting Suno Cover Creator") print(f"šŸ”‘ SunoKey: {'āœ… Set' if SUNO_API_KEY else 'āŒ Not set'}") print("šŸ”— Use URL parameter: http://localhost:7860/?taskid=YOUR_TASK_ID") app.launch( server_name="0.0.0.0", server_port=7860, share=False )