sonilo-cli 0.9.0__tar.gz → 0.10.0__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,3 +1,19 @@
1
+ Metadata-Version: 2.4
2
+ Name: sonilo-cli
3
+ Version: 0.10.0
4
+ Summary: Command-line interface for the Sonilo API: generate music and sound effects from text or video
5
+ Project-URL: Repository, https://github.com/sonilo-ai/sonilo-python
6
+ Author: Sonilo AI
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Keywords: ai,cli,music,sfx,sonilo,text-to-music,video-to-music
10
+ Requires-Python: >=3.9
11
+ Requires-Dist: sonilo<0.13,>=0.12.0
12
+ Provides-Extra: dev
13
+ Requires-Dist: pytest>=8; extra == 'dev'
14
+ Requires-Dist: respx>=0.21; extra == 'dev'
15
+ Description-Content-Type: text/markdown
16
+
1
17
  # sonilo-cli
2
18
 
3
19
  Command-line interface for the [Sonilo API](https://github.com/sonilo-ai/sonilo-python) — generate music and sound effects from text or video.
@@ -90,6 +106,17 @@ request — and values above 1 are never covered by the free trial.
90
106
  - On `video-to-sound` / `video-to-video-sound`, `--stem` is applied per variant too, e.g.
91
107
  `take.0.music.m4a`.
92
108
 
109
+ ### Prompt influence
110
+
111
+ `--prompt-influence` (0-1, API default 0.5) sets how strongly the generated music follows the
112
+ prompt, on `video-to-music` and `video-to-video-music` only. Lower values let the video lead;
113
+ higher values follow the prompt more literally. It is free of charge, and unlike `--format wav` it
114
+ does not force the async path — it works on the streaming default too. Left unset, the field is
115
+ not sent at all and the API's own 0.5 default applies; `--prompt-influence 0` is a real value
116
+ ("let the video lead entirely") and is sent. Out-of-range values earn a `422` from the API.
117
+
118
+ sonilo video-to-music --video clip.mp4 --prompt "tense synths" --prompt-influence 0.8
119
+
93
120
  ### Scored video
94
121
 
95
122
  `video-to-video-music` and `video-to-video-sfx` are the video-out counterparts of `video-to-music`
@@ -112,6 +139,8 @@ file (default `output.mp4`):
112
139
  - For music *and* effects in one call, use `video-to-video-sound` below.
113
140
  - `video-to-video-music` also takes `--variants` — see [Variants](#variants) above.
114
141
  `video-to-video-sfx` does not.
142
+ - `video-to-video-music` also takes `--prompt-influence` — see
143
+ [Prompt influence](#prompt-influence) above. `video-to-video-sfx` does not.
115
144
 
116
145
  ### Combined soundtracks
117
146
 
@@ -1,19 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: sonilo-cli
3
- Version: 0.9.0
4
- Summary: Command-line interface for the Sonilo API: generate music and sound effects from text or video
5
- Project-URL: Repository, https://github.com/sonilo-ai/sonilo-python
6
- Author: Sonilo AI
7
- License-Expression: MIT
8
- License-File: LICENSE
9
- Keywords: ai,cli,music,sfx,sonilo,text-to-music,video-to-music
10
- Requires-Python: >=3.9
11
- Requires-Dist: sonilo<0.12,>=0.11.0
12
- Provides-Extra: dev
13
- Requires-Dist: pytest>=8; extra == 'dev'
14
- Requires-Dist: respx>=0.21; extra == 'dev'
15
- Description-Content-Type: text/markdown
16
-
17
1
  # sonilo-cli
18
2
 
19
3
  Command-line interface for the [Sonilo API](https://github.com/sonilo-ai/sonilo-python) — generate music and sound effects from text or video.
@@ -106,6 +90,17 @@ request — and values above 1 are never covered by the free trial.
106
90
  - On `video-to-sound` / `video-to-video-sound`, `--stem` is applied per variant too, e.g.
107
91
  `take.0.music.m4a`.
108
92
 
93
+ ### Prompt influence
94
+
95
+ `--prompt-influence` (0-1, API default 0.5) sets how strongly the generated music follows the
96
+ prompt, on `video-to-music` and `video-to-video-music` only. Lower values let the video lead;
97
+ higher values follow the prompt more literally. It is free of charge, and unlike `--format wav` it
98
+ does not force the async path — it works on the streaming default too. Left unset, the field is
99
+ not sent at all and the API's own 0.5 default applies; `--prompt-influence 0` is a real value
100
+ ("let the video lead entirely") and is sent. Out-of-range values earn a `422` from the API.
101
+
102
+ sonilo video-to-music --video clip.mp4 --prompt "tense synths" --prompt-influence 0.8
103
+
109
104
  ### Scored video
110
105
 
111
106
  `video-to-video-music` and `video-to-video-sfx` are the video-out counterparts of `video-to-music`
@@ -128,6 +123,8 @@ file (default `output.mp4`):
128
123
  - For music *and* effects in one call, use `video-to-video-sound` below.
129
124
  - `video-to-video-music` also takes `--variants` — see [Variants](#variants) above.
130
125
  `video-to-video-sfx` does not.
126
+ - `video-to-video-music` also takes `--prompt-influence` — see
127
+ [Prompt influence](#prompt-influence) above. `video-to-video-sfx` does not.
131
128
 
132
129
  ### Combined soundtracks
133
130
 
@@ -4,13 +4,13 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sonilo-cli"
7
- version = "0.9.0"
7
+ version = "0.10.0"
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.11.0,<0.12"]
13
+ dependencies = ["sonilo>=0.12.0,<0.13"]
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.9.0"
1
+ __version__ = "0.10.0"
2
2
 
3
3
  __all__ = ["__version__"]
@@ -309,12 +309,15 @@ def cmd_video_to_music(client: Sonilo, args: argparse.Namespace) -> None:
309
309
  preserve_speech=args.preserve_speech or None,
310
310
  output_format=fmt if fmt != "m4a" else None,
311
311
  variants_num=args.variants,
312
+ prompt_influence=args.prompt_influence,
312
313
  )
313
314
  _save_music_variants(result, out)
314
315
  else:
316
+ # prompt_influence rides the streaming path too — it is a generation
317
+ # parameter, not a finalize-time one, so it never forces async.
315
318
  track = client.video_to_music.generate(
316
319
  video=args.video, video_url=args.video_url, prompt=args.prompt,
317
- segments=segments,
320
+ segments=segments, prompt_influence=args.prompt_influence,
318
321
  )
319
322
  path = track.save(out)
320
323
  _wrote(path, len(track.audio))
@@ -435,6 +438,7 @@ def cmd_video_to_video_music(client: Sonilo, args: argparse.Namespace) -> None:
435
438
  preserve_speech=True if args.preserve_speech else None,
436
439
  isolate_vocals=True if args.isolate_vocals else None,
437
440
  variants_num=args.variants,
441
+ prompt_influence=args.prompt_influence,
438
442
  )
439
443
 
440
444
 
@@ -543,6 +547,19 @@ def _add_segments(parser: argparse.ArgumentParser, shape: _SegmentShape) -> None
543
547
  parser.set_defaults(segments_shape=shape)
544
548
 
545
549
 
550
+ def _add_prompt_influence(parser: argparse.ArgumentParser) -> None:
551
+ # Only the two music-from-video commands take this — the API accepts it
552
+ # nowhere else. type=float so 0 arrives as 0.0, a real value ("let the
553
+ # video lead entirely"), distinct from the unset None that keeps the
554
+ # field off the wire and leaves the API its own 0.5 default.
555
+ parser.add_argument(
556
+ "--prompt-influence", dest="prompt_influence", type=float, default=None,
557
+ help="How strongly the generated music follows the prompt, 0-1 "
558
+ "(API default 0.5). Lower values let the video lead; higher "
559
+ "values follow the prompt more literally. Free of charge.",
560
+ )
561
+
562
+
546
563
  def _add_variants(parser: argparse.ArgumentParser) -> None:
547
564
  parser.add_argument(
548
565
  "--variants", type=int, default=None,
@@ -601,6 +618,7 @@ def build_parser() -> argparse.ArgumentParser:
601
618
  p_v2m.add_argument("--async", dest="use_async", action="store_true",
602
619
  help="Submit and poll instead of streaming.")
603
620
  _add_variants(p_v2m)
621
+ _add_prompt_influence(p_v2m)
604
622
  p_v2m.set_defaults(func=cmd_video_to_music)
605
623
 
606
624
  p_t2s = sub.add_parser("text-to-sfx", help="Generate a sound effect from a text prompt")
@@ -677,6 +695,7 @@ def build_parser() -> argparse.ArgumentParser:
677
695
  help="Legacy alias for --preserve-speech; no separate stem.")
678
696
  p_v2vm.add_argument("--output", default=None, help="Where to save the scored video.")
679
697
  _add_variants(p_v2vm)
698
+ _add_prompt_influence(p_v2vm)
680
699
  p_v2vm.set_defaults(func=cmd_video_to_video_music)
681
700
 
682
701
  p_v2vfx = sub.add_parser(
@@ -1096,3 +1096,64 @@ def test_video_to_video_commands_are_listed_in_top_level_help(command, capsys):
1096
1096
  with pytest.raises(SystemExit):
1097
1097
  main(["--help"])
1098
1098
  assert command in capsys.readouterr().out
1099
+
1100
+
1101
+ # --- --prompt-influence -------------------------------------------------------
1102
+ #
1103
+ # Only video-to-music and video-to-video-music offer the flag — the API
1104
+ # accepts prompt_influence nowhere else. Unset forwards None (field absent,
1105
+ # API default 0.5 stands); 0 is a real value and must be sent as 0.0.
1106
+
1107
+
1108
+ @respx.mock
1109
+ def test_video_to_music_prompt_influence_rides_the_streaming_path(tmp_path):
1110
+ """prompt_influence is a generation parameter, valid on stream and async
1111
+ alike, so unlike --format wav it must NOT force the async path."""
1112
+ route = respx.post(f"{BASE}/v1/video-to-music").mock(
1113
+ return_value=httpx.Response(200, text=_music_stream_body())
1114
+ )
1115
+ run(["video-to-music", "--video-url", "http://x/y.mp4",
1116
+ "--prompt-influence", "0.8", "--output", str(tmp_path / "song.m4a")])
1117
+ body = route.calls.last.request.content.decode()
1118
+ assert "prompt_influence=0.8" in body
1119
+
1120
+
1121
+ @respx.mock
1122
+ def test_video_to_music_prompt_influence_zero_is_sent(tmp_path):
1123
+ """0 means "let the video lead entirely" — a real request, distinct from
1124
+ unset, so it goes on the wire (as 0.0: argparse's float parse)."""
1125
+ route = respx.post(f"{BASE}/v1/video-to-music").mock(
1126
+ return_value=httpx.Response(200, text=_music_stream_body())
1127
+ )
1128
+ run(["video-to-music", "--video-url", "http://x/y.mp4",
1129
+ "--prompt-influence", "0", "--output", str(tmp_path / "song.m4a")])
1130
+ body = route.calls.last.request.content.decode()
1131
+ assert "prompt_influence=0.0" in body
1132
+
1133
+
1134
+ @respx.mock
1135
+ def test_video_to_music_omits_prompt_influence_when_unset(tmp_path):
1136
+ route = respx.post(f"{BASE}/v1/video-to-music").mock(
1137
+ return_value=httpx.Response(200, text=_music_stream_body())
1138
+ )
1139
+ run(["video-to-music", "--video-url", "http://x/y.mp4",
1140
+ "--output", str(tmp_path / "song.m4a")])
1141
+ # Absent, not "None" and not an explicit 0.5 pinning the API's default.
1142
+ assert b"prompt_influence" not in route.calls.last.request.content
1143
+
1144
+
1145
+ @respx.mock
1146
+ def test_video_to_video_music_prompt_influence_reaches_the_request_body(tmp_path):
1147
+ route = _mock_video_task("video-to-video-music", "vmpi1", "video_to_video_music")
1148
+ run(["video-to-video-music", "--video-url", "http://x/y.mp4",
1149
+ "--prompt-influence", "0.3", "--output", str(tmp_path / "s.mp4")])
1150
+ body = route.calls.last.request.content.decode()
1151
+ assert "prompt_influence=0.3" in body
1152
+
1153
+
1154
+ @respx.mock
1155
+ def test_video_to_video_music_omits_prompt_influence_when_unset(tmp_path):
1156
+ route = _mock_video_task("video-to-video-music", "vmpi2", "video_to_video_music")
1157
+ run(["video-to-video-music", "--video-url", "http://x/y.mp4",
1158
+ "--output", str(tmp_path / "s.mp4")])
1159
+ assert b"prompt_influence" not in route.calls.last.request.content
File without changes
File without changes