From 33b1673acf92b04c85410cf1c9d66f8a4ecc047d Mon Sep 17 00:00:00 2001 From: Steven Pan <57612207+steven-panxd@users.noreply.github.com> Date: Tue, 20 Oct 2026 14:21:35 -0700 Subject: [PATCH 1/2] Raise the default task wait from 600s to 2700s The API now lets a task wait up to 2400s in its upstream provider's queue before giving up, so a 600s client wait abandoned tasks the server was still working on and would bill for if they succeeded. 2700s is that queue ceiling plus margin for generation. The CLI's stems wait keeps its "default wait + 30-minute separation ceiling" sizing (now 4500s, was 2400s), and `video-analysis --timeout` and `tasks wait --timeout` now use the SDK constant instead of a hard-coded 600. Co-Authored-By: Claude Opus 5.5 (1M context) --- README.md | 12 ++++++------ context7.json | 2 +- sonilo-cli/README.md | 8 ++++---- sonilo-cli/src/sonilo_cli/__main__.py | 16 ++++++++-------- sonilo-cli/tests/test_cli.py | 6 +++--- src/sonilo/resources/tasks.py | 5 ++++- src/sonilo/resources/text_to_music.py | 10 +++++----- src/sonilo/resources/video_to_music.py | 10 +++++----- 8 files changed, 36 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index e9d8ad0..51d5d98 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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. @@ -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 @@ -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 ``` diff --git a/context7.json b/context7.json index dd165bf..4f976c8 100644 --- a/context7.json +++ b/context7.json @@ -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.", diff --git a/sonilo-cli/README.md b/sonilo-cli/README.md index f34cf2c..ed2228d 100644 --- a/sonilo-cli/README.md +++ b/sonilo-cli/README.md @@ -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 - sonilo tasks wait --poll-interval 2 --timeout 600 + sonilo tasks wait --poll-interval 2 --timeout 2700 ### Notes @@ -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 `. - Separation can also come up short without failing the run: streams that did not separate are @@ -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 `. - Edit the files, then feed them straight into `dubbing`, which makes the dub speak your exact @@ -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 `. ## Free trial diff --git a/sonilo-cli/src/sonilo_cli/__main__.py b/sonilo-cli/src/sonilo_cli/__main__.py index 3c5ad48..9163060 100644 --- a/sonilo-cli/src/sonilo_cli/__main__.py +++ b/sonilo-cli/src/sonilo_cli/__main__.py @@ -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") @@ -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 @@ -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 `.", ) p_va.set_defaults(func=cmd_video_analysis) @@ -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 `.", ) p_pr.set_defaults(func=cmd_proofread) @@ -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 diff --git a/sonilo-cli/tests/test_cli.py b/sonilo-cli/tests/test_cli.py index 0990d5e..4f76fc6 100644 --- a/sonilo-cli/tests/test_cli.py +++ b/sonilo-cli/tests/test_cli.py @@ -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.""" @@ -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): diff --git a/src/sonilo/resources/tasks.py b/src/sonilo/resources/tasks.py index 5bd8808..f3efff7 100644 --- a/src/sonilo/resources/tasks.py +++ b/src/sonilo/resources/tasks.py @@ -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 diff --git a/src/sonilo/resources/text_to_music.py b/src/sonilo/resources/text_to_music.py index a7a5940..9f76a1a 100644 --- a/src/sonilo/resources/text_to_music.py +++ b/src/sonilo/resources/text_to_music.py @@ -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 @@ -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( @@ -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, diff --git a/src/sonilo/resources/video_to_music.py b/src/sonilo/resources/video_to_music.py index a35a5bb..79ee7ee 100644 --- a/src/sonilo/resources/video_to_music.py +++ b/src/sonilo/resources/video_to_music.py @@ -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, @@ -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( @@ -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, From 6fcddca37df91f4c0edf474cc151272af1722b08 Mon Sep 17 00:00:00 2001 From: Steven Pan <57612207+steven-panxd@users.noreply.github.com> Date: Tue, 20 Oct 2026 14:45:02 -0700 Subject: [PATCH 2/2] Release sonilo 0.21.1 and sonilo-cli 0.20.1 (45-minute default task wait) Co-Authored-By: Claude Opus 5.5 (1M context) --- pyproject.toml | 2 +- sonilo-cli/pyproject.toml | 4 ++-- sonilo-cli/src/sonilo_cli/__init__.py | 2 +- src/sonilo/_version.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5e031d5..493a696 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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" diff --git a/sonilo-cli/pyproject.toml b/sonilo-cli/pyproject.toml index d05d1f2..6234874 100644 --- a/sonilo-cli/pyproject.toml +++ b/sonilo-cli/pyproject.toml @@ -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] diff --git a/sonilo-cli/src/sonilo_cli/__init__.py b/sonilo-cli/src/sonilo_cli/__init__.py index 28db132..ad854f5 100644 --- a/sonilo-cli/src/sonilo_cli/__init__.py +++ b/sonilo-cli/src/sonilo_cli/__init__.py @@ -1,3 +1,3 @@ -__version__ = "0.20.0" +__version__ = "0.20.1" __all__ = ["__version__"] diff --git a/src/sonilo/_version.py b/src/sonilo/_version.py index 6a726d8..76f2458 100644 --- a/src/sonilo/_version.py +++ b/src/sonilo/_version.py @@ -1 +1 @@ -__version__ = "0.21.0" +__version__ = "0.21.1"