Skip to content

Instantly share code, notes, and snippets.

@bemijonathan
Created July 18, 2025 12:55
Show Gist options
  • Select an option

  • Save bemijonathan/3edcc701de35aadaa5b54ab5be75dd42 to your computer and use it in GitHub Desktop.

Select an option

Save bemijonathan/3edcc701de35aadaa5b54ab5be75dd42 to your computer and use it in GitHub Desktop.
creating a music generator app
# 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