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.
- sonilo_cli-0.9.0/README.md → sonilo_cli-0.10.0/PKG-INFO +29 -0
- sonilo_cli-0.9.0/PKG-INFO → sonilo_cli-0.10.0/README.md +13 -16
- {sonilo_cli-0.9.0 → sonilo_cli-0.10.0}/pyproject.toml +2 -2
- {sonilo_cli-0.9.0 → sonilo_cli-0.10.0}/src/sonilo_cli/__init__.py +1 -1
- {sonilo_cli-0.9.0 → sonilo_cli-0.10.0}/src/sonilo_cli/__main__.py +20 -1
- {sonilo_cli-0.9.0 → sonilo_cli-0.10.0}/tests/test_cli.py +61 -0
- {sonilo_cli-0.9.0 → sonilo_cli-0.10.0}/.gitignore +0 -0
- {sonilo_cli-0.9.0 → sonilo_cli-0.10.0}/LICENSE +0 -0
- {sonilo_cli-0.9.0 → sonilo_cli-0.10.0}/tests/__init__.py +0 -0
- {sonilo_cli-0.9.0 → sonilo_cli-0.10.0}/tests/test_context7.py +0 -0
- {sonilo_cli-0.9.0 → sonilo_cli-0.10.0}/tests/test_smoke.py +0 -0
|
@@ -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.
|
|
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.
|
|
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]
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|