Phoenixak99 commited on
Commit
fe19106
·
verified ·
1 Parent(s): e880ebe

Upload 7 files

Browse files
Files changed (6) hide show
  1. README.md +65 -7
  2. app.py +0 -0
  3. app_gradio.py +1555 -0
  4. app_gradio_backup.py +879 -0
  5. packages.txt +1 -0
  6. requirements.txt +8 -15
README.md CHANGED
@@ -1,13 +1,71 @@
1
  ---
2
- title: Songlab Melody
3
- emoji: 👀
4
- colorFrom: yellow
5
  colorTo: indigo
6
- sdk: streamlit
7
- sdk_version: 1.38.0
8
- app_file: app.py
9
  pinned: false
10
  license: unknown
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Songlab AI
3
+ emoji: 🎵
4
+ colorFrom: purple
5
  colorTo: indigo
6
+ sdk: gradio
7
+ sdk_version: 4.44.0
8
+ app_file: app_gradio.py
9
  pinned: false
10
  license: unknown
11
  ---
12
 
13
+ # SongLab AI - Music Generation Platform
14
+
15
+ Professional music generation platform with WordPress integration for authentication and credits.
16
+
17
+ ## Features
18
+
19
+ - 🎵 Unlimited music generation (no limits!)
20
+ - 🎤 Vocals and lyrics support
21
+ - 🎚️ Professional audio effects
22
+ - 💳 Credit-based download system
23
+ - 🔐 WordPress SSO authentication
24
+ - ⚡ Gradio-powered modern UI
25
+
26
+ ## Setup
27
+
28
+ ### Environment Variables
29
+
30
+ Required environment variables for deployment:
31
+
32
+ ```bash
33
+ LYRICS_API_URL=<Your lyrics generation API endpoint>
34
+ BEARER_TOKEN=<Your API bearer token>
35
+ ```
36
+
37
+ ### WordPress Integration
38
+
39
+ The app expects JWT token authentication via URL parameters:
40
+
41
+ ```
42
+ https://your-space.hf.space?jwt_token=<TOKEN>&user_id=<USER_ID>
43
+ ```
44
+
45
+ WordPress should send these parameters when embedding the Space.
46
+
47
+ ## Local Development
48
+
49
+ ```bash
50
+ pip install -r requirements.txt
51
+ python app_gradio.py
52
+ ```
53
+
54
+ ## WordPress Plugin
55
+
56
+ A companion WordPress plugin is available to embed this Space seamlessly with automatic authentication.
57
+
58
+ Install the `songlab-ai-embed` plugin and use the shortcode:
59
+
60
+ ```
61
+ [songlab_ai_app]
62
+ ```
63
+
64
+ ## Credits System
65
+
66
+ - **Generation**: Unlimited and free for all users
67
+ - **Downloads**:
68
+ - Tier 1: 10-second downloads (requires 1 Tier 1 credit)
69
+ - Tier 2: 20-second downloads (requires 1 Tier 2 credit)
70
+
71
+ Credits are managed via WordPress and deducted upon download.
app.py CHANGED
The diff for this file is too large to render. See raw diff
 
app_gradio.py ADDED
@@ -0,0 +1,1555 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ SongLab AI - Gradio Interface (Suno AI Style) - COMPLETE VERSION
3
+ Modern, minimalist music generation interface with WordPress integration
4
+ Includes: Music, Vocals, Video Generation, Audio Effects
5
+ """
6
+
7
+ import os
8
+ import io
9
+ import json
10
+ import uuid
11
+ import base64
12
+ import time
13
+ import tempfile
14
+ import subprocess
15
+ import threading
16
+ from typing import Optional, Tuple, Dict, Any, List
17
+ from datetime import datetime
18
+
19
+ import gradio as gr
20
+ import requests
21
+ import numpy as np
22
+ from pydub import AudioSegment
23
+ from pydub.effects import low_pass_filter, high_pass_filter
24
+ from gradio_client import Client
25
+ import jwt
26
+
27
+ try:
28
+ from huggingface_hub import HfApi, upload_file
29
+ HF_AVAILABLE = True
30
+ except ImportError:
31
+ HF_AVAILABLE = False
32
+
33
+ try:
34
+ from gtts import gTTS
35
+ GTTS_AVAILABLE = True
36
+ except ImportError:
37
+ GTTS_AVAILABLE = False
38
+
39
+ # Configuration
40
+ WORDPRESS_BASE_URL = "https://songlabai.com"
41
+ LYRICS_API_URL = os.environ.get("LYRICS_API_URL")
42
+ BEARER_TOKEN = os.environ.get("BEARER_TOKEN")
43
+
44
+ # Video Configuration
45
+ VIDEO_ENDPOINT_URL = os.environ.get("VIDEO_ENDPOINT_URL", "")
46
+ VIDEO_HF_TOKEN = os.environ.get("VIDEO_HF_TOKEN", "")
47
+ VIDEO_REPO_ID = os.environ.get("VIDEO_REPO_ID", "Phoenixak99/savedvideos")
48
+ HF_TOKEN_DOWNLOAD = os.environ.get("HF_TOKEN_DOWNLOAD", "")
49
+
50
+ # Global state management
51
+ user_sessions = {}
52
+
53
+ # Example commercial ads
54
+ EXAMPLE_COMMERCIAL_ADS = {
55
+ "Select an example...": {"jingle": "", "lyrics": "", "video": ""},
56
+ "Luxury Car Launch": {
57
+ "jingle": "cinematic orchestral, powerful strings, epic drums, sophisticated and premium, rising crescendo",
58
+ "lyrics": "Drive the future, feel the power, elegance in motion, every hour",
59
+ "video": "Sleek black luxury sedan driving through mountain roads at golden hour, dramatic low angle shots, cinematic camera movements, reflections on polished surface, mist rolling through valleys"
60
+ },
61
+ "Coffee Shop Morning": {
62
+ "jingle": "acoustic guitar, warm and cozy, jazzy piano, uplifting morning vibes, folk pop",
63
+ "lyrics": "Morning starts with you, fresh and true, every sip a new day, brewing your way",
64
+ "video": "Steaming coffee cup on rustic wooden table by cafe window, warm morning sunlight streaming through, barista pouring latte art, cozy atmosphere with bokeh background"
65
+ },
66
+ "Fitness App Energy": {
67
+ "jingle": "electronic dance, energetic beat, motivational synths, upbeat tempo, powerful bass",
68
+ "lyrics": "Push your limits, break the wall, stronger faster, give your all",
69
+ "video": "Dynamic fitness montage with athletes training, slow motion sweat drops, vibrant gym lighting, rapid cuts between exercises, motivational energy"
70
+ },
71
+ "Gourmet Restaurant": {
72
+ "jingle": "smooth jazz, elegant piano, sophisticated strings, upscale dining ambiance",
73
+ "lyrics": "Taste perfection, pure delight, culinary art, every bite",
74
+ "video": "Close-up of gourmet dish being plated with artistic precision, chef's hands in motion, golden lighting on fine china, wine being poured, elegant restaurant atmosphere"
75
+ },
76
+ "Adventure Travel": {
77
+ "jingle": "world music fusion, adventurous drums, uplifting melodies, wanderlust vibes",
78
+ "lyrics": "Explore the world, discover more, adventure waits, through every door",
79
+ "video": "Epic landscape shots transitioning through mountains, beaches, cities, aerial drone views, backpacker silhouettes at sunset, dynamic travel montage"
80
+ },
81
+ "Tech Innovation": {
82
+ "jingle": "futuristic electronic, pulsing synths, modern tech sounds, innovative vibes",
83
+ "lyrics": "Future is here, innovation clear, technology near, progress we steer",
84
+ "video": "Sleek technology interfaces with holographic displays, circuit board macro shots, blue LED lighting, smooth product reveals, modern minimalist aesthetic"
85
+ },
86
+ "Summer Beach Party": {
87
+ "jingle": "tropical house, beach vibes, summer energy, catchy melody, feel-good beats",
88
+ "lyrics": "Sun and sand, fun at hand, summer days, ocean waves",
89
+ "video": "Beach party scenes with friends laughing, sunset over ocean, volleyball in slow motion, colorful beach umbrellas, festive summer atmosphere"
90
+ },
91
+ "Luxury Perfume": {
92
+ "jingle": "elegant orchestral, mysterious atmosphere, sophisticated strings, sensual rhythm",
93
+ "lyrics": "Essence of elegance, fragrance divine, timeless beauty, forever shine",
94
+ "video": "Perfume bottle rotating on reflective black surface, dramatic lighting with golden highlights, elegant hand reaching for bottle, misty atmospheric effects, luxury aesthetic"
95
+ }
96
+ }
97
+
98
+
99
+ class AudioProcessor:
100
+ """Handles all audio processing operations"""
101
+
102
+ @staticmethod
103
+ def apply_stereo_effect(audio: AudioSegment, separation: int = 30) -> AudioSegment:
104
+ """Apply stereo widening effect"""
105
+ if audio.channels == 1:
106
+ audio = audio.set_channels(2)
107
+
108
+ left = audio.split_to_mono()[0]
109
+ right = audio.split_to_mono()[1] if audio.channels == 2 else left
110
+
111
+ left_filtered = high_pass_filter(left, 200)
112
+ right_filtered = low_pass_filter(right, 8000)
113
+
114
+ return AudioSegment.from_mono_audiosegments(left_filtered, right_filtered)
115
+
116
+ @staticmethod
117
+ def apply_reverse(audio: AudioSegment) -> AudioSegment:
118
+ """Reverse the audio"""
119
+ return audio.reverse()
120
+
121
+ @staticmethod
122
+ def adjust_volume(audio: AudioSegment, db_change: float) -> AudioSegment:
123
+ """Adjust volume by dB"""
124
+ return audio + db_change
125
+
126
+ @staticmethod
127
+ def change_pitch(audio: AudioSegment, semitones: int) -> AudioSegment:
128
+ """Change pitch by semitones"""
129
+ if semitones == 0:
130
+ return audio
131
+ new_sample_rate = int(audio.frame_rate * (2.0 ** (semitones / 12.0)))
132
+ return audio._spawn(audio.raw_data, overrides={'frame_rate': new_sample_rate}).set_frame_rate(audio.frame_rate)
133
+
134
+ @staticmethod
135
+ def trim_audio(audio: AudioSegment, duration_seconds: int) -> AudioSegment:
136
+ """Trim audio to specified duration"""
137
+ return audio[:duration_seconds * 1000]
138
+
139
+
140
+ class WordPressAPI:
141
+ """Handles all WordPress API interactions"""
142
+
143
+ def __init__(self, base_url: str):
144
+ self.base_url = base_url
145
+
146
+ def _get_headers(self, jwt_token: str) -> Dict[str, str]:
147
+ """Generate API headers with JWT token"""
148
+ return {
149
+ "Authorization": f"Bearer {jwt_token}",
150
+ "Content-Type": "application/json",
151
+ "Cache-Control": "no-store"
152
+ }
153
+
154
+ def get_user_credits(self, jwt_token: str) -> Dict[str, int]:
155
+ """Fetch user's audio credits"""
156
+ try:
157
+ response = requests.get(
158
+ f"{self.base_url}/wp-json/custom-api/v1/user-credits-hf",
159
+ headers=self._get_headers(jwt_token),
160
+ timeout=10
161
+ )
162
+ response.raise_for_status()
163
+ data = response.json()
164
+ return {
165
+ 'tier1_credits': data.get('tier1_credits', 0),
166
+ 'tier2_credits': data.get('tier2_credits', 0),
167
+ 'total_credits': data.get('total_credits', 0)
168
+ }
169
+ except Exception as e:
170
+ print(f"Error fetching credits: {e}")
171
+ return {'tier1_credits': 0, 'tier2_credits': 0, 'total_credits': 0}
172
+
173
+ def get_video_credits(self, jwt_token: str) -> Dict[str, Any]:
174
+ """Fetch user's video credits"""
175
+ try:
176
+ response = requests.get(
177
+ f"{self.base_url}/wp-json/video-api/v1/video-credits",
178
+ headers=self._get_headers(jwt_token),
179
+ timeout=10
180
+ )
181
+ response.raise_for_status()
182
+ return response.json()
183
+ except Exception as e:
184
+ print(f"Error fetching video credits: {e}")
185
+ return {}
186
+
187
+ def check_video_eligibility(self, jwt_token: str) -> Dict[str, Any]:
188
+ """Check if user can generate videos"""
189
+ try:
190
+ response = requests.post(
191
+ f"{self.base_url}/wp-json/video-api/v1/check-eligibility",
192
+ headers=self._get_headers(jwt_token),
193
+ timeout=10
194
+ )
195
+ response.raise_for_status()
196
+ return response.json()
197
+ except Exception as e:
198
+ print(f"Error checking video eligibility: {e}")
199
+ return {'can_generate': False, 'reason': 'exception'}
200
+
201
+ def use_video_credit(self, jwt_token: str, generation_data: Dict[str, Any]) -> bool:
202
+ """Deduct video credit after successful generation"""
203
+ try:
204
+ response = requests.post(
205
+ f"{self.base_url}/wp-json/video-api/v1/use-video-credit",
206
+ headers=self._get_headers(jwt_token),
207
+ json=generation_data,
208
+ timeout=10
209
+ )
210
+ response.raise_for_status()
211
+ return True
212
+ except Exception as e:
213
+ print(f"Error using video credit: {e}")
214
+ return False
215
+
216
+ def use_audio_credit(self, jwt_token: str, tier: int) -> bool:
217
+ """Deduct an audio credit"""
218
+ try:
219
+ response = requests.post(
220
+ f"{self.base_url}/wp-json/custom-api/v1/use-credit-hf",
221
+ headers=self._get_headers(jwt_token),
222
+ json={'tier': tier},
223
+ timeout=10
224
+ )
225
+ response.raise_for_status()
226
+ return response.json().get('success', False)
227
+ except Exception as e:
228
+ print(f"Error using credit: {e}")
229
+ return False
230
+
231
+ def upload_track(self, jwt_token: str, audio_bytes: bytes, metadata: Dict[str, Any]) -> bool:
232
+ """Upload track to WordPress"""
233
+ try:
234
+ # Decode JWT to get user info
235
+ decoded = jwt.decode(jwt_token, options={"verify_signature": False})
236
+ user_info = decoded.get('data', {}).get('user', {})
237
+
238
+ files = {'audio_file': ('track.wav', audio_bytes, 'audio/wav')}
239
+ data = {
240
+ 'user_id': str(user_info.get('id', '')),
241
+ 'user_email': user_info.get('user_email', ''),
242
+ 'user_name': user_info.get('display_name', ''),
243
+ 'subscription_tier': 'free',
244
+ 'saved_by_user': '1',
245
+ **metadata
246
+ }
247
+
248
+ response = requests.post(
249
+ f"{self.base_url}/wp-admin/admin-ajax.php?action=upload_audio_direct",
250
+ files=files,
251
+ data=data,
252
+ headers={"Authorization": f"Bearer {jwt_token}"},
253
+ timeout=30
254
+ )
255
+ response.raise_for_status()
256
+ return True
257
+ except Exception as e:
258
+ print(f"Error uploading track: {e}")
259
+ return False
260
+
261
+
262
+ class MusicGenerator:
263
+ """Handles music generation via MusicGen"""
264
+
265
+ def __init__(self):
266
+ self.client = None
267
+ self.space_name = "Healthydater/musicgen"
268
+
269
+ def get_client(self) -> Client:
270
+ """Get or create MusicGen client with retry logic"""
271
+ if self.client is None:
272
+ for attempt in range(3):
273
+ try:
274
+ self.client = Client(self.space_name)
275
+ return self.client
276
+ except Exception as e:
277
+ if attempt < 2:
278
+ time.sleep(120)
279
+ else:
280
+ raise Exception(f"Failed to connect to MusicGen: {e}")
281
+ return self.client
282
+
283
+ def generate_music(self, prompt: str, duration: float, progress=gr.Progress()) -> Tuple[int, np.ndarray]:
284
+ """Generate music from prompt"""
285
+ try:
286
+ progress(0.1, desc="Connecting to MusicGen...")
287
+ client = self.get_client()
288
+
289
+ progress(0.3, desc="Generating music...")
290
+ result = client.predict(
291
+ prompt=prompt,
292
+ duration=duration,
293
+ temperature=1.0,
294
+ top_k=250,
295
+ top_p=0.0,
296
+ cfg_coef=3.0,
297
+ use_sampling=True,
298
+ extend_stride=18.0,
299
+ api_name="/predict"
300
+ )
301
+
302
+ progress(0.9, desc="Processing audio...")
303
+ # Result is a file path
304
+ audio = AudioSegment.from_file(result)
305
+ samples = np.array(audio.get_array_of_samples()).astype(np.float32) / 32768.0
306
+
307
+ if audio.channels == 2:
308
+ samples = samples.reshape((-1, 2))
309
+
310
+ return audio.frame_rate, samples
311
+ except Exception as e:
312
+ raise Exception(f"Generation failed: {str(e)}")
313
+
314
+ def generate_with_lyrics(self, prompt: str, lyrics: str, duration: float, progress=gr.Progress()) -> Tuple[int, np.ndarray]:
315
+ """Generate music with vocals/lyrics"""
316
+ if not LYRICS_API_URL or not BEARER_TOKEN:
317
+ raise Exception("Lyrics API not configured")
318
+
319
+ try:
320
+ progress(0.1, desc="Preparing lyrics generation...")
321
+
322
+ payload = {
323
+ "inputs": {
324
+ "prompt": prompt,
325
+ "lyrics": lyrics
326
+ },
327
+ "parameters": {
328
+ "duration": duration,
329
+ "infer_step": 60,
330
+ "guidance_scale": 15,
331
+ "scheduler_type": "euler",
332
+ "cfg_type": "apg",
333
+ "omega_scale": 10
334
+ }
335
+ }
336
+
337
+ progress(0.3, desc="Generating vocals...")
338
+ response = requests.post(
339
+ LYRICS_API_URL,
340
+ headers={
341
+ "Authorization": f"Bearer {BEARER_TOKEN}",
342
+ "Content-Type": "application/json"
343
+ },
344
+ json=payload,
345
+ timeout=300
346
+ )
347
+ response.raise_for_status()
348
+
349
+ progress(0.9, desc="Processing audio...")
350
+ audio_bytes = response.content
351
+ audio = AudioSegment.from_file(io.BytesIO(audio_bytes))
352
+ samples = np.array(audio.get_array_of_samples()).astype(np.float32) / 32768.0
353
+
354
+ if audio.channels == 2:
355
+ samples = samples.reshape((-1, 2))
356
+
357
+ return audio.frame_rate, samples
358
+ except Exception as e:
359
+ raise Exception(f"Lyrics generation failed: {str(e)}")
360
+
361
+
362
+ class VideoGenerator:
363
+ """Handles video generation with retry logic"""
364
+
365
+ def __init__(self):
366
+ self.api = HfApi() if HF_AVAILABLE else None
367
+
368
+ def calculate_temp(self, duration: float, fps: int) -> int:
369
+ """Calculate temp value based on duration and FPS"""
370
+ # Reference: 31 temp = 10s at 24fps (240 frames)
371
+ return int((duration * fps * 31) / 240)
372
+
373
+ def warmup_endpoint(self):
374
+ """Warmup video endpoint (non-blocking)"""
375
+ try:
376
+ warmup_payload = {
377
+ "inputs": {
378
+ "prompt": "warmup",
379
+ "mode": "text_to_video",
380
+ "width": 640,
381
+ "height": 384,
382
+ "temp": 16,
383
+ "guidance_scale": 7.0,
384
+ "video_guidance_scale": 5.0,
385
+ "num_inference_steps": [5, 5, 5],
386
+ "video_num_inference_steps": [3, 3, 3],
387
+ "fps": 24
388
+ }
389
+ }
390
+ headers = {
391
+ "Authorization": f"Bearer {VIDEO_HF_TOKEN}",
392
+ "Content-Type": "application/json"
393
+ }
394
+ requests.post(VIDEO_ENDPOINT_URL, headers=headers, json=warmup_payload, timeout=10)
395
+ except:
396
+ pass # Silent fail
397
+
398
+ def generate_video(self, mode: str, prompt: str, duration: float, fps: int,
399
+ guidance_scale: float, video_guidance_scale: float,
400
+ image_base64: Optional[str] = None, progress=gr.Progress()) -> Optional[str]:
401
+ """Generate video with retry logic"""
402
+
403
+ if not VIDEO_ENDPOINT_URL or not VIDEO_HF_TOKEN:
404
+ raise Exception("Video generation not configured")
405
+
406
+ temp_value = self.calculate_temp(duration, fps)
407
+
408
+ payload = {
409
+ "inputs": {
410
+ "prompt": prompt,
411
+ "mode": mode,
412
+ "width": 1280,
413
+ "height": 768,
414
+ "temp": temp_value,
415
+ "guidance_scale": guidance_scale,
416
+ "video_guidance_scale": video_guidance_scale,
417
+ "num_inference_steps": [20, 20, 20],
418
+ "video_num_inference_steps": [10, 10, 10],
419
+ "fps": fps
420
+ }
421
+ }
422
+
423
+ if mode == "image_to_video" and image_base64:
424
+ payload["inputs"]["image"] = image_base64
425
+
426
+ headers = {
427
+ "Authorization": f"Bearer {VIDEO_HF_TOKEN}",
428
+ "Content-Type": "application/json"
429
+ }
430
+
431
+ # Retry logic for cold starts
432
+ max_retries = 5
433
+ retry_delay = 60
434
+
435
+ for attempt in range(max_retries):
436
+ try:
437
+ progress(0.1 + (attempt * 0.1), desc=f"Sending request (attempt {attempt + 1}/{max_retries})...")
438
+
439
+ response = requests.post(VIDEO_ENDPOINT_URL, headers=headers, json=payload, timeout=60)
440
+
441
+ if response.status_code == 503:
442
+ if attempt < max_retries - 1:
443
+ progress(0.2, desc=f"Service warming up... waiting {retry_delay}s")
444
+ time.sleep(retry_delay)
445
+ continue
446
+ else:
447
+ raise Exception(f"Service failed to warm up after {max_retries} attempts")
448
+ elif response.status_code != 200:
449
+ raise Exception(f"HTTP {response.status_code}: {response.text}")
450
+
451
+ # Success - now monitor for completion
452
+ break
453
+
454
+ except requests.exceptions.Timeout:
455
+ if attempt < max_retries - 1:
456
+ progress(0.2, desc=f"Request timeout, retrying...")
457
+ time.sleep(retry_delay)
458
+ else:
459
+ raise Exception("Request timed out after all retries")
460
+
461
+ # Monitor for video completion
462
+ return self.monitor_video_completion(duration, fps, progress)
463
+
464
+ def monitor_video_completion(self, duration: float, fps: int, progress=gr.Progress()) -> Optional[str]:
465
+ """Monitor HuggingFace dataset for video completion"""
466
+
467
+ if not self.api or not HF_TOKEN_DOWNLOAD:
468
+ raise Exception("HuggingFace API not available")
469
+
470
+ # Get initial file count
471
+ try:
472
+ files = self.api.list_repo_files(
473
+ repo_id=VIDEO_REPO_ID,
474
+ repo_type="dataset",
475
+ token=HF_TOKEN_DOWNLOAD
476
+ )
477
+ initial_count = len([f for f in files if f.endswith('.mp4')])
478
+ except Exception as e:
479
+ raise Exception(f"Failed to access video repository: {e}")
480
+
481
+ # Calculate timeout
482
+ estimated_minutes = (duration * fps) / 16
483
+ max_wait_seconds = int((estimated_minutes + 3) * 60 * 1.2)
484
+ check_interval = 10
485
+ start_time = time.time()
486
+
487
+ progress(0.3, desc=f"Generating video (est: ~{int(estimated_minutes)} min)...")
488
+
489
+ while True:
490
+ elapsed = time.time() - start_time
491
+
492
+ if elapsed > max_wait_seconds:
493
+ raise Exception(f"Video generation timeout ({int(max_wait_seconds/60)} minutes)")
494
+
495
+ # Update progress
496
+ progress_pct = min(0.3 + (elapsed / (max_wait_seconds * 0.95)) * 0.6, 0.9)
497
+ progress(progress_pct, desc=f"Generating... {elapsed/60:.1f} min elapsed")
498
+
499
+ # Check for new video
500
+ try:
501
+ files = self.api.list_repo_files(
502
+ repo_id=VIDEO_REPO_ID,
503
+ repo_type="dataset",
504
+ token=HF_TOKEN_DOWNLOAD
505
+ )
506
+ video_files = [f for f in files if f.endswith('.mp4')]
507
+
508
+ if len(video_files) > initial_count:
509
+ # New video detected
510
+ latest_video = sorted(video_files)[-1]
511
+ video_url = f"https://huggingface.co/datasets/{VIDEO_REPO_ID}/resolve/main/{latest_video}"
512
+
513
+ progress(0.95, desc="Downloading video...")
514
+
515
+ # Download video
516
+ download_headers = {"Authorization": f"Bearer {HF_TOKEN_DOWNLOAD}"}
517
+ video_response = requests.get(video_url, headers=download_headers, timeout=120)
518
+
519
+ if video_response.status_code == 200:
520
+ # Save to temp file
521
+ with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as f:
522
+ f.write(video_response.content)
523
+ progress(1.0, desc="Video complete!")
524
+ return f.name
525
+
526
+ raise Exception("Failed to download video")
527
+
528
+ except Exception as e:
529
+ print(f"Error checking for video: {e}")
530
+
531
+ time.sleep(check_interval)
532
+
533
+ def merge_audio_video(self, video_path: str, audio_path: str) -> str:
534
+ """Merge jingle audio with video using FFmpeg"""
535
+ output_path = tempfile.mktemp(suffix="_final.mp4")
536
+
537
+ cmd = [
538
+ "ffmpeg", "-y",
539
+ "-i", video_path,
540
+ "-i", audio_path,
541
+ "-c:v", "copy",
542
+ "-c:a", "aac",
543
+ "-b:a", "192k",
544
+ "-shortest",
545
+ output_path
546
+ ]
547
+
548
+ result = subprocess.run(cmd, capture_output=True, text=True)
549
+
550
+ if result.returncode != 0:
551
+ raise Exception(f"FFmpeg merge failed: {result.stderr}")
552
+
553
+ return output_path
554
+
555
+ def generate_intro_with_voiceover(self, intro_prompt: str, company_name: str,
556
+ progress=gr.Progress()) -> Optional[str]:
557
+ """Generate 5-second intro with voiceover"""
558
+
559
+ # Generate 5s intro video at 8fps
560
+ progress(0.1, desc="Generating intro video...")
561
+ intro_path = self.generate_video(
562
+ mode="text_to_video",
563
+ prompt=intro_prompt,
564
+ duration=5.0,
565
+ fps=8,
566
+ guidance_scale=7.0,
567
+ video_guidance_scale=5.0,
568
+ progress=progress
569
+ )
570
+
571
+ if not intro_path or not GTTS_AVAILABLE or not company_name:
572
+ return intro_path
573
+
574
+ # Generate voiceover
575
+ try:
576
+ progress(0.8, desc="Adding voiceover...")
577
+ tts = gTTS(text=company_name, lang='en', slow=False)
578
+ voiceover_path = tempfile.mktemp(suffix="_voice.mp3")
579
+ tts.save(voiceover_path)
580
+
581
+ # Merge intro + voiceover
582
+ output_path = tempfile.mktemp(suffix="_intro_final.mp4")
583
+
584
+ cmd = [
585
+ 'ffmpeg', '-y',
586
+ '-i', intro_path,
587
+ '-i', voiceover_path,
588
+ '-c:v', 'copy',
589
+ '-c:a', 'aac',
590
+ '-b:a', '192k',
591
+ '-filter_complex', '[1:a]afade=t=in:st=0:d=0.5,afade=t=out:st=4.5:d=0.5[a]',
592
+ '-map', '0:v',
593
+ '-map', '[a]',
594
+ '-shortest',
595
+ output_path
596
+ ]
597
+
598
+ result = subprocess.run(cmd, capture_output=True, text=True)
599
+
600
+ if result.returncode == 0:
601
+ os.remove(intro_path)
602
+ os.remove(voiceover_path)
603
+ return output_path
604
+
605
+ except Exception as e:
606
+ print(f"Voiceover generation failed: {e}")
607
+
608
+ return intro_path
609
+
610
+ def concatenate_videos(self, intro_path: str, main_path: str) -> str:
611
+ """Concatenate intro + main video"""
612
+ concat_list = tempfile.mktemp(suffix="_concat.txt")
613
+ output_path = tempfile.mktemp(suffix="_combined.mp4")
614
+
615
+ with open(concat_list, 'w') as f:
616
+ f.write(f"file '{intro_path}'\n")
617
+ f.write(f"file '{main_path}'\n")
618
+
619
+ cmd = [
620
+ 'ffmpeg', '-y',
621
+ '-f', 'concat',
622
+ '-safe', '0',
623
+ '-i', concat_list,
624
+ '-c', 'copy',
625
+ output_path
626
+ ]
627
+
628
+ result = subprocess.run(cmd, capture_output=True, text=True)
629
+
630
+ if result.returncode != 0:
631
+ raise Exception(f"Video concatenation failed: {result.stderr}")
632
+
633
+ os.remove(concat_list)
634
+ return output_path
635
+
636
+
637
+ # Initialize global instances
638
+ wp_api = WordPressAPI(WORDPRESS_BASE_URL)
639
+ music_gen = MusicGenerator()
640
+ audio_processor = AudioProcessor()
641
+ video_gen = VideoGenerator()
642
+
643
+
644
+ def get_session(session_id: str) -> Dict[str, Any]:
645
+ """Get or create user session"""
646
+ if session_id not in user_sessions:
647
+ user_sessions[session_id] = {
648
+ 'jwt_token': None,
649
+ 'user_info': None,
650
+ 'credits': {'tier1_credits': 0, 'tier2_credits': 0, 'total_credits': 0},
651
+ 'video_credits': {},
652
+ 'generated_audio': None,
653
+ 'processed_audio': None,
654
+ 'metadata': {},
655
+ 'video_path': None,
656
+ 'jingle_path': None
657
+ }
658
+ return user_sessions[session_id]
659
+
660
+
661
+ def authenticate_user(jwt_token: str, user_id: str, session_id: str) -> Tuple[str, str, str]:
662
+ """Authenticate user via JWT token from WordPress"""
663
+ if not jwt_token or jwt_token == "null":
664
+ return (
665
+ "⚠️ Not Logged In",
666
+ "Please log in to WordPress to use SongLab AI",
667
+ "Login required to access generation features"
668
+ )
669
+
670
+ try:
671
+ # Decode JWT (without verification for client-side)
672
+ decoded = jwt.decode(jwt_token, options={"verify_signature": False})
673
+ user_info = decoded.get('data', {}).get('user', {})
674
+
675
+ # Store in session
676
+ session = get_session(session_id)
677
+ session['jwt_token'] = jwt_token
678
+ session['user_info'] = user_info
679
+
680
+ # Fetch credits
681
+ credits = wp_api.get_user_credits(jwt_token)
682
+ session['credits'] = credits
683
+
684
+ # Fetch video credits
685
+ video_credits = wp_api.get_video_credits(jwt_token)
686
+ session['video_credits'] = video_credits
687
+
688
+ display_name = user_info.get('display_name', 'User')
689
+ credit_info = f"Audio: {credits['tier1_credits']} (10s) | {credits['tier2_credits']} (20s) | Video: {video_credits.get('remaining_credits', 0)}"
690
+
691
+ return (
692
+ f"✓ Logged in as {display_name}",
693
+ credit_info,
694
+ f"Welcome, {display_name}! You have unlimited generations."
695
+ )
696
+ except Exception as e:
697
+ return (
698
+ "⚠️ Authentication Error",
699
+ str(e),
700
+ "Please refresh and try again"
701
+ )
702
+
703
+
704
+ def format_prompt(genre: str, energy: str, tempo: int, description: str) -> str:
705
+ """Format generation prompt"""
706
+ return f"Genre: {genre}, Energy Level: {energy}, Tempo: {tempo} bpm, Description: {description}"
707
+
708
+
709
+ def generate_track(genre: str, energy: str, tempo: int, description: str, duration: int, session_id: str, progress=gr.Progress()) -> Tuple[Any, str, str]:
710
+ """Generate music track"""
711
+ session = get_session(session_id)
712
+
713
+ if not session['jwt_token']:
714
+ return None, "❌ Please log in first", ""
715
+
716
+ if not description or len(description.strip()) < 10:
717
+ return None, "❌ Please provide a detailed description (at least 10 characters)", ""
718
+
719
+ try:
720
+ prompt = format_prompt(genre, energy, tempo, description)
721
+ sample_rate, audio_data = music_gen.generate_music(prompt, float(duration), progress)
722
+
723
+ # Convert to AudioSegment for storage
724
+ audio_bytes = io.BytesIO()
725
+ audio_int = (audio_data * 32768).astype(np.int16)
726
+ audio_seg = AudioSegment(
727
+ audio_int.tobytes(),
728
+ frame_rate=sample_rate,
729
+ sample_width=2,
730
+ channels=2 if len(audio_int.shape) > 1 else 1
731
+ )
732
+ audio_seg.export(audio_bytes, format="wav")
733
+
734
+ # Store in session
735
+ session['generated_audio'] = audio_seg
736
+ session['metadata'] = {
737
+ 'genre': genre,
738
+ 'energy': energy,
739
+ 'tempo': tempo,
740
+ 'description': description,
741
+ 'duration': duration
742
+ }
743
+
744
+ return (sample_rate, audio_data), "✓ Generation complete!", f"Generated {duration}s track: {genre} ({energy} energy, {tempo} bpm)"
745
+ except Exception as e:
746
+ return None, f"❌ Generation failed: {str(e)}", ""
747
+
748
+
749
+ def generate_with_vocals(genre: str, energy: str, tempo: int, description: str, lyrics: str, duration: int, session_id: str, progress=gr.Progress()) -> Tuple[Any, str, str]:
750
+ """Generate track with vocals"""
751
+ session = get_session(session_id)
752
+
753
+ if not session['jwt_token']:
754
+ return None, "❌ Please log in first", ""
755
+
756
+ if not description or not lyrics:
757
+ return None, "❌ Please provide both description and lyrics", ""
758
+
759
+ try:
760
+ prompt = format_prompt(genre, energy, tempo, description)
761
+ sample_rate, audio_data = music_gen.generate_with_lyrics(prompt, lyrics, float(duration), progress)
762
+
763
+ # Convert to AudioSegment
764
+ audio_bytes = io.BytesIO()
765
+ audio_int = (audio_data * 32768).astype(np.int16)
766
+ audio_seg = AudioSegment(
767
+ audio_int.tobytes(),
768
+ frame_rate=sample_rate,
769
+ sample_width=2,
770
+ channels=2 if len(audio_int.shape) > 1 else 1
771
+ )
772
+ audio_seg.export(audio_bytes, format="wav")
773
+
774
+ session['generated_audio'] = audio_seg
775
+ session['metadata'] = {
776
+ 'genre': genre,
777
+ 'energy': energy,
778
+ 'tempo': tempo,
779
+ 'description': description,
780
+ 'lyrics': lyrics,
781
+ 'duration': duration
782
+ }
783
+
784
+ return (sample_rate, audio_data), "✓ Generation with vocals complete!", f"Generated {duration}s track with vocals"
785
+ except Exception as e:
786
+ return None, f"❌ Generation failed: {str(e)}", ""
787
+
788
+
789
+ def download_track(tier: int, session_id: str) -> Tuple[Optional[str], str]:
790
+ """Download track with credit deduction"""
791
+ session = get_session(session_id)
792
+
793
+ if not session['jwt_token']:
794
+ return None, "❌ Please log in first"
795
+
796
+ if session['generated_audio'] is None:
797
+ return None, "❌ No track generated yet"
798
+
799
+ # Check credits
800
+ credits = session['credits']
801
+ tier_key = f'tier{tier}_credits'
802
+
803
+ if credits.get(tier_key, 0) <= 0:
804
+ return None, f"❌ No credits available for Tier {tier}. Please purchase more credits."
805
+
806
+ # Deduct credit
807
+ if not wp_api.use_audio_credit(session['jwt_token'], tier):
808
+ return None, "❌ Failed to deduct credit. Please try again."
809
+
810
+ # Trim audio
811
+ duration = 10 if tier == 1 else 20
812
+ trimmed_audio = audio_processor.trim_audio(session['generated_audio'], duration)
813
+
814
+ # Export to file
815
+ output_path = f"/tmp/songlab_track_{uuid.uuid4().hex}_{duration}s.wav"
816
+ trimmed_audio.export(output_path, format="wav")
817
+
818
+ # Update credits
819
+ session['credits'][tier_key] -= 1
820
+
821
+ return output_path, f"✓ Downloaded {duration}s track! {credits[tier_key] - 1} credits remaining."
822
+
823
+
824
+ def apply_effects(stereo: bool, reverse: bool, volume: float, pitch: int, session_id: str) -> Tuple[Any, str]:
825
+ """Apply audio effects"""
826
+ session = get_session(session_id)
827
+
828
+ if session['generated_audio'] is None:
829
+ return None, "❌ No track generated yet"
830
+
831
+ try:
832
+ audio = session['generated_audio']
833
+
834
+ if stereo:
835
+ audio = audio_processor.apply_stereo_effect(audio)
836
+
837
+ if reverse:
838
+ audio = audio_processor.apply_reverse(audio)
839
+
840
+ if volume != 0:
841
+ audio = audio_processor.adjust_volume(audio, volume)
842
+
843
+ if pitch != 0:
844
+ audio = audio_processor.change_pitch(audio, pitch)
845
+
846
+ session['processed_audio'] = audio
847
+
848
+ # Convert to numpy for playback
849
+ samples = np.array(audio.get_array_of_samples()).astype(np.float32) / 32768.0
850
+ if audio.channels == 2:
851
+ samples = samples.reshape((-1, 2))
852
+
853
+ return (audio.frame_rate, samples), "✓ Effects applied!"
854
+ except Exception as e:
855
+ return None, f"❌ Effect processing failed: {str(e)}"
856
+
857
+
858
+ def generate_commercial_ad(
859
+ mode: str,
860
+ video_prompt: str,
861
+ jingle_prompt: str,
862
+ jingle_lyrics: str,
863
+ uploaded_image: Any,
864
+ duration: int,
865
+ fps: int,
866
+ guidance_scale: float,
867
+ video_guidance_scale: float,
868
+ is_custom: bool,
869
+ add_intro: bool,
870
+ intro_prompt: str,
871
+ company_name: str,
872
+ session_id: str,
873
+ progress=gr.Progress()
874
+ ) -> Tuple[Optional[str], str]:
875
+ """Generate commercial ad (video, jingle+video, or image-to-video)"""
876
+
877
+ session = get_session(session_id)
878
+
879
+ if not session['jwt_token']:
880
+ return None, "❌ Please log in first"
881
+
882
+ # Check eligibility
883
+ eligibility = wp_api.check_video_eligibility(session['jwt_token'])
884
+ if not eligibility.get('can_generate', False):
885
+ reason = eligibility.get('reason', 'unknown')
886
+ return None, f"❌ Cannot generate video: {reason}. Please purchase credits."
887
+
888
+ try:
889
+ video_path = None
890
+ jingle_path = None
891
+
892
+ # Generate jingle if needed
893
+ if mode == "jingle_video":
894
+ if not jingle_prompt:
895
+ return None, "❌ Please provide jingle description"
896
+
897
+ progress(0.1, desc="Generating jingle...")
898
+
899
+ # Generate jingle with retry logic
900
+ max_retries = 5
901
+ retry_delay = 60
902
+
903
+ for attempt in range(max_retries):
904
+ try:
905
+ jingle_payload = {
906
+ "inputs": {"prompt": jingle_prompt, "lyrics": jingle_lyrics or ""},
907
+ "parameters": {
908
+ "duration": float(duration),
909
+ "infer_step": 60,
910
+ "guidance_scale": 15,
911
+ "scheduler_type": "euler",
912
+ "cfg_type": "apg",
913
+ "omega_scale": 10
914
+ }
915
+ }
916
+
917
+ response = requests.post(
918
+ LYRICS_API_URL,
919
+ headers={"Authorization": f"Bearer {BEARER_TOKEN}", "Content-Type": "application/json"},
920
+ json=jingle_payload,
921
+ timeout=180
922
+ )
923
+
924
+ if response.status_code == 200:
925
+ jingle_path = tempfile.mktemp(suffix=".wav")
926
+ with open(jingle_path, 'wb') as f:
927
+ f.write(response.content)
928
+ break
929
+ elif response.status_code == 503 and attempt < max_retries - 1:
930
+ progress(0.15, desc=f"Jingle service warming up... waiting {retry_delay}s")
931
+ time.sleep(retry_delay)
932
+ else:
933
+ raise Exception(f"Jingle API error: HTTP {response.status_code}")
934
+
935
+ except requests.exceptions.Timeout:
936
+ if attempt < max_retries - 1:
937
+ progress(0.15, desc=f"Timeout, retrying...")
938
+ time.sleep(retry_delay)
939
+ else:
940
+ raise Exception("Jingle generation timed out")
941
+
942
+ progress(0.3, desc="Jingle complete! Starting video...")
943
+
944
+ # Warmup video endpoint in background
945
+ warmup_thread = threading.Thread(target=video_gen.warmup_endpoint)
946
+ warmup_thread.daemon = True
947
+ warmup_thread.start()
948
+
949
+ # Generate intro if Expert tier
950
+ intro_path = None
951
+ if add_intro and intro_prompt:
952
+ progress(0.35, desc="Generating 5s intro...")
953
+ intro_path = video_gen.generate_intro_with_voiceover(intro_prompt, company_name, progress)
954
+ progress(0.5, desc="Intro complete! Generating main video...")
955
+
956
+ # Generate main video
957
+ if mode == "text_to_video" or mode == "jingle_video":
958
+ if not video_prompt:
959
+ return None, "❌ Please provide video description"
960
+
961
+ video_path = video_gen.generate_video(
962
+ mode="text_to_video",
963
+ prompt=video_prompt,
964
+ duration=float(duration),
965
+ fps=fps,
966
+ guidance_scale=guidance_scale,
967
+ video_guidance_scale=video_guidance_scale,
968
+ progress=progress
969
+ )
970
+
971
+ elif mode == "image_to_video":
972
+ if not uploaded_image:
973
+ return None, "❌ Please upload an image"
974
+
975
+ # Convert uploaded image to base64
976
+ image_bytes = uploaded_image.read() if hasattr(uploaded_image, 'read') else uploaded_image
977
+ image_base64 = base64.b64encode(image_bytes).decode('utf-8')
978
+
979
+ video_path = video_gen.generate_video(
980
+ mode="image_to_video",
981
+ prompt=video_prompt,
982
+ duration=float(duration),
983
+ fps=fps,
984
+ guidance_scale=guidance_scale,
985
+ video_guidance_scale=video_guidance_scale,
986
+ image_base64=image_base64,
987
+ progress=progress
988
+ )
989
+
990
+ if not video_path:
991
+ return None, "❌ Video generation failed"
992
+
993
+ # Combine intro + main if needed
994
+ if intro_path:
995
+ progress(0.95, desc="Combining intro + main video...")
996
+ video_path = video_gen.concatenate_videos(intro_path, video_path)
997
+
998
+ # Merge jingle + video if needed
999
+ if jingle_path:
1000
+ progress(0.98, desc="Merging jingle with video...")
1001
+ video_path = video_gen.merge_audio_video(video_path, jingle_path)
1002
+
1003
+ # Deduct credit
1004
+ generation_data = {
1005
+ 'mode': mode,
1006
+ 'duration': duration,
1007
+ 'fps': fps,
1008
+ 'guidance_scale': guidance_scale,
1009
+ 'video_guidance_scale': video_guidance_scale,
1010
+ 'is_preset': not is_custom,
1011
+ 'is_expert_intro': add_intro,
1012
+ 'prompt': video_prompt
1013
+ }
1014
+
1015
+ wp_api.use_video_credit(session['jwt_token'], generation_data)
1016
+
1017
+ progress(1.0, desc="Complete!")
1018
+ return video_path, f"✓ {'Expert commercial' if add_intro else 'Commercial ad'} generated successfully!"
1019
+
1020
+ except Exception as e:
1021
+ return None, f"❌ Generation failed: {str(e)}"
1022
+
1023
+
1024
+ def load_example_ad(example_name: str) -> Tuple[str, str, str]:
1025
+ """Load example commercial ad"""
1026
+ if example_name in EXAMPLE_COMMERCIAL_ADS:
1027
+ example = EXAMPLE_COMMERCIAL_ADS[example_name]
1028
+ return example["jingle"], example["lyrics"], example["video"]
1029
+ return "", "", ""
1030
+
1031
+
1032
+ # Custom CSS for Suno-style theming
1033
+ custom_css = """
1034
+ /* Suno AI Dark Theme */
1035
+ .gradio-container {
1036
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
1037
+ }
1038
+
1039
+ /* Dark background */
1040
+ body, .gradio-container {
1041
+ background-color: #212126 !important;
1042
+ }
1043
+
1044
+ /* Card styling */
1045
+ .gr-box, .gr-form, .gr-panel {
1046
+ background-color: #26262B !important;
1047
+ border: 1px solid #3a3a3f !important;
1048
+ border-radius: 12px !important;
1049
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3) !important;
1050
+ }
1051
+
1052
+ /* Input fields */
1053
+ .gr-input, .gr-text-input, .gr-text-area, .gr-dropdown {
1054
+ background-color: #1a1a1e !important;
1055
+ border: 1px solid #3a3a3f !important;
1056
+ border-radius: 8px !important;
1057
+ color: #ffffff !important;
1058
+ }
1059
+
1060
+ /* Buttons */
1061
+ .gr-button {
1062
+ background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%) !important;
1063
+ border: none !important;
1064
+ border-radius: 8px !important;
1065
+ color: white !important;
1066
+ font-weight: 600 !important;
1067
+ padding: 12px 24px !important;
1068
+ transition: all 0.3s ease !important;
1069
+ }
1070
+
1071
+ .gr-button:hover {
1072
+ transform: translateY(-2px) !important;
1073
+ box-shadow: 0 6px 12px rgba(99, 102, 241, 0.4) !important;
1074
+ }
1075
+
1076
+ .gr-button-primary {
1077
+ background: linear-gradient(135deg, #10b981 0%, #059669 100%) !important;
1078
+ }
1079
+
1080
+ .gr-button-secondary {
1081
+ background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%) !important;
1082
+ }
1083
+
1084
+ /* Sliders */
1085
+ .gr-slider input[type="range"] {
1086
+ background: #3a3a3f !important;
1087
+ }
1088
+
1089
+ .gr-slider input[type="range"]::-webkit-slider-thumb {
1090
+ background: #6366f1 !important;
1091
+ }
1092
+
1093
+ /* Audio/Video players */
1094
+ audio, video {
1095
+ width: 100% !important;
1096
+ background-color: #1a1a1e !important;
1097
+ border-radius: 8px !important;
1098
+ }
1099
+
1100
+ /* Text colors */
1101
+ .gr-label, .gr-text, .markdown-text {
1102
+ color: #ffffff !important;
1103
+ }
1104
+
1105
+ /* Header styling */
1106
+ h1, h2, h3 {
1107
+ color: #ffffff !important;
1108
+ font-weight: 700 !important;
1109
+ }
1110
+
1111
+ /* Tabs */
1112
+ .tab-nav button {
1113
+ background-color: #26262B !important;
1114
+ border: 1px solid #3a3a3f !important;
1115
+ color: #999 !important;
1116
+ border-radius: 8px 8px 0 0 !important;
1117
+ transition: all 0.3s ease !important;
1118
+ }
1119
+
1120
+ .tab-nav button.selected {
1121
+ background-color: #6366f1 !important;
1122
+ color: white !important;
1123
+ border-bottom: 3px solid #6366f1 !important;
1124
+ }
1125
+
1126
+ /* Loading animation */
1127
+ @keyframes pulse {
1128
+ 0%, 100% { opacity: 1; }
1129
+ 50% { opacity: 0.5; }
1130
+ }
1131
+
1132
+ .generating {
1133
+ animation: pulse 2s ease-in-out infinite;
1134
+ }
1135
+ """
1136
+
1137
+
1138
+ def create_ui():
1139
+ """Create the main Gradio interface"""
1140
+
1141
+ with gr.Blocks(css=custom_css, title="SongLab AI", theme=gr.themes.Base()) as app:
1142
+ # Session state
1143
+ session_id = gr.State(value=lambda: str(uuid.uuid4()))
1144
+
1145
+ # URL parameters for WordPress authentication
1146
+ jwt_token_param = gr.State(value=None)
1147
+ user_id_param = gr.State(value=None)
1148
+
1149
+ # Header
1150
+ gr.Markdown("# 🎵 SongLab AI", elem_classes=["header"])
1151
+ gr.Markdown("*Professional Music & Video Generation - Powered by AI*")
1152
+
1153
+ # Authentication section
1154
+ with gr.Row():
1155
+ with gr.Column(scale=2):
1156
+ auth_status = gr.Markdown("⚠️ Checking authentication...")
1157
+ with gr.Column(scale=1):
1158
+ credit_display = gr.Markdown("Credits: Loading...")
1159
+
1160
+ auth_message = gr.Markdown("")
1161
+
1162
+ # Main tabs
1163
+ with gr.Tabs() as tabs:
1164
+ # Tab 1: Music Generation
1165
+ with gr.Tab("🎵 Generate Music"):
1166
+ gr.Markdown("### Create Your Track")
1167
+ gr.Markdown("*Unlimited free generations. Credits only needed for downloads.*")
1168
+
1169
+ with gr.Row():
1170
+ with gr.Column(scale=2):
1171
+ genre = gr.Dropdown(
1172
+ choices=[
1173
+ "Pop", "Rock", "Hip-Hop", "Electronic", "Jazz",
1174
+ "Classical", "R&B", "Country", "Reggae", "Blues",
1175
+ "Metal", "Funk", "Soul", "Disco", "House",
1176
+ "Techno", "Ambient", "Lo-fi", "Trap", "Dubstep",
1177
+ "Indie", "Folk", "Latin", "Gospel", "Afrobeat"
1178
+ ],
1179
+ label="Genre",
1180
+ value="Pop"
1181
+ )
1182
+ energy = gr.Radio(
1183
+ choices=["Low", "Medium", "High"],
1184
+ label="Energy Level",
1185
+ value="Medium"
1186
+ )
1187
+ tempo = gr.Slider(
1188
+ minimum=40,
1189
+ maximum=200,
1190
+ value=120,
1191
+ step=1,
1192
+ label="Tempo (BPM)"
1193
+ )
1194
+ duration = gr.Slider(
1195
+ minimum=15,
1196
+ maximum=140,
1197
+ value=30,
1198
+ step=5,
1199
+ label="Duration (seconds)"
1200
+ )
1201
+
1202
+ with gr.Column(scale=3):
1203
+ description = gr.Textbox(
1204
+ label="Track Description",
1205
+ placeholder="Describe your track in detail... (e.g., 'Upbeat summer pop song with catchy chorus and bright synths')",
1206
+ lines=4
1207
+ )
1208
+
1209
+ generate_btn = gr.Button("🎵 Generate Track", variant="primary", size="lg")
1210
+
1211
+ status_music = gr.Markdown("")
1212
+ info_music = gr.Markdown("")
1213
+
1214
+ audio_output = gr.Audio(label="Generated Track", interactive=False)
1215
+
1216
+ with gr.Row():
1217
+ download_10s_btn = gr.Button("⬇️ Download 10s (Tier 1)", variant="secondary")
1218
+ download_20s_btn = gr.Button("⬇️ Download 20s (Tier 2)", variant="secondary")
1219
+
1220
+ download_file = gr.File(label="Your Download")
1221
+ download_status = gr.Markdown("")
1222
+
1223
+ # Tab 2: Music with Vocals
1224
+ with gr.Tab("🎤 Generate with Vocals"):
1225
+ gr.Markdown("### Create Track with Lyrics")
1226
+ gr.Markdown("*Add vocals and lyrics to your music*")
1227
+
1228
+ with gr.Row():
1229
+ with gr.Column(scale=2):
1230
+ genre_v = gr.Dropdown(
1231
+ choices=[
1232
+ "Pop", "Rock", "Hip-Hop", "Electronic", "Jazz",
1233
+ "Classical", "R&B", "Country", "Reggae", "Blues",
1234
+ "Metal", "Funk", "Soul", "Disco", "House",
1235
+ "Techno", "Ambient", "Lo-fi", "Trap", "Dubstep",
1236
+ "Indie", "Folk", "Latin", "Gospel", "Afrobeat"
1237
+ ],
1238
+ label="Genre",
1239
+ value="Pop"
1240
+ )
1241
+ energy_v = gr.Radio(
1242
+ choices=["Low", "Medium", "High"],
1243
+ label="Energy Level",
1244
+ value="Medium"
1245
+ )
1246
+ tempo_v = gr.Slider(
1247
+ minimum=40,
1248
+ maximum=200,
1249
+ value=120,
1250
+ step=1,
1251
+ label="Tempo (BPM)"
1252
+ )
1253
+ duration_v = gr.Slider(
1254
+ minimum=15,
1255
+ maximum=140,
1256
+ value=30,
1257
+ step=5,
1258
+ label="Duration (seconds)"
1259
+ )
1260
+
1261
+ with gr.Column(scale=3):
1262
+ description_v = gr.Textbox(
1263
+ label="Track Description",
1264
+ placeholder="Describe your track...",
1265
+ lines=3
1266
+ )
1267
+ lyrics_v = gr.Textbox(
1268
+ label="Lyrics",
1269
+ placeholder="Enter your lyrics here...\n\nVerse 1:\n...\n\nChorus:\n...",
1270
+ lines=8
1271
+ )
1272
+
1273
+ generate_vocals_btn = gr.Button("🎤 Generate with Vocals", variant="primary", size="lg")
1274
+
1275
+ status_vocals = gr.Markdown("")
1276
+ info_vocals = gr.Markdown("")
1277
+
1278
+ audio_output_v = gr.Audio(label="Generated Track with Vocals", interactive=False)
1279
+
1280
+ with gr.Row():
1281
+ download_10s_btn_v = gr.Button("⬇️ Download 10s (Tier 1)", variant="secondary")
1282
+ download_20s_btn_v = gr.Button("⬇️ Download 20s (Tier 2)", variant="secondary")
1283
+
1284
+ download_file_v = gr.File(label="Your Download")
1285
+ download_status_v = gr.Markdown("")
1286
+
1287
+ # Tab 3: Audio Effects
1288
+ with gr.Tab("🎚️ Audio Effects"):
1289
+ gr.Markdown("### Process Your Track")
1290
+ gr.Markdown("*Apply effects to your generated music*")
1291
+
1292
+ with gr.Row():
1293
+ stereo_effect = gr.Checkbox(label="Stereo Widening", value=False)
1294
+ reverse_effect = gr.Checkbox(label="Reverse", value=False)
1295
+
1296
+ with gr.Row():
1297
+ volume_slider = gr.Slider(
1298
+ minimum=-20,
1299
+ maximum=20,
1300
+ value=0,
1301
+ step=1,
1302
+ label="Volume (dB)"
1303
+ )
1304
+ pitch_slider = gr.Slider(
1305
+ minimum=-12,
1306
+ maximum=12,
1307
+ value=0,
1308
+ step=1,
1309
+ label="Pitch (semitones)"
1310
+ )
1311
+
1312
+ apply_effects_btn = gr.Button("✨ Apply Effects", variant="primary", size="lg")
1313
+
1314
+ status_effects = gr.Markdown("")
1315
+ audio_processed = gr.Audio(label="Processed Track", interactive=False)
1316
+
1317
+ reset_btn = gr.Button("🔄 Reset Effects")
1318
+
1319
+ # Tab 4: Commercial Ad (VIDEO)
1320
+ with gr.Tab("🎬 Commercial Ad"):
1321
+ gr.Markdown("### Generate Professional Commercial Ads")
1322
+ gr.Markdown("*Create video ads with optional jingles*")
1323
+
1324
+ # Mode selector
1325
+ ad_mode = gr.Radio(
1326
+ choices=["text_to_video", "image_to_video", "jingle_video"],
1327
+ label="Generation Mode",
1328
+ value="text_to_video",
1329
+ info="Choose how to create your ad"
1330
+ )
1331
+
1332
+ # Example selector
1333
+ example_selector = gr.Dropdown(
1334
+ choices=list(EXAMPLE_COMMERCIAL_ADS.keys()),
1335
+ label="📋 Example Commercial Ads",
1336
+ value="Select an example..."
1337
+ )
1338
+ use_example_btn = gr.Button("Use This Example")
1339
+
1340
+ # Inputs (conditional based on mode)
1341
+ with gr.Row():
1342
+ with gr.Column():
1343
+ video_prompt_input = gr.Textbox(
1344
+ label="Video Description",
1345
+ placeholder="Describe your video scene...",
1346
+ lines=4
1347
+ )
1348
+
1349
+ image_upload = gr.File(
1350
+ label="Upload Image (for Image-to-Video)",
1351
+ file_types=["image"],
1352
+ visible=False
1353
+ )
1354
+
1355
+ jingle_prompt_input = gr.Textbox(
1356
+ label="Jingle Description (for Jingle+Video)",
1357
+ placeholder="Describe the music style...",
1358
+ lines=3,
1359
+ visible=False
1360
+ )
1361
+
1362
+ jingle_lyrics_input = gr.Textbox(
1363
+ label="Jingle Lyrics (Optional)",
1364
+ placeholder="Enter lyrics or slogan...",
1365
+ lines=2,
1366
+ visible=False
1367
+ )
1368
+
1369
+ with gr.Column():
1370
+ # Settings
1371
+ custom_mode = gr.Checkbox(label="🎨 Customize Settings (Requires Credits)", value=False)
1372
+
1373
+ duration_video = gr.Slider(
1374
+ minimum=2,
1375
+ maximum=15,
1376
+ value=3,
1377
+ step=1,
1378
+ label="Duration (seconds)",
1379
+ interactive=False
1380
+ )
1381
+
1382
+ fps_selector = gr.Dropdown(
1383
+ choices=["Standard (16fps)"],
1384
+ label="Quality (FPS)",
1385
+ value="Standard (16fps)",
1386
+ interactive=False
1387
+ )
1388
+
1389
+ creativity_slider = gr.Slider(
1390
+ minimum=1.0,
1391
+ maximum=15.0,
1392
+ value=7.0,
1393
+ step=0.5,
1394
+ label="🎨 Creativity",
1395
+ interactive=False
1396
+ )
1397
+
1398
+ motion_slider = gr.Slider(
1399
+ minimum=1.0,
1400
+ maximum=10.0,
1401
+ value=5.0,
1402
+ step=0.5,
1403
+ label="⚡ Motion",
1404
+ interactive=False
1405
+ )
1406
+
1407
+ # Expert intro
1408
+ expert_intro_check = gr.Checkbox(
1409
+ label="🎬 Add 5s Intro with Voiceover (Expert Tier)",
1410
+ value=False,
1411
+ visible=False
1412
+ )
1413
+
1414
+ intro_prompt_input = gr.Textbox(
1415
+ label="Intro Description",
1416
+ placeholder="Logo on gradient background...",
1417
+ visible=False
1418
+ )
1419
+
1420
+ company_name_input = gr.Textbox(
1421
+ label="Company Name (for voiceover)",
1422
+ placeholder="YourBrand Inc.",
1423
+ visible=False
1424
+ )
1425
+
1426
+ generate_ad_btn = gr.Button("🎬 Generate Commercial Ad", variant="primary", size="lg")
1427
+
1428
+ ad_status = gr.Markdown("")
1429
+ ad_video_output = gr.Video(label="Your Commercial Ad")
1430
+ download_ad_btn = gr.Button("⬇️ Download Ad (MP4)", variant="secondary")
1431
+
1432
+ # Event handlers - Mode switcher for video
1433
+ def update_video_ui(mode):
1434
+ return {
1435
+ image_upload: gr.update(visible=(mode == "image_to_video")),
1436
+ jingle_prompt_input: gr.update(visible=(mode == "jingle_video")),
1437
+ jingle_lyrics_input: gr.update(visible=(mode == "jingle_video"))
1438
+ }
1439
+
1440
+ ad_mode.change(
1441
+ fn=update_video_ui,
1442
+ inputs=[ad_mode],
1443
+ outputs=[image_upload, jingle_prompt_input, jingle_lyrics_input]
1444
+ )
1445
+
1446
+ # Custom mode toggle
1447
+ def update_custom_ui(is_custom):
1448
+ return {
1449
+ duration_video: gr.update(interactive=is_custom),
1450
+ fps_selector: gr.update(interactive=is_custom),
1451
+ creativity_slider: gr.update(interactive=is_custom),
1452
+ motion_slider: gr.update(interactive=is_custom)
1453
+ }
1454
+
1455
+ custom_mode.change(
1456
+ fn=update_custom_ui,
1457
+ inputs=[custom_mode],
1458
+ outputs=[duration_video, fps_selector, creativity_slider, motion_slider]
1459
+ )
1460
+
1461
+ # Example loader
1462
+ use_example_btn.click(
1463
+ fn=load_example_ad,
1464
+ inputs=[example_selector],
1465
+ outputs=[jingle_prompt_input, jingle_lyrics_input, video_prompt_input]
1466
+ )
1467
+
1468
+ # Generate music
1469
+ generate_btn.click(
1470
+ fn=generate_track,
1471
+ inputs=[genre, energy, tempo, description, duration, session_id],
1472
+ outputs=[audio_output, status_music, info_music]
1473
+ )
1474
+
1475
+ # Generate with vocals
1476
+ generate_vocals_btn.click(
1477
+ fn=generate_with_vocals,
1478
+ inputs=[genre_v, energy_v, tempo_v, description_v, lyrics_v, duration_v, session_id],
1479
+ outputs=[audio_output_v, status_vocals, info_vocals]
1480
+ )
1481
+
1482
+ # Download handlers
1483
+ download_10s_btn.click(
1484
+ fn=lambda sid: download_track(1, sid),
1485
+ inputs=[session_id],
1486
+ outputs=[download_file, download_status]
1487
+ )
1488
+
1489
+ download_20s_btn.click(
1490
+ fn=lambda sid: download_track(2, sid),
1491
+ inputs=[session_id],
1492
+ outputs=[download_file, download_status]
1493
+ )
1494
+
1495
+ download_10s_btn_v.click(
1496
+ fn=lambda sid: download_track(1, sid),
1497
+ inputs=[session_id],
1498
+ outputs=[download_file_v, download_status_v]
1499
+ )
1500
+
1501
+ download_20s_btn_v.click(
1502
+ fn=lambda sid: download_track(2, sid),
1503
+ inputs=[session_id],
1504
+ outputs=[download_file_v, download_status_v]
1505
+ )
1506
+
1507
+ # Effects
1508
+ apply_effects_btn.click(
1509
+ fn=apply_effects,
1510
+ inputs=[stereo_effect, reverse_effect, volume_slider, pitch_slider, session_id],
1511
+ outputs=[audio_processed, status_effects]
1512
+ )
1513
+
1514
+ reset_btn.click(
1515
+ fn=lambda: (False, False, 0, 0, None, "✓ Effects reset"),
1516
+ outputs=[stereo_effect, reverse_effect, volume_slider, pitch_slider, audio_processed, status_effects]
1517
+ )
1518
+
1519
+ # Generate commercial ad
1520
+ def generate_ad_wrapper(mode, video_prompt, jingle_prompt, jingle_lyrics, image, duration, fps_str, creativity, motion, is_custom, add_intro, intro_prompt, company_name, session_id, progress=gr.Progress()):
1521
+ # Parse FPS from string
1522
+ fps = 16 # default
1523
+ if "8" in fps_str:
1524
+ fps = 8
1525
+ elif "24" in fps_str:
1526
+ fps = 24
1527
+
1528
+ return generate_commercial_ad(
1529
+ mode, video_prompt, jingle_prompt, jingle_lyrics, image,
1530
+ duration, fps, creativity, motion, is_custom,
1531
+ add_intro, intro_prompt, company_name, session_id, progress
1532
+ )
1533
+
1534
+ generate_ad_btn.click(
1535
+ fn=generate_ad_wrapper,
1536
+ inputs=[
1537
+ ad_mode, video_prompt_input, jingle_prompt_input, jingle_lyrics_input,
1538
+ image_upload, duration_video, fps_selector, creativity_slider, motion_slider,
1539
+ custom_mode, expert_intro_check, intro_prompt_input, company_name_input, session_id
1540
+ ],
1541
+ outputs=[ad_video_output, ad_status]
1542
+ )
1543
+
1544
+ return app
1545
+
1546
+
1547
+ if __name__ == "__main__":
1548
+ demo = create_ui()
1549
+ demo.queue(max_size=20)
1550
+ demo.launch(
1551
+ server_name="0.0.0.0",
1552
+ server_port=7860,
1553
+ share=False,
1554
+ show_error=True
1555
+ )
app_gradio_backup.py ADDED
@@ -0,0 +1,879 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ SongLab AI - Gradio Interface (Suno AI Style)
3
+ Modern, minimalist music generation interface with WordPress integration
4
+ """
5
+
6
+ import os
7
+ import io
8
+ import json
9
+ import uuid
10
+ import base64
11
+ import time
12
+ from typing import Optional, Tuple, Dict, Any
13
+ import gradio as gr
14
+ import requests
15
+ import numpy as np
16
+ from pydub import AudioSegment
17
+ from pydub.effects import low_pass_filter, high_pass_filter
18
+ from gradio_client import Client
19
+ import jwt
20
+
21
+ # Configuration
22
+ WORDPRESS_BASE_URL = "https://songlabai.com"
23
+ LYRICS_API_URL = os.environ.get("LYRICS_API_URL")
24
+ BEARER_TOKEN = os.environ.get("BEARER_TOKEN")
25
+
26
+ # Global state management
27
+ user_sessions = {}
28
+
29
+
30
+ class AudioProcessor:
31
+ """Handles all audio processing operations"""
32
+
33
+ @staticmethod
34
+ def apply_stereo_effect(audio: AudioSegment, separation: int = 30) -> AudioSegment:
35
+ """Apply stereo widening effect"""
36
+ if audio.channels == 1:
37
+ audio = audio.set_channels(2)
38
+
39
+ left = audio.split_to_mono()[0]
40
+ right = audio.split_to_mono()[1] if audio.channels == 2 else left
41
+
42
+ left_filtered = high_pass_filter(left, 200)
43
+ right_filtered = low_pass_filter(right, 8000)
44
+
45
+ return AudioSegment.from_mono_audiosegments(left_filtered, right_filtered)
46
+
47
+ @staticmethod
48
+ def apply_reverse(audio: AudioSegment) -> AudioSegment:
49
+ """Reverse the audio"""
50
+ return audio.reverse()
51
+
52
+ @staticmethod
53
+ def adjust_volume(audio: AudioSegment, db_change: float) -> AudioSegment:
54
+ """Adjust volume by dB"""
55
+ return audio + db_change
56
+
57
+ @staticmethod
58
+ def change_pitch(audio: AudioSegment, semitones: int) -> AudioSegment:
59
+ """Change pitch by semitones"""
60
+ if semitones == 0:
61
+ return audio
62
+ new_sample_rate = int(audio.frame_rate * (2.0 ** (semitones / 12.0)))
63
+ return audio._spawn(audio.raw_data, overrides={'frame_rate': new_sample_rate}).set_frame_rate(audio.frame_rate)
64
+
65
+ @staticmethod
66
+ def trim_audio(audio: AudioSegment, duration_seconds: int) -> AudioSegment:
67
+ """Trim audio to specified duration"""
68
+ return audio[:duration_seconds * 1000]
69
+
70
+
71
+ class WordPressAPI:
72
+ """Handles all WordPress API interactions"""
73
+
74
+ def __init__(self, base_url: str):
75
+ self.base_url = base_url
76
+
77
+ def _get_headers(self, jwt_token: str) -> Dict[str, str]:
78
+ """Generate API headers with JWT token"""
79
+ return {
80
+ "Authorization": f"Bearer {jwt_token}",
81
+ "Content-Type": "application/json",
82
+ "Cache-Control": "no-store"
83
+ }
84
+
85
+ def get_user_credits(self, jwt_token: str) -> Dict[str, int]:
86
+ """Fetch user's audio credits"""
87
+ try:
88
+ response = requests.get(
89
+ f"{self.base_url}/wp-json/custom-api/v1/user-credits-hf",
90
+ headers=self._get_headers(jwt_token),
91
+ timeout=10
92
+ )
93
+ response.raise_for_status()
94
+ data = response.json()
95
+ return {
96
+ 'tier1_credits': data.get('tier1_credits', 0),
97
+ 'tier2_credits': data.get('tier2_credits', 0),
98
+ 'total_credits': data.get('total_credits', 0)
99
+ }
100
+ except Exception as e:
101
+ print(f"Error fetching credits: {e}")
102
+ return {'tier1_credits': 0, 'tier2_credits': 0, 'total_credits': 0}
103
+
104
+ def get_video_credits(self, jwt_token: str) -> Dict[str, Any]:
105
+ """Fetch user's video credits"""
106
+ try:
107
+ response = requests.get(
108
+ f"{self.base_url}/wp-json/video-api/v1/video-credits",
109
+ headers=self._get_headers(jwt_token),
110
+ timeout=10
111
+ )
112
+ response.raise_for_status()
113
+ return response.json()
114
+ except Exception as e:
115
+ print(f"Error fetching video credits: {e}")
116
+ return {}
117
+
118
+ def use_audio_credit(self, jwt_token: str, tier: int) -> bool:
119
+ """Deduct an audio credit"""
120
+ try:
121
+ response = requests.post(
122
+ f"{self.base_url}/wp-json/custom-api/v1/use-credit-hf",
123
+ headers=self._get_headers(jwt_token),
124
+ json={'tier': tier},
125
+ timeout=10
126
+ )
127
+ response.raise_for_status()
128
+ return response.json().get('success', False)
129
+ except Exception as e:
130
+ print(f"Error using credit: {e}")
131
+ return False
132
+
133
+ def upload_track(self, jwt_token: str, audio_bytes: bytes, metadata: Dict[str, Any]) -> bool:
134
+ """Upload track to WordPress"""
135
+ try:
136
+ # Decode JWT to get user info
137
+ decoded = jwt.decode(jwt_token, options={"verify_signature": False})
138
+ user_info = decoded.get('data', {}).get('user', {})
139
+
140
+ files = {'audio_file': ('track.wav', audio_bytes, 'audio/wav')}
141
+ data = {
142
+ 'user_id': str(user_info.get('id', '')),
143
+ 'user_email': user_info.get('user_email', ''),
144
+ 'user_name': user_info.get('display_name', ''),
145
+ 'subscription_tier': 'free',
146
+ 'saved_by_user': '1',
147
+ **metadata
148
+ }
149
+
150
+ response = requests.post(
151
+ f"{self.base_url}/wp-admin/admin-ajax.php?action=upload_audio_direct",
152
+ files=files,
153
+ data=data,
154
+ headers={"Authorization": f"Bearer {jwt_token}"},
155
+ timeout=30
156
+ )
157
+ response.raise_for_status()
158
+ return True
159
+ except Exception as e:
160
+ print(f"Error uploading track: {e}")
161
+ return False
162
+
163
+
164
+ class MusicGenerator:
165
+ """Handles music generation via MusicGen"""
166
+
167
+ def __init__(self):
168
+ self.client = None
169
+ self.space_name = "Healthydater/musicgen"
170
+
171
+ def get_client(self) -> Client:
172
+ """Get or create MusicGen client with retry logic"""
173
+ if self.client is None:
174
+ for attempt in range(3):
175
+ try:
176
+ self.client = Client(self.space_name)
177
+ return self.client
178
+ except Exception as e:
179
+ if attempt < 2:
180
+ time.sleep(120)
181
+ else:
182
+ raise Exception(f"Failed to connect to MusicGen: {e}")
183
+ return self.client
184
+
185
+ def generate_music(self, prompt: str, duration: float, progress=gr.Progress()) -> Tuple[int, np.ndarray]:
186
+ """Generate music from prompt"""
187
+ try:
188
+ progress(0.1, desc="Connecting to MusicGen...")
189
+ client = self.get_client()
190
+
191
+ progress(0.3, desc="Generating music...")
192
+ result = client.predict(
193
+ prompt=prompt,
194
+ duration=duration,
195
+ temperature=1.0,
196
+ top_k=250,
197
+ top_p=0.0,
198
+ cfg_coef=3.0,
199
+ use_sampling=True,
200
+ extend_stride=18.0,
201
+ api_name="/predict"
202
+ )
203
+
204
+ progress(0.9, desc="Processing audio...")
205
+ # Result is a file path
206
+ audio = AudioSegment.from_file(result)
207
+ samples = np.array(audio.get_array_of_samples()).astype(np.float32) / 32768.0
208
+
209
+ if audio.channels == 2:
210
+ samples = samples.reshape((-1, 2))
211
+
212
+ return audio.frame_rate, samples
213
+ except Exception as e:
214
+ raise Exception(f"Generation failed: {str(e)}")
215
+
216
+ def generate_with_lyrics(self, prompt: str, lyrics: str, duration: float, progress=gr.Progress()) -> Tuple[int, np.ndarray]:
217
+ """Generate music with vocals/lyrics"""
218
+ if not LYRICS_API_URL or not BEARER_TOKEN:
219
+ raise Exception("Lyrics API not configured")
220
+
221
+ try:
222
+ progress(0.1, desc="Preparing lyrics generation...")
223
+
224
+ payload = {
225
+ "inputs": {
226
+ "prompt": prompt,
227
+ "lyrics": lyrics
228
+ },
229
+ "parameters": {
230
+ "duration": duration,
231
+ "infer_step": 60,
232
+ "guidance_scale": 15,
233
+ "scheduler_type": "euler",
234
+ "cfg_type": "apg",
235
+ "omega_scale": 10
236
+ }
237
+ }
238
+
239
+ progress(0.3, desc="Generating vocals...")
240
+ response = requests.post(
241
+ LYRICS_API_URL,
242
+ headers={
243
+ "Authorization": f"Bearer {BEARER_TOKEN}",
244
+ "Content-Type": "application/json"
245
+ },
246
+ json=payload,
247
+ timeout=300
248
+ )
249
+ response.raise_for_status()
250
+
251
+ progress(0.9, desc="Processing audio...")
252
+ audio_bytes = response.content
253
+ audio = AudioSegment.from_file(io.BytesIO(audio_bytes))
254
+ samples = np.array(audio.get_array_of_samples()).astype(np.float32) / 32768.0
255
+
256
+ if audio.channels == 2:
257
+ samples = samples.reshape((-1, 2))
258
+
259
+ return audio.frame_rate, samples
260
+ except Exception as e:
261
+ raise Exception(f"Lyrics generation failed: {str(e)}")
262
+
263
+
264
+ # Initialize global instances
265
+ wp_api = WordPressAPI(WORDPRESS_BASE_URL)
266
+ music_gen = MusicGenerator()
267
+ audio_processor = AudioProcessor()
268
+
269
+
270
+ def get_session(session_id: str) -> Dict[str, Any]:
271
+ """Get or create user session"""
272
+ if session_id not in user_sessions:
273
+ user_sessions[session_id] = {
274
+ 'jwt_token': None,
275
+ 'user_info': None,
276
+ 'credits': {'tier1_credits': 0, 'tier2_credits': 0, 'total_credits': 0},
277
+ 'generated_audio': None,
278
+ 'processed_audio': None,
279
+ 'metadata': {}
280
+ }
281
+ return user_sessions[session_id]
282
+
283
+
284
+ def authenticate_user(jwt_token: str, user_id: str, session_id: str) -> Tuple[str, str, str]:
285
+ """Authenticate user via JWT token from WordPress"""
286
+ if not jwt_token or jwt_token == "null":
287
+ return (
288
+ "⚠️ Not Logged In",
289
+ "Please log in to WordPress to use SongLab AI",
290
+ "Login required to access generation features"
291
+ )
292
+
293
+ try:
294
+ # Decode JWT (without verification for client-side)
295
+ decoded = jwt.decode(jwt_token, options={"verify_signature": False})
296
+ user_info = decoded.get('data', {}).get('user', {})
297
+
298
+ # Store in session
299
+ session = get_session(session_id)
300
+ session['jwt_token'] = jwt_token
301
+ session['user_info'] = user_info
302
+
303
+ # Fetch credits
304
+ credits = wp_api.get_user_credits(jwt_token)
305
+ session['credits'] = credits
306
+
307
+ display_name = user_info.get('display_name', 'User')
308
+ credit_info = f"Audio Credits: {credits['tier1_credits']} (10s) | {credits['tier2_credits']} (20s)"
309
+
310
+ return (
311
+ f"✓ Logged in as {display_name}",
312
+ credit_info,
313
+ f"Welcome, {display_name}! You have unlimited generations."
314
+ )
315
+ except Exception as e:
316
+ return (
317
+ "⚠️ Authentication Error",
318
+ str(e),
319
+ "Please refresh and try again"
320
+ )
321
+
322
+
323
+ def format_prompt(genre: str, energy: str, tempo: int, description: str) -> str:
324
+ """Format generation prompt"""
325
+ return f"Genre: {genre}, Energy Level: {energy}, Tempo: {tempo} bpm, Description: {description}"
326
+
327
+
328
+ def generate_track(genre: str, energy: str, tempo: int, description: str, duration: int, session_id: str, progress=gr.Progress()) -> Tuple[Any, str, str]:
329
+ """Generate music track"""
330
+ session = get_session(session_id)
331
+
332
+ if not session['jwt_token']:
333
+ return None, "❌ Please log in first", ""
334
+
335
+ if not description or len(description.strip()) < 10:
336
+ return None, "❌ Please provide a detailed description (at least 10 characters)", ""
337
+
338
+ try:
339
+ prompt = format_prompt(genre, energy, tempo, description)
340
+ sample_rate, audio_data = music_gen.generate_music(prompt, float(duration), progress)
341
+
342
+ # Convert to AudioSegment for storage
343
+ audio_bytes = io.BytesIO()
344
+ # Convert numpy array to int16
345
+ audio_int = (audio_data * 32768).astype(np.int16)
346
+ audio_seg = AudioSegment(
347
+ audio_int.tobytes(),
348
+ frame_rate=sample_rate,
349
+ sample_width=2,
350
+ channels=2 if len(audio_int.shape) > 1 else 1
351
+ )
352
+ audio_seg.export(audio_bytes, format="wav")
353
+
354
+ # Store in session
355
+ session['generated_audio'] = audio_seg
356
+ session['metadata'] = {
357
+ 'genre': genre,
358
+ 'energy': energy,
359
+ 'tempo': tempo,
360
+ 'description': description,
361
+ 'duration': duration
362
+ }
363
+
364
+ return (sample_rate, audio_data), "✓ Generation complete!", f"Generated {duration}s track: {genre} ({energy} energy, {tempo} bpm)"
365
+ except Exception as e:
366
+ return None, f"❌ Generation failed: {str(e)}", ""
367
+
368
+
369
+ def generate_with_vocals(genre: str, energy: str, tempo: int, description: str, lyrics: str, duration: int, session_id: str, progress=gr.Progress()) -> Tuple[Any, str, str]:
370
+ """Generate track with vocals"""
371
+ session = get_session(session_id)
372
+
373
+ if not session['jwt_token']:
374
+ return None, "❌ Please log in first", ""
375
+
376
+ if not description or not lyrics:
377
+ return None, "❌ Please provide both description and lyrics", ""
378
+
379
+ try:
380
+ prompt = format_prompt(genre, energy, tempo, description)
381
+ sample_rate, audio_data = music_gen.generate_with_lyrics(prompt, lyrics, float(duration), progress)
382
+
383
+ # Convert to AudioSegment
384
+ audio_bytes = io.BytesIO()
385
+ audio_int = (audio_data * 32768).astype(np.int16)
386
+ audio_seg = AudioSegment(
387
+ audio_int.tobytes(),
388
+ frame_rate=sample_rate,
389
+ sample_width=2,
390
+ channels=2 if len(audio_int.shape) > 1 else 1
391
+ )
392
+ audio_seg.export(audio_bytes, format="wav")
393
+
394
+ session['generated_audio'] = audio_seg
395
+ session['metadata'] = {
396
+ 'genre': genre,
397
+ 'energy': energy,
398
+ 'tempo': tempo,
399
+ 'description': description,
400
+ 'lyrics': lyrics,
401
+ 'duration': duration
402
+ }
403
+
404
+ return (sample_rate, audio_data), "✓ Generation with vocals complete!", f"Generated {duration}s track with vocals"
405
+ except Exception as e:
406
+ return None, f"❌ Generation failed: {str(e)}", ""
407
+
408
+
409
+ def download_track(tier: int, session_id: str) -> Tuple[Optional[str], str]:
410
+ """Download track with credit deduction"""
411
+ session = get_session(session_id)
412
+
413
+ if not session['jwt_token']:
414
+ return None, "❌ Please log in first"
415
+
416
+ if session['generated_audio'] is None:
417
+ return None, "❌ No track generated yet"
418
+
419
+ # Check credits
420
+ credits = session['credits']
421
+ tier_key = f'tier{tier}_credits'
422
+
423
+ if credits.get(tier_key, 0) <= 0:
424
+ return None, f"❌ No credits available for Tier {tier}. Please purchase more credits."
425
+
426
+ # Deduct credit
427
+ if not wp_api.use_audio_credit(session['jwt_token'], tier):
428
+ return None, "❌ Failed to deduct credit. Please try again."
429
+
430
+ # Trim audio
431
+ duration = 10 if tier == 1 else 20
432
+ trimmed_audio = audio_processor.trim_audio(session['generated_audio'], duration)
433
+
434
+ # Export to file
435
+ output_path = f"/tmp/songlab_track_{uuid.uuid4().hex}_{duration}s.wav"
436
+ trimmed_audio.export(output_path, format="wav")
437
+
438
+ # Update credits
439
+ session['credits'][tier_key] -= 1
440
+
441
+ return output_path, f"✓ Downloaded {duration}s track! {credits[tier_key] - 1} credits remaining."
442
+
443
+
444
+ def apply_effects(stereo: bool, reverse: bool, volume: float, pitch: int, session_id: str) -> Tuple[Any, str]:
445
+ """Apply audio effects"""
446
+ session = get_session(session_id)
447
+
448
+ if session['generated_audio'] is None:
449
+ return None, "❌ No track generated yet"
450
+
451
+ try:
452
+ audio = session['generated_audio']
453
+
454
+ if stereo:
455
+ audio = audio_processor.apply_stereo_effect(audio)
456
+
457
+ if reverse:
458
+ audio = audio_processor.apply_reverse(audio)
459
+
460
+ if volume != 0:
461
+ audio = audio_processor.adjust_volume(audio, volume)
462
+
463
+ if pitch != 0:
464
+ audio = audio_processor.change_pitch(audio, pitch)
465
+
466
+ session['processed_audio'] = audio
467
+
468
+ # Convert to numpy for playback
469
+ samples = np.array(audio.get_array_of_samples()).astype(np.float32) / 32768.0
470
+ if audio.channels == 2:
471
+ samples = samples.reshape((-1, 2))
472
+
473
+ return (audio.frame_rate, samples), "✓ Effects applied!"
474
+ except Exception as e:
475
+ return None, f"❌ Effect processing failed: {str(e)}"
476
+
477
+
478
+ # Custom CSS for Suno-style theming
479
+ custom_css = """
480
+ /* Suno AI Dark Theme */
481
+ .gradio-container {
482
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
483
+ }
484
+
485
+ /* Dark background */
486
+ body, .gradio-container {
487
+ background-color: #212126 !important;
488
+ }
489
+
490
+ /* Card styling */
491
+ .gr-box, .gr-form, .gr-panel {
492
+ background-color: #26262B !important;
493
+ border: 1px solid #3a3a3f !important;
494
+ border-radius: 12px !important;
495
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3) !important;
496
+ }
497
+
498
+ /* Input fields */
499
+ .gr-input, .gr-text-input, .gr-text-area, .gr-dropdown {
500
+ background-color: #1a1a1e !important;
501
+ border: 1px solid #3a3a3f !important;
502
+ border-radius: 8px !important;
503
+ color: #ffffff !important;
504
+ }
505
+
506
+ /* Buttons */
507
+ .gr-button {
508
+ background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%) !important;
509
+ border: none !important;
510
+ border-radius: 8px !important;
511
+ color: white !important;
512
+ font-weight: 600 !important;
513
+ padding: 12px 24px !important;
514
+ transition: all 0.3s ease !important;
515
+ }
516
+
517
+ .gr-button:hover {
518
+ transform: translateY(-2px) !important;
519
+ box-shadow: 0 6px 12px rgba(99, 102, 241, 0.4) !important;
520
+ }
521
+
522
+ .gr-button-primary {
523
+ background: linear-gradient(135deg, #10b981 0%, #059669 100%) !important;
524
+ }
525
+
526
+ .gr-button-secondary {
527
+ background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%) !important;
528
+ }
529
+
530
+ /* Sliders */
531
+ .gr-slider input[type="range"] {
532
+ background: #3a3a3f !important;
533
+ }
534
+
535
+ .gr-slider input[type="range"]::-webkit-slider-thumb {
536
+ background: #6366f1 !important;
537
+ }
538
+
539
+ /* Audio player */
540
+ audio {
541
+ width: 100% !important;
542
+ background-color: #1a1a1e !important;
543
+ border-radius: 8px !important;
544
+ }
545
+
546
+ /* Text colors */
547
+ .gr-label, .gr-text, .markdown-text {
548
+ color: #ffffff !important;
549
+ }
550
+
551
+ /* Header styling */
552
+ h1, h2, h3 {
553
+ color: #ffffff !important;
554
+ font-weight: 700 !important;
555
+ }
556
+
557
+ /* Tabs */
558
+ .tab-nav button {
559
+ background-color: #26262B !important;
560
+ border: 1px solid #3a3a3f !important;
561
+ color: #999 !important;
562
+ border-radius: 8px 8px 0 0 !important;
563
+ transition: all 0.3s ease !important;
564
+ }
565
+
566
+ .tab-nav button.selected {
567
+ background-color: #6366f1 !important;
568
+ color: white !important;
569
+ border-bottom: 3px solid #6366f1 !important;
570
+ }
571
+
572
+ /* Status messages */
573
+ .gr-markdown p {
574
+ padding: 12px !important;
575
+ border-radius: 8px !important;
576
+ margin: 8px 0 !important;
577
+ }
578
+
579
+ /* Success messages */
580
+ .gr-markdown p:contains("✓") {
581
+ background-color: rgba(16, 185, 129, 0.1) !important;
582
+ border-left: 4px solid #10b981 !important;
583
+ }
584
+
585
+ /* Error messages */
586
+ .gr-markdown p:contains("❌") {
587
+ background-color: rgba(239, 68, 68, 0.1) !important;
588
+ border-left: 4px solid #ef4444 !important;
589
+ }
590
+
591
+ /* Loading animation */
592
+ @keyframes pulse {
593
+ 0%, 100% { opacity: 1; }
594
+ 50% { opacity: 0.5; }
595
+ }
596
+
597
+ .generating {
598
+ animation: pulse 2s ease-in-out infinite;
599
+ }
600
+ """
601
+
602
+
603
+ def create_ui():
604
+ """Create the main Gradio interface"""
605
+
606
+ with gr.Blocks(css=custom_css, title="SongLab AI", theme=gr.themes.Base()) as app:
607
+ # Session state
608
+ session_id = gr.State(value=lambda: str(uuid.uuid4()))
609
+
610
+ # URL parameters for WordPress authentication
611
+ jwt_token_param = gr.State(value=None)
612
+ user_id_param = gr.State(value=None)
613
+
614
+ # Header
615
+ gr.Markdown("# 🎵 SongLab AI", elem_classes=["header"])
616
+ gr.Markdown("*Professional Music Generation - Powered by AI*")
617
+
618
+ # Authentication section
619
+ with gr.Row():
620
+ with gr.Column(scale=2):
621
+ auth_status = gr.Markdown("⚠️ Checking authentication...")
622
+ with gr.Column(scale=1):
623
+ credit_display = gr.Markdown("Credits: Loading...")
624
+
625
+ auth_message = gr.Markdown("")
626
+
627
+ # Main tabs
628
+ with gr.Tabs() as tabs:
629
+ # Tab 1: Music Generation
630
+ with gr.Tab("🎵 Generate Music"):
631
+ gr.Markdown("### Create Your Track")
632
+ gr.Markdown("*Unlimited free generations. Credits only needed for downloads.*")
633
+
634
+ with gr.Row():
635
+ with gr.Column(scale=2):
636
+ genre = gr.Dropdown(
637
+ choices=[
638
+ "Pop", "Rock", "Hip-Hop", "Electronic", "Jazz",
639
+ "Classical", "R&B", "Country", "Reggae", "Blues",
640
+ "Metal", "Funk", "Soul", "Disco", "House",
641
+ "Techno", "Ambient", "Lo-fi", "Trap", "Dubstep",
642
+ "Indie", "Folk", "Latin", "Gospel", "Afrobeat"
643
+ ],
644
+ label="Genre",
645
+ value="Pop"
646
+ )
647
+ energy = gr.Radio(
648
+ choices=["Low", "Medium", "High"],
649
+ label="Energy Level",
650
+ value="Medium"
651
+ )
652
+ tempo = gr.Slider(
653
+ minimum=40,
654
+ maximum=200,
655
+ value=120,
656
+ step=1,
657
+ label="Tempo (BPM)"
658
+ )
659
+ duration = gr.Slider(
660
+ minimum=15,
661
+ maximum=140,
662
+ value=30,
663
+ step=5,
664
+ label="Duration (seconds)"
665
+ )
666
+
667
+ with gr.Column(scale=3):
668
+ description = gr.Textbox(
669
+ label="Track Description",
670
+ placeholder="Describe your track in detail... (e.g., 'Upbeat summer pop song with catchy chorus and bright synths')",
671
+ lines=4
672
+ )
673
+
674
+ generate_btn = gr.Button("🎵 Generate Track", variant="primary", size="lg")
675
+
676
+ status_music = gr.Markdown("")
677
+ info_music = gr.Markdown("")
678
+
679
+ audio_output = gr.Audio(label="Generated Track", interactive=False)
680
+
681
+ with gr.Row():
682
+ download_10s_btn = gr.Button("⬇️ Download 10s (Tier 1)", variant="secondary")
683
+ download_20s_btn = gr.Button("⬇️ Download 20s (Tier 2)", variant="secondary")
684
+
685
+ download_file = gr.File(label="Your Download")
686
+ download_status = gr.Markdown("")
687
+
688
+ # Tab 2: Music with Vocals
689
+ with gr.Tab("🎤 Generate with Vocals"):
690
+ gr.Markdown("### Create Track with Lyrics")
691
+ gr.Markdown("*Add vocals and lyrics to your music*")
692
+
693
+ with gr.Row():
694
+ with gr.Column(scale=2):
695
+ genre_v = gr.Dropdown(
696
+ choices=[
697
+ "Pop", "Rock", "Hip-Hop", "Electronic", "Jazz",
698
+ "Classical", "R&B", "Country", "Reggae", "Blues",
699
+ "Metal", "Funk", "Soul", "Disco", "House",
700
+ "Techno", "Ambient", "Lo-fi", "Trap", "Dubstep",
701
+ "Indie", "Folk", "Latin", "Gospel", "Afrobeat"
702
+ ],
703
+ label="Genre",
704
+ value="Pop"
705
+ )
706
+ energy_v = gr.Radio(
707
+ choices=["Low", "Medium", "High"],
708
+ label="Energy Level",
709
+ value="Medium"
710
+ )
711
+ tempo_v = gr.Slider(
712
+ minimum=40,
713
+ maximum=200,
714
+ value=120,
715
+ step=1,
716
+ label="Tempo (BPM)"
717
+ )
718
+ duration_v = gr.Slider(
719
+ minimum=15,
720
+ maximum=140,
721
+ value=30,
722
+ step=5,
723
+ label="Duration (seconds)"
724
+ )
725
+
726
+ with gr.Column(scale=3):
727
+ description_v = gr.Textbox(
728
+ label="Track Description",
729
+ placeholder="Describe your track...",
730
+ lines=3
731
+ )
732
+ lyrics_v = gr.Textbox(
733
+ label="Lyrics",
734
+ placeholder="Enter your lyrics here...\n\nVerse 1:\n...\n\nChorus:\n...",
735
+ lines=8
736
+ )
737
+
738
+ generate_vocals_btn = gr.Button("🎤 Generate with Vocals", variant="primary", size="lg")
739
+
740
+ status_vocals = gr.Markdown("")
741
+ info_vocals = gr.Markdown("")
742
+
743
+ audio_output_v = gr.Audio(label="Generated Track with Vocals", interactive=False)
744
+
745
+ with gr.Row():
746
+ download_10s_btn_v = gr.Button("⬇️ Download 10s (Tier 1)", variant="secondary")
747
+ download_20s_btn_v = gr.Button("⬇️ Download 20s (Tier 2)", variant="secondary")
748
+
749
+ download_file_v = gr.File(label="Your Download")
750
+ download_status_v = gr.Markdown("")
751
+
752
+ # Tab 3: Audio Effects
753
+ with gr.Tab("🎚️ Audio Effects"):
754
+ gr.Markdown("### Process Your Track")
755
+ gr.Markdown("*Apply effects to your generated music*")
756
+
757
+ with gr.Row():
758
+ stereo_effect = gr.Checkbox(label="Stereo Widening", value=False)
759
+ reverse_effect = gr.Checkbox(label="Reverse", value=False)
760
+
761
+ with gr.Row():
762
+ volume_slider = gr.Slider(
763
+ minimum=-20,
764
+ maximum=20,
765
+ value=0,
766
+ step=1,
767
+ label="Volume (dB)"
768
+ )
769
+ pitch_slider = gr.Slider(
770
+ minimum=-12,
771
+ maximum=12,
772
+ value=0,
773
+ step=1,
774
+ label="Pitch (semitones)"
775
+ )
776
+
777
+ apply_effects_btn = gr.Button("✨ Apply Effects", variant="primary", size="lg")
778
+
779
+ status_effects = gr.Markdown("")
780
+ audio_processed = gr.Audio(label="Processed Track", interactive=False)
781
+
782
+ reset_btn = gr.Button("🔄 Reset Effects")
783
+
784
+ # Event handlers - Authentication (runs on load)
785
+ app.load(
786
+ fn=lambda: gr.update(value=None), # Will be populated by JavaScript
787
+ outputs=[jwt_token_param]
788
+ )
789
+
790
+ # Generate music
791
+ generate_btn.click(
792
+ fn=generate_track,
793
+ inputs=[genre, energy, tempo, description, duration, session_id],
794
+ outputs=[audio_output, status_music, info_music]
795
+ )
796
+
797
+ # Generate with vocals
798
+ generate_vocals_btn.click(
799
+ fn=generate_with_vocals,
800
+ inputs=[genre_v, energy_v, tempo_v, description_v, lyrics_v, duration_v, session_id],
801
+ outputs=[audio_output_v, status_vocals, info_vocals]
802
+ )
803
+
804
+ # Download handlers
805
+ download_10s_btn.click(
806
+ fn=lambda sid: download_track(1, sid),
807
+ inputs=[session_id],
808
+ outputs=[download_file, download_status]
809
+ )
810
+
811
+ download_20s_btn.click(
812
+ fn=lambda sid: download_track(2, sid),
813
+ inputs=[session_id],
814
+ outputs=[download_file, download_status]
815
+ )
816
+
817
+ download_10s_btn_v.click(
818
+ fn=lambda sid: download_track(1, sid),
819
+ inputs=[session_id],
820
+ outputs=[download_file_v, download_status_v]
821
+ )
822
+
823
+ download_20s_btn_v.click(
824
+ fn=lambda sid: download_track(2, sid),
825
+ inputs=[session_id],
826
+ outputs=[download_file_v, download_status_v]
827
+ )
828
+
829
+ # Effects
830
+ apply_effects_btn.click(
831
+ fn=apply_effects,
832
+ inputs=[stereo_effect, reverse_effect, volume_slider, pitch_slider, session_id],
833
+ outputs=[audio_processed, status_effects]
834
+ )
835
+
836
+ reset_btn.click(
837
+ fn=lambda: (False, False, 0, 0, None, "✓ Effects reset"),
838
+ outputs=[stereo_effect, reverse_effect, volume_slider, pitch_slider, audio_processed, status_effects]
839
+ )
840
+
841
+ # JavaScript for URL parameter extraction
842
+ app.load(
843
+ fn=None,
844
+ outputs=None,
845
+ js="""
846
+ function() {
847
+ const urlParams = new URLSearchParams(window.location.search);
848
+ const jwtToken = urlParams.get('jwt_token');
849
+ const userId = urlParams.get('user_id');
850
+
851
+ if (jwtToken) {
852
+ // Store in session storage for persistence
853
+ sessionStorage.setItem('songlab_jwt', jwtToken);
854
+ sessionStorage.setItem('songlab_user_id', userId);
855
+
856
+ // Trigger authentication
857
+ setTimeout(() => {
858
+ const authButton = document.querySelector('#authenticate_btn');
859
+ if (authButton) authButton.click();
860
+ }, 500);
861
+ }
862
+
863
+ return [jwtToken, userId];
864
+ }
865
+ """
866
+ )
867
+
868
+ return app
869
+
870
+
871
+ if __name__ == "__main__":
872
+ demo = create_ui()
873
+ demo.queue(max_size=20)
874
+ demo.launch(
875
+ server_name="0.0.0.0",
876
+ server_port=7860,
877
+ share=False,
878
+ show_error=True
879
+ )
packages.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ ffmpeg
requirements.txt CHANGED
@@ -1,25 +1,18 @@
1
  numpy
2
  scipy
3
  librosa
4
- ffmpeg-python
5
- soundfile
6
- streamlit_vertical_slider
7
- streamlit_toggle
8
  soundfile
9
  pydub
10
  python-wordpress-xmlrpc
11
  woocommerce
12
- altair
13
  pandas
14
- pydeck
15
- ffmpeg
16
- streamlit-extras
17
- streamlit_toggle
18
-
19
-
20
  torch
21
- torchaudio
22
- transformers
23
- gradio
 
24
  PyJWT==2.3.0
25
- extra-streamlit-components
 
 
 
 
1
  numpy
2
  scipy
3
  librosa
 
 
 
 
4
  soundfile
5
  pydub
6
  python-wordpress-xmlrpc
7
  woocommerce
 
8
  pandas
 
 
 
 
 
 
9
  torch
10
+ torchaudio
11
+ transformers
12
+ gradio>=4.0.0
13
+ gradio_client
14
  PyJWT==2.3.0
15
+ requests
16
+ gTTS
17
+ huggingface_hub
18
+ ffmpeg-python