fomext commited on
Commit
f53d0e4
·
verified ·
1 Parent(s): a177b57

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +131 -33
app.py CHANGED
@@ -1,8 +1,11 @@
1
- from fastapi import FastAPI, UploadFile, File
 
2
  import torch
3
  import os
4
  import uuid
5
  import subprocess
 
 
6
 
7
  app = FastAPI()
8
 
@@ -15,27 +18,87 @@ os.makedirs(OUTPUT_DIR, exist_ok=True)
15
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
16
 
17
  QUALITY_PRESETS = {
18
- "low": {
19
- "shifts": 0,
20
- "overlap": 0.25
21
- },
22
- "medium": {
23
- "shifts": 1,
24
- "overlap": 0.25
25
- },
26
- "high": {
27
- "shifts": 2,
28
- "overlap": 0.5
29
- }
30
  }
31
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  @app.post("/separate")
33
  async def separate_audio(
34
  file: UploadFile = File(...),
35
  quality: str = "medium"
36
  ):
37
  if quality not in QUALITY_PRESETS:
38
- return {"error": "quality must be low, medium, or high"}
39
 
40
  job_id = uuid.uuid4().hex
41
  input_path = os.path.join(UPLOAD_DIR, f"{job_id}_{file.filename}")
@@ -43,29 +106,64 @@ async def separate_audio(
43
  with open(input_path, "wb") as f:
44
  f.write(await file.read())
45
 
46
- preset = QUALITY_PRESETS[quality]
47
- output_path = os.path.join(OUTPUT_DIR, job_id)
 
 
 
 
 
48
 
49
- cmd = [
50
- "python3", "-m", "demucs",
51
- "--device", DEVICE,
52
- "--shifts", str(preset["shifts"]),
53
- "--overlap", str(preset["overlap"]),
54
- "--out", output_path,
55
- input_path
56
- ]
57
 
58
- subprocess.run(cmd, check=True)
 
 
 
59
 
60
- stems_dir = os.path.join(output_path, "htdemucs", os.path.splitext(os.path.basename(input_path))[0])
 
 
 
 
 
 
 
61
 
62
  return {
63
  "job_id": job_id,
64
- "quality": quality,
65
- "stems": {
66
- "vocals": f"{stems_dir}/vocals.wav",
67
- "drums": f"{stems_dir}/drums.wav",
68
- "bass": f"{stems_dir}/bass.wav",
69
- "other": f"{stems_dir}/other.wav"
70
- }
71
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, UploadFile, File, BackgroundTasks, HTTPException
2
+ from fastapi.responses import FileResponse
3
  import torch
4
  import os
5
  import uuid
6
  import subprocess
7
+ import threading
8
+ from typing import Dict
9
 
10
  app = FastAPI()
11
 
 
18
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
19
 
20
  QUALITY_PRESETS = {
21
+ "low": {"shifts": 0, "overlap": 0.25},
22
+ "medium": {"shifts": 1, "overlap": 0.25},
23
+ "high": {"shifts": 2, "overlap": 0.5},
 
 
 
 
 
 
 
 
 
24
  }
25
 
26
+ # -----------------------------
27
+ # In-memory job store
28
+ # -----------------------------
29
+ jobs: Dict[str, dict] = {}
30
+
31
+ # -----------------------------
32
+ # Worker function
33
+ # -----------------------------
34
+ def run_demucs(job_id: str, input_path: str, quality: str):
35
+ try:
36
+ jobs[job_id]["status"] = "processing"
37
+ jobs[job_id]["progress"] = 10
38
+
39
+ preset = QUALITY_PRESETS[quality]
40
+ output_path = os.path.join(OUTPUT_DIR, job_id)
41
+
42
+ cmd = [
43
+ "python3", "-m", "demucs",
44
+ "--device", DEVICE,
45
+ "--shifts", str(preset["shifts"]),
46
+ "--overlap", str(preset["overlap"]),
47
+ "--out", output_path,
48
+ input_path
49
+ ]
50
+
51
+ subprocess.run(cmd, check=True)
52
+
53
+ base = os.path.splitext(os.path.basename(input_path))[0]
54
+ stems_dir = os.path.join(output_path, "htdemucs", base)
55
+
56
+ jobs[job_id]["stems"] = {
57
+ "vocals": f"{stems_dir}/vocals.wav",
58
+ "drums": f"{stems_dir}/drums.wav",
59
+ "bass": f"{stems_dir}/bass.wav",
60
+ "other": f"{stems_dir}/other.wav",
61
+ }
62
+
63
+ jobs[job_id]["progress"] = 100
64
+ jobs[job_id]["status"] = "completed"
65
+
66
+ except Exception as e:
67
+ jobs[job_id]["status"] = "failed"
68
+ jobs[job_id]["error"] = str(e)
69
+
70
+ # -----------------------------
71
+ # Cleanup helper
72
+ # -----------------------------
73
+ def cleanup_job(job_id: str):
74
+ job = jobs.get(job_id)
75
+ if not job:
76
+ return
77
+
78
+ for path in job.get("stems", {}).values():
79
+ if os.path.exists(path):
80
+ os.remove(path)
81
+
82
+ input_file = job.get("input_path")
83
+ if input_file and os.path.exists(input_file):
84
+ os.remove(input_file)
85
+
86
+ output_dir = os.path.join(OUTPUT_DIR, job_id)
87
+ if os.path.exists(output_dir):
88
+ subprocess.run(["rm", "-rf", output_dir])
89
+
90
+ jobs.pop(job_id, None)
91
+
92
+ # -----------------------------
93
+ # Create job
94
+ # -----------------------------
95
  @app.post("/separate")
96
  async def separate_audio(
97
  file: UploadFile = File(...),
98
  quality: str = "medium"
99
  ):
100
  if quality not in QUALITY_PRESETS:
101
+ raise HTTPException(400, "quality must be low, medium, or high")
102
 
103
  job_id = uuid.uuid4().hex
104
  input_path = os.path.join(UPLOAD_DIR, f"{job_id}_{file.filename}")
 
106
  with open(input_path, "wb") as f:
107
  f.write(await file.read())
108
 
109
+ jobs[job_id] = {
110
+ "status": "queued",
111
+ "progress": 0,
112
+ "quality": quality,
113
+ "input_path": input_path,
114
+ "stems": None,
115
+ }
116
 
117
+ thread = threading.Thread(
118
+ target=run_demucs,
119
+ args=(job_id, input_path, quality),
120
+ daemon=True
121
+ )
122
+ thread.start()
 
 
123
 
124
+ return {
125
+ "job_id": job_id,
126
+ "status": "queued"
127
+ }
128
 
129
+ # -----------------------------
130
+ # Progress polling
131
+ # -----------------------------
132
+ @app.get("/status/{job_id}")
133
+ def job_status(job_id: str):
134
+ job = jobs.get(job_id)
135
+ if not job:
136
+ raise HTTPException(404, "Job not found")
137
 
138
  return {
139
  "job_id": job_id,
140
+ "status": job["status"],
141
+ "progress": job["progress"],
142
+ "stems": job.get("stems")
 
 
 
 
143
  }
144
+
145
+ # -----------------------------
146
+ # Download stem + auto cleanup
147
+ # -----------------------------
148
+ @app.get("/download/{job_id}/{stem}")
149
+ def download_stem(
150
+ job_id: str,
151
+ stem: str,
152
+ background_tasks: BackgroundTasks
153
+ ):
154
+ job = jobs.get(job_id)
155
+ if not job or job["status"] != "completed":
156
+ raise HTTPException(404, "Job not completed")
157
+
158
+ path = job["stems"].get(stem)
159
+ if not path or not os.path.exists(path):
160
+ raise HTTPException(404, "Stem not found")
161
+
162
+ # delete after response finishes
163
+ background_tasks.add_task(cleanup_job, job_id)
164
+
165
+ return FileResponse(
166
+ path,
167
+ media_type="audio/wav",
168
+ filename=f"{stem}.wav"
169
+ )