feat: added ACE-Step 1.5 powered repaint API
This commit is contained in:
@@ -111,6 +111,59 @@ Optional form fields (omit to use defaults):
|
||||
|
||||
Poll `/v1/fullsong/result/{task_id}` and download via `/v1/fullsong/audio/{task_id}` — same flow as `/v1/fullsong/generate`.
|
||||
|
||||
# `/v1/fullsong/repaint`
|
||||
|
||||
## Request
|
||||
|
||||
```bash
|
||||
curl -X 'POST' \
|
||||
'http://127.0.0.1:8000/v1/fullsong/repaint' \
|
||||
-H 'accept: application/json' \
|
||||
-F 'audio_file=@source.mp3;type=audio/mpeg' \
|
||||
-F 'caption=Genre: Jazz piano. Style: Smooth, mellow, intimate.' \
|
||||
-F 'repainting_start=30.0' \
|
||||
-F 'repainting_end=60.0' \
|
||||
-F 'repaint_strength=0.5'
|
||||
```
|
||||
|
||||
Optional form fields (omit to use defaults):
|
||||
|
||||
| Field | Type | Default | Description |
|
||||
|-------|------|---------|-------------|
|
||||
| `caption` | string | — | Style description for the repainted region |
|
||||
| `lyrics` | string | — | New lyrics for the repainted region |
|
||||
| `instrumental` | bool | — | Suppress vocals |
|
||||
| `inference_steps` | int | — | Diffusion steps |
|
||||
| `guidance_scale` | float | — | CFG guidance strength |
|
||||
| `use_random_seed` | bool | — | Randomise seed |
|
||||
| `seed` | int | — | Fixed seed for reproducibility |
|
||||
| `thinking` | bool | — | Enable chain-of-thought reasoning |
|
||||
| `batch_size` | int | — | Number of outputs |
|
||||
| `audio_format` | string | — | Output format (`mp3`, `wav`, …) |
|
||||
| `repainting_start` | float | `0.0` | Start of repaint region in seconds |
|
||||
| `repainting_end` | float | `-1.0` | End of repaint region in seconds; -1 = until end of track |
|
||||
| `repaint_strength` | float | `0.5` | 0=preserve source closely, 1=full regeneration |
|
||||
|
||||
`repaint_mode` is fixed to `"balanced"`.
|
||||
|
||||
## Response
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"task_id": "a1b2c3d4-0000-0000-0000-000000000000",
|
||||
"status": "queued",
|
||||
"queue_position": 1
|
||||
},
|
||||
"code": 200,
|
||||
"error": null,
|
||||
"timestamp": 1776227065582,
|
||||
"extra": null
|
||||
}
|
||||
```
|
||||
|
||||
Poll `/v1/fullsong/result/{task_id}` and download via `/v1/fullsong/audio/{task_id}` — same flow as `/v1/fullsong/generate`.
|
||||
|
||||
# `/v1/fullsong/result/{task_id}`
|
||||
|
||||
## Request
|
||||
|
||||
@@ -283,6 +283,83 @@ async def fullsong_remix(
|
||||
)
|
||||
|
||||
|
||||
@app.post(
|
||||
"/v1/fullsong/repaint",
|
||||
tags=["fullsong"],
|
||||
summary="Repaint a time region of an existing song (ACE-Step 1.5)",
|
||||
)
|
||||
async def fullsong_repaint(
|
||||
request: Request,
|
||||
audio_file: UploadFile = File(...),
|
||||
caption: Optional[str] = Form(None),
|
||||
lyrics: Optional[str] = Form(None),
|
||||
instrumental: Optional[bool] = Form(None),
|
||||
inference_steps: Optional[int] = Form(None),
|
||||
guidance_scale: Optional[float] = Form(None),
|
||||
use_random_seed: Optional[bool] = Form(None),
|
||||
seed: Optional[int] = Form(None),
|
||||
thinking: Optional[bool] = Form(None),
|
||||
batch_size: Optional[int] = Form(None),
|
||||
audio_format: Optional[str] = Form(None),
|
||||
repainting_start: float = Form(0.0),
|
||||
repainting_end: float = Form(-1.0),
|
||||
repaint_strength: float = Form(0.5),
|
||||
):
|
||||
"""Upload a source audio file and re-generate a time region in a new style.
|
||||
|
||||
- `audio_file`: source audio (MP3, WAV, FLAC, …)
|
||||
- `caption`: style description for the repainted region
|
||||
- `repainting_start`: start of the region to repaint in seconds (default 0.0)
|
||||
- `repainting_end`: end of the region in seconds; -1 = until end of track (default -1.0)
|
||||
- `repaint_strength`: 0=preserve source closely, 1=full regeneration (default 0.5)
|
||||
|
||||
Returns `{"data": {"task_id": "...", "status": "queued", ...}}`.
|
||||
Poll `/v1/fullsong/result/{task_id}`, then download via `/v1/fullsong/audio/{task_id}`.
|
||||
"""
|
||||
_require("fullsong")
|
||||
suffix = Path(audio_file.filename or "audio").suffix or ".mp3"
|
||||
with tempfile.NamedTemporaryFile(suffix=suffix, prefix="kg_repaint_", delete=False) as tmp:
|
||||
shutil.copyfileobj(audio_file.file, tmp)
|
||||
src_audio_path = tmp.name
|
||||
|
||||
payload: dict = {
|
||||
"task_type": "repaint",
|
||||
"src_audio_path": src_audio_path,
|
||||
"repaint_mode": "balanced",
|
||||
"repainting_start": repainting_start,
|
||||
"repainting_end": repainting_end,
|
||||
"repaint_strength": repaint_strength,
|
||||
}
|
||||
for key, val in [
|
||||
("caption", caption),
|
||||
("lyrics", lyrics),
|
||||
("instrumental", instrumental),
|
||||
("inference_steps", inference_steps),
|
||||
("guidance_scale", guidance_scale),
|
||||
("use_random_seed", use_random_seed),
|
||||
("seed", seed),
|
||||
("thinking", thinking),
|
||||
("batch_size", batch_size),
|
||||
("audio_format", audio_format),
|
||||
]:
|
||||
if val is not None:
|
||||
payload[key] = val
|
||||
|
||||
try:
|
||||
resp = await request.app.state.http_client.post(
|
||||
f"{ACESTEP_BASE_URL}/release_task",
|
||||
json=payload,
|
||||
)
|
||||
except httpx.ConnectError:
|
||||
raise HTTPException(503, "Sub-service unreachable — is the model loaded?")
|
||||
return Response(
|
||||
content=resp.content,
|
||||
status_code=resp.status_code,
|
||||
headers={k: v for k, v in resp.headers.items() if k.lower() not in ("transfer-encoding",)},
|
||||
media_type=resp.headers.get("content-type"),
|
||||
)
|
||||
|
||||
|
||||
@app.get(
|
||||
"/v1/fullsong/result/{task_id}",
|
||||
tags=["fullsong"],
|
||||
|
||||
@@ -248,6 +248,44 @@ async def fullsong_remix(
|
||||
}
|
||||
|
||||
|
||||
@app.post(
|
||||
"/v1/fullsong/repaint",
|
||||
tags=["fullsong"],
|
||||
summary="Repaint a time region of an existing song (ACE-Step 1.5)",
|
||||
)
|
||||
async def fullsong_repaint(
|
||||
audio_file: UploadFile = File(...),
|
||||
caption: Optional[str] = Form(None),
|
||||
lyrics: Optional[str] = Form(None),
|
||||
instrumental: Optional[bool] = Form(None),
|
||||
inference_steps: Optional[int] = Form(None),
|
||||
guidance_scale: Optional[float] = Form(None),
|
||||
use_random_seed: Optional[bool] = Form(None),
|
||||
seed: Optional[int] = Form(None),
|
||||
thinking: Optional[bool] = Form(None),
|
||||
batch_size: Optional[int] = Form(None),
|
||||
audio_format: Optional[str] = Form(None),
|
||||
repainting_start: float = Form(0.0),
|
||||
repainting_end: float = Form(-1.0),
|
||||
repaint_strength: float = Form(0.5),
|
||||
):
|
||||
"""Mock repaint endpoint — accepts the same multipart form as the real server."""
|
||||
_require("fullsong")
|
||||
task_id = str(uuid.uuid4())
|
||||
_tasks[task_id] = {"created_at": time.time(), "model_filename": None}
|
||||
return {
|
||||
"data": {
|
||||
"task_id": task_id,
|
||||
"status": "queued",
|
||||
"queue_position": 1,
|
||||
},
|
||||
"code": 200,
|
||||
"error": None,
|
||||
"timestamp": int(time.time() * 1000),
|
||||
"extra": None,
|
||||
}
|
||||
|
||||
|
||||
@app.get(
|
||||
"/v1/fullsong/result/{task_id}",
|
||||
tags=["fullsong"],
|
||||
|
||||
Reference in New Issue
Block a user