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/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/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/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/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/_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" 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,