Created
July 18, 2025 12:55
-
-
Save bemijonathan/3edcc701de35aadaa5b54ab5be75dd42 to your computer and use it in GitHub Desktop.
creating a music generator app
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # install gradio | |
| # install langchain | |
| # install langchain-google-genai | |
| import gradio as gr | |
| from langchain_google_genai import ChatGoogleGenerativeAI | |
| from langchain_core.output_parsers import StrOutputParser | |
| from langchain_core.prompts import PromptTemplate | |
| import requests | |
| import json | |
| import os | |
| from pathlib import Path | |
| from datetime import datetime | |
| import urllib.request | |
| class SunoMusicGenerator: | |
| def __init__(self): | |
| self.suno_api_key = os.environ.get('SUNO_API_KEY') | |
| self.gemini_api_key = os.environ.get('GEMINI_API_KEY') | |
| self.base_url = "https://api.sunoapi.org" | |
| self.generated_files = [] | |
| self.gemini_model = ChatGoogleGenerativeAI( | |
| model="gemini-2.5-flash", temperature=0.1, api_key=self.gemini_api_key) | |
| def generate_lyrics(self, theme, genre, mood, verse_count=2) -> str: | |
| try: | |
| prompt = PromptTemplate( | |
| template=""" | |
| Create original song lyrics with the following specifications: | |
| - Theme: {theme} | |
| - Genre: {genre} | |
| - Mood: {mood} | |
| - Structure: {verse_count} verses, chorus, bridge | |
| Format the lyrics with proper structure tags: | |
| [Intro] | |
| [Verse 1] | |
| [Chorus] | |
| [Verse 2] | |
| [Chorus] | |
| [Bridge] | |
| [Chorus] | |
| [Outro] | |
| Make the lyrics creative, original, and emotionally engaging. | |
| Keep verses to 4-6 lines and chorus to 3-4 lines. | |
| """, | |
| input_variables=["theme", "genre", "mood", "verse_count"]) | |
| chain = prompt | self.gemini_model | StrOutputParser() | |
| response = chain.invoke({ | |
| "theme": theme, | |
| "genre": genre, | |
| "mood": mood, | |
| "verse_count": verse_count | |
| }) | |
| return response | |
| except Exception as e: | |
| return f"β Error generating lyrics: {str(e)}" | |
| def generate_music(self, lyrics, style, title, callback_url=None, custom_mode=True, instrumental=False) -> str: | |
| try: | |
| headers = { | |
| "Authorization": f"Bearer {self.suno_api_key}", | |
| "Content-Type": "application/json" | |
| } | |
| payload = { | |
| "customMode": custom_mode, | |
| "instrumental": instrumental, | |
| "prompt": lyrics, | |
| "style": style, | |
| "title": title, | |
| "model": "V4" | |
| } | |
| if callback_url: | |
| payload["callback_url"] = callback_url | |
| response = requests.post( | |
| f"{self.base_url}/api/v1/generate", headers=headers, json=payload) | |
| if response.status_code == 200: | |
| result = response.json() | |
| return f"π response: {result}", None, None | |
| else: | |
| return f"β Generation failed ({response.status_code}): {response.text}", None, None | |
| except Exception as e: | |
| return f"β Error: {str(e)}", None, None | |
| def check_status(self, task_id): | |
| """Check the status of music generation and return all tracks with metadata""" | |
| try: | |
| headers = { | |
| "Authorization": f"Bearer {self.suno_api_key}", | |
| 'Accept': 'application/json' | |
| } | |
| data = requests.get( | |
| f"https://api.sunoapi.org/api/v1/generate/record-info?taskId={task_id}", headers=headers) | |
| response = json.loads(data.text) | |
| if response["code"] == 200: | |
| data = response['data'] | |
| if data.get('status') == 'SUCCESS': | |
| tracks = data.get('response', {}).get('sunoData', []) | |
| if tracks: | |
| # Return all tracks instead of just the first one | |
| all_tracks = [] | |
| for track in tracks: | |
| audio_url = (track.get('audioUrl') or | |
| track.get('sourceAudioUrl') or | |
| track.get('streamAudioUrl')) | |
| if audio_url: | |
| track_info = { | |
| 'id': track.get('id'), | |
| 'title': track.get('title', 'Untitled'), | |
| 'duration': track.get('duration', 0), | |
| 'tags': track.get('tags', ''), | |
| 'model': track.get('modelName', 'Unknown'), | |
| 'created': track.get('createTime', 'Unknown'), | |
| 'audio_url': audio_url, | |
| 'image_url': track.get('imageUrl') or track.get('sourceImageUrl'), | |
| 'prompt': track.get('prompt', '') | |
| } | |
| all_tracks.append(track_info) | |
| if all_tracks: | |
| return "β Generation complete!", all_tracks, None | |
| else: | |
| return "β No valid audio URLs found", [], None | |
| else: | |
| return "β No tracks found", [], None | |
| elif data.get('status') == 'processing': | |
| return "β³ Still processing... Please wait and check again", [], None | |
| else: | |
| return f"β Generation failed with status: {data.get('status')}", [], None | |
| else: | |
| return f"β Status check failed: {response.text}", [], None | |
| except Exception as e: | |
| return f"β Error checking status: {str(e)}", [], None | |
| # Initialize the generator | |
| generator = SunoMusicGenerator() | |
| # Gradio interface functions | |
| def create_lyrics(theme, genre, mood, verse_count): | |
| return generator.generate_lyrics(theme, genre, mood, verse_count) | |
| def generate_song(lyrics, style, title, custom_mode, instrumental, callback_url=None): | |
| status, task_id, _ = generator.generate_music( | |
| lyrics, style, title, callback_url, custom_mode, instrumental) | |
| return status, task_id | |
| # ======= UI COMPONENTS ========= | |
| def check_status_ui(task_id): | |
| """Updated UI function to handle multiple tracks""" | |
| status, tracks, _ = generator.check_status(task_id) | |
| if tracks and len(tracks) > 0: | |
| # Create HTML for displaying multiple tracks | |
| tracks_html = "<div style='display: flex; flex-direction: column; gap: 20px;'>" | |
| # Audio components for each track | |
| audio_components = [] | |
| for i, track in enumerate(tracks): | |
| # Track info HTML | |
| duration_min = int(track.get('duration', 0) // 60) | |
| duration_sec = int(track.get('duration', 0) % 60) | |
| track_html = f""" | |
| <div style='border: 1px solid #ddd; border-radius: 10px; padding: 15px; background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);'> | |
| <div style='display: flex; gap: 15px; align-items: flex-start;'> | |
| <div style='flex-shrink: 0;'> | |
| <img src="{track.get('image_url', '')}" | |
| alt="Album Cover" | |
| style='width: 120px; height: 120px; border-radius: 8px; object-fit: cover; box-shadow: 0 4px 8px rgba(0,0,0,0.1);' | |
| onerror="this.style.display='none'"> | |
| </div> | |
| <div style='flex-grow: 1;'> | |
| <h3 style='margin: 0 0 10px 0; color: #2c3e50; font-size: 1.2em;'>π΅ {track.get('title', 'Untitled')}</h3> | |
| <div style='display: flex; flex-wrap: wrap; gap: 15px; margin-bottom: 10px;'> | |
| <span style='background: #3498db; color: white; padding: 4px 8px; border-radius: 4px; font-size: 0.8em;'> | |
| β±οΈ {duration_min}:{duration_sec:02d} | |
| </span> | |
| <span style='background: #e74c3c; color: white; padding: 4px 8px; border-radius: 4px; font-size: 0.8em;'> | |
| π€ {track.get('model', 'Unknown')} | |
| </span> | |
| </div> | |
| <div style='margin-bottom: 8px;'> | |
| <strong style='color: #34495e;'>π·οΈ Tags:</strong> | |
| <span style='color: #7f8c8d;'>{track.get('tags', 'No tags')}</span> | |
| </div> | |
| <div style='margin-bottom: 8px;'> | |
| <strong style='color: #34495e;'>π Created:</strong> | |
| <span style='color: #7f8c8d;'>{track.get('created', 'Unknown')}</span> | |
| </div> | |
| <div style='margin-bottom: 8px;'> | |
| <strong style='color: #34495e;'>π Track ID:</strong> | |
| <span style='color: #7f8c8d; font-family: monospace; font-size: 0.9em;'>{track.get('id', 'Unknown')}</span> | |
| </div> | |
| <details style='margin-top: 10px;'> | |
| <summary style='cursor: pointer; color: #3498db; font-weight: bold;'>π View Lyrics/Prompt</summary> | |
| <div style='margin-top: 8px; padding: 10px; background: rgba(255,255,255,0.7); border-radius: 5px; white-space: pre-wrap; font-family: monospace; font-size: 0.9em; max-height: 200px; overflow-y: auto;'> | |
| {track.get('prompt', 'No prompt available')} | |
| </div> | |
| </details> | |
| </div> | |
| </div> | |
| </div> | |
| """ | |
| tracks_html += track_html | |
| # Create audio component for this track | |
| audio_components.append(track.get('audio_url')) | |
| tracks_html += "</div>" | |
| # Return the first audio URL for the main player, and HTML for all tracks | |
| return status, tracks_html, audio_components[0] if audio_components else None, audio_components | |
| else: | |
| return status, "No tracks available", None, [] | |
| # Create Gradio interface | |
| with gr.Blocks(title="π΅ Suno Music Generator with AI Lyrics", theme=gr.themes.Soft()) as app: | |
| gr.Markdown(""" | |
| # π΅ AI Music Generator | |
| ### Create music with AI-generated lyrics using Suno API and Google Gemini | |
| **Steps:** | |
| 1. Configure your API keys | |
| 2. Generate lyrics using AI or write your own | |
| 3. Generate music from lyrics | |
| 4. Download and enjoy your creation! | |
| """) | |
| with gr.Tab("βοΈ Lyrics Generation"): | |
| gr.Markdown("### Generate original lyrics using AI") | |
| with gr.Row(): | |
| with gr.Column(): | |
| theme_input = gr.Textbox( | |
| label="Theme/Topic", | |
| placeholder="e.g., love, adventure, overcoming challenges", | |
| value="friendship and good times" | |
| ) | |
| genre_input = gr.Textbox( | |
| label="Genre", | |
| placeholder="e.g., pop, rock, country, hip-hop", | |
| value="pop" | |
| ) | |
| mood_input = gr.Textbox( | |
| label="Mood", | |
| placeholder="e.g., upbeat, melancholic, energetic", | |
| value="upbeat and joyful" | |
| ) | |
| verse_count = gr.Slider( | |
| label="Number of Verses", | |
| minimum=1, | |
| maximum=4, | |
| value=2, | |
| step=1 | |
| ) | |
| generate_lyrics_btn = gr.Button( | |
| "π Generate Lyrics", variant="primary") | |
| lyrics_output = gr.Textbox( | |
| label="Generated Lyrics", | |
| lines=15, | |
| placeholder="Your AI-generated lyrics will appear here..." | |
| ) | |
| generate_lyrics_btn.click( | |
| create_lyrics, | |
| inputs=[theme_input, genre_input, mood_input, verse_count], | |
| outputs=[lyrics_output] | |
| ) | |
| with gr.Tab("π΅ Music Generation"): | |
| gr.Markdown("### Convert lyrics to music") | |
| with gr.Row(): | |
| with gr.Column(): | |
| lyrics_input = gr.Textbox( | |
| label="Lyrics", | |
| lines=10, | |
| placeholder="Paste your lyrics here or use the generated ones from the previous tab" | |
| ) | |
| style_input = gr.Textbox( | |
| label="Music Style", | |
| placeholder="e.g., acoustic pop, rock ballad, electronic dance", | |
| value="acoustic pop, upbeat, guitar-driven" | |
| ) | |
| title_input = gr.Textbox( | |
| label="Song Title", | |
| placeholder="Enter song title", | |
| value="My AI Song" | |
| ) | |
| # Add webhook URL input | |
| webhook_input = gr.Textbox( | |
| label="Webhook URL (Optional)", | |
| placeholder="e.g., https://your-server.com/webhook/suno", | |
| info="If provided, Suno will send completion notifications to this URL" | |
| ) | |
| with gr.Row(): | |
| custom_mode = gr.Checkbox(label="Custom Mode", value=True) | |
| instrumental = gr.Checkbox( | |
| label="Instrumental Only", value=False) | |
| generate_music_btn = gr.Button( | |
| "π΅ Generate Music", variant="primary") | |
| generation_status = gr.Textbox( | |
| label="Generation Status", interactive=False) | |
| task_id_display = gr.Textbox(label="Task ID", interactive=False) | |
| generate_music_btn.click( | |
| generate_song, | |
| inputs=[lyrics_input, style_input, title_input, | |
| custom_mode, instrumental, webhook_input], | |
| outputs=[generation_status, task_id_display] | |
| ) | |
| with gr.Tab("π₯ Download & Play"): | |
| gr.Markdown("### Check generation status and play your music") | |
| with gr.Row(): | |
| task_id_input = gr.Textbox( | |
| label="Task ID", | |
| placeholder="Enter task ID from generation step" | |
| ) | |
| check_btn = gr.Button("π Check Status", variant="primary") | |
| status_output = gr.Textbox(label="Status", interactive=False) | |
| # Main audio player for the first track | |
| with gr.Row(): | |
| main_audio_player = gr.Audio( | |
| label="π΅ Main Player - First Track", | |
| type="filepath" | |
| ) | |
| # Tracks display area | |
| tracks_display = gr.HTML( | |
| label="πΆ Generated Tracks", | |
| value="<p style='text-align: center; color: #7f8c8d; font-style: italic;'>Track information will appear here after checking status...</p>" | |
| ) | |
| # Additional audio players for multiple tracks | |
| with gr.Accordion("π΅ All Audio Players", open=False): | |
| audio_player_1 = gr.Audio( | |
| label="π΅ Track 1", type="filepath", visible=False) | |
| audio_player_2 = gr.Audio( | |
| label="π΅ Track 2", type="filepath", visible=False) | |
| audio_player_3 = gr.Audio( | |
| label="π΅ Track 3", type="filepath", visible=False) | |
| audio_player_4 = gr.Audio( | |
| label="π΅ Track 4", type="filepath", visible=False) | |
| def update_audio_players(task_id): | |
| status, tracks_html, main_audio, all_audio_urls = check_status_ui( | |
| task_id) | |
| # Update visibility and values for audio players | |
| updates = [] | |
| for i in range(4): | |
| if i < len(all_audio_urls): | |
| updates.append( | |
| gr.update(value=all_audio_urls[i], visible=True)) | |
| else: | |
| updates.append(gr.update(value=None, visible=False)) | |
| return [status, tracks_html, main_audio] + updates | |
| check_btn.click( | |
| update_audio_players, | |
| inputs=[task_id_input], | |
| outputs=[status_output, tracks_display, main_audio_player, | |
| audio_player_1, audio_player_2, audio_player_3, audio_player_4] | |
| ) | |
| # Download section | |
| gr.Markdown("### πΎ Download Options") | |
| with gr.Row(): | |
| download_url_input = gr.Textbox( | |
| label="Audio URL", | |
| placeholder="Paste audio URL to download", | |
| info="Copy the audio URL from the track display above" | |
| ) | |
| download_btn = gr.Button("π₯ Download Audio") | |
| download_status = gr.Textbox( | |
| label="Download Status", interactive=False) | |
| def download_from_url(audio_url): | |
| if not audio_url: | |
| return "β Please provide an audio URL" | |
| try: | |
| # Extract filename from URL or use timestamp | |
| timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") | |
| filename = f"suno_track_{timestamp}.mp3" | |
| downloads_dir = Path("downloads") | |
| downloads_dir.mkdir(exist_ok=True) | |
| filepath = downloads_dir / filename | |
| urllib.request.urlretrieve(audio_url, filepath) | |
| return f"β Downloaded: {filepath}" | |
| except Exception as e: | |
| return f"β Download failed: {str(e)}" | |
| download_btn.click( | |
| download_from_url, | |
| inputs=[download_url_input], | |
| outputs=[download_status] | |
| ) | |
| # Quick copy buttons for audio URLs | |
| gr.Markdown("### π Quick Actions") | |
| gr.Markdown( | |
| "*Right-click on audio players above to copy audio URLs directly*") | |
| with gr.Tab("βΉοΈ Help"): | |
| gr.Markdown(""" | |
| ## How to Use | |
| ### 1. API Configuration | |
| - Get your Suno API key from [SunoAPI.org](https://sunoapi.org) | |
| - Get your Google Gemini API key from [Google AI Studio](https://makersuite.google.com/app/apikey) | |
| - Enter both keys in the API Configuration tab | |
| ### 2. Generate Lyrics | |
| - Use the Lyrics Generation tab to create AI-powered lyrics | |
| - Specify theme, genre, mood, and verse count | |
| - Copy the generated lyrics to use in music generation | |
| ### 3. Generate Music | |
| - Paste your lyrics in the Music Generation tab | |
| - Specify music style (e.g., "acoustic pop, upbeat, guitar-driven") | |
| - Choose custom mode for more control | |
| - Click generate and note the Task ID | |
| ### 4. Download & Play | |
| - Use the Task ID to check generation status | |
| - Once complete, play and download your music | |
| - Files are automatically saved to the downloads folder | |
| ## Tips | |
| - Be specific with music style descriptions | |
| - Use proper lyrics formatting with [Verse], [Chorus] tags | |
| - Generation typically takes 1-3 minutes | |
| - Files are retained for 15 days on Suno's servers | |
| ## Troubleshooting | |
| - Ensure API keys are valid and have sufficient quota | |
| - Check character limits (lyrics: 3000-5000 chars, style: 200-1000 chars) | |
| - Wait for generation to complete before checking status | |
| ## Webhook Support (Updated Format) | |
| - You can provide a callback URL when generating music using the `callback_url` parameter | |
| - Suno will POST a JSON payload to your callback URL when generation completes | |
| - The webhook endpoint for this app is: `<your-gradio-url>/call/webhook/suno` | |
| - Expected webhook payload format: | |
| ```json | |
| { | |
| "success": true, | |
| "task_id": "your_task_id", | |
| "data": [ | |
| { | |
| "id": "track_id", | |
| "title": "Song Title", | |
| "audio_url": "https://...", | |
| "state": "succeeded", | |
| "duration": 120.5, | |
| "tags": "pop, upbeat", | |
| "model": "chirp-v4" | |
| } | |
| ] | |
| } | |
| ``` | |
| """) | |
| # Add webhook endpoint using gr.api() | |
| def webhook_endpoint(callback_data: dict) -> str: | |
| """Webhook endpoint for Suno API callbacks | |
| Expects JSON payload in the format: | |
| { | |
| "success": true, | |
| "task_id": "task_id_here", | |
| "data": [ | |
| { | |
| "id": "track_id", | |
| "title": "Song Title", | |
| "audio_url": "https://...", | |
| "state": "succeeded", | |
| "duration": 120.5, | |
| "tags": "pop, upbeat", | |
| "model": "chirp-v4" | |
| } | |
| ] | |
| } | |
| """ | |
| return generator.handle_webhook(callback_data) | |
| # Register the webhook endpoint | |
| gr.api(webhook_endpoint, api_name="webhook/suno") | |
| # Launch the app | |
| if __name__ == "__main__": | |
| app.launch( | |
| share=True, | |
| server_name="0.0.0.0", | |
| server_port=7860, | |
| show_error=True | |
| ) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment