ghostai1 commited on
Commit
959a928
·
verified ·
1 Parent(s): 8dadb56

Create api.py

Browse files

api backend for music server

Files changed (1) hide show
  1. api.py +330 -0
api.py ADDED
@@ -0,0 +1,330 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from gradio_client import Client
3
+ import os
4
+ import json
5
+ import random
6
+ from datetime import datetime
7
+ import numpy as np
8
+ from pydub import AudioSegment
9
+ import logging
10
+ import configparser
11
+
12
+ # Set up logging
13
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
14
+ logger = logging.getLogger(__name__)
15
+
16
+ # Configuration
17
+ BASE_DIR = "/home/pi5/muzax"
18
+ MP3_DIR = os.path.join(BASE_DIR, "mp3")
19
+ JSON_DIR = os.path.join(BASE_DIR, "json")
20
+ JSON_LOG = os.path.join(JSON_DIR, "render_log.json")
21
+ INI_FILE = os.path.join(BASE_DIR, "band_styles.ini")
22
+ API_URL = "http://192.168.0.155:9999/"
23
+ SONG_DURATION = 120 # API-compatible duration (integer)
24
+ TARGET_DURATION_MS = 180000 # 180 seconds in milliseconds
25
+
26
+ # Load band styles from INI
27
+ config = configparser.ConfigParser()
28
+ if not os.path.exists(INI_FILE):
29
+ logger.error(f"INI file not found: {INI_FILE}")
30
+ raise FileNotFoundError(f"INI file not found: {INI_FILE}")
31
+ config.read(INI_FILE)
32
+ ALLOWED_BANDS = config.sections()
33
+
34
+ # Create directories
35
+ for directory in [BASE_DIR, MP3_DIR, JSON_DIR]:
36
+ if not os.path.exists(directory):
37
+ os.makedirs(directory)
38
+ logger.info(f"Created directory: {directory}")
39
+
40
+ # Initialize JSON log
41
+ if not os.path.exists(JSON_LOG):
42
+ with open(JSON_LOG, "w") as f:
43
+ json.dump([], f)
44
+ logger.info(f"Initialized JSON log: {JSON_LOG}")
45
+
46
+ def generate_random_params(band):
47
+ """Generate random parameters from INI file."""
48
+ style = config[band]
49
+ bpm = random.randint(int(style["bpm_min"]), int(style["bpm_max"]))
50
+ drum_beat = random.choice(style["drum_beat"].split(","))
51
+ synthesizer = random.choice(style["synthesizer"].split(","))
52
+ rhythmic_steps = random.choice(style["rhythmic_steps"].split(","))
53
+ bass_style = random.choice(style["bass_style"].split(","))
54
+ guitar_style = random.choice(style["guitar_style"].split(","))
55
+ return bpm, drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style
56
+
57
+ def generate_music_placeholder(prompt, duration, bpm, drum_beat, bass_style, guitar_style):
58
+ """Placeholder for music generation."""
59
+ sample_rate = 44100
60
+ duration_ms = int(duration) * 1000
61
+ audio = AudioSegment.silent(duration=duration_ms)
62
+ t = np.linspace(0, float(duration), int(sample_rate * float(duration)), endpoint=False)
63
+ freq = 440 if guitar_style == "clean" else 220
64
+ sine_wave = 0.5 * np.sin(2 * np.pi * freq * t)
65
+ audio_samples = (sine_wave * 32767).astype(np.int16)
66
+ audio = AudioSegment(
67
+ audio_samples.tobytes(),
68
+ frame_rate=sample_rate,
69
+ sample_width=2,
70
+ channels=1
71
+ )
72
+ return audio
73
+
74
+ def extend_audio(audio, target_duration_ms):
75
+ """Extend audio to target duration by looping."""
76
+ current_duration = len(audio)
77
+ if current_duration >= target_duration_ms:
78
+ return audio[:target_duration_ms] # Trim to exact duration
79
+ # Loop audio to exceed target duration
80
+ extended_audio = audio
81
+ while len(extended_audio) < target_duration_ms:
82
+ extended_audio += audio
83
+ return extended_audio[:target_duration_ms] # Trim to exact duration
84
+
85
+ def save_to_mp3(audio, filename):
86
+ """Save audio to MP3."""
87
+ filepath = os.path.join(MP3_DIR, filename)
88
+ audio.export(filepath, format="mp3")
89
+ logger.info(f"Saved MP3 to {filepath}")
90
+ return filepath
91
+
92
+ def update_json_log(band, params, filepath, status):
93
+ """Update JSON log."""
94
+ with open(JSON_LOG, "r") as f:
95
+ log = json.load(f)
96
+ render_entry = {
97
+ "timestamp": datetime.now().isoformat(),
98
+ "band": band,
99
+ "parameters": params,
100
+ "filepath": filepath,
101
+ "status": status
102
+ }
103
+ log.append(render_entry)
104
+ with open(JSON_LOG, "w") as f:
105
+ json.dump(log, f, indent=2)
106
+ logger.info(f"Updated JSON log: {render_entry}")
107
+
108
+ def generate_song(band, bpm, drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style):
109
+ """Generate and save a 180-second song."""
110
+ try:
111
+ client = Client(API_URL)
112
+ params = {
113
+ "bpm": bpm,
114
+ "drum_beat": drum_beat,
115
+ "synthesizer": synthesizer,
116
+ "rhythmic_steps": rhythmic_steps,
117
+ "bass_style": bass_style,
118
+ "guitar_style": guitar_style,
119
+ "api_name": config[band]["api_name"]
120
+ }
121
+ prompt = client.predict(**params)
122
+ logger.info(f"Prompt for {band}: {prompt}")
123
+
124
+ music_params = {
125
+ "instrumental_prompt": prompt,
126
+ "cfg_scale": 3.0,
127
+ "top_k": 300,
128
+ "top_p": 0.9,
129
+ "temperature": 0.8,
130
+ "total_duration": SONG_DURATION,
131
+ "bpm": bpm,
132
+ "drum_beat": drum_beat,
133
+ "synthesizer": synthesizer,
134
+ "rhythmic_steps": rhythmic_steps,
135
+ "bass_style": bass_style,
136
+ "guitar_style": guitar_style,
137
+ "target_volume": -23.0,
138
+ "preset": "rock",
139
+ "vram_status": "",
140
+ "api_name": "/generate_music"
141
+ }
142
+
143
+ result = client.predict(**music_params)
144
+ filepath, status, _ = result
145
+
146
+ if not filepath:
147
+ logger.warning("API returned no audio, using placeholder.")
148
+ audio = generate_music_placeholder(
149
+ prompt, SONG_DURATION, bpm, drum_beat, bass_style, guitar_style
150
+ )
151
+ audio = extend_audio(audio, TARGET_DURATION_MS)
152
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
153
+ filename = f"{band}_{timestamp}.mp3"
154
+ filepath = save_to_mp3(audio, filename)
155
+ status = "Generated with placeholder, extended to 180 seconds"
156
+ else:
157
+ # Load API-generated audio and extend
158
+ audio = AudioSegment.from_file(filepath)
159
+ audio = extend_audio(audio, TARGET_DURATION_MS)
160
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
161
+ filename = f"{band}_{timestamp}.mp3"
162
+ filepath = save_to_mp3(audio, filename)
163
+ status = "Generated with API, extended to 180 seconds"
164
+
165
+ update_json_log(band, music_params, filepath, status)
166
+ return filepath, status
167
+
168
+ except Exception as e:
169
+ logger.error(f"Error generating song: {str(e)}")
170
+ return None, f"Error: {str(e)}"
171
+
172
+ def get_last_five_songs():
173
+ """Get the last 5 songs from JSON log."""
174
+ try:
175
+ with open(JSON_LOG, "r") as f:
176
+ log = json.load(f)
177
+ log.sort(key=lambda x: x["timestamp"], reverse=True)
178
+ return [
179
+ {
180
+ "timestamp": entry["timestamp"],
181
+ "band": entry["band"].replace("_", " ").title(),
182
+ "filepath": entry["filepath"],
183
+ "parameters": entry["parameters"],
184
+ "status": entry["status"]
185
+ }
186
+ for entry in log[:5]
187
+ ]
188
+ except Exception as e:
189
+ logger.error(f"Error reading JSON log: {str(e)}")
190
+ return []
191
+
192
+ def create_gradio_interface():
193
+ """Create Gradio interface."""
194
+ css = """
195
+ .gradio-container {background-color: #2b2b2b; color: #ffffff; font-family: Arial, sans-serif;}
196
+ .gr-button-primary {background-color: #4a90e2; color: #ffffff; border: none; padding: 10px 20px; border-radius: 5px;}
197
+ .gr-button-primary:hover {background-color: #357abd;}
198
+ .gr-button-secondary {background-color: #4a4a4a; color: #ffffff; border: none; padding: 10px 20px; border-radius: 5px;}
199
+ .gr-button-secondary:hover {background-color: #333333;}
200
+ .gr-panel {background-color: #3c3c3c; border: none; border-radius: 8px; padding: 15px;}
201
+ .gr-textbox, .gr-slider, .gr-dropdown, .gr-audio {background-color: #4a4a4a; color: #ffffff; border: none; border-radius: 5px;}
202
+ .gr-markdown h1, .gr-markdown h2, .gr-markdown h3 {color: #ffffff;}
203
+ """
204
+ with gr.Blocks(title="Muzax Rock Generator", css=css) as demo:
205
+ gr.Markdown(
206
+ """
207
+ # Muzax Rock Song Generator
208
+ Create 3-minute rock songs inspired by top bands. Save MP3s to /home/pi5/muzax/mp3.
209
+ """
210
+ )
211
+
212
+ with gr.Tabs():
213
+ for band in ALLOWED_BANDS:
214
+ with gr.Tab(label=band.replace("_", " ").title()):
215
+ gr.Markdown(f"### {band.replace('_', ' ').title()} Song Generator")
216
+ with gr.Column():
217
+ bpm = gr.Slider(
218
+ minimum=60,
219
+ maximum=180,
220
+ value=120,
221
+ step=1,
222
+ label="Tempo (BPM) 🎵",
223
+ info="Song speed in beats per minute."
224
+ )
225
+ drum_beat = gr.Dropdown(
226
+ choices=["none", "standard rock", "funk groove", "techno kick", "jazz swing"],
227
+ value="standard rock",
228
+ label="Drum Beat 🥁",
229
+ info="Drum style."
230
+ )
231
+ synthesizer = gr.Dropdown(
232
+ choices=["none", "analog synth", "digital pad", "arpeggiated synth"],
233
+ value="none",
234
+ label="Synthesizer 🎹",
235
+ info="Synth sound."
236
+ )
237
+ rhythmic_steps = gr.Dropdown(
238
+ choices=["none", "syncopated steps", "steady steps", "complex steps"],
239
+ value="steady steps",
240
+ label="Rhythmic Steps 👣",
241
+ info="Rhythm complexity."
242
+ )
243
+ bass_style = gr.Dropdown(
244
+ choices=["none", "slap bass", "deep bass", "melodic bass"],
245
+ value="deep bass",
246
+ label="Bass Style 🎸",
247
+ info="Bass guitar style."
248
+ )
249
+ guitar_style = gr.Dropdown(
250
+ choices=["none", "distorted", "clean", "jangle"],
251
+ value="distorted",
252
+ label="Guitar Style 🎸",
253
+ info="Guitar sound."
254
+ )
255
+
256
+ with gr.Row():
257
+ randomize_btn = gr.Button("Randomize", variant="secondary")
258
+ generate_btn = gr.Button("Generate Song", variant="primary")
259
+
260
+ audio_output = gr.Audio(
261
+ label="Generated Song 🎵",
262
+ type="filepath",
263
+ interactive=False
264
+ )
265
+ status_output = gr.Textbox(
266
+ label="Status 📢",
267
+ placeholder="Status updates here.",
268
+ interactive=False
269
+ )
270
+
271
+ randomize_btn.click(
272
+ fn=lambda: generate_random_params(band),
273
+ outputs=[bpm, drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style]
274
+ )
275
+
276
+ generate_btn.click(
277
+ fn=generate_song,
278
+ inputs=[gr.State(value=band), bpm, drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style],
279
+ outputs=[audio_output, status_output]
280
+ )
281
+
282
+ with gr.Tab("Recent Songs"):
283
+ gr.Markdown("### Last 5 Songs")
284
+ recent_songs = gr.State(value=get_last_five_songs())
285
+
286
+ for i in range(5):
287
+ with gr.Group():
288
+ gr.Markdown(f"#### Song {i+1}")
289
+ audio_player = gr.Audio(label=f"Song {i+1}", type="filepath", interactive=False)
290
+ info_text = gr.Textbox(label=f"Details {i+1}", interactive=False)
291
+ play_btn = gr.Button(f"Play Song {i+1}", variant="primary")
292
+
293
+ def play_song(song_list, index=i):
294
+ if index < len(song_list) and os.path.exists(song_list[index]["filepath"]):
295
+ return (
296
+ song_list[index]["filepath"],
297
+ f"Band: {song_list[index]['band']}\nTime: {song_list[index]['timestamp']}\nParams: {json.dumps(song_list[index]['parameters'], indent=2)}\nStatus: {song_list[index]['status']}"
298
+ )
299
+ return None, "Song unavailable."
300
+
301
+ play_btn.click(
302
+ fn=play_song,
303
+ inputs=[recent_songs],
304
+ outputs=[audio_player, info_text]
305
+ )
306
+
307
+ refresh_btn = gr.Button("Refresh Songs", variant="secondary")
308
+ refresh_btn.click(
309
+ fn=get_last_five_songs,
310
+ outputs=[recent_songs]
311
+ )
312
+
313
+ with gr.Tab("Render Log"):
314
+ gr.Markdown("### Render Log")
315
+ log_output = gr.JSON(label="All Renders", value=lambda: json.load(open(JSON_LOG)))
316
+ refresh_log_btn = gr.Button("Refresh Log", variant="primary")
317
+ refresh_log_btn.click(
318
+ fn=lambda: json.load(open(JSON_LOG)),
319
+ outputs=[log_output]
320
+ )
321
+
322
+ return demo
323
+
324
+ if __name__ == "__main__":
325
+ try:
326
+ demo = create_gradio_interface()
327
+ demo.launch(server_name="0.0.0.0", server_port=3223, share=True)
328
+ logger.info("Gradio launched on 0.0.0.0:3223 with public sharing enabled")
329
+ except Exception as e:
330
+ logger.error(f"Failed to launch Gradio: {str(e)}")