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
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ result = client.text_to_music.generate_async(
prompt="cinematic orchestral score",
duration=60,
stems=True,
timeout=2400, # separation can run long — see below
timeout=4500, # separation can run long — see below
)
result.save("track.m4a")
entry = result.stems_for(0) # look up by stream_index, never list position
Expand Down Expand Up @@ -201,9 +201,9 @@ The result gains two **independent** fields:

Separation runs **after** generation: it typically adds 2-6 minutes to the
wait and gives up after 30. The SDK's default wait is `DEFAULT_WAIT_TIMEOUT`
(600 seconds), which a legitimate stems task can outlive — pass a longer
`timeout` explicitly (2400 seconds covers the separation ceiling on top of a
normal generation, and is what the CLI uses), or prefer `submit()` plus your
(2700 seconds), which a legitimate stems task can outlive — pass a longer
`timeout` explicitly (4500 seconds covers the separation ceiling on top of the
default generation wait, and is what the CLI uses), or prefer `submit()` plus your
own `client.tasks.wait(...)`, the same advice as for dubbing. A timed-out
wait only stops waiting; the task keeps running server-side.

Expand Down Expand Up @@ -424,7 +424,7 @@ at most 300 seconds long, and billing is per language: a 3-language call
costs three times as much as one. Dubbing's one free run is a 15-second
preview — see [Free trial](#free-trial).

The SDK's default wait is `DEFAULT_WAIT_TIMEOUT` (600 seconds), but the
The SDK's default wait is `DEFAULT_WAIT_TIMEOUT` (2700 seconds), but the
dubbing pipeline can take much longer than that — especially with several
languages in one call. Pass a longer `timeout` explicitly: 7200 seconds
matches the backend's own ceiling for a dubbing job, and is what the CLI
Expand Down Expand Up @@ -688,7 +688,7 @@ task = client.video_to_sfx.submit(
segments=[{"start": 0, "end": 2.5, "prompt": "footsteps on gravel"}],
audio_format="wav",
)
result = client.tasks.wait(task.task_id, poll_interval=2.0, timeout=600.0)
result = client.tasks.wait(task.task_id, poll_interval=2.0, timeout=2700.0)
result.save("audio.wav") # video-to-sfx returns the generated audio only
```

Expand Down
2 changes: 1 addition & 1 deletion context7.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"Read the API key from the SONILO_API_KEY environment variable by default; pass api_key= explicitly only when the caller has a reason to override it.",
"text-to-music and video-to-music: use generate() (streaming) for short synchronous tracks. It does NOT support output_format=\"wav\", isolate_vocals, preserve_speech or ducking.",
"For those options use generate_async(), or submit() + client.tasks.wait(parser=parse_music_result). isolate_vocals/preserve_speech/ducking are video-to-music only; text-to-music takes output_format alone.",
"stems=True on text_to_music / video_to_music submit()/generate_async() splits the GENERATED music (never a video's own audio) into drums/bass/vocals/other. Free, async-only; separation adds 2-6 min (30-min cap) — pass timeout=2400, not the 600s default.",
"stems=True on text_to_music / video_to_music submit()/generate_async() splits the GENERATED music (never a video's own audio) into drums/bass/vocals/other. Free, async-only; separation adds 2-6 min (30-min cap) — pass timeout=4500, not the 2700s default.",
"Look MusicResult.stems entries up by stream_index (stems_for(i), save_stem(which=, stream_index=)), never list position — the list can be shorter than audio. stems_error can accompany a PARTIAL stems list; never treat it as \"no stems\".",
"text-to-sfx and video-to-sfx are always async: submit() returns a task and generate() (or tasks.wait()) polls it to completion. Neither has a streaming variant.",
"client.video_to_sound and client.video_to_video_sound score one clip with a music bed AND sound effects in a single call \u2014 prefer them over chaining video-to-music with video-to-sfx, which is charged twice.",
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "sonilo"
version = "0.21.0"
version = "0.21.1"
description = "Official Python client for the Sonilo API"
readme = "README.md"
license = "MIT"
Expand Down
8 changes: 4 additions & 4 deletions sonilo-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ production sign-in coexist without overwriting each other.
sonilo dubbing --video-url https://example.com/clip.mp4 --languages es,fr --output dubbed.mp4
# writes dubbed.es.mp4 and dubbed.fr.mp4
sonilo tasks get <task-id>
sonilo tasks wait <task-id> --poll-interval 2 --timeout 600
sonilo tasks wait <task-id> --poll-interval 2 --timeout 2700

### Notes

Expand Down Expand Up @@ -171,7 +171,7 @@ splits the *generated* music, never the video's own audio.
# writes take.m4a, take.drums.m4a, take.bass.m4a, take.vocals.m4a, take.other.m4a

- Separation runs **after** generation and typically adds 2-6 minutes to the wait; the CLI
waits up to 2400 seconds on these runs (covering the separation service's own 30-minute
waits up to 4500 seconds on these runs (covering the separation service's own 30-minute
ceiling, the way dubbing's `--timeout` covers its backend's). If the wait still times out,
the task keeps running server-side — resume it with `sonilo tasks wait <task-id>`.
- Separation can also come up short without failing the run: streams that did not separate are
Expand Down Expand Up @@ -300,7 +300,7 @@ rendered.
multiplied by the number of target languages at $0.001/second, a transcript-only request counts as
one, and billing has a 10-second floor; there are 2 free runs — see [Free trial](#free-trial)
below.
- `--timeout` defaults to 600 seconds, the usual default: a proofread job typically finishes in well
- `--timeout` defaults to 2700 seconds, the usual default: a proofread job typically finishes in well
under a minute. If the wait does time out, the task keeps running server-side — resume it with
`sonilo tasks wait <task-id>`.
- Edit the files, then feed them straight into `dubbing`, which makes the dub speak your exact
Expand Down Expand Up @@ -350,7 +350,7 @@ rendered.
[Free trial](#free-trial) below; the preview's message is printed right under the `Wrote …` line.
- `--timeout` defaults to 7200 seconds, matching the backend's own ceiling for a dubbing job
(far longer than other commands' default, since dubbing can run well past the usual
`tasks wait --timeout 600`). If the wait still times out, the task keeps running
`tasks wait --timeout 2700`). If the wait still times out, the task keeps running
server-side — resume watching it with `sonilo tasks wait <task-id>`.

## Free trial
Expand Down
4 changes: 2 additions & 2 deletions sonilo-cli/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ build-backend = "hatchling.build"

[project]
name = "sonilo-cli"
version = "0.20.0"
version = "0.20.1"
description = "Command-line interface for the Sonilo API: generate music and sound effects from text or video"
readme = "README.md"
license = "MIT"
requires-python = ">=3.9"
authors = [{ name = "Sonilo AI" }]
dependencies = ["sonilo>=0.21.0,<0.22"]
dependencies = ["sonilo>=0.21.1,<0.22"]
keywords = ["sonilo", "cli", "music", "sfx", "text-to-music", "video-to-music", "ai"]

[project.urls]
Expand Down
2 changes: 1 addition & 1 deletion sonilo-cli/src/sonilo_cli/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
__version__ = "0.20.0"
__version__ = "0.20.1"

__all__ = ["__version__"]
16 changes: 8 additions & 8 deletions sonilo-cli/src/sonilo_cli/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,10 +294,10 @@ def _save_music_variants(result: Any, out: str) -> None:

# Matched to the separation service's own ceiling on top of the generation
# wait: separation runs after generation, typically adds 2-6 minutes and gives
# up after 30 (1800s), so the SDK's generic DEFAULT_WAIT_TIMEOUT of 600s would
# up after 30 (1800s), so the SDK's generic DEFAULT_WAIT_TIMEOUT alone would
# abandon a stems task the user has already been charged the generation for —
# the same reasoning as DUBBING_WAIT_TIMEOUT below.
STEMS_WAIT_TIMEOUT = 2400.0
STEMS_WAIT_TIMEOUT = DEFAULT_WAIT_TIMEOUT + 1800.0

_MUSIC_STEMS = ("drums", "bass", "vocals", "other")

Expand Down Expand Up @@ -624,7 +624,7 @@ def cmd_video_analysis(client: Sonilo, args: argparse.Namespace) -> None:

# Matched to the dubbing backend's own ceiling: it polls its pipeline for up
# to 7200s (2 hours), so anything shorter abandons a job the user has already
# been charged for. The SDK's generic DEFAULT_WAIT_TIMEOUT of 600s is far too
# been charged for. The SDK's generic DEFAULT_WAIT_TIMEOUT of 2700s is far too
# short for this endpoint. --timeout overrides.
DUBBING_WAIT_TIMEOUT = 7200.0

Expand Down Expand Up @@ -1147,8 +1147,8 @@ def build_parser() -> argparse.ArgumentParser:
help="Write the brief to this .json file instead of printing it to stdout.",
)
p_va.add_argument(
"--timeout", type=float, default=600.0,
help="Give up waiting after this many seconds. Default: 600. A timed-out "
"--timeout", type=float, default=DEFAULT_WAIT_TIMEOUT,
help=f"Give up waiting after this many seconds. Default: {DEFAULT_WAIT_TIMEOUT:g}. A timed-out "
"task may still finish — resume it with `sonilo tasks get <task-id>`.",
)
p_va.set_defaults(func=cmd_video_analysis)
Expand Down Expand Up @@ -1240,7 +1240,7 @@ def build_parser() -> argparse.ArgumentParser:
)
p_pr.add_argument(
"--timeout", type=float, default=DEFAULT_WAIT_TIMEOUT,
help="Give up waiting after this many seconds. Default: 600. A timed-out "
help=f"Give up waiting after this many seconds. Default: {DEFAULT_WAIT_TIMEOUT:g}. A timed-out "
"task may still finish — resume it with `sonilo tasks wait <task-id>`.",
)
p_pr.set_defaults(func=cmd_proofread)
Expand All @@ -1259,8 +1259,8 @@ def build_parser() -> argparse.ArgumentParser:
p_wait.add_argument("task_id", help="The task id to poll.")
p_wait.add_argument("--poll-interval", dest="poll_interval", type=float, default=2.0,
help="Seconds between polls. Default: 2")
p_wait.add_argument("--timeout", type=float, default=600.0,
help="Give up after this many seconds. Default: 600")
p_wait.add_argument("--timeout", type=float, default=DEFAULT_WAIT_TIMEOUT,
help=f"Give up after this many seconds. Default: {DEFAULT_WAIT_TIMEOUT:g}")
p_wait.set_defaults(func=cmd_tasks_wait)

return parser
Expand Down
6 changes: 3 additions & 3 deletions sonilo-cli/tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2027,7 +2027,7 @@ def test_video_to_music_stems_partial_failure_warns_but_saves_the_rest(tmp_path,


def test_stems_passes_the_long_timeout_and_tri_state(tmp_path, monkeypatch):
"""--stems switches the wait to STEMS_WAIT_TIMEOUT — the SDK's 600s
"""--stems switches the wait to STEMS_WAIT_TIMEOUT — the SDK's 2700s
default would abandon a separation that legitimately runs up to 30
minutes past generation — while a stems-less async run keeps the
default and sends no stems field at all."""
Expand Down Expand Up @@ -2055,9 +2055,9 @@ def fake_generate_async(self, **kwargs):
run(["text-to-music", "--prompt", "x", "--duration", "10",
"--format", "wav", "--output", str(tmp_path / "b.wav")])
assert calls[0]["stems"] is True
assert calls[0]["timeout"] == 2400.0
assert calls[0]["timeout"] == 4500.0
assert calls[1]["stems"] is None
assert calls[1]["timeout"] == 600.0
assert calls[1]["timeout"] == 2700.0


def test_stems_warns_when_none_come_back(tmp_path, monkeypatch, capsys):
Expand Down
2 changes: 1 addition & 1 deletion src/sonilo/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.21.0"
__version__ = "0.21.1"
5 changes: 4 additions & 1 deletion src/sonilo/resources/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@
from sonilo._client import Sonilo

DEFAULT_POLL_INTERVAL = 2.0
DEFAULT_WAIT_TIMEOUT = 600.0
# The API lets a task wait up to 2400s in its upstream provider's queue before
# giving up, then needs time to generate. Giving up any earlier abandons a task
# that may still succeed — and is billed when it does.
DEFAULT_WAIT_TIMEOUT = 2700.0

# Test seams: monkeypatched in tests so polling is instant and deterministic.
_sleep = time.sleep
Expand Down
10 changes: 5 additions & 5 deletions src/sonilo/resources/text_to_music.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def submit(
by `stream_index`, never position) and possibly a `stems_error`.
Separation runs after generation and typically adds 2-6 minutes,
giving up after 30; when polling yourself, pass tasks.wait() a
`timeout` well above its 600-second default (2400 covers the ceiling).
`timeout` well above its 2700-second default (4500 covers the ceiling).
"""
data = build_t2m_async_data(
prompt, duration, segments, mode, output_format, variants_num, stems
Expand All @@ -90,8 +90,8 @@ def generate_async(
) -> MusicResult:
"""submit() + tasks.wait(), returning the parsed MusicResult.

With `stems=True`, pass a `timeout` well above the 600-second default
(2400 covers the separation service's 30-minute ceiling) — see
With `stems=True`, pass a `timeout` well above the 2700-second default
(4500 covers the separation service's 30-minute ceiling) — see
submit().
"""
task = self.submit(
Expand Down Expand Up @@ -169,8 +169,8 @@ async def generate_async(
) -> MusicResult:
"""submit() + tasks.wait(), returning the parsed MusicResult.

With `stems=True`, pass a `timeout` well above the 600-second default
(2400 covers the separation service's 30-minute ceiling).
With `stems=True`, pass a `timeout` well above the 2700-second default
(4500 covers the separation service's 30-minute ceiling).
"""
task = await self.submit(
prompt=prompt, duration=duration, segments=segments,
Expand Down
10 changes: 5 additions & 5 deletions src/sonilo/resources/video_to_music.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def submit(
position) and possibly a `stems_error`. Separation runs after
generation and typically adds 2-6 minutes, giving up after 30; when
polling yourself, pass tasks.wait() a `timeout` well above its
600-second default (2400 covers the ceiling).
2700-second default (4500 covers the ceiling).
"""
data, files, opened = build_v2m_async_parts(
video, video_url, prompt, segments, mode, isolate_vocals,
Expand Down Expand Up @@ -139,8 +139,8 @@ def generate_async(
) -> MusicResult:
"""submit() + tasks.wait(), returning the parsed MusicResult.

With `stems=True`, pass a `timeout` well above the 600-second default
(2400 covers the separation service's 30-minute ceiling) — see
With `stems=True`, pass a `timeout` well above the 2700-second default
(4500 covers the separation service's 30-minute ceiling) — see
submit().
"""
task = self.submit(
Expand Down Expand Up @@ -275,8 +275,8 @@ async def generate_async(
) -> MusicResult:
"""submit() + tasks.wait(), returning the parsed MusicResult.

With `stems=True`, pass a `timeout` well above the 600-second default
(2400 covers the separation service's 30-minute ceiling).
With `stems=True`, pass a `timeout` well above the 2700-second default
(4500 covers the separation service's 30-minute ceiling).
"""
task = await self.submit(
video=video,
Expand Down
Loading