fomext commited on
Commit
0be8d78
·
verified ·
1 Parent(s): 5fae22d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +116 -48
app.py CHANGED
@@ -1,39 +1,38 @@
1
- import diffusers
2
- import huggingface_hub
3
- print(diffusers.__version__)
4
- print(huggingface_hub.__version__)
5
-
6
 
7
- from fastapi import FastAPI
8
  import torch
9
- import uuid
10
- import os
11
  import soundfile as sf
 
 
12
  from diffusers import DiffusionPipeline
 
13
 
14
-
15
 
16
  app = FastAPI()
17
 
18
  OUTPUT_DIR = "outputs"
19
  os.makedirs(OUTPUT_DIR, exist_ok=True)
20
 
21
- # -------- Device & dtype handling (HF-safe) --------
22
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
23
  DTYPE = torch.float16 if DEVICE == "cuda" else torch.float32
24
 
25
  pipe = None # lazy-loaded
26
 
 
27
 
28
- from huggingface_hub import login
29
  login(token=os.environ["HF_TOKEN"])
30
 
 
31
 
32
  def load_pipeline():
33
  global pipe
34
  if pipe is None:
35
  print("Loading Stable Audio Open pipeline...")
36
-
37
  pipe = DiffusionPipeline.from_pretrained(
38
  "stabilityai/stable-audio-open-1.0",
39
  torch_dtype=DTYPE,
@@ -41,58 +40,127 @@ def load_pipeline():
41
  revision="main",
42
  low_cpu_mem_usage=False,
43
  )
44
-
45
  pipe.enable_attention_slicing()
46
  pipe.to(DEVICE)
47
-
48
  print("Pipeline loaded on", DEVICE)
49
 
50
-
51
-
52
  @app.on_event("startup")
53
  def startup():
54
  load_pipeline()
 
55
 
56
- # -------- API --------
57
- @app.post("/generate")
58
- async def generate(prompt: str, duration: float = 10.0):
59
- load_pipeline()
60
 
61
- # ✅ Stable Audio Open expects duration as a pipeline attribute
62
- pipe.audio_length_in_s = float(duration)
 
 
 
 
 
 
 
63
 
64
- with torch.no_grad():
65
- output = pipe(
66
- prompt=prompt,
67
- guidance_scale=7.5,
68
- num_inference_steps=150,
69
- )
 
 
 
 
 
 
 
70
 
71
- audio = output.audios[0]
72
 
73
- # Convert torch → numpy
74
- if isinstance(audio, torch.Tensor):
75
- audio = audio.detach().cpu().numpy()
76
-
77
- # If shape is (channels, samples), transpose it
78
- if audio.ndim == 2 and audio.shape[0] < audio.shape[1]:
79
- audio = audio.T
80
-
81
- # Ensure float32
82
- audio = audio.astype("float32")
83
-
84
- # Clamp to valid range
85
- audio = audio.clip(-1.0, 1.0)
86
 
 
87
 
88
- filename = f"{uuid.uuid4().hex}.wav"
89
- path = os.path.join(OUTPUT_DIR, filename)
90
 
91
- sf.write(path, audio, samplerate=44100)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
 
93
  return {
94
- "prompt": prompt,
95
- "duration": duration,
96
- "file": filename,
 
 
97
  }
98
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import uuid
3
+ import threading
4
+ import queue
5
+ from typing import Dict, Optional
6
 
 
7
  import torch
 
 
8
  import soundfile as sf
9
+ from fastapi import FastAPI, BackgroundTasks, HTTPException
10
+ from fastapi.responses import FileResponse
11
  from diffusers import DiffusionPipeline
12
+ from huggingface_hub import login
13
 
14
+ # -------------------- App --------------------
15
 
16
  app = FastAPI()
17
 
18
  OUTPUT_DIR = "outputs"
19
  os.makedirs(OUTPUT_DIR, exist_ok=True)
20
 
 
21
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
22
  DTYPE = torch.float16 if DEVICE == "cuda" else torch.float32
23
 
24
  pipe = None # lazy-loaded
25
 
26
+ # -------------------- HF Login --------------------
27
 
 
28
  login(token=os.environ["HF_TOKEN"])
29
 
30
+ # -------------------- Pipeline --------------------
31
 
32
  def load_pipeline():
33
  global pipe
34
  if pipe is None:
35
  print("Loading Stable Audio Open pipeline...")
 
