sonilo-cli 0.7.0__tar.gz → 0.8.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sonilo-cli
3
- Version: 0.7.0
3
+ Version: 0.8.1
4
4
  Summary: Command-line interface for the Sonilo API: generate music and sound effects from text or video
5
5
  Project-URL: Repository, https://github.com/sonilo-ai/sonilo-python
6
6
  Author: Sonilo AI
@@ -8,7 +8,7 @@ License-Expression: MIT
8
8
  License-File: LICENSE
9
9
  Keywords: ai,cli,music,sfx,sonilo,text-to-music,video-to-music
10
10
  Requires-Python: >=3.9
11
- Requires-Dist: sonilo<0.11,>=0.10.0
11
+ Requires-Dist: sonilo<0.12,>=0.11.0
12
12
  Provides-Extra: dev
13
13
  Requires-Dist: pytest>=8; extra == 'dev'
14
14
  Requires-Dist: respx>=0.21; extra == 'dev'
@@ -197,3 +197,19 @@ Because the summary is on stderr, `sonilo account | jq .trial` still sees clean
197
197
  Once an endpoint's free runs are used up, calls to it bill at the normal rate — or, if the account
198
198
  has never been funded, fail with `HTTP 402: ... (trial_exhausted)` until a payment method is added.
199
199
  That is the one 402 a retry can never fix.
200
+
201
+ ## Rate limits
202
+
203
+ Two separate limits return `HTTP 429`, and they want opposite handling. The CLI prints the API's
204
+ own sentence, so the wording says which one you hit:
205
+
206
+ sonilo: HTTP 429: Rate limit exceeded: your account allows 60 requests per minute. Rejected requests count toward the limit too, so wait for the next minute window (up to 60 sec) rather than retrying right away. To raise your limit, contact info@sonilo.com. (rate_limit_exceeded)
207
+ sonilo: HTTP 429: Too many concurrent generations: 5 of 5 in progress. Wait for one to finish before starting another. To raise your limit, contact info@sonilo.com. (rate_limit_exceeded)
208
+
209
+ The first means calls are going out too fast. The counter runs on a fixed 60-second window and
210
+ rejected calls count toward it too, so wait the window out instead of retrying inside it. The
211
+ second means every generation slot is busy — waiting alone frees nothing, a running generation has
212
+ to finish first.
213
+
214
+ `sonilo account` prints the account's own `rpm_limit` and `concurrency_limit`; the numbers above
215
+ are the standard-tier defaults. Email info@sonilo.com to raise either.
@@ -181,3 +181,19 @@ Because the summary is on stderr, `sonilo account | jq .trial` still sees clean
181
181
  Once an endpoint's free runs are used up, calls to it bill at the normal rate — or, if the account
182
182
  has never been funded, fail with `HTTP 402: ... (trial_exhausted)` until a payment method is added.
183
183
  That is the one 402 a retry can never fix.
184
+
185
+ ## Rate limits
186
+
187
+ Two separate limits return `HTTP 429`, and they want opposite handling. The CLI prints the API's
188
+ own sentence, so the wording says which one you hit:
189
+
190
+ sonilo: HTTP 429: Rate limit exceeded: your account allows 60 requests per minute. Rejected requests count toward the limit too, so wait for the next minute window (up to 60 sec) rather than retrying right away. To raise your limit, contact info@sonilo.com. (rate_limit_exceeded)
191
+ sonilo: HTTP 429: Too many concurrent generations: 5 of 5 in progress. Wait for one to finish before starting another. To raise your limit, contact info@sonilo.com. (rate_limit_exceeded)
192
+
193
+ The first means calls are going out too fast. The counter runs on a fixed 60-second window and
194
+ rejected calls count toward it too, so wait the window out instead of retrying inside it. The
195
+ second means every generation slot is busy — waiting alone frees nothing, a running generation has
196
+ to finish first.
197
+
198
+ `sonilo account` prints the account's own `rpm_limit` and `concurrency_limit`; the numbers above
199
+ are the standard-tier defaults. Email info@sonilo.com to raise either.
@@ -4,13 +4,13 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sonilo-cli"
7
- version = "0.7.0"
7
+ version = "0.8.1"
8
8
  description = "Command-line interface for the Sonilo API: generate music and sound effects from text or video"
9
9
  readme = "README.md"
10
10
  license = "MIT"
11
11
  requires-python = ">=3.9"
12
12
  authors = [{ name = "Sonilo AI" }]
13
- dependencies = ["sonilo>=0.10.0,<0.11"]
13
+ dependencies = ["sonilo>=0.11.0,<0.12"]
14
14
  keywords = ["sonilo", "cli", "music", "sfx", "text-to-music", "video-to-music", "ai"]
15
15
 
16
16
  [project.urls]
@@ -1,3 +1,3 @@
1
- __version__ = "0.7.0"
1
+ __version__ = "0.8.0"
2
2
 
3
3
  __all__ = ["__version__"]
@@ -331,6 +331,13 @@ def _stem_path(out: str, stem: str, media: Any) -> str:
331
331
 
