ghostai1 commited on
Commit
00c59ff
·
verified ·
1 Parent(s): 621b6ab

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +467 -0
app.py ADDED
@@ -0,0 +1,467 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import torch
3
+ import torchaudio
4
+ import psutil
5
+ import time
6
+ import sys
7
+ import numpy as np
8
+ import gc
9
+ import gradio as gr
10
+ from pydub import AudioSegment
11
+ from audiocraft.models import MusicGen
12
+ from torch.cuda.amp import autocast
13
+
14
+ # Set PYTORCH_CUDA_ALLOC_CONF to manage memory fragmentation
15
+ os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "max_split_size_mb:32"
16
+
17
+ # Setup instructions:
18
+ # 1. Create a virtual environment: python3 -m venv venv
19
+ # 2. Activate the environment: source venv/bin/activate
20
+ # 3. Install dependencies:
21
+ # pip install torch==2.1.0 torchvision==0.16.0 torchaudio==2.1.0 --index-url https://download.pytorch.org/whl/cu121
22
+ # pip install -r requirements.txt
23
+ # 4. Install ffmpeg for MP3 export:
24
+ # sudo apt-get install ffmpeg
25
+ # 5. Ensure model weights are in /home/ubuntu/ghostai_music_generator/models/musicgen-medium
26
+ # 6. Log in to Hugging Face CLI: huggingface-cli login
27
+ # 7. Request access to model if needed: https://huggingface.co/facebook/musicgen-medium
28
+
29
+ # Check critical dependencies
30
+ if np.__version__ != "1.23.5":
31
+ print(f"ERROR: NumPy version {np.__version__} is not compatible. Please install numpy==1.23.5.")
32
+ sys.exit(1)
33
+ if not torch.__version__.startswith(("2.1.0", "2.3.1")):
34
+ print(f"WARNING: PyTorch version {torch.__version__} may not be compatible. Expected torch==2.1.0 or 2.3.1.")
35
+
36
+ # 1) DEVICE SETUP
37
+ device = "cuda" if torch.cuda.is_available() else "cpu"
38
+ if device != "cuda":
39
+ print("ERROR: CUDA is required for GPU rendering. CPU rendering is disabled to avoid slow performance.")
40
+ sys.exit(1)
41
+ print(f"CUDA is available. Using GPU: {torch.cuda.get_device_name(0)}")
42
+
43
+ # 2) LOAD MUSICGEN INTO VRAM
44
+ try:
45
+ print("Loading MusicGen model into VRAM...")
46
+ local_model_path = "/home/ubuntu/ghostai_music_generator/models/musicgen-medium"
47
+ if not os.path.exists(local_model_path):
48
+ print(f"ERROR: Local model path {local_model_path} does not exist. Please ensure the model weights are downloaded.")
49
+ sys.exit(1)
50
+ musicgen_model = MusicGen.get_pretrained(local_model_path, device=device)
51
+ except Exception as e:
52
+ print(f"ERROR: Failed to load MusicGen model: {e}")
53
+ print("Please ensure the model weights are in the correct path and dependencies are installed.")
54
+ sys.exit(1)
55
+
56
+ # 3) RESOURCE MONITORING FUNCTION
57
+ def print_resource_usage(stage: str):
58
+ print(f"--- {stage} ---")
59
+ print(f"GPU Memory Allocated: {torch.cuda.memory_allocated() / (1024**3):.2f} GB")
60
+ print(f"GPU Memory Reserved: {torch.cuda.memory_reserved() / (1024**3):.2f} GB")
61
+ print("---------------")
62
+
63
+ # 4) GENRE PROMPT FUNCTIONS (Updated for distinct song sections with balanced energy)
64
+ def set_rock_prompt():
65
+ return "Hard rock with a dynamic intro, expressive verse, and powerful chorus, featuring electric guitars, steady heavy drums, and deep bass"
66
+
67
+ def set_techno_prompt():
68
+ return "Techno with a pulsing intro, intense build-up, and energetic drop, featuring dark synths, driving bass, and rhythmic fast drums"
69
+
70
+ def set_jazz_prompt():
71
+ return "Smooth jazz with a warm intro, expressive theme, and lively outro, featuring saxophone, piano, and soft rhythmic drums"
72
+
73
+ def set_classical_prompt():
74
+ return "Classical orchestral piece with a gentle intro, dramatic development, and grand finale, featuring strings, piano, and subtle rhythmic percussion"
75
+
76
+ def set_hiphop_prompt():
77
+ return "Hip-hop with a groovy intro, rhythmic verse, and catchy hook, featuring deep bass, tight crisp drums, and funky synths"
78
+
79
+ # 5) AUDIO PROCESSING FUNCTIONS
80
+ def apply_chorus(segment):
81
+ # Enhanced chorus effect for richer sound
82
+ delayed = segment - 4 # Slightly louder delayed copy at -4 dB (was -6 dB)
83
+ delayed = delayed.set_frame_rate(segment.frame_rate)
84
+ return segment.overlay(delayed, position=20) # Increased delay to 20ms for more noticeable effect
85
+
86
+ def apply_eq(segment):
87
+ # Apply EQ: Balanced filters for cleaner sound
88
+ segment = segment.low_pass_filter(6000) # Loosen low-pass filter for brighter highs
89
+ segment = segment.high_pass_filter(100) # Slightly lower cutoff for low rumble
90
+ return segment
91
+
92
+ def apply_limiter(segment, max_db=-8.0):
93
+ # Apply a strict limiter to control peaks
94
+ if segment.dBFS > max_db:
95
+ segment = segment - (segment.dBFS - max_db) # Reduce gain to prevent clipping
96
+ return segment
97
+
98
+ # 6) GENERATION & I/O FUNCTIONS
99
+ def generate_music(instrumental_prompt: str, cfg_scale: float, top_k: int, top_p: float, temperature: float, total_duration: int, crossfade_duration: int):
100
+ global musicgen_model # Use global to modify the model instance
101
+ if not instrumental_prompt.strip():
102
+ return None, "⚠️ Please enter a valid instrumental prompt!"
103
+ try:
104
+ # Record start time for total duration calculation
105
+ start_time = time.time()
106
+
107
+ # Ensure total duration is within reasonable bounds
108
+ total_duration = min(max(total_duration, 10), 60) # Between 10 and 60 seconds
109
+ chunk_duration = 15 # Duration of each chunk (e.g., 2 chunks of 15 seconds for a 30-second track)
110
+ num_chunks = max(2, (total_duration + chunk_duration - 1) // chunk_duration) # Ensure at least 2 chunks
111
+ chunk_duration = total_duration / num_chunks # Adjust chunk duration to fit total duration
112
+
113
+ # Generate slightly longer chunks for overlap
114
+ overlap_duration = min(1.0, crossfade_duration / 1000.0) # Convert ms to seconds, max 1 second
115
+ generation_duration = chunk_duration + overlap_duration # Add overlap to each chunk
116
+
117
+ # Initialize list to store audio chunks
118
+ audio_chunks = []
119
+ sample_rate = musicgen_model.sample_rate
120
+
121
+ # Generate audio in chunks with distinct prompts for song structure
122
+ for i in range(num_chunks):
123
+ # Vary the prompt for each chunk to create clear song sections
124
+ if i == 0:
125
+ chunk_prompt = instrumental_prompt + ", focusing on a dynamic intro and expressive verse"
126
+ else:
127
+ chunk_prompt = instrumental_prompt + ", focusing on a powerful chorus and energetic outro"
128
+
129
+ print(f"Generating chunk {i+1}/{num_chunks} on GPU (prompt: {chunk_prompt})...")
130
+ musicgen_model.set_generation_params(
131
+ duration=generation_duration,
132
+ use_sampling=True,
133
+ top_k=top_k,
134
+ top_p=top_p,
135
+ temperature=temperature,
136
+ cfg_coef=cfg_scale
137
+ )
138
+
139
+ # Print resource usage before each chunk
140
+ print_resource_usage(f"Before Chunk {i+1} Generation")
141
+
142
+ with torch.no_grad():
143
+ with autocast(): # Use mixed precision for lower VRAM usage
144
+ audio_chunk = musicgen_model.generate([chunk_prompt], progress=True)[0] # Shape: (2, samples)
145
+
146
+ # Normalize to stereo
147
+ audio_chunk = audio_chunk.cpu().to(dtype=torch.float32)
148
+ if audio_chunk.dim() == 1:
149
+ audio_chunk = torch.stack([audio_chunk, audio_chunk], dim=0) # Mono to stereo
150
+ elif audio_chunk.dim() == 2 and audio_chunk.shape[0] == 1:
151
+ audio_chunk = torch.cat([audio_chunk, audio_chunk], dim=0)
152
+ elif audio_chunk.dim() == 2 and audio_chunk.shape[0] != 2:
153
+ audio_chunk = audio_chunk[:1, :]
154
+ audio_chunk = torch.cat([audio_chunk, audio_chunk], dim=0)
155
+ elif audio_chunk.dim() > 2:
156
+ audio_chunk = audio_chunk.view(2, -1)
157
+
158
+ if audio_chunk.shape[0] != 2:
159
+ raise ValueError(f"Expected stereo audio with shape (2, samples), got shape {audio_chunk.shape}")
160
+
161
+ # Save chunk temporarily as WAV, then convert to MP3
162
+ temp_wav_path = f"temp_chunk_{i}.wav"
163
+ chunk_path = f"chunk_{i}.mp3"
164
+ torchaudio.save(temp_wav_path, audio_chunk, sample_rate, bits_per_sample=24)
165
+ segment = AudioSegment.from_wav(temp_wav_path)
166
+ segment = apply_limiter(segment, max_db=-8.0) # Apply limiter early to control peaks
167
+ segment.export(chunk_path, format="mp3", bitrate="320k")
168
+ os.remove(temp_wav_path) # Clean up temporary WAV file
169
+ audio_chunks.append(chunk_path)
170
+
171
+ # Free memory after generating each chunk
172
+ torch.cuda.empty_cache()
173
+ gc.collect()
174
+ time.sleep(0.5) # Small delay to ensure memory is released
175
+ print_resource_usage(f"After Chunk {i+1} Generation")
176
+
177
+ # Combine chunks using pydub with crossfade to smooth transitions
178
+ print("Combining audio chunks...")
179
+ final_segment = AudioSegment.from_mp3(audio_chunks[0])
180
+ for i in range(1, len(audio_chunks)):
181
+ next_segment = AudioSegment.from_mp3(audio_chunks[i])
182
+ # Apply a gentle gain boost to raise quieter sections
183
+ next_segment = next_segment + 2 # Boost by 2 dB to avoid amplitude dips
184
+ next_segment = apply_limiter(next_segment, max_db=-8.0) # Re-apply limiter after gain boost
185
+ final_segment = final_segment.append(next_segment, crossfade=crossfade_duration)
186
+
187
+ # Trim to exact total duration (in milliseconds)
188
+ final_segment = final_segment[:total_duration * 1000]
189
+
190
+ # Post-process to enhance audio quality
191
+ print("Post-processing final track...")
192
+ final_segment = apply_eq(final_segment)
193
+ final_segment = apply_chorus(final_segment)
194
+
195
+ # Apply final limiter and normalization with large headroom
196
+ final_segment = apply_limiter(final_segment, max_db=-8.0)
197
+ final_segment = final_segment.normalize(headroom=-10.0) # Large headroom to prevent clipping
198
+
199
+ # Export as MP3 only
200
+ mp3_path = "output_cleaned.mp3"
201
+ final_segment.export(
202
+ mp3_path,
203
+ format="mp3",
204
+ bitrate="320k",
205
+ tags={"title": "GhostAI Instrumental", "artist": "GhostAI"}
206
+ )
207
+ print(f"Saved final audio to {mp3_path}")
208
+
209
+ # Clean up temporary chunk files
210
+ for chunk_path in audio_chunks:
211
+ os.remove(chunk_path)
212
+
213
+ # Print resource usage after generation
214
+ print_resource_usage("After Final Generation")
215
+ print(f"Total Generation Time: {time.time() - start_time:.2f} seconds")
216
+
217
+ return mp3_path, "✅ Done!"
218
+ except Exception as e:
219
+ return None, f"❌ Generation failed: {e}"
220
+ finally:
221
+ torch.cuda.empty_cache()
222
+ gc.collect()
223
+
224
+ def clear_inputs():
225
+ return "", 3.0, 300, 0.95, 1.0, 30, 500
226
+
227
+ # 7) CUSTOM CSS
228
+ css = """
229
+ body {
230
+ background: linear-gradient(135deg, #0A0A0A 0%, #1C2526 100%);
231
+ color: #E0E0E0;
232
+ font-family: 'Orbitron', sans-serif;
233
+ margin: 0;
234
+ padding: 0;
235
+ }
236
+ .header-container {
237
+ text-align: center;
238
+ padding: 15px 20px;
239
+ background: rgba(0, 0, 0, 0.9);
240
+ border-bottom: 1px solid #00FF9F;
241
+ box-shadow: 0 0 10px rgba(161, 0, 255, 0.3);
242
+ }
243
+ #ghost-logo {
244
+ font-size: 60px;
245
+ display: block;
246
+ margin: 0 auto;
247
+ animation: glitch-ghost 1.5s infinite;
248
+ text-shadow: 0 0 10px #A100FF, 0 0 20px #00FF9F;
249
+ }
250
+ h1 {
251
+ color: #A100FF;
252
+ font-size: 28px;
253
+ margin: 5px 0;
254
+ text-shadow: 0 0 5px #A100FF, 0 0 10px #00FF9F;
255
+ animation: glitch-text 2s infinite;
256
+ }
257
+ p {
258
+ color: #E0E0E0;
259
+ font-size: 14px;
260
+ margin: 5px 0;
261
+ }
262
+ .input-container {
263
+ max-width: 1000px;
264
+ margin: 20px auto;
265
+ padding: 20px;
266
+ background: rgba(28, 37, 38, 0.8);
267
+ border-radius: 10px;
268
+ box-shadow: 0 0 15px rgba(0, 255, 159, 0.3);
269
+ }
270
+ .textbox {
271
+ background: #1A1A1A;
272
+ border: 1px solid #A100FF;
273
+ color: #E0E0E0;
274
+ border-radius: 5px;
275
+ padding: 10px;
276
+ margin-bottom: 20px;
277
+ }
278
+ .genre-buttons {
279
+ display: flex;
280
+ justify-content: center;
281
+ gap: 15px;
282
+ margin-bottom: 20px;
283
+ }
284
+ .genre-btn {
285
+ background: linear-gradient(45deg, #A100FF, #00FF9F);
286
+ border: none;
287
+ color: #0A0A0A;
288
+ font-weight: bold;
289
+ padding: 10px 20px;
290
+ border-radius: 5px;
291
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
292
+ }
293
+ .genre-btn:hover {
294
+ transform: scale(1.05);
295
+ box-shadow: 0 0 15px #00FF9F;
296
+ }
297
+ .settings-container {
298
+ max-width: 1000px;
299
+ margin: 20px auto;
300
+ padding: 20px;
301
+ background: rgba(28, 37, 38, 0.8);
302
+ border-radius: 10px;
303
+ box-shadow: 0 0 15px rgba(0, 255, 159, 0.3);
304
+ }
305
+ .action-buttons {
306
+ display: flex;
307
+ justify-content: center;
308
+ gap: 20px;
309
+ margin-top: 20px;
310
+ }
311
+ button {
312
+ background: linear-gradient(45deg, #A100FF, #00FF9F);
313
+ border: none;
314
+ color: #0A0A0A;
315
+ font-weight: bold;
316
+ padding: 12px 24px;
317
+ border-radius: 5px;
318
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
319
+ }
320
+ button:hover {
321
+ transform: scale(1.05);
322
+ box-shadow: 0 0 15px #00FF9F;
323
+ }
324
+ .output-container {
325
+ max-width: 1000px;
326
+ margin: 20px auto;
327
+ padding: 20px;
328
+ background: rgba(28, 37, 38, 0.8);
329
+ border-radius: 10px;
330
+ box-shadow: 0 0 15px rgba(0, 255, 159, 0.3);
331
+ text-align: center;
332
+ }
333
+ @keyframes glitch-ghost {
334
+ 0% { transform: translate(0, 0); opacity: 1; }
335
+ 20% { transform: translate(-5px, 2px); opacity: 0.8; }
336
+ 40% { transform: translate(5px, -2px); opacity: 0.6; }
337
+ 60% { transform: translate(-3px, 1px); opacity: 0.9; }
338
+ 80% { transform: translate(3px, -1px); opacity: 0.7; }
339
+ 100% { transform: translate(0, 0); opacity: 1; }
340
+ }
341
+ @keyframes glitch-text {
342
+ 0% { transform: translate(0, 0); text-shadow: 0 0 10px #A100FF, 0 0 20px #00FF9F; }
343
+ 20% { transform: translate(-2px, 1px); text-shadow: 0 0 15px #00FF9F, 0 0 25px #A100FF; }
344
+ 40% { transform: translate(2px, -1px); text-shadow: 0 0 10px #A100FF, 0 0 30px #00FF9F; }
345
+ 60% { transform: translate(-1px, 2px); text-shadow: 0 0 15px #00FF9F, 0 0 20px #A100FF; }
346
+ 80% { transform: translate(1px, -2px); text-shadow: 0 0 10px #A100FF, 0 0 25px #00FF9F; }
347
+ 100% { transform: translate(0, 0); text-shadow: 0 0 10px #A100FF, 0 0 20px #00FF9F; }
348
+ }
349
+ @font-face {
350
+ font-family: 'Orbitron';
351
+ src: url('https://fonts.gstatic.com/s/orbitron/v29/yMJRMIlzdpvBhQQL_Qq7dy0.woff2') format('woff2');
352
+ }
353
+ """
354
+
355
+ # 8) BUILD WITH BLOCKS
356
+ with gr.Blocks(css=css) as demo:
357
+ gr.Markdown("""
358
+ <div class="header-container">
359
+ <div id="ghost-logo">👻</div>
360
+ <h1>GhostAI Music Generator</h1>
361
+ <p>Summon the Sound of the Unknown</p>
362
+ </div>
363
+ """)
364
+
365
+ with gr.Column(elem_classes="input-container"):
366
+ instrumental_prompt = gr.Textbox(
367
+ label="Instrumental Prompt",
368
+ placeholder="Click a genre button below or type your own instrumental prompt",
369
+ lines=4,
370
+ elem_classes="textbox"
371
+ )
372
+ with gr.Row(elem_classes="genre-buttons"):
373
+ rock_btn = gr.Button("Rock", elem_classes="genre-btn")
374
+ techno_btn = gr.Button("Techno", elem_classes="genre-btn")
375
+ jazz_btn = gr.Button("Jazz", elem_classes="genre-btn")
376
+ classical_btn = gr.Button("Classical", elem_classes="genre-btn")
377
+ hiphop_btn = gr.Button("Hip-Hop", elem_classes="genre-btn")
378
+
379
+ with gr.Column(elem_classes="settings-container"):
380
+ cfg_scale = gr.Slider(
381
+ label="Guidance Scale (CFG)",
382
+ minimum=1.0,
383
+ maximum=10.0,
384
+ value=3.0,
385
+ step=0.1,
386
+ info="Higher values make the instrumental more closely follow the prompt, but may reduce diversity."
387
+ )
388
+ top_k = gr.Slider(
389
+ label="Top-K Sampling",
390
+ minimum=10,
391
+ maximum=500,
392
+ value=300,
393
+ step=10,
394
+ info="Limits sampling to the top k most likely tokens. Higher values increase diversity."
395
+ )
396
+ top_p = gr.Slider(
397
+ label="Top-P Sampling (Nucleus Sampling)",
398
+ minimum=0.0,
399
+ maximum=1.0,
400
+ value=0.95,
401
+ step=0.1,
402
+ info="Keeps tokens with cumulative probability above p. Higher values increase diversity."
403
+ )
404
+ temperature = gr.Slider(
405
+ label="Temperature",
406
+ minimum=0.1,
407
+ maximum=2.0,
408
+ value=1.0,
409
+ step=0.1,
410
+ info="Controls randomness. Higher values make output more diverse but less predictable."
411
+ )
412
+ total_duration = gr.Slider(
413
+ label="Total Duration (seconds)",
414
+ minimum=10,
415
+ maximum=60,
416
+ value=30,
417
+ step=1,
418
+ info="Total duration of the track (10 to 60 seconds)."
419
+ )
420
+ crossfade_duration = gr.Slider(
421
+ label="Crossfade Duration (ms)",
422
+ minimum=100,
423
+ maximum=2000,
424
+ value=500,
425
+ step=100,
426
+ info="Crossfade duration between chunks for smoother transitions."
427
+ )
428
+ with gr.Row(elem_classes="action-buttons"):
429
+ gen_btn = gr.Button("Generate Music")
430
+ clr_btn = gr.Button("Clear Inputs")
431
+
432
+ with gr.Column(elem_classes="output-container"):
433
+ out_audio = gr.Audio(label="Generated Stereo Instrumental Track", type="filepath")
434
+ status = gr.Textbox(label="Status", interactive=False)
435
+
436
+ rock_btn.click(set_rock_prompt, inputs=None, outputs=[instrumental_prompt])
437
+ techno_btn.click(set_techno_prompt, inputs=None, outputs=[instrumental_prompt])
438
+ jazz_btn.click(set_jazz_prompt, inputs=None, outputs=[instrumental_prompt])
439
+ classical_btn.click(set_classical_prompt, inputs=None, outputs=[instrumental_prompt])
440
+ hiphop_btn.click(set_hiphop_prompt, inputs=None, outputs=[instrumental_prompt])
441
+ gen_btn.click(
442
+ generate_music,
443
+ inputs=[instrumental_prompt, cfg_scale, top_k, top_p, temperature, total_duration, crossfade_duration],
444
+ outputs=[out_audio, status]
445
+ )
446
+ clr_btn.click(
447
+ clear_inputs,
448
+ inputs=None,
449
+ outputs=[instrumental_prompt, cfg_scale, top_k, top_p, temperature, total_duration, crossfade_duration]
450
+ )
451
+
452
+ # 9) TURN OFF OPENAPI/DOCS to avoid the gradio-client schema bug
453
+ app = demo.launch(
454
+ server_name="0.0.0.0",
455
+ server_port=9999,
456
+ share=False,
457
+ inbrowser=False,
458
+ show_error=True # keep this so you still see stack traces in console
459
+ )
460
+ # Access the underlying FastAPI and disable its docs
461
+ try:
462
+ fastapi_app = demo._server.app # Gradio v4.44+ puts FastAPI here
463
+ fastapi_app.docs_url = None
464
+ fastapi_app.redoc_url = None
465
+ fastapi_app.openapi_url = None
466
+ except Exception:
467
+ pass