Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion references/api-claims.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,27 @@ Added 2026-08-16 with the endpoint itself; verified against the shipped backend
- [x] The variation prompts are **narrower than what the upstream produces** by product decision: `negative_prompt`, `thinking`, `structure_source` and the variation title/summary/tags are stripped before the envelope is built and are not recoverable from the task.
- [x] ⚠️ **Input differs by MCP server**: the hosted server exposes `video_url` only; local `sonilo-mcp` (0.17.0+) also takes `video_path`. Both SDKs and both CLIs accept a local file or a URL.

## stems (text_to_music + video_to_music)

Added 2026-08-17, verified against the shipped backend (live on REST `/v1/text-to-music` + `/v1/video-to-music` and on the hosted MCP server's `text_to_music` + `video_to_music`).

- [x] **Free of charge.** Splits each generated track into four separated instrument tracks — `drums`, `bass`, `vocals`, `other` — delivered as a `stems` array alongside the clean `audio` in the task result.
- [x] REST requires `mode=async` (`stems=true` in stream mode = **400**); MCP is always async, so the param just works there.
- [x] Result entry shape: `{ stream_index, drums, bass, vocals, other }`, each stem `{ url, content_type, file_size }`. **Look entries up by `stream_index`, never by position** — a stream whose separation failed is absent, so `stems` can be shorter than `audio`.
- [x] `stems_error` (string) appears when separation failed wholly/partly or was skipped, and **can appear alongside a partial `stems`**. The generation itself succeeded and the audio URLs are valid — a missing extra, never a failed generation.
- [x] Separation runs after generation: typically **+2–6 min**, gives up after **30 min**. Stems normally follow `output_format`; each stem's `content_type` reports what was delivered.
- [x] On `video_to_music` it splits the **generated** music, never the video's own audio (source speech = `preserve_speech`, unrelated).
- [x] The four stem names are fixed (htdemucs): melodic instruments land in `other`; on instrumental tracks `vocals` is near-silent — correct behavior, not a bug.
- [x] **Surface gap closed 2026-08-17** (same day): sonilo-mcp 0.18.0, npm sonilo 0.16.0 / sonilo-cli 0.15.0, and PyPI sonilo 0.15.0 / sonilo-cli 0.14.0 all ship `stems`; `tests/tool_surface.json` refreshed against the published 0.18.0. Every surface now accepts it.

## Billing / general

- [x] Charged up front at submission; **failed generations auto-refunded**. Caller retries = new charge. **No preview/low-cost mode.** Music + SFX = separate task types, separate per-second rates, separate prepay minute pools. `variants_num` scales v2m cost linearly; N>1 never covered by free trial.
- [x] MCP vs REST — field names + numeric limits match. Three structural differences (⚠️ document, don't claim identity):
1. MCP input = **`video_url` only**, no file upload
2. MCP **always async** — no `mode` param; returns `task_id`, results via `get_generation_task` (named `get_sfx_task` on the local server)
3. MCP `video_to_music` has **no `segments` param** — segmented music via MCP only through section-shaped prompt text (prompt-analysis path)
- [x] Output = **audio files only** (not stems-in-DAW-sense, not video). v2m: m4a default, `output_format=wav` optional (async-only on REST; always on MCP); preserve_speech adds vocals track + mux; ducking adds ducked music URLs. v2sfx: single file, aac default, wav/mp3/flac optional. Video out = separate `/v1/video-to-video-*` endpoints + corresponding MCP tools.
- [x] Output = **audio files only** (not video). v2m: m4a default, `output_format=wav` optional (async-only on REST; always on MCP); preserve_speech adds vocals track + mux; ducking adds ducked music URLs. v2sfx: single file, aac default, wav/mp3/flac optional. Video out = separate `/v1/video-to-video-*` endpoints + corresponding MCP tools. ⚠️ An earlier pass said "not stems-in-DAW-sense" — no longer true: `stems=true` on t2m/v2m (added 2026-08-17, see the stems section below) returns exactly that.
- [x] Multi-track input — default ffmpeg stream selection (typically first audio track) for ducking/speech. Wording: "for multi-track videos, the default audio track is used."

## Empirical test (2026-07-29)
Expand Down
2 changes: 1 addition & 1 deletion task-recovery/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ The MCP tool does one status check and, if `status` is terminal, downloads and s
| `processing` | Returns a "still processing, try again later" message. No file saved. Call again after a short wait. |
| `succeeded` | Downloads and saves the result — audio for `text_to_sfx`/`video_to_sfx`; a single `.wav` or `.mp4` for `audio_ducking`; a single `.mp4` for `video_to_video_music`/`video_to_video_sfx`/`video_to_video_sound`; a single `.wav` for `video_to_sound`; one `.mp4` per language for `dubbing`; for a `video_to_music(preserve_speech=true)` task, the music stream(s) plus the `vocals` stem plus the `mux`. An `analyze_video` task is the exception: it has no file at all, so the creative brief comes back inline as JSON and nothing is written to disk. |
| `failed` | Raises an error including the backend's error code/message and whether the charge was **refunded** — check this before telling the user they've been billed for nothing. |
| task id not found (404) | The id is wrong/typo'd, or belongs to a purely streaming call (`text_to_music`, or `video_to_music` without `preserve_speech`/`ducking`/`output_format="wav"`) — those have no task at all and can't be recovered this way. Nothing to retry. |
| task id not found (404) | The id is wrong/typo'd, or belongs to a purely streaming call (`text_to_music` in its default streaming form — no `wav`, no `variants_num` above 1, no `stems` — or `video_to_music` without `preserve_speech`/`ducking`/`stems`/`output_format="wav"`) — those have no task at all and can't be recovered this way. Nothing to retry. |
| any other status (not `processing`/`succeeded`/`failed`) | Raised as an "unexpected task status" error naming the task id. Treat as transient — this is a backend state this tool doesn't otherwise special-case — and call `get_sfx_task(task_id)` again shortly. |

## Workflow Tips
Expand Down
29 changes: 18 additions & 11 deletions tests/tool_surface.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"_generated": {
"local_from": "sonilo-mcp 0.17.0 (PyPI)",
"hosted_from": "sonilo-api-dashboard backend/app/mcp_server.py",
"date": "2026-08-16"
"date": "2026-08-17",
"_note": "`stems` is hosted-only for now: the deployed hosted server has it on text_to_music and video_to_music, the published sonilo-mcp 0.17.0 does not yet. When the next sonilo-mcp release ships it, --refresh will pick it up here."
},
"hosted": {
"get_account_services": [],
Expand All @@ -32,7 +33,8 @@
"prompt",
"duration",
"output_format",
"variants_num"
"variants_num",
"stems"
],
"video_to_music": [
"video_url",
Expand All @@ -41,7 +43,8 @@
"output_format",
"ducking",
"variants_num",
"prompt_influence"
"prompt_influence",
"stems"
],
"video_to_sfx": [
"video_url",
Expand Down Expand Up @@ -105,6 +108,7 @@
"duration",
"output_format",
"variants_num",
"stems",
"output_directory"
],
"video_to_music": [
Expand All @@ -116,6 +120,7 @@
"ducking",
"variants_num",
"prompt_influence",
"stems",
"output_directory"
],
"text_to_sfx": [
Expand Down Expand Up @@ -186,6 +191,12 @@
"task_id",
"output_directory"
],
"analyze_video": [
"video_path",
"video_url",
"prompt",
"variants_num"
],
"audio_ducking": [
"voice_path",
"voice_url",
Expand All @@ -199,12 +210,6 @@
],
"play_audio": [
"input_file_path"
],
"analyze_video": [
"video_path",
"video_url",
"prompt",
"variants_num"
]
},
"cli": {
Expand Down Expand Up @@ -411,7 +416,8 @@
"preserve_speech",
"ducking",
"variants_num",
"prompt_influence"
"prompt_influence",
"stems"
],
"video_to_video_music": [
"keep_original_sound",
Expand All @@ -431,7 +437,8 @@
"variants_num"
],
"text_to_music": [
"variants_num"
"variants_num",
"stems"
],
"dubbing": [
"languages",
Expand Down
48 changes: 45 additions & 3 deletions text-to-music/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ curl -X POST "https://api.sonilo.com/v1/text-to-music" \

| Tool | Description |
|------|-------------|
| `text_to_music(prompt, duration, output_format?, variants_num?, output_directory?)` | Generate music from a text description only — no video. |
| `text_to_music(prompt, duration, output_format?, variants_num?, stems?, output_directory?)` | Generate music from a text description only — no video. |

## Parameters

Expand All @@ -96,8 +96,49 @@ curl -X POST "https://api.sonilo.com/v1/text-to-music" \
| `duration` | int | — | Required. 1–360 seconds. Unlike the video tools, there is no source to take the length from, so you must set it. |
| `variants_num` | int | `1` | 1–10. Generates that many distinct creative directions in one request — different takes, not re-renders of one. **Cost scales linearly with the count, and any value above 1 is never covered by the free trial**, so confirm the number with the user first. Above 1 writes one file per variant and forces the backend's async mode. |
| `output_format` | string | `m4a` | `m4a` or `wav`. `wav` triggers the backend's async mode internally — no user-facing "mode" param needed. |
| `stems` | bool | `false` | **Free.** Additionally splits each generated track into four separated instrument tracks — `drums`, `bass`, `vocals`, `other` — returned alongside the untouched full mix. Async-only on REST (`stems=true` without `mode=async` is a `400`). See [Stems](#stems). |
| `output_directory` | string | `SONILO_MCP_BASE_PATH` | Absolute, or relative to the base path. |

## Stems

`stems=true` additionally returns each generated track split into four
separated instrument tracks — `drums`, `bass`, `vocals`, `other` — **free of
charge**. The full mix is untouched; the stems arrive alongside it in the task
result as a `stems` array next to `audio`:

```json
"stems": [
{
"stream_index": 0,
"drums": { "url": "…", "content_type": "audio/mp4", "file_size": 2913044 },
"bass": { "url": "…", "content_type": "audio/mp4", "file_size": 2870211 },
"vocals": { "url": "…", "content_type": "audio/mp4", "file_size": 2794560 },
"other": { "url": "…", "content_type": "audio/mp4", "file_size": 3011830 }
}
]
```

What matters when you use it:

- **Async only on REST.** `stems=true` requires `mode=async` (a `400` otherwise): you get a `202` + `task_id` and poll `/v1/tasks/{task_id}`. The MCP tools are always async, so on the hosted server the param just works.
- **Available on every surface** (verified 2026-08-17): REST, the hosted MCP server, the local `sonilo-mcp` package (>= 0.18.0), the SDKs (`sonilo` npm >= 0.16.0, PyPI >= 0.15.0), and the CLIs (`--stems`, npm `sonilo-cli` >= 0.15.0, PyPI `sonilo-cli` >= 0.14.0).
- **Match stems to tracks by `stream_index`, never by array position.** A stream whose separation failed is simply absent, so `stems` can be shorter than `audio`.
- **`stems_error` is not a failed generation.** When separation failed wholly or partly, or was skipped, the task carries a `stems_error` string — possibly *alongside* a partial `stems` array. The generation itself succeeded and every `audio` URL is valid: treat missing stems as a missing extra, never as a reason to retry or refund.
- **Timing:** separation runs after generation finishes — typically another 2–6 min, giving up after 30 min (then `stems_error`).
- **The four stem names are fixed** (htdemucs separation): melodic instruments — piano, synths, guitar, strings — land in `other`, and on instrumental tracks `vocals` is near-silent. That is correct behavior, not a bug.
- **Formats:** stems normally follow `output_format`; trust each stem's `content_type` for what was actually delivered.

```bash
# REST: submit with stems, then poll the task
curl -X POST "https://api.sonilo.com/v1/text-to-music" \
-H "Authorization: Bearer $SONILO_API_KEY" \
--data-urlencode "prompt=A chill lo-fi hip hop beat with jazzy piano chords" \
--data-urlencode "duration=30" \
--data-urlencode "mode=async" \
--data-urlencode "stems=true"
# → {"task_id": "…"} — poll GET /v1/tasks/{task_id} for audio + stems
```

## Prompting

The prompt is the only input — there is no footage to lean on. Describe genre,
Expand All @@ -117,12 +158,13 @@ auto-refund, but your own retry is a new charge.
- **If the user has a finished video, you are in the wrong skill.** [video-to-music](../video-to-music) syncs to the actual cut instead of producing a generic track of matching length.
- **Duration is required here.** Don't guess it — ask if the user hasn't said.
- **Several takes in one go:** `variants_num=3` returns three distinct directions for one request instead of three re-rolls. It costs 3×, and it is never free-trial covered — say the price before calling.
- **User wants the track's instruments as separate files** (to remix, re-balance, or drop one)? `stems=true` — it's free, but async-only and not on every surface yet; see [Stems](#stems).
- **Content restriction:** prompts cannot reference specific artists, bands, or copyrighted lyrics.

## Recovering a Timed-Out Call

`text_to_music` streams its result in one call unless `output_format="wav"` or
`variants_num` above 1 triggers the backend's async mode. If an async variant
`text_to_music` streams its result in one call unless `output_format="wav"`,
`variants_num` above 1, or `stems=true` triggers the backend's async mode. If an async variant
times out, the error message includes a `task_id`; the generation keeps running
(and is already charged) on the backend. Call `get_sfx_task(task_id)` —
`get_generation_task(task_id)` on the hosted server — to retrieve the result;
Expand Down
Loading
Loading