Command-line interface for the Sonilo API — generate music and sound effects from text or video.
pip install sonilo-cli
Set your API key once:
export SONILO_API_KEY=sk-...
or pass --api-key sk-... on any command.
sonilo account # plan limits and available services
sonilo usage --days 7 # usage summary
sonilo text-to-music --prompt "warm lo-fi piano, rain" --duration 30
sonilo video-to-music --video clip.mp4 --prompt "tense synths" --format wav
sonilo text-to-sfx --prompt "glass shattering on concrete" --duration 3
sonilo video-to-sfx --video clip.mp4 --output whoosh.wav
sonilo video-to-sfx --video clip.mp4 --segments @segments.json
sonilo video-to-sound --video clip.mp4 \
--music-prompt "uplifting orchestral score" --sfx-prompt "match the on-screen action"
sonilo video-to-video-music --video clip.mp4 --prompt "tense synths" --output scored.mp4
sonilo video-to-video-sfx --video clip.mp4 --segments @segments.json --output scored.mp4
sonilo video-to-video-sound --video clip.mp4 --music-prompt "tense synths"
sonilo dubbing --video-url https://example.com/clip.mp4 --languages es,fr --output dubbed.mp4
# writes dubbed.es.mp4 and dubbed.fr.mp4
sonilo tasks get <task-id>
sonilo tasks wait <task-id> --poll-interval 2 --timeout 600
text-to-music/video-to-musicstream a short.m4aby default.--format wav,--preserve-speech,--variantsabove 1, and the legacy alias--isolate-vocalseach switch to the async submit-and-poll path.text-to-sfx/video-to-sfxare always async;--formatacceptswav|mp3|aac|flac.- Output defaults to
./output.<ext>; override with--output.
--segments scores a timeline instead of one whole-clip prompt. It takes a JSON array, in one of
three forms — inline, from a file, or from stdin:
sonilo text-to-music --prompt "warm lo-fi piano" --duration 30 \
--segments '[{"start":0,"label":"intro","prompt":"airy pads"}]'
sonilo video-to-sfx --video clip.mp4 --segments @segments.json
jq -c '.cues' storyboard.json | sonilo video-to-sfx --video clip.mp4 --segments @-
A value starting with @ names a source to read the JSON from, and @- reads standard input — the
same convention as curl, gh and aws. Anything else is parsed as JSON directly.
The two segment shapes are not interchangeable:
| Shape | Commands | Fields |
|---|---|---|
| Music | text-to-music, video-to-music |
{start, prompt, label?} |
| SFX | video-to-sfx, video-to-video-sfx, video-to-sound, video-to-video-sound |
{start, end, prompt} |
start/endare seconds from the start of the track or clip.- Passing one shape to a command that takes the other is rejected before any request is made, with a message naming the shape that command expects.
- Only the shape is checked locally. Timing rules — the first segment starting at 0, minimum
spacing between segments, the
labelvocabulary, how many segments are allowed — are enforced by the API, which answers with a422describing what it rejected. - Keys the CLI does not recognise are forwarded as-is, so a newly added API field works without upgrading the CLI.
text-to-sfxtakes no segments (its output is a single effect, not a timeline).video-to-video-musictakes no segments either — the API scores the whole clip in one pass.
--variants N (1-10, default 1) generates that many distinct variants in one request instead of
one, on text-to-music, video-to-music, video-to-video-music, video-to-sound, and
video-to-video-sound. Cost scales linearly — --variants 3 costs three times a single-variant
request — and values above 1 are never covered by the free trial.
sonilo text-to-music --prompt "warm lo-fi piano" --duration 30 --variants 3 --output take.m4a
# writes take.0.m4a, take.1.m4a, take.2.m4a
--variantsabove 1 forces the async submit-and-poll path (see Notes above).- With
--variantsunset (or1), a command writes the single--outputfile exactly as before this flag existed. Above 1, it instead writes one file per variant, with the variant index spliced before the extension:take.m4abecomestake.0.m4a,take.1.m4a, etc. — the same naming--stemand dubbing's per-language output already use. - On
video-to-sound/video-to-video-sound,--stemis applied per variant too, e.g.take.0.music.m4a.
--prompt-influence (0-1, API default 0.5) sets how strongly the generated music follows the
prompt, on video-to-music and video-to-video-music only. Lower values let the video lead;
higher values follow the prompt more literally. It is free of charge, and unlike --format wav it
does not force the async path — it works on the streaming default too. Left unset, the field is
not sent at all and the API's own 0.5 default applies; --prompt-influence 0 is a real value
("let the video lead entirely") and is sent. Out-of-range values earn a 422 from the API.
sonilo video-to-music --video clip.mp4 --prompt "tense synths" --prompt-influence 0.8
video-to-video-music and video-to-video-sfx are the video-out counterparts of video-to-music
and video-to-sfx: same generation, but what comes back is the source picture with the new audio
already muxed in, so there is nothing to line up afterwards. Both are async-only and write a single
file (default output.mp4):
sonilo video-to-video-music --video clip.mp4 --prompt "tense synths" --output scored.mp4
sonilo video-to-video-sfx --video clip.mp4 \
--segments '[{"start":0,"end":5,"prompt":"footsteps on gravel"}]' --output scored.mp4
--promptis optional on both; without it the model scores from the picture alone.video-to-video-musicalso takes--preserve-speech, which keeps source speech in the mix; omitting it leaves the server default untouched.--isolate-vocalsis a legacy alias for the same flag — the API ORs the two together, and this endpoint returns one muxed video with no separate vocals stem.video-to-video-sfxtakes--segmentsin the SFX shape{start, end, prompt}— see Segments.- Neither command exposes
--format: the output is a video, not an audio file. - For music and effects in one call, use
video-to-video-soundbelow. video-to-video-musicalso takes--variants— see Variants above.video-to-video-sfxdoes not.video-to-video-musicalso takes--prompt-influence— see Prompt influence above.video-to-video-sfxdoes not.
video-to-sound and video-to-video-sound score a clip with a music bed and sound effects in one
call (one charge, instead of chaining two requests). Both are async-only and take the same options —
they differ only in what comes back: video-to-sound writes the mixed audio (default
output.wav), video-to-video-sound writes the source video with that audio muxed in (default
output.mp4).
sonilo video-to-sound --video clip.mp4 \
--music-prompt "uplifting orchestral score" \
--sfx-prompt "match the on-screen action" \
--output soundtrack.wav --stem music --stem sfx
--music-prompt/--sfx-promptsteer the two layers separately; both are optional.--segmentsplaces individual effects on the timeline, in the SFX shape{start, end, prompt}— see Segments.--preserve-speechkeeps speech from the source video in the mix.- Ducking is off by default. Pass
--duckingto bring the source video's own speech into the mix with the generated bed dipped under it — without it the result is the generated music and effects alone. Omitting both flags leaves the server default untouched;--no-duckingstill works and now just states that default explicitly. --stemis repeatable (music,music_processed,sfx) and saves the individual layers next to the combined output, so you can re-balance the mix yourself. With--output soundtrack.wav, the music stem lands atsoundtrack.music.m4a.music_processedexists only when--preserve-speechor ducking altered the music bed.- Both also take
--variants— see Variants above.
dubbing dubs a video into one or more target languages in a single async call:
sonilo dubbing --video-url https://example.com/clip.mp4 --languages es,fr --output dubbed.mp4
# writes dubbed.es.mp4 and dubbed.fr.mp4
--languagesis comma-separated; omit it to use the server defaultzh_cn,es,fr. Supported codes:en, zh_cn, ja, ko, pt, es, de, fr, it, ru.- Source videos may be at most 180 seconds long.
--outputis a filename template, not a single destination: a dubbing task returns one video per language, so--output clip.mp4writesclip.es.mp4,clip.fr.mp4, etc.- Billing is per language, and dubbing has no free trial runs — see Free trial below.
--timeoutdefaults to 7200 seconds, matching the backend's own ceiling for a dubbing job (far longer than other commands' default, since dubbing can run well past the usualtasks wait --timeout 600). If the wait still times out, the task keeps running server-side — resume watching it withsonilo tasks wait <task-id>.
Accounts created through self-serve signup start with free runs on most endpoints — no card required:
| Free runs | Endpoints |
|---|---|
| 2 each | text-to-music, text-to-sfx, audio-ducking |
| 1 each | video-to-music, video-to-sfx, video-to-video-music, video-to-video-sfx, video-to-sound, video-to-video-sound |
| 0 | dubbing |
Dubbing bills video duration × number of languages, so a free run on it would be worth far more
than a free run on any other endpoint — it has no free allowance and bills from the first call.
The table above is the current default. sonilo account prints the live numbers: the account JSON
goes to stdout, and when the account has a free-trial allowance one summary line goes to stderr:
Free trial: text-to-music 1/2 left, video-to-music 0/1 left
Because the summary is on stderr, sonilo account | jq .trial still sees clean JSON.
Once an endpoint's free runs are used up, calls to it bill at the normal rate — or, if the account
has never been funded, fail with HTTP 402: ... (trial_exhausted) until a payment method is added.
That is the one 402 a retry can never fix.
Two separate limits return HTTP 429, and they want opposite handling. The CLI prints the API's
own sentence, so the wording says which one you hit:
sonilo: HTTP 429: Rate limit exceeded: your account allows 60 requests per minute. Please retry after 1 minute. To raise your limit, please contact info@sonilo.com. (rate_limit_exceeded)
sonilo: HTTP 429: Too many concurrent generations: 5 of 5 in progress. Please wait for one to finish before starting another. To raise your limit, please contact info@sonilo.com. (rate_limit_exceeded)
The first means calls are going out too fast. The counter runs on a fixed 60-second window and rejected calls count toward it too, so wait the window out instead of retrying inside it. The second means every generation slot is busy — waiting alone frees nothing, a running generation has to finish first.
sonilo account prints the account's own rpm_limit and concurrency_limit; the numbers above
are the standard-tier defaults. Email info@sonilo.com to raise either.