Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 72 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,24 @@ Python ≥ 3.9. Sync and async clients included.
pip install sonilo
```

## Authentication

Create an API key in your [Sonilo dashboard](https://platform.sonilo.com/dashboard/api-keys),
then give it to the client either as an environment variable (recommended) or
inline:

```bash
export SONILO_API_KEY=sk_...
```

```python
client = Sonilo() # reads SONILO_API_KEY
client = Sonilo(api_key="sk_...") # or pass it directly
```

Keep your key secret — use it only server-side, never commit it, and prefer the
environment variable over hardcoding it.

## Quickstart

```python
Expand All @@ -32,21 +50,23 @@ track = client.video_to_music.generate(video="my_video.mp4", prompt="upbeat")
track = client.video_to_music.generate(video_url="https://example.com/clip.mp4")
```

### Vocal isolation (async)
### Preserve speech (async)

Pass `isolate_vocals=True` to also get an isolated vocal stem and a muxed
video, alongside the scored audio. This requires async processing — submit
returns a `task_id` immediately, and `generate_async()` wraps submit + poll:
Pass `preserve_speech=True` to keep the source speech/vocals in the result.
You also get a separate speech stem (`vocals`) and a mux (the generated music
mixed with the preserved speech) alongside the scored audio. This requires async
processing — submit returns a `task_id` immediately, and `generate_async()`
wraps submit + poll:

```python
result = client.video_to_music.generate_async(
video="my_video.mp4",
prompt="upbeat",
isolate_vocals=True, # implies mode="async"; omit mode to let it auto-select
preserve_speech=True, # implies mode="async"; omit mode to let it auto-select
)
result.save("mix.m4a") # result.audio[0] — the full mix
result.save("vocals.m4a", which="vocals")
result.save("video.mp4", which="mux") # video muxed with the generated audio
result.save("video.mp4", which="mux") # generated music muxed with the preserved speech
print(result.title.title if result.title else None)
```

Expand All @@ -55,16 +75,60 @@ Or control submission and polling yourself:
```python
from sonilo.resources.tasks import parse_music_result

task = client.video_to_music.submit(video_url="https://example.com/clip.mp4", isolate_vocals=True)
task = client.video_to_music.submit(video_url="https://example.com/clip.mp4", preserve_speech=True)
result = client.tasks.wait(
task.task_id,
parser=parse_music_result, # required: tasks.wait()/get() default to the SFX parser
)
```

`isolate_vocals=True` with an explicit non-async `mode` raises `SoniloError`
`preserve_speech=True` with an explicit non-async `mode` raises `SoniloError`
locally before any request is sent.

### Ducking, speech & output format (async video-to-music)

`submit()` / `generate_async()` also accept:

- `preserve_speech` — keep the source speech/vocals in the result (see
[Preserve speech](#preserve-speech-async) above).
- `ducking` — duck the generated music under the source voice. It is **on by
default** in async mode; pass `ducking=False` to opt out. When it runs, the
result gains a `ducked` list alongside `audio`.
- `output_format` — `"m4a"` (default) or `"wav"` (requires async mode).

```python
result = client.video_to_music.generate_async(
video="my_video.mp4",
preserve_speech=True,
output_format="wav",
# ducking defaults on in async — pass ducking=False to disable
)
result.save("track.wav")
if result.ducked:
result.save("ducked.wav", which="ducked")
```

## Video to video

Generate music or sound effects and get back a **re-hosted video** with the
audio muxed in — not just an audio file. Both endpoints are async; `generate()`
submits and polls to a `VideoResult`:

```python
music = client.video_to_video_music.generate(
video="my_video.mp4", # path, bytes, open file, or use video_url=
prompt="cinematic orchestral swell",
preserve_speech=True,
)
music.save("scored.mp4")

sfx = client.video_to_video_sfx.generate(
video="my_video.mp4",
segments=[{"start": 0, "end": 2, "prompt": "footsteps on gravel"}],
)
sfx.save("with_sfx.mp4")
```

## Streaming

```python
Expand Down
Loading