File size: 4,073 Bytes
8b80b8c
 
 
 
 
 
 
dde3768
 
 
 
8b80b8c
 
dde3768
 
 
70fccfa
dde3768
 
 
 
 
 
70fccfa
dde3768
 
 
 
 
8b80b8c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70fccfa
 
 
 
 
 
 
 
7c483bf
 
8b80b8c
 
 
 
 
7c483bf
 
 
8b80b8c
7c483bf
 
70fccfa
7c483bf
8b80b8c
 
7c483bf
 
 
70fccfa
22e4707
8b80b8c
 
22e4707
8b80b8c
 
 
 
 
22e4707
 
 
8b80b8c
 
22e4707
8b80b8c
 
22e4707
8b80b8c
 
 
 
 
 
70fccfa
 
 
8b80b8c
 
 
 
 
 
 
 
 
dde3768
 
8b80b8c
dde3768
8b80b8c
 
 
 
 
 
dde3768
8b80b8c
dde3768
 
8b80b8c
 
dde3768
8b80b8c
 
 
 
 
 
 
 
 
 
 
5295d97
 
8b80b8c
 
5295d97
8b80b8c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
from fastapi import FastAPI, BackgroundTasks, HTTPException
from fastapi.responses import FileResponse
import threading
import queue
import uuid
import os
import time

import audiocraft
from audiocraft.models import MusicGen
from audiocraft.data.audio import audio_write

print("Audiocraft version:", audiocraft.__version__)

app = FastAPI()

MODEL_NAME = "facebook/musicgen-small"
OUTPUT_DIR = "outputs"
os.makedirs(OUTPUT_DIR, exist_ok=True)

print("Loading MusicGen model...")
model = MusicGen.get_pretrained(MODEL_NAME)
model.set_generation_params(
    duration=10,
    temperature=1.0,
    top_k=250,
    top_p=0.0
)

# -------------------------
# Job system (in-memory)
# -------------------------

job_queue = queue.Queue()
jobs = {}  # job_id -> metadata


class JobStatus:
    QUEUED = "queued"
    PROCESSING = "processing"
    COMPLETED = "completed"
    FAILED = "failed"


import signal

class GenerationTimeout(Exception):
    pass

def timeout_handler(signum, frame):
    raise GenerationTimeout()

MAX_RUNTIME = 600  # 10 minutes (CPU-safe)

def worker():
    while True:
        job_id = job_queue.get()
        job = jobs.get(job_id)

        if not job:
            job_queue.task_done()
            continue

        start_time = time.time()
        jobs[job_id]["status"] = JobStatus.PROCESSING

        try:
            wav = model.generate([job["prompt"]])[0]

            elapsed = time.time() - start_time
            if elapsed > MAX_RUNTIME:
                raise TimeoutError("Generation exceeded max runtime")

            base_path = os.path.join(OUTPUT_DIR, job_id)

            audio_write(
                base_path,
                wav.cpu(),
                model.sample_rate,
                strategy="loudness",
                loudness_compressor=True
            )
            
            final_path = base_path + ".wav"
            
            jobs[job_id].update({
                "status": JobStatus.COMPLETED,
                "file_path": final_path
            })


        except Exception as e:
            jobs[job_id].update({
                "status": JobStatus.FAILED,
                "error": str(e)
            })

        finally:
            job_queue.task_done()



# Start worker thread
threading.Thread(target=worker, daemon=True).start()

# -------------------------
# API endpoints
# -------------------------

@app.post("/generate")
async def generate(prompt: str):
    job_id = uuid.uuid4().hex

    jobs[job_id] = {
        "status": JobStatus.QUEUED,
        "prompt": prompt,
        "created_at": time.time(),
        "file_path": None
    }

    job_queue.put(job_id)

    return {
        "job_id": job_id,
        "status_url": f"/status/{job_id}"
    }


@app.get("/status/{job_id}")
async def status(job_id: str):
    job = jobs.get(job_id)

    if not job:
        raise HTTPException(status_code=404, detail="Job not found")

    response = {
        "job_id": job_id,
        "status": job["status"],
        "elapsed_seconds": int(time.time() - job["created_at"])
    }


    if job["status"] == JobStatus.COMPLETED:
        response["download_url"] = f"/download/{job_id}"

    if job["status"] == JobStatus.FAILED:
        response["error"] = job.get("error")

    return response


def delete_file(path: str, delay: int = 10):
    """Deletes file after response is sent"""
    time.sleep(delay)
    if os.path.exists(path):
        os.remove(path)


@app.get("/download/{job_id}")
async def download(job_id: str, background_tasks: BackgroundTasks):
    job = jobs.get(job_id)

    if not job:
        raise HTTPException(status_code=404, detail="Job not found")

    if job["status"] != JobStatus.COMPLETED:
        raise HTTPException(status_code=400, detail="Job not completed")

    path = job["file_path"]

    if not path or not os.path.exists(path):
        raise HTTPException(status_code=404, detail="File not found")

    background_tasks.add_task(delete_file, path)

    return FileResponse(
        path,
        media_type="audio/wav",
        filename=os.path.basename(path)
    )