CoolFace
Apppublic

ShanitG/stem-splitter

sourceHugging Faceupdated 2y agoView on Hugging Face
0likes
main.py149 linesDownload Raw Back to root
1from fastapi import FastAPI, UploadFile, HTTPException
2from fastapi.middleware.cors import CORSMiddleware
3from fastapi.responses import FileResponse, JSONResponse
4import uvicorn
5import os
6import uuid
7import torch
8import soundfile as sf
9import numpy as np
10from demucs.pretrained import get_pretrained
11from demucs.apply import apply_model
12import asyncio
13import aiofiles
14from pathlib import Path
15import logging
16
17# Configure logging
18logging.basicConfig(level=logging.INFO)
19logger = logging.getLogger(__name__)
20
21app = FastAPI()
22
23# CORS configuration
24app.add_middleware(
25    CORSMiddleware,
26    allow_origins=["*"],
27    allow_credentials=False,
28    allow_methods=["*"],
29    allow_headers=["*"],
30)
31
32# Create directories
33UPLOAD_DIR = Path("uploads")
34PROCESSED_DIR = Path("processed")
35UPLOAD_DIR.mkdir(exist_ok=True)
36PROCESSED_DIR.mkdir(exist_ok=True)
37
38# Global variables
39processing_status = {}
40model = None
41
42@app.on_event("startup")
43async def startup_event():
44    global model
45    try:
46        logger.info("Loading Demucs model...")
47        model = get_pretrained("htdemucs")
48        model.cuda() if torch.cuda.is_available() else model.cpu()
49        logger.info("Model loaded successfully")
50    except Exception as e:
51        logger.error(f"Error loading model: {e}")
52        raise
53
54async def process_audio_file(input_path: Path, job_id: str):
55    try:
56        logger.info(f"Processing file: {input_path}")
57        
58        # Create output directory
59        output_dir = PROCESSED_DIR / job_id
60        output_dir.mkdir(exist_ok=True)
61        
62        # Load and process audio
63        audio, sr = sf.read(str(input_path))
64        if len(audio.shape) == 1:
65            audio = np.expand_dims(audio, axis=1)
66        
67        # Convert to tensor
68        audio_tensor = torch.tensor(audio.T, dtype=torch.float32)
69        audio_tensor = audio_tensor.unsqueeze(0)
70
71        # Process with model
72        with torch.no_grad():
73            sources = apply_model(model, audio_tensor, device="cuda" if torch.cuda.is_available() else "cpu")[0]
74            sources = sources.cpu().numpy()
75
76        # Save stems
77        stem_names = ["vocals", "drums", "bass", "other"]
78        for source, name in zip(sources, stem_names):
79            output_path = output_dir / f"{name}.wav"
80            sf.write(str(output_path), source.T, sr)
81            processing_status[job_id]["progress"] += 25
82
83        processing_status[job_id]["status"] = "completed"
84        logger.info(f"Processing completed for job {job_id}")
85        
86    except Exception as e:
87        logger.error(f"Error processing file: {e}")
88        processing_status[job_id]["status"] = "failed"
89        processing_status[job_id]["error"] = str(e)
90        raise
91
92@app.get("/health")
93async def health_check():
94    return {"status": "ok", "model_loaded": model is not None}
95
96@app.post("/process")
97async def process_file(audio: UploadFile):
98    try:
99        if not audio.filename.lower().endswith(('.mp3', '.wav', '.ogg', '.m4a')):
100            raise HTTPException(400, detail="Unsupported file format")
101
102        # Generate job ID and save file
103        job_id = str(uuid.uuid4())
104        input_path = UPLOAD_DIR / f"{job_id}_{audio.filename}"
105        
106        async with aiofiles.open(input_path, 'wb') as f:
107            content = await audio.read()
108            await f.write(content)
109
110        # Initialize processing status
111        processing_status[job_id] = {
112            "status": "processing",
113            "progress": 0,
114            "error": None
115        }
116
117        # Start processing in background
118        asyncio.create_task(process_audio_file(input_path, job_id))
119        
120        return JSONResponse(content={"id": job_id})
121        
122    except Exception as e:
123        logger.error(f"Error handling upload: {e}")
124        raise HTTPException(status_code=500, detail=str(e))
125
126@app.get("/status/{job_id}")
127async def get_status(job_id: str):
128    if job_id not in processing_status:
129        raise HTTPException(404, detail="Job not found")
130    return processing_status[job_id]
131
132@app.get("/download/{job_id}/{stem_name}")
133async def download_stem(job_id: str, stem_name: str):
134    try:
135        if stem_name not in ["vocals", "drums", "bass", "other"]:
136            raise HTTPException(400, detail="Invalid stem name")
137
138        stem_path = PROCESSED_DIR / job_id / f"{stem_name}.wav"
139        if not stem_path.exists():
140            raise HTTPException(404, detail="Stem not found")
141
142        return FileResponse(
143            path=stem_path,
144            filename=f"{stem_name}.wav",
145            media_type="audio/wav"
146        )
147    except Exception as e:
148        logger.error(f"Error downloading stem: {e}")
149        raise