36
  pipe = DiffusionPipeline.from_pretrained(
37
  "stabilityai/stable-audio-open-1.0",
38
  torch_dtype=DTYPE,
 
40
  revision="main",
41
  low_cpu_mem_usage=False,
42
  )
 
43
  pipe.enable_attention_slicing()
44
  pipe.to(DEVICE)
 
45
  print("Pipeline loaded on", DEVICE)
46
 
 
 
47
  @app.on_event("startup")
48
  def startup():
49
  load_pipeline()
50
+ start_worker()
51
 
52
+ # -------------------- Job State --------------------
 
 
 
53
 
54
+ class Job:
55
+ def __init__(self, prompt: str, duration: float):
56
+ self.id = uuid.uuid4().hex
57
+ self.prompt = prompt
58
+ self.duration = duration
59
+ self.progress = 0
60
+ self.status = "queued" # queued | running | done | error
61
+ self.filepath: Optional[str] = None
62
+ self.error: Optional[str] = None
63
 
64
+ jobs: Dict[str, Job] = {}
65
+ job_queue: queue.Queue[Job] = queue.Queue()
66
+
67
+ # -------------------- Worker Thread --------------------
68
+
69
+ def worker_loop():
70
+ while True:
71
+ job: Job = job_queue.get()
72
+ try:
73
+ job.status = "running"
74
+ job.progress = 5
75
+
76
+ load_pipeline()
77
 
78
+ pipe.audio_length_in_s = float(job.duration)
79
 
80
+ with torch.no_grad():
81
+ output = pipe(
82
+ prompt=job.prompt,
83
+ guidance_scale=7.5,
84
+ num_inference_steps=150,
85
+ )
 
 
 
 
 
 
 
86
 
87
+ job.progress = 90
88
 
89
+ audio = output.audios[0]
 
90
 
91
+ if isinstance(audio, torch.Tensor):
92
+ audio = audio.detach().cpu().numpy()
93
+
94
+ if audio.ndim == 2 and audio.shape[0] < audio.shape[1]:
95
+ audio = audio.T
96
+
97
+ audio = audio.astype("float32").clip(-1.0, 1.0)
98
+
99
+ filename = f"{job.id}.wav"
100
+ path = os.path.join(OUTPUT_DIR, filename)
101
+ sf.write(path, audio, samplerate=44100)
102
+
103
+ job.filepath = path
104
+ job.progress = 100
105
+ job.status = "done"
106
+
107
+ except Exception as e:
108
+ job.status = "error"
109
+ job.error = str(e)
110
+
111
+ finally:
112
+ job_queue.task_done()
113
+
114
+ def start_worker():
115
+ t = threading.Thread(target=worker_loop, daemon=True)
116
+ t.start()
117
+
118
+ # -------------------- API --------------------
119
+
120
+ @app.post("/generate")
121
+ def generate(prompt: str, duration: float = 10.0):
122
+ job = Job(prompt=prompt, duration=duration)
123
+ jobs[job.id] = job
124
+ job_queue.put(job)
125
+
126
+ return {
127
+ "job_id": job.id,
128
+ "status": job.status,
129
+ }
130
+
131
+ @app.get("/status/{job_id}")
132
+ def status(job_id: str):
133
+ job = jobs.get(job_id)
134
+ if not job:
135
+ raise HTTPException(status_code=404, detail="Job not found")
136
 
137
  return {
138
+ "job_id": job.id,
139
+ "status": job.status,
140
+ "progress": job.progress,
141
+ "error": job.error,
142
+ "ready": job.status == "done",
143
  }
144
 
145
+ def cleanup_file(path: str):
146
+ try:
147
+ os.remove(path)
148
+ except Exception:
149
+ pass
150
+
151
+ @app.get("/download/{job_id}")
152
+ def download(job_id: str, background_tasks: BackgroundTasks):
153
+ job = jobs.get(job_id)
154
+ if not job:
155
+ raise HTTPException(status_code=404, detail="Job not found")
156
+
157
+ if job.status != "done" or not job.filepath:
158
+ raise HTTPException(status_code=400, detail="File not ready")
159
+
160
+ background_tasks.add_task(cleanup_file, job.filepath)
161
+
162
+ return FileResponse(
163
+ path=job.filepath,
164
+ media_type="audio/wav",
165
+ filename=os.path.basename(job.filepath),
166
+ )