npx skills add ...
npx skills add sonilo-ai/skills --skill audio-ducking
Duck a music bed under a voice track using Sonilo — automatically lowers the music wherever the voice speaks and lifts it back in the gaps. Use when mixing a separately-generated or existing music track under narration, dialogue, or a video's own voice track, without manual volume automation.
npx skills add sonilo-ai/skills --skill audio-ducking
Automatically duck a music bed under a voice track: Sonilo lowers the music wherever the voice is speaking and lifts it back in the gaps, then returns the mixed result. The voice input may be a video — its audio track is used as the voice, and the ducked mix is muxed back into a new video.
Setup: See the setup-api-key skill.
⚠️ Cost: makes an API call that may incur charges. Only call when explicitly requested.
Pick one at the start of the session and stay on it. Do not mix the two inside a single job, and do not announce the choice.
audio_ducking and friends) — use them. This is the preferred path: it needs no shell, and it is the only one that survives a very long generation. If a call fails to authenticate — rather than failing on its inputs — this transport is not usable in this session: go to 2 instead of retrying it.sonilo account exits 0 — use the CLI commands below. Same API, same account, same credential file. Probe with sonilo account, not sonilo whoami: whoami exits 0 even when signed out, so it cannot tell the two states apart.api.sonilo.com with curl to work around it; both transports handle uploads, polling and retries that a bare request does not.pip install "sonilo>=0.13")npm install sonilo@>=0.14)npm install -g sonilo-cli or pip install sonilo-cli)Always async under the hood — the CLI submits and polls for you. Exactly one of --voice/--voice-url and one of --music/--music-url. The default output name follows what comes back (output.wav, or output.mp4 when the voice input was a video); --output overrides it. A local --music file must have an audio extension — the CLI rejects a video there up front, for the same reason the MCP tool does.
A local file uses the voice_file/music_file multipart fields; a remote source uses voice_url/music_url form fields instead (mix and match freely between the two inputs).
| Tool | Description |
|---|---|
audio_ducking(voice_path? | voice_url?, music_path? | music_url?, output_directory?) | Mix music under voice, ducking automatically wherever the voice speaks. |
| Parameter | Type | Notes |
|---|---|---|
voice_path | string | Absolute path, or relative to SONILO_MCP_BASE_PATH. Audio or video: .wav/.mp3/.m4a/.aac/.ogg/.flac or .mp4/.mov/.avi/.wmv/.webm/.mkv. |
voice_url | string | HTTPS URL to the voice audio/video. Exactly one of voice_path/voice_url. |
music_path | string | Absolute path, or relative to the base path. Audio only — a video here is not treated specially and will be mishandled. |
music_url | string | HTTPS URL to the music audio. Exactly one of music_path/music_url. |
output_directory | string | Defaults to SONILO_MCP_BASE_PATH. |
Each input is capped at 360 seconds (6 minutes) and by the account's upload-size limit (typically 300 MB).
text_to_music/video_to_music), then feed the result in here as music_path.voice_path — Sonilo extracts its audio track, ducks the music under it, and re-muxes the ducked mix back into a new video automatically.video_to_music(ducking=true) when the music itself is also being generated for that same video — those tools duck internally as part of generation, so you don't need a separate ducking call. Reach for audio_ducking specifically when the music track is fixed/external and you just need the mix.This tool submits an async task on the backend. If the call times out, the error carries a task_id — the job keeps running (already charged). Call get_sfx_task(task_id) later (get_generation_task(task_id) on the hosted server); see task-recovery.
A single file: a .wav if the voice input was audio, or a .mp4 (ducked mix re-muxed in) if the voice input was a video. Named after the voice input (e.g. interview.mp4 → interview-ducked.mp4), falling back to ducked-<first 8 chars of the task id>.
Common errors: 401 invalid key, 402 insufficient balance / trial exhausted, 413 file too large, 422 invalid parameters, 429 rate limit. See the account skill.
import { SoniloClient, download } from "sonilo";
import { writeFile } from "node:fs/promises";
const client = new SoniloClient(); // reads SONILO_API_KEY
const result = await client.audioDucking.generate({
voice: "./interview.mp4", // audio or video; also voiceUrl
musicUrl: "https://example.com/background-track.wav", // audio only; also music
});
await writeFile(
result.output_type === "video" ? "ducked.mp4" : "ducked.wav",
await download(result.output_url!),
);sonilo audio-ducking --voice interview.mp4 --music-url https://example.com/background-track.wavcurl -X POST "https://api.sonilo.com/v1/audio-ducking" \
-H "Authorization: Bearer $SONILO_API_KEY" \
-F "voice_file=@interview.mp4" \
-F "music_file=@background-track.wav"
# -> {"task_id": "..."} poll GET /v1/tasks/{task_id}