sonilo-cli 0.8.2__tar.gz → 0.9.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.8.2 → sonilo_cli-0.9.0}/PKG-INFO +5 -3
- {sonilo_cli-0.8.2 → sonilo_cli-0.9.0}/README.md +4 -2
- {sonilo_cli-0.8.2 → sonilo_cli-0.9.0}/pyproject.toml +1 -1
- {sonilo_cli-0.8.2 → sonilo_cli-0.9.0}/src/sonilo_cli/__init__.py +1 -1
- {sonilo_cli-0.8.2 → sonilo_cli-0.9.0}/src/sonilo_cli/__main__.py +48 -13
- {sonilo_cli-0.8.2 → sonilo_cli-0.9.0}/tests/test_cli.py +22 -6
- {sonilo_cli-0.8.2 → sonilo_cli-0.9.0}/.gitignore +0 -0
- {sonilo_cli-0.8.2 → sonilo_cli-0.9.0}/LICENSE +0 -0
- {sonilo_cli-0.8.2 → sonilo_cli-0.9.0}/tests/__init__.py +0 -0
- {sonilo_cli-0.8.2 → sonilo_cli-0.9.0}/tests/test_context7.py +0 -0
- {sonilo_cli-0.8.2 → sonilo_cli-0.9.0}/tests/test_smoke.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sonilo-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
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
|
|
@@ -146,8 +146,10 @@ they differ only in what comes back: `video-to-sound` writes the mixed **audio**
|
|
|
146
146
|
- `--segments` places individual effects on the timeline, in the SFX shape `{start, end, prompt}` —
|
|
147
147
|
see [Segments](#segments).
|
|
148
148
|
- `--preserve-speech` keeps speech from the source video in the mix.
|
|
149
|
-
- **Ducking is
|
|
150
|
-
the
|
|
149
|
+
- **Ducking is off by default.** Pass `--ducking` to bring the source video's own speech into the
|
|
150
|
+
mix with the generated bed dipped under it — without it the result is the generated music and
|
|
151
|
+
effects alone. Omitting both flags leaves the server default untouched; `--no-ducking` still
|
|
152
|
+
works and now just states that default explicitly.
|
|
151
153
|
- `--stem` is repeatable (`music`, `music_processed`, `sfx`) and saves the individual layers next to
|
|
152
154
|
the combined output, so you can re-balance the mix yourself. With `--output soundtrack.wav`, the
|
|
153
155
|
music stem lands at `soundtrack.music.m4a`. `music_processed` exists only when `--preserve-speech`
|
|
@@ -130,8 +130,10 @@ they differ only in what comes back: `video-to-sound` writes the mixed **audio**
|
|
|
130
130
|
- `--segments` places individual effects on the timeline, in the SFX shape `{start, end, prompt}` —
|
|
131
131
|
see [Segments](#segments).
|
|
132
132
|
- `--preserve-speech` keeps speech from the source video in the mix.
|
|
133
|
-
- **Ducking is
|
|
134
|
-
the
|
|
133
|
+
- **Ducking is off by default.** Pass `--ducking` to bring the source video's own speech into the
|
|
134
|
+
mix with the generated bed dipped under it — without it the result is the generated music and
|
|
135
|
+
effects alone. Omitting both flags leaves the server default untouched; `--no-ducking` still
|
|
136
|
+
works and now just states that default explicitly.
|
|
135
137
|
- `--stem` is repeatable (`music`, `music_processed`, `sfx`) and saves the individual layers next to
|
|
136
138
|
the combined output, so you can re-balance the mix yourself. With `--output soundtrack.wav`, the
|
|
137
139
|
music stem lands at `soundtrack.music.m4a`. `music_processed` exists only when `--preserve-speech`
|
|
@@ -167,6 +167,30 @@ def parse_segments(
|
|
|
167
167
|
return value
|
|
168
168
|
|
|
169
169
|
|
|
170
|
+
def _ducking(args: argparse.Namespace) -> Optional[bool]:
|
|
171
|
+
"""Resolve --ducking / --no-ducking, or None to let the server default win.
|
|
172
|
+
|
|
173
|
+
`ducking` used to be default-ON server-side, so the only direction worth
|
|
174
|
+
expressing was turning it off and --no-ducking was the only flag. It is now
|
|
175
|
+
default-OFF, which makes --ducking the useful one. --no-ducking is kept
|
|
176
|
+
because dropping it would turn every script that passes it into a hard
|
|
177
|
+
argparse error; it now sends the explicit False the server would have
|
|
178
|
+
applied anyway.
|
|
179
|
+
|
|
180
|
+
Passing both is a contradiction with no sensible winner, so it exits rather
|
|
181
|
+
than silently picking one.
|
|
182
|
+
"""
|
|
183
|
+
on = getattr(args, "ducking", False)
|
|
184
|
+
off = getattr(args, "no_ducking", False)
|
|
185
|
+
if on and off:
|
|
186
|
+
raise SystemExit("pass at most one of --ducking or --no-ducking")
|
|
187
|
+
if on:
|
|
188
|
+
return True
|
|
189
|
+
if off:
|
|
190
|
+
return False
|
|
191
|
+
return None
|
|
192
|
+
|
|
193
|
+
|
|
170
194
|
def _segments(args: argparse.Namespace) -> Optional[List[Dict[str, Any]]]:
|
|
171
195
|
"""parse_segments() for whichever subcommand is running."""
|
|
172
196
|
return parse_segments(args.segments, args.segments_shape, args.command)
|
|
@@ -345,7 +369,7 @@ def _run_sound(client: Sonilo, args: argparse.Namespace, resource: Any, default_
|
|
|
345
369
|
sfx_prompt=args.sfx_prompt,
|
|
346
370
|
segments=_segments(args),
|
|
347
371
|
preserve_speech=True if args.preserve_speech else None,
|
|
348
|
-
ducking=
|
|
372
|
+
ducking=_ducking(args),
|
|
349
373
|
variants_num=args.variants,
|
|
350
374
|
**extra,
|
|
351
375
|
)
|
|
@@ -403,12 +427,11 @@ def cmd_video_to_video_music(client: Sonilo, args: argparse.Namespace) -> None:
|
|
|
403
427
|
client.video_to_video_music,
|
|
404
428
|
prompt=args.prompt,
|
|
405
429
|
# Unset flags forward None, not False, so the server default stands —
|
|
406
|
-
# same reasoning as --
|
|
407
|
-
#
|
|
408
|
-
#
|
|
409
|
-
# the default, never to restate it.
|
|
430
|
+
# same reasoning as --ducking on the sound commands. Both ducking and
|
|
431
|
+
# keep_original_sound are default-OFF server-side, so each is only ever
|
|
432
|
+
# sent to change the default, never to restate it.
|
|
410
433
|
keep_original_sound=True if args.keep_original_sound else None,
|
|
411
|
-
ducking=
|
|
434
|
+
ducking=_ducking(args),
|
|
412
435
|
preserve_speech=True if args.preserve_speech else None,
|
|
413
436
|
isolate_vocals=True if args.isolate_vocals else None,
|
|
414
437
|
variants_num=args.variants,
|
|
@@ -611,8 +634,14 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
611
634
|
_add_segments(p_v2sd, SFX_SHAPE)
|
|
612
635
|
p_v2sd.add_argument("--preserve-speech", dest="preserve_speech", action="store_true",
|
|
613
636
|
help="Keep source speech in the mix.")
|
|
637
|
+
p_v2sd.add_argument("--ducking", dest="ducking", action="store_true",
|
|
638
|
+
help="Bring the source video's own speech into the mix and duck "
|
|
639
|
+
"the generated bed under it. Off by default, so by default "
|
|
640
|
+
"the result is the generated music and effects alone and "
|
|
641
|
+
"there is no music_processed stem.")
|
|
614
642
|
p_v2sd.add_argument("--no-ducking", dest="no_ducking", action="store_true",
|
|
615
|
-
help="
|
|
643
|
+
help="Explicit opt-out. Same as the default; kept so existing "
|
|
644
|
+
"scripts keep working.")
|
|
616
645
|
p_v2sd.add_argument("--stem", dest="stems", action="append", choices=_SOUND_STEMS,
|
|
617
646
|
default=None, help="Also save an individual stem. Repeatable.")
|
|
618
647
|
p_v2sd.add_argument("--output", default=None, help="Where to save the combined audio.")
|
|
@@ -631,10 +660,13 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
631
660
|
"generated music under it. Off by default, so by default "
|
|
632
661
|
"the result's audio is the generated music alone. "
|
|
633
662
|
"Supersedes --preserve-speech.")
|
|
663
|
+
p_v2vm.add_argument("--ducking", dest="ducking", action="store_true",
|
|
664
|
+
help="Duck the music under the voice instead of mixing it in at "
|
|
665
|
+
"a static level. No effect without --keep-original-sound "
|
|
666
|
+
"or --preserve-speech.")
|
|
634
667
|
p_v2vm.add_argument("--no-ducking", dest="no_ducking", action="store_true",
|
|
635
|
-
help="
|
|
636
|
-
"
|
|
637
|
-
"--keep-original-sound or --preserve-speech.")
|
|
668
|
+
help="Explicit opt-out. Same as the default; kept so existing "
|
|
669
|
+
"scripts keep working.")
|
|
638
670
|
p_v2vm.add_argument("--preserve-speech", dest="preserve_speech", action="store_true",
|
|
639
671
|
help="Keep only the source's isolated speech in the mix.")
|
|
640
672
|
# Same aliasing as video-to-music, and here the endpoint collapses the two
|
|
@@ -676,10 +708,13 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
676
708
|
"--preserve-speech. This command only.")
|
|
677
709
|
p_v2vsd.add_argument("--preserve-speech", dest="preserve_speech", action="store_true",
|
|
678
710
|
help="Keep only the source's isolated speech in the mix.")
|
|
679
|
-
p_v2vsd.add_argument("--
|
|
680
|
-
help="
|
|
681
|
-
"
|
|
711
|
+
p_v2vsd.add_argument("--ducking", dest="ducking", action="store_true",
|
|
712
|
+
help="Duck the generated bed under the voice instead of mixing "
|
|
713
|
+
"it in at a static level. No effect without "
|
|
682
714
|
"--keep-original-sound or --preserve-speech.")
|
|
715
|
+
p_v2vsd.add_argument("--no-ducking", dest="no_ducking", action="store_true",
|
|
716
|
+
help="Explicit opt-out. Same as the default; kept so existing "
|
|
717
|
+
"scripts keep working.")
|
|
683
718
|
p_v2vsd.add_argument("--stem", dest="stems", action="append", choices=_SOUND_STEMS,
|
|
684
719
|
default=None, help="Also save an individual stem. Repeatable.")
|
|
685
720
|
p_v2vsd.add_argument("--output", default=None, help="Where to save the combined video.")
|
|
@@ -369,15 +369,25 @@ def test_video_to_sound_ducking_absent_omits_field(tmp_path):
|
|
|
369
369
|
)
|
|
370
370
|
run(["video-to-sound", "--video-url", "http://x/y.mp4",
|
|
371
371
|
"--output", str(tmp_path / "s.wav")])
|
|
372
|
-
# ducking is default-
|
|
373
|
-
# `None`, not `False
|
|
374
|
-
# form-encoded body (per build_v2s_parts).
|
|
372
|
+
# ducking is default-OFF server-side, and neither flag was passed, so the
|
|
373
|
+
# CLI must forward `None`, not `False` — the field has to be absent from
|
|
374
|
+
# the form-encoded body (per build_v2s_parts) and let the server decide.
|
|
375
375
|
body = route.calls.last.request.content.decode()
|
|
376
376
|
assert "ducking=" not in body
|
|
377
377
|
|
|
378
378
|
|
|
379
379
|
@respx.mock
|
|
380
|
-
|
|
380
|
+
@pytest.mark.parametrize(
|
|
381
|
+
"flag, wire",
|
|
382
|
+
[
|
|
383
|
+
# --ducking is the direction that does something now that the server
|
|
384
|
+
# default is off; --no-ducking predates the flip, still parses so
|
|
385
|
+
# existing scripts do not break, and states the default explicitly.
|
|
386
|
+
("--ducking", "ducking=true"),
|
|
387
|
+
("--no-ducking", "ducking=false"),
|
|
388
|
+
],
|
|
389
|
+
)
|
|
390
|
+
def test_video_to_sound_ducking_flags(tmp_path, flag, wire):
|
|
381
391
|
route = respx.post(f"{BASE}/v1/video-to-sound").mock(
|
|
382
392
|
return_value=httpx.Response(200, json={"task_id": "sd4", "status": "processing"})
|
|
383
393
|
)
|
|
@@ -388,9 +398,15 @@ def test_video_to_sound_no_ducking_sets_false(tmp_path):
|
|
|
388
398
|
return_value=httpx.Response(200, content=b"MIXED")
|
|
389
399
|
)
|
|
390
400
|
run(["video-to-sound", "--video-url", "http://x/y.mp4",
|
|
391
|
-
"--output", str(tmp_path / "s.wav"),
|
|
401
|
+
"--output", str(tmp_path / "s.wav"), flag])
|
|
392
402
|
body = route.calls.last.request.content.decode()
|
|
393
|
-
assert
|
|
403
|
+
assert wire in body
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def test_video_to_sound_rejects_both_ducking_flags(tmp_path):
|
|
407
|
+
with pytest.raises(SystemExit):
|
|
408
|
+
run(["video-to-sound", "--video-url", "http://x/y.mp4",
|
|
409
|
+
"--output", str(tmp_path / "s.wav"), "--ducking", "--no-ducking"])
|
|
394
410
|
|
|
395
411
|
|
|
396
412
|
@respx.mock
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|