ambujm22 commited on
Commit
02a313e
·
verified ·
1 Parent(s): addbeeb

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +35 -267
app.py CHANGED
@@ -1,282 +1,50 @@
1
-
2
  import os
3
  os.environ.setdefault("GRADIO_USE_CDN", "true")
4
 
5
  import spaces
 
 
 
 
 
 
 
6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  @spaces.GPU(duration=10)
8
  def gpu_probe(a: int = 1, b: int = 1) -> int:
9
- # Never called; exists so ZeroGPU startup check passes.
10
  return a + b
11
 
12
-
13
  @spaces.GPU(duration=10)
14
  def gpu_echo(x: str = "ok") -> str:
15
  return x
16
 
 
 
 
 
 
 
 
17
 
18
- # ---------- Standard imports ----------
19
- from pathlib import Path
20
- from typing import Optional, Tuple, List
21
- import subprocess
22
- import sys
23
- import traceback
24
-
25
- import gradio as gr
26
- import numpy as np
27
- import soundfile as sf
28
- from huggingface_hub import hf_hub_download
29
-
30
- # ---------- Config ----------
31
- SPACE_ROOT = Path(__file__).parent.resolve()
32
- REPO_DIR = SPACE_ROOT / "SonicMasterRepo"
33
- REPO_URL = "https://github.com/AMAAI-Lab/SonicMaster"
34
- WEIGHTS_REPO = "amaai-lab/SonicMaster"
35
- WEIGHTS_FILE = "model.safetensors"
36
- CACHE_DIR = SPACE_ROOT / "weights"
37
- CACHE_DIR.mkdir(parents=True, exist_ok=True)
38
-
39
- # ZeroGPU detection (heuristic)
40
- USE_ZEROGPU = os.getenv("SPACE_RUNTIME", "").lower() == "zerogpu"
41
-
42
- # ---------- Lazy resources ----------
43
- _weights_path: Optional[Path] = None
44
- _repo_ready: bool = False
45
-
46
- def get_weights_path(progress: Optional[gr.Progress] = None) -> Path:
47
- """Fetch model weights lazily and cache the resolved path."""
48
- global _weights_path
49
- if _weights_path is None:
50
- if progress:
51
- progress(0.10, desc="Downloading model weights (first run)")
52
- wp = hf_hub_download(
53
- repo_id=WEIGHTS_REPO,
54
- filename=WEIGHTS_FILE,
55
- local_dir=str(CACHE_DIR),
56
- local_dir_use_symlinks=False,
57
- force_download=False,
58
- resume_download=True,
59
- )
60
- _weights_path = Path(wp)
61
- return _weights_path
62
-
63
- def ensure_repo(progress: Optional[gr.Progress] = None) -> Path:
64
- """Clone the inference repo lazily and put it on sys.path."""
65
- global _repo_ready
66
- if not _repo_ready:
67
- if not REPO_DIR.exists():
68
- if progress:
69
- progress(0.18, desc="Cloning SonicMaster repo (first run)")
70
- subprocess.run(
71
- ["git", "clone", "--depth", "1", REPO_URL, REPO_DIR.as_posix()],
72
- check=True,
73
- )
74
- if REPO_DIR.as_posix() not in sys.path:
75
- sys.path.append(REPO_DIR.as_posix())
76
- _repo_ready = True
77
- return REPO_DIR
78
-
79
- # ---------- Audio helpers ----------
80
- def save_temp_wav(wav: np.ndarray, sr: int, path: Path):
81
- # Ensure (N, C) shape for soundfile
82
- if wav.ndim == 1:
83
- data = wav
84
- else:
85
- # (channels, samples) -> (samples, channels)
86
- data = wav.T if wav.shape[0] < wav.shape[1] else wav
87
- if data.dtype == np.float64:
88
- data = data.astype(np.float32)
89
- sf.write(path.as_posix(), data, sr)
90
-
91
- def read_audio(path: str) -> Tuple[np.ndarray, int]:
92
- wav, sr = sf.read(path, always_2d=False)
93
- if wav.dtype == np.float64:
94
- wav = wav.astype(np.float32)
95
- return wav, sr
96
-
97
- # ---------- CLI runner ----------
98
- def _candidate_commands(py: str, script: Path, ckpt: Path, inp: Path, prompt: str, out: Path) -> List[List[str]]:
99
- """Try multiple arg styles commonly found in repos."""
100
- combos = [
101
- # infer_single.py (common)
102
- [py, script.as_posix(), "--ckpt", ckpt.as_posix(), "--input", inp.as_posix(), "--prompt", prompt, "--output", out.as_posix()],
103
- [py, script.as_posix(), "--weights", ckpt.as_posix(), "--input", inp.as_posix(), "--text", prompt, "--out", out.as_posix()],
104
- # other possible entrypoints
105
- [py, script.as_posix(), "--ckpt", ckpt.as_posix(), "--input", inp.as_posix(), "--text", prompt, "--output", out.as_posix()],
106
- ]
107
- return combos
108
-
109
- def run_sonicmaster_cli(
110
- input_wav_path: Path,
111
- prompt: str,
112
- out_path: Path,
113
- progress: Optional[gr.Progress] = None,
114
- ) -> Tuple[bool, str]:
115
- """
116
- Returns (ok, message). Captures stderr/stdout and returns first non-empty output file.
117
- """
118
- if progress:
119
- progress(0.14, desc="Preparing inference")
120
- ckpt = get_weights_path(progress=progress)
121
- repo = ensure_repo(progress=progress)
122
-
123
- # Candidate scripts to try
124
- script_candidates = [
125
- repo / "infer_single.py",
126
- repo / "inference_fullsong.py",
127
- repo / "inference_ptload_batch.py",
128
- ]
129
- scripts = [s for s in script_candidates if s.exists()]
130
- if not scripts:
131
- return False, "No inference script found in the repo (expected infer_single.py or similar)."
132
-
133
- py = sys.executable or "python3"
134
- env = os.environ.copy() # keep CUDA_VISIBLE_DEVICES etc.
135
-
136
- last_err = ""
137
- for idx, script in enumerate(scripts, start=1):
138
- for jdx, cmd in enumerate(_candidate_commands(py, script, ckpt, input_wav_path, prompt, out_path), start=1):
139
- try:
140
- if progress:
141
- progress(min(0.20 + 0.08 * (idx + jdx), 0.70), desc=f"Running {script.name} (try {idx}.{jdx})")
142
- res = subprocess.run(cmd, capture_output=True, text=True, check=True, env=env)
143
- if out_path.exists() and out_path.stat().st_size > 0:
144
- if progress:
145
- progress(0.88, desc="Post-processing output")
146
- # Return any informative stdout as message
147
- msg = (res.stdout or "").strip()
148
- return True, msg if msg else "Inference completed."
149
- else:
150
- last_err = f"{script.name} produced no output file."
151
- except subprocess.CalledProcessError as e:
152
- # Collect stderr/stdout for the user
153
- snippet = "\n".join(filter(None, [e.stdout or "", e.stderr or ""])).strip()
154
- last_err = snippet if snippet else f"{script.name} failed with return code {e.returncode}."
155
- except Exception as e:
156
- last_err = f"Unexpected error: {e}\n{traceback.format_exc()}"
157
-
158
- return False, last_err or "All candidate commands failed without an error message."
159
-
160
- # ---------- REAL GPU function (called only if using ZeroGPU / GPU available) ----------
161
- @spaces.GPU(duration=180)
162
- def enhance_on_gpu(input_path: str, prompt: str, output_path: str) -> Tuple[bool, str]:
163
- try:
164
- # Initialize CUDA inside the GPU context
165
- import torch # noqa: F401
166
- except Exception:
167
- pass
168
- from pathlib import Path as _P
169
- return run_sonicmaster_cli(_P(input_path), prompt, _P(output_path), progress=None)
170
-
171
- def _has_cuda() -> bool:
172
- try:
173
- import torch
174
- return torch.cuda.is_available()
175
- except Exception:
176
- return False
177
-
178
- # ---------- UI callback ----------
179
- def enhance_audio_ui(
180
- audio_path: str,
181
- prompt: str,
182
- progress=gr.Progress(track_tqdm=True),
183
- ) -> Tuple[Optional[Tuple[int, np.ndarray]], str]:
184
- """
185
- Returns (audio, message). On failure, audio=None and message=error text.
186
- """
187
- try:
188
- if not prompt:
189
- raise gr.Error("Please provide a text prompt.")
190
- if not audio_path:
191
- raise gr.Error("Please upload or select an input audio file.")
192
-
193
- wav, sr = read_audio(audio_path)
194
-
195
- tmp_in = SPACE_ROOT / "tmp_in.wav"
196
- tmp_out = SPACE_ROOT / "tmp_out.wav"
197
- if tmp_out.exists():
198
- try:
199
- tmp_out.unlink()
200
- except Exception:
201
- pass
202
-
203
- if progress:
204
- progress(0.06, desc="Preparing audio")
205
- save_temp_wav(wav, sr, tmp_in)
206
-
207
- # Choose execution path: prefer real GPU if available, else CPU
208
- use_gpu_call = USE_ZEROGPU or _has_cuda()
209
-
210
- if progress:
211
- progress(0.12, desc="Starting inference")
212
- if use_gpu_call:
213
- ok, msg = enhance_on_gpu(tmp_in.as_posix(), prompt, tmp_out.as_posix())
214
- else:
215
- ok, msg = run_sonicmaster_cli(tmp_in, prompt, tmp_out, progress=progress)
216
-
217
- if ok and tmp_out.exists() and tmp_out.stat().st_size > 0:
218
- # Return output audio by filepath (lighter than big arrays)
219
- # Gradio Audio accepts a (sr, np.ndarray) OR a file path; giving file path is fine.
220
- return (None, f"Saved output: {tmp_out.name}\n{msg or ''}") if False else (read_audio(tmp_out.as_posix()), msg or "Done.")
221
- else:
222
- # On failure: DON'T echo input audio — return None and the error message
223
- if not msg:
224
- msg = "Inference failed without a specific error message."
225
- return (None, msg.strip())
226
-
227
- except gr.Error as e:
228
- return (None, str(e))
229
- except Exception as e:
230
- return (None, f"Unexpected error: {e}\n{traceback.format_exc()}")
231
-
232
- # ---------- Gradio UI ----------
233
- PROMPT_EXAMPLES = [
234
- ["Increase the clarity of this song by emphasizing treble frequencies."],
235
- ["Make this song sound more boomy by amplifying the low end bass frequencies."],
236
- ["Make the audio smoother and less distorted."],
237
- ["Improve the balance in this song."],
238
- ["Reduce roominess/echo (dereverb)."],
239
- ["Raise the level of the vocals."],
240
- ["Give the song a wider stereo image."],
241
- ]
242
-
243
- with gr.Blocks(title="SonicMaster – Text-Guided Restoration & Mastering", fill_height=True) as demo:
244
- gr.Markdown("## 🎧 SonicMaster\nUpload audio, enter a prompt, then click **Enhance**.\n"
245
- "- Progress appears below during the first run (weights/repo download).\n"
246
- "- If something fails, you'll see the **error message** instead of the input audio.")
247
- with gr.Row():
248
- with gr.Column(scale=1):
249
- in_audio = gr.Audio(label="Input Audio", type="filepath")
250
- prompt = gr.Textbox(label="Text Prompt", placeholder="e.g., Reduce reverb and brighten the vocals.")
251
- run_btn = gr.Button("🚀 Enhance", variant="primary")
252
- gr.Examples(
253
- examples=PROMPT_EXAMPLES,
254
- inputs=[prompt], # prompt-only examples to avoid heavy file ops at startup
255
- label="Prompt Examples",
256
- )
257
- with gr.Column(scale=1):
258
- out_audio = gr.Audio(label="Enhanced Audio (output)")
259
- status = gr.Textbox(label="Status / Messages", interactive=False, lines=6)
260
-
261
- # On click, return audio + message
262
- run_btn.click(
263
- fn=enhance_audio_ui,
264
- inputs=[in_audio, prompt],
265
- outputs=[out_audio, status],
266
- concurrency_limit=1,
267
- )
268
-
269
- # Queue BEFORE mounting so the mounted app is ready immediately
270
- demo = demo.queue(max_size=16)
271
-
272
- # ---------- FastAPI mount & health ----------
273
- from fastapi import FastAPI, Request
274
- from starlette.responses import PlainTextResponse
275
- try:
276
- from starlette.exceptions import ClientDisconnect # Starlette ≥0.27
277
- except Exception:
278
- from starlette.requests import ClientDisconnect # fallback for older versions
279
 
 
280
  app = FastAPI()
281
 
282
  @app.get("/health")
@@ -284,12 +52,12 @@ def _health():
284
  return {"ok": True}
285
 
286
  @app.exception_handler(ClientDisconnect)
287
- async def client_disconnect_handler(request: Request, exc: ClientDisconnect):
288
  return PlainTextResponse("Client disconnected", status_code=499)
289
 
290
- # Mount Gradio at root (Spaces looks here)
291
  app = gr.mount_gradio_app(app, demo, path="/")
292
 
293
  if __name__ == "__main__":
294
  import uvicorn
295
- uvicorn.run(app, host="0.0.0.0", port=7860)
 
1
+ # ---------- MUST BE FIRST ----------
2
  import os
3
  os.environ.setdefault("GRADIO_USE_CDN", "true")
4
 
5
  import spaces
6
+ import gradio as gr
7
+ from fastapi import FastAPI
8
+ from starlette.responses import PlainTextResponse
9
+ try:
10
+ from starlette.exceptions import ClientDisconnect # Starlette ≥0.27
11
+ except Exception:
12
+ from starlette.requests import ClientDisconnect # fallback
13
 
14
+ # Log versions to container logs for sanity
15
+ try:
16
+ print("== Sanity ==")
17
+ print("spaces.__version__:", getattr(spaces, "__version__", "unknown"))
18
+ import gradio
19
+ print("gradio.__version__:", getattr(gradio, "__version__", "unknown"))
20
+ import sys
21
+ print("python:", sys.version)
22
+ print("SPACE_RUNTIME:", os.getenv("SPACE_RUNTIME"))
23
+ print("HF_SPACE_ENTRYPOINT default: app.py")
24
+ except Exception as _e:
25
+ print("version log error:", _e)
26
+
27
+ # ---------- ZeroGPU probes (PUBLIC NAMES) ----------
28
  @spaces.GPU(duration=10)
29
  def gpu_probe(a: int = 1, b: int = 1) -> int:
30
+ # Not called; existence is enough for ZeroGPU startup check
31
  return a + b
32
 
 
33
  @spaces.GPU(duration=10)
34
  def gpu_echo(x: str = "ok") -> str:
35
  return x
36
 
37
+ # ---------- Tiny Gradio UI ----------
38
+ with gr.Blocks(title="ZeroGPU Probe") as demo:
39
+ gr.Markdown("### ✅ Minimal app is running.\n"
40
+ "If you see this UI, import succeeded and GPU probes are registered.")
41
+ t = gr.Textbox(label="Echo input", value="hello")
42
+ o = gr.Textbox(label="Echo output")
43
+ t.submit(lambda s: f"echo: {s}", t, o)
44
 
45
+ demo = demo.queue(max_size=8)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
 
47
+ # ---------- ASGI app with fast health ----------
48
  app = FastAPI()
49
 
50
  @app.get("/health")
 
52
  return {"ok": True}
53
 
54
  @app.exception_handler(ClientDisconnect)
55
+ async def client_disconnect_handler(request, exc):
56
  return PlainTextResponse("Client disconnected", status_code=499)
57
 
58
+ # Mount at root (Spaces health-check expects this)
59
  app = gr.mount_gradio_app(app, demo, path="/")
60
 
61
  if __name__ == "__main__":
62
  import uvicorn
63
+ uvicorn.run(app, host="0.0.0.0", port=7860)