cazyundee commited on
Commit
b7387cb
·
verified ·
1 Parent(s): 12e32d6

Add API routes directly to Gradio FastAPI app

Browse files
Files changed (1) hide show
  1. app.py +76 -96
app.py CHANGED
@@ -7,7 +7,6 @@ import torch
7
  import torchaudio
8
  import gradio as gr
9
  import spaces
10
- from fastapi import FastAPI
11
 
12
  # Hugging Face Spaces should use UTF-8; explicitly configure streams so model
13
  # libraries cannot inherit a platform-specific charmap encoding.
@@ -36,10 +35,28 @@ try:
36
  return "ok"
37
  _gpu_startup_touch()
38
  except Exception as e:
39
- _log(f"Warning: ZeroGPU touch failed (running CPU-only): {e}")
40
 
41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  # API metadata
 
 
43
  API_RESOURCES = {
44
  "audio_generation": {
45
  "name": "Audio generation",
@@ -52,51 +69,12 @@ API_RESOURCES = {
52
  "steps": "integer (1-50)",
53
  "cfg_scale": "number (0-10)",
54
  "seed": "integer (-1 for random)",
55
- "model": "small-music | small-sfx"
56
  },
57
- "output": "WAV audio file"
58
  }
59
  }
60
 
61
- def get_server_specs():
62
- storage = shutil.disk_usage(os.getcwd())
63
- return {
64
- "name": "Respite API",
65
- "version": "1.0.0",
66
- "description": "General-purpose AI API server with audio generation capabilities.",
67
- "base_path": "/api",
68
- "authentication": "none",
69
- "content_types": ["application/json", "audio/wav"],
70
- "resources_endpoint": "/api/resources",
71
- "specs_endpoint": "/api/specs",
72
- "runtime": {
73
- "platform": platform.platform(),
74
- "python_version": platform.python_version(),
75
- "cpu_cores": os.cpu_count(),
76
- "ram_bytes": _get_ram_bytes(),
77
- "storage_total_bytes": storage.total,
78
- "storage_used_bytes": storage.used,
79
- "storage_free_bytes": storage.free
80
- },
81
- "limits": {
82
- "max_concurrent_requests": 1,
83
- "max_queue_size": 4,
84
- "audio_max_duration_seconds": 120
85
- }
86
- }
87
-
88
-
89
- def _get_ram_bytes():
90
- try:
91
- with open("/proc/meminfo", "r", encoding="utf-8") as meminfo:
92
- for line in meminfo:
93
- if line.startswith("MemTotal:"):
94
- return int(line.split()[1]) * 1024
95
- except (FileNotFoundError, OSError, ValueError):
96
- pass
97
- return None
98
-
99
-
100
  API_SPECS = {
101
  "name": "Respite API",
102
  "version": "1.0.0",
@@ -109,24 +87,28 @@ API_SPECS = {
109
  "limits": {
110
  "max_concurrent_requests": 1,
111
  "max_queue_size": 4,
112
- "audio_max_duration_seconds": 120
113
- }
114
  }
115
 
116
 
117
- def get_resources():
118
- return {"resources": API_RESOURCES}
119
-
120
-
121
- def get_specs():
122
- return {**API_SPECS, "runtime": get_server_specs()["runtime"]}
123
-
124
-
125
- def _log(message):
126
- print(message.encode("ascii", "backslashreplace").decode("ascii"), flush=True)
 
127
 
128
 
 
129
  # Model cache
 
 
130
  MODEL_CACHE = {}
131
 
132
 
@@ -134,10 +116,7 @@ def load_model(model_name):
134
  """Load model on demand and cache it."""
135
  if model_name not in MODEL_CACHE:
136
  _log(f"Loading {model_name} model...")
137
- model = StableAudioModel.from_pretrained(
138
- model_name,
139
- device="cpu"
140
- )
141
  MODEL_CACHE[model_name] = model
142
  _log(f"{model_name} loaded successfully!")
143
  return MODEL_CACHE[model_name]
@@ -148,7 +127,10 @@ def load_model(model_name):
148
 
149
 
150
  def generate_audio(prompt, duration, steps, cfg_scale, seed, model_name):
151
- _log(f"Generating with {model_name}: prompt='{prompt}', duration={duration}s, steps={steps}, cfg={cfg_scale}, seed={seed}")
 
 
 
152
 
153
  model = load_model(model_name)
154
 
@@ -158,82 +140,80 @@ def generate_audio(prompt, duration, steps, cfg_scale, seed, model_name):
158
  steps=steps,
159
  cfg_scale=cfg_scale,
160
  seed=seed,
161
- batch_size=1
162
  )
163
 
164
  # Post-process: (batch, channels, samples) -> stereo waveform
165
  audio = rearrange(audio, "b d n -> d (b n)")
166
  audio = audio.to(torch.float32).clamp(-1, 1).mul(32767).to(torch.int16).cpu()
167
 
168
- output_path = os.path.join(tempfile.gettempdir(), f"stable_audio_{seed}_{hash(prompt) & 0xFFFFFFFF:08x}.wav")
 
 
 
169
  torchaudio.save(output_path, audio, 44100)
170
  _log("Generation complete!")
171
  return output_path
172
 
173
 
174
- api = FastAPI(title=API_SPECS["name"], version=API_SPECS["version"])
175
-
176
-
177
- @api.get("/api/resources")
178
- def resources():
179
- return JSONResponse(get_resources())
180
-
181
-
182
- @api.get("/api/specs")
183
- def specs():
184
- return JSONResponse(get_specs())
185
-
186
 
187
  with gr.Blocks(title="Respite API") as demo:
188
  gr.Markdown("# Respite API - Music & SFX Generation")
189
- gr.Markdown("Generate music and sound effects using Stability AI's Stable Audio 3 Small models.")
 
 
 
190
 
191
  with gr.Row():
192
  with gr.Column():
193
  model_name = gr.Dropdown(
194
  choices=["small-music", "small-sfx"],
195
  value="small-music",
196
- label="Model"
197
  )
198
  prompt = gr.Textbox(
199
  label="Prompt",
200
  placeholder="Describe the music or sound effect you want to generate...",
201
- lines=2
202
  )
203
  duration = gr.Slider(
204
- minimum=1, maximum=120, value=30, step=1,
205
- label="Duration (seconds)"
206
  )
207
  steps = gr.Slider(
208
- minimum=1, maximum=50, value=8, step=1,
209
- label="Steps"
210
  )
211
  cfg_scale = gr.Slider(
212
- minimum=0.0, maximum=10.0, value=1.0, step=0.1,
213
- label="CFG Scale"
214
- )
215
- seed = gr.Number(
216
- value=-1, label="Seed (-1 for random)"
217
  )
 
218
  btn = gr.Button("Generate", variant="primary")
219
 
220
  with gr.Column():
221
- audio_output = gr.Audio(
222
- label="Generated Audio",
223
- type="filepath"
224
- )
225
 
226
  btn.click(
227
  fn=generate_audio,
228
  inputs=[prompt, duration, steps, cfg_scale, seed, model_name],
229
- outputs=audio_output
230
  )
231
 
232
 
233
- # Mount the Gradio UI under the FastAPI application so API routes remain
234
- # available at /api/* instead of being handled by Gradio's catch-all route.
 
 
 
235
  demo.queue(max_size=4, default_concurrency_limit=1)
236
- app = gr.mount_gradio_app(api, demo, path="/")
237
 
238
- # Spaces (sdk: python) serves the ASGI `app` variable directly.
239
- # For local development: uvicorn app:app --host 0.0.0.0 --port 7860
 
 
 
 
 
 
 
 
7
  import torchaudio
8
  import gradio as gr
9
  import spaces
 
10
 
11
  # Hugging Face Spaces should use UTF-8; explicitly configure streams so model
12
  # libraries cannot inherit a platform-specific charmap encoding.
 
35
  return "ok"
36
  _gpu_startup_touch()
37
  except Exception as e:
38
+ print(f"Warning: ZeroGPU touch failed (running CPU-only): {e}", flush=True)
39
 
40
 
41
+ def _log(message):
42
+ print(message.encode("ascii", "backslashreplace").decode("ascii"), flush=True)
43
+
44
+
45
+ def _get_ram_bytes():
46
+ try:
47
+ with open("/proc/meminfo", "r", encoding="utf-8") as meminfo:
48
+ for line in meminfo:
49
+ if line.startswith("MemTotal:"):
50
+ return int(line.split()[1]) * 1024
51
+ except (FileNotFoundError, OSError, ValueError):
52
+ pass
53
+ return None
54
+
55
+
56
+ # ---------------------------------------------------------------------------
57
  # API metadata
58
+ # ---------------------------------------------------------------------------
59
+
60
  API_RESOURCES = {
61
  "audio_generation": {
62
  "name": "Audio generation",
 
69
  "steps": "integer (1-50)",
70
  "cfg_scale": "number (0-10)",
71
  "seed": "integer (-1 for random)",
72
+ "model": "small-music | small-sfx",
73
  },
74
+ "output": "WAV audio file",
75
  }
76
  }
77
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  API_SPECS = {
79
  "name": "Respite API",
80
  "version": "1.0.0",
 
87
  "limits": {
88
  "max_concurrent_requests": 1,
89
  "max_queue_size": 4,
90
+ "audio_max_duration_seconds": 120,
91
+ },
92
  }
93
 
94
 
95
+ def _get_runtime_specs():
96
+ storage = shutil.disk_usage(os.getcwd())
97
+ return {
98
+ "platform": platform.platform(),
99
+ "python_version": platform.python_version(),
100
+ "cpu_cores": os.cpu_count(),
101
+ "ram_bytes": _get_ram_bytes(),
102
+ "storage_total_bytes": storage.total,
103
+ "storage_used_bytes": storage.used,
104
+ "storage_free_bytes": storage.free,
105
+ }
106
 
107
 
108
+ # ---------------------------------------------------------------------------
109
  # Model cache
110
+ # ---------------------------------------------------------------------------
111
+
112
  MODEL_CACHE = {}
113
 
114
 
 
116
  """Load model on demand and cache it."""
117
  if model_name not in MODEL_CACHE:
118
  _log(f"Loading {model_name} model...")
119
+ model = StableAudioModel.from_pretrained(model_name, device="cpu")
 
 
 
120
  MODEL_CACHE[model_name] = model
121
  _log(f"{model_name} loaded successfully!")
122
  return MODEL_CACHE[model_name]
 
127
 
128
 
129
  def generate_audio(prompt, duration, steps, cfg_scale, seed, model_name):
130
+ _log(
131
+ f"Generating with {model_name}: prompt='{prompt}', "
132
+ f"duration={duration}s, steps={steps}, cfg={cfg_scale}, seed={seed}"
133
+ )
134
 
135
  model = load_model(model_name)
136
 
 
140
  steps=steps,
141
  cfg_scale=cfg_scale,
142
  seed=seed,
143
+ batch_size=1,
144
  )
145
 
146
  # Post-process: (batch, channels, samples) -> stereo waveform
147
  audio = rearrange(audio, "b d n -> d (b n)")
148
  audio = audio.to(torch.float32).clamp(-1, 1).mul(32767).to(torch.int16).cpu()
149
 
150
+ output_path = os.path.join(
151
+ tempfile.gettempdir(),
152
+ f"stable_audio_{seed}_{hash(prompt) & 0xFFFFFFFF:08x}.wav",
153
+ )
154
  torchaudio.save(output_path, audio, 44100)
155
  _log("Generation complete!")
156
  return output_path
157
 
158
 
159
+ # ---------------------------------------------------------------------------
160
+ # Gradio UI
161
+ # ---------------------------------------------------------------------------
 
 
 
 
 
 
 
 
 
162
 
163
  with gr.Blocks(title="Respite API") as demo:
164
  gr.Markdown("# Respite API - Music & SFX Generation")
165
+ gr.Markdown(
166
+ "Generate music and sound effects using "
167
+ "Stability AI's Stable Audio 3 Small models."
168
+ )
169
 
170
  with gr.Row():
171
  with gr.Column():
172
  model_name = gr.Dropdown(
173
  choices=["small-music", "small-sfx"],
174
  value="small-music",
175
+ label="Model",
176
  )
177
  prompt = gr.Textbox(
178
  label="Prompt",
179
  placeholder="Describe the music or sound effect you want to generate...",
180
+ lines=2,
181
  )
182
  duration = gr.Slider(
183
+ minimum=1, maximum=120, value=30, step=1, label="Duration (seconds)"
 
184
  )
185
  steps = gr.Slider(
186
+ minimum=1, maximum=50, value=8, step=1, label="Steps"
 
187
  )
188
  cfg_scale = gr.Slider(
189
+ minimum=0.0, maximum=10.0, value=1.0, step=0.1, label="CFG Scale"
 
 
 
 
190
  )
191
+ seed = gr.Number(value=-1, label="Seed (-1 for random)")
192
  btn = gr.Button("Generate", variant="primary")
193
 
194
  with gr.Column():
195
+ audio_output = gr.Audio(label="Generated Audio", type="filepath")
 
 
 
196
 
197
  btn.click(
198
  fn=generate_audio,
199
  inputs=[prompt, duration, steps, cfg_scale, seed, model_name],
200
+ outputs=audio_output,
201
  )
202
 
203
 
204
+ # ---------------------------------------------------------------------------
205
+ # Mount API discovery routes directly onto Gradio's FastAPI app.
206
+ # This avoids conflicts with Gradio's internal /api proxy.
207
+ # ---------------------------------------------------------------------------
208
+
209
  demo.queue(max_size=4, default_concurrency_limit=1)
 
210
 
211
+
212
+ @demo.app.get("/api/resources")
213
+ def resources():
214
+ return JSONResponse({"resources": API_RESOURCES})
215
+
216
+
217
+ @demo.app.get("/api/specs")
218
+ def specs():
219
+ return JSONResponse({**API_SPECS, "runtime": _get_runtime_specs()})