332
332
  def _run_sound(client: Sonilo, args: argparse.Namespace, resource: Any, default_ext: str) -> None:
333
333
  out = args.output if args.output is not None else f"output.{default_ext}"
334
+ # video-to-video-sound only. Its parser is the only one that defines the
335
+ # flag, and video_to_sound.generate() does not accept the keyword at all,
336
+ # so it has to be omitted here rather than forwarded as None — the same
337
+ # split the SDK enforces by never passing it from the audio resource.
338
+ extra: Dict[str, Any] = {}
339
+ if getattr(args, "keep_original_sound", False):
340
+ extra["keep_original_sound"] = True
334
341
  result = resource.generate(
335
342
  video=args.video,
336
343
  video_url=args.video_url,
@@ -340,6 +347,7 @@ def _run_sound(client: Sonilo, args: argparse.Namespace, resource: Any, default_
340
347
  preserve_speech=True if args.preserve_speech else None,
341
348
  ducking=False if args.no_ducking else None,
342
349
  variants_num=args.variants,
350
+ **extra,
343
351
  )
344
352
  multi = args.variants is not None and args.variants > 1 and len(result.outputs) > 1
345
353
  if not multi:
@@ -395,7 +403,12 @@ def cmd_video_to_video_music(client: Sonilo, args: argparse.Namespace) -> None:
395
403
  client.video_to_video_music,
396
404
  prompt=args.prompt,
397
405
  # Unset flags forward None, not False, so the server default stands —
398
- # same reasoning as --no-ducking on the sound commands.
406
+ # same reasoning as --no-ducking on the sound commands. The two
407
+ # defaults run opposite ways: ducking is default-ON server-side and
408
+ # keep_original_sound default-OFF, so each is only ever sent to change
409
+ # the default, never to restate it.
410
+ keep_original_sound=True if args.keep_original_sound else None,
411
+ ducking=False if args.no_ducking else None,
399
412
  preserve_speech=True if args.preserve_speech else None,
400
413
  isolate_vocals=True if args.isolate_vocals else None,
401
414
  variants_num=args.variants,
@@ -612,8 +625,18 @@ def build_parser() -> argparse.ArgumentParser:
612
625
  _add_global(p_v2vm)
613
626
  _add_video_source(p_v2vm)
614
627
  p_v2vm.add_argument("--prompt", default=None, help="Optional creative direction.")
628
+ p_v2vm.add_argument("--keep-original-sound", dest="keep_original_sound",
629
+ action="store_true",
630
+ help="Keep the source video's whole original audio, with the "
631
+ "generated music under it. Off by default, so by default "
632
+ "the result's audio is the generated music alone. "
633
+ "Supersedes --preserve-speech.")
634
+ p_v2vm.add_argument("--no-ducking", dest="no_ducking", action="store_true",
635
+ help="Combine the voice and the music as a static mix instead "
636
+ "of a dynamic duck. No effect without "
637
+ "--keep-original-sound or --preserve-speech.")
615
638
  p_v2vm.add_argument("--preserve-speech", dest="preserve_speech", action="store_true",
616
- help="Keep source speech in the mix.")
639
+ help="Keep only the source's isolated speech in the mix.")
617
640
  # Same aliasing as video-to-music, and here the endpoint collapses the two
618
641
  # into a single boolean before it reaches the model (video_to_video.py:
619
642
  # `keep_speech = bool(preserve_speech) or bool(isolate_vocals)`), with no
@@ -644,10 +667,19 @@ def build_parser() -> argparse.ArgumentParser:
644
667
  p_v2vsd.add_argument("--sfx-prompt", dest="sfx_prompt", default=None,
645
668
  help="Optional creative direction for the sound effects.")
646
669
  _add_segments(p_v2vsd, SFX_SHAPE)
670
+ p_v2vsd.add_argument("--keep-original-sound", dest="keep_original_sound",
671
+ action="store_true",
672
+ help="Keep the source video's whole original audio, with the "
673
+ "generated music and effects under it. Off by default, so "
674
+ "by default the result's audio is the generated audio "
675
+ "alone and there is no music_processed stem. Supersedes "
676
+ "--preserve-speech. This command only.")
647
677
  p_v2vsd.add_argument("--preserve-speech", dest="preserve_speech", action="store_true",
648
- help="Keep source speech in the mix.")
678
+ help="Keep only the source's isolated speech in the mix.")
649
679
  p_v2vsd.add_argument("--no-ducking", dest="no_ducking", action="store_true",
650
- help="Disable automatic ducking (on by default server-side).")
680
+ help="Combine the voice and the generated bed as a static mix "
681
+ "instead of a dynamic duck. No effect without "
682
+ "--keep-original-sound or --preserve-speech.")
651
683
  p_v2vsd.add_argument("--stem", dest="stems", action="append", choices=_SOUND_STEMS,
652
684
  default=None, help="Also save an individual stem. Repeatable.")
653
685
  p_v2vsd.add_argument("--output", default=None, help="Where to save the combined video.")
File without changes
File without changes
File without changes
File without changes