Gary10 commited on
Commit
6bb73d5
·
verified ·
1 Parent(s): c32617b

SONICS detect API (FastAPI wrapper, SpecTTTra-alpha-120s)

Browse files
Files changed (3) hide show
  1. Dockerfile +24 -0
  2. README.md +14 -4
  3. app.py +80 -0
Dockerfile ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ RUN apt-get update && \
4
+ apt-get install -y --no-install-recommends ffmpeg libsndfile1 git && \
5
+ rm -rf /var/lib/apt/lists/*
6
+
7
+ RUN useradd -m -u 1000 user
8
+ USER user
9
+ ENV HOME=/home/user \
10
+ PATH=/home/user/.local/bin:$PATH \
11
+ HF_HOME=/home/user/.cache/huggingface
12
+ WORKDIR /home/user/app
13
+
14
+ RUN pip install --no-cache-dir --user torch --index-url https://download.pytorch.org/whl/cpu && \
15
+ pip install --no-cache-dir --user librosa soundfile numpy fastapi "uvicorn[standard]" python-multipart huggingface_hub timm pandas && \
16
+ pip install --no-cache-dir --user git+https://github.com/awsaf49/sonics.git
17
+
18
+ # Bake model weights into the image so cold starts skip the download.
19
+ RUN python -c "from sonics import HFAudioClassifier; HFAudioClassifier.from_pretrained('awsaf49/sonics-spectttra-alpha-120s')"
20
+
21
+ COPY --chown=user app.py .
22
+
23
+ EXPOSE 7860
24
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
README.md CHANGED
@@ -1,10 +1,20 @@
1
  ---
2
  title: Sonics Detect Api
3
- emoji: 🏢
4
- colorFrom: green
5
- colorTo: blue
6
  sdk: docker
 
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
1
  ---
2
  title: Sonics Detect Api
3
+ emoji: 🎵
4
+ colorFrom: indigo
5
+ colorTo: purple
6
  sdk: docker
7
+ app_port: 7860
8
  pinned: false
9
+ license: mit
10
+ short_description: AI-generated song detection API (SONICS SpecTTTra)
11
  ---
12
 
13
+ # SONICS Detect API
14
+
15
+ Minimal REST wrapper around [SONICS SpecTTTra](https://huggingface.co/awsaf49/sonics-spectttra-alpha-120s)
16
+ (ICLR 2025, MIT) for end-to-end AI-generated song detection.
17
+
18
+ - `GET /` — health check
19
+ - `POST /detect` — multipart form with an `audio` file, returns `{"ai_prob": 0..1}`.
20
+ Requires `X-Detect-Key` header when the `DETECT_API_KEY` secret is set.
app.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import tempfile
3
+
4
+ import librosa
5
+ import numpy as np
6
+ import torch
7
+ from fastapi import FastAPI, File, Header, HTTPException, UploadFile
8
+
9
+ MODEL_ID = os.environ.get("MODEL_ID", "awsaf49/sonics-spectttra-alpha-120s")
10
+ API_KEY = os.environ.get("DETECT_API_KEY", "")
11
+ MAX_BYTES = 25 * 1024 * 1024
12
+
13
+ torch.set_num_threads(2)
14
+
15
+ app = FastAPI(title="SONICS Detect API")
16
+ model = None
17
+
18
+
19
+ @app.on_event("startup")
20
+ def load_model():
21
+ global model
22
+ from sonics import HFAudioClassifier
23
+
24
+ m = HFAudioClassifier.from_pretrained(MODEL_ID)
25
+ m.eval()
26
+ model = m
27
+
28
+
29
+ @app.get("/")
30
+ def health():
31
+ return {"ok": True, "model": MODEL_ID, "loaded": model is not None}
32
+
33
+
34
+ @app.post("/detect")
35
+ def detect(audio: UploadFile = File(...), x_detect_key: str = Header(default="")):
36
+ if API_KEY and x_detect_key != API_KEY:
37
+ raise HTTPException(status_code=401, detail="Invalid detect key")
38
+ if model is None:
39
+ raise HTTPException(status_code=503, detail="Model still loading, retry shortly")
40
+
41
+ data = audio.file.read()
42
+ if not data:
43
+ raise HTTPException(status_code=400, detail="Empty file")
44
+ if len(data) > MAX_BYTES:
45
+ raise HTTPException(status_code=413, detail="File too large (max 25MB)")
46
+
47
+ suffix = os.path.splitext(audio.filename or "")[1] or ".mp3"
48
+ try:
49
+ with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as tmp:
50
+ tmp.write(data)
51
+ tmp_path = tmp.name
52
+ try:
53
+ y, sr = librosa.load(tmp_path, sr=16000, mono=True)
54
+ finally:
55
+ os.unlink(tmp_path)
56
+ except Exception:
57
+ raise HTTPException(status_code=400, detail="Could not decode audio")
58
+
59
+ if y.size < sr * 3:
60
+ raise HTTPException(status_code=400, detail="Audio too short (min 3s)")
61
+
62
+ # Same chunking as the official SONICS demo: score the middle max_time window.
63
+ max_time = model.config.audio.max_time
64
+ chunk_samples = int(max_time * sr)
65
+ total_chunks = len(y) // chunk_samples
66
+ middle_idx = total_chunks // 2
67
+ start = middle_idx * chunk_samples
68
+ chunk = y[start : start + chunk_samples]
69
+ if len(chunk) < chunk_samples:
70
+ chunk = np.pad(chunk, (0, chunk_samples - len(chunk)))
71
+
72
+ with torch.no_grad():
73
+ t = torch.from_numpy(chunk).float().unsqueeze(0)
74
+ ai_prob = float(torch.sigmoid(model(t)).cpu().numpy().reshape(-1)[0])
75
+
76
+ return {
77
+ "ai_prob": round(ai_prob, 4),
78
+ "duration_s": round(len(y) / sr, 1),
79
+ "model": MODEL_ID,
80
+ }