Repository navigation
feat: add sound effects timed to the animation with ElevenLabs - #14
Merged
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #13
Why
Narration and BGM alone leave explainer videos feeling flat. Short sound effects on visible events (a card appearing, a chip arriving, a step lighting up) make them feel finished. The ElevenLabs Sound Effects API works on pay-as-you-go without a subscription, at about $0.002 per second of output.
Two findings from trying it shaped the design:
gemini-3.8-flashgot 10–12 of 18 right and called silence a "soft UI pop".gemini-3.1-pro-previewcalled the pop "silence" three times out of three. So effects are kept short and quiet, silence is detected mechanically, and a preview file lets a person listen to all of them.What changed
script.json: an optionalsfxlist per scene (atseconds from the scene start,prompt,duration0.5–10 s with a default of 1.5,volume0–1) and a script-widesfxVolume(default 1). All values are validated as plain numbers, like the other values written into the filter graph.motion-render sfx <work>(newsfx.ts):POST /v1/sound-generationwithmp3_44100_128, the best format pay-as-you-go allows. The provider call is one function, so it can be swapped for fal.ai.audio/sfx/<hash>.mp3keyed by model, prompt and duration, so an unchanged effect is never paid for twice and identical effects are shared across scenes.volumedetect. A peak below -60 dB is a failed attempt, retried with the same 90 s timeout and 3 attempts as Gemini.audio/sfx/preview.m4a, every effect in script order at its mixing level with a 0.6 s gap, and prints the order.build:at(placeSfx, likeplaceOnFilm), so effects follow the animation when narration moves scenes. Warns about an effect past its scene's end.sfxTrack,narrationLevel): each effect is brought to 4 dB under the narration's average level and lowered by itsvolume. Its peak is never allowed past -1 dB. The narration level is measured from the scene WAVs, and -16 dB, Gemini TTS's usual level, is assumed before any narration exists.mix(): each effect is cut toduration, given its gain, faded at both ends and delayed into place, then joins the finalamixbeforealimiter. A script withoutsfxproduces exactly the same graph as before, which a test asserts.audio.ts: the retry loop moved intowithRetries(), shared by Gemini and ElevenLabs.runFfmpegandTIMEOUT_MSare exported forsfx.ts.cli.ts: the commands that take only a work dir (narrate,bgm,sfx) go through a table. This lowersmain's complexity from 25 to 22, and the quality baseline is updated to match.SKILL.md:ELEVENLABS_API_KEYis set.🎧:11.aion the title scene. Audio made outside a paid subscription is non-commercial and must be credited in its title, per the ElevenLabs help center.docs/elevenlabs-key(.ja).mdcovering the pay-as-you-go top-up, a key restricted to Sound Effects, cost and licensing. The README (en/ja) gains rows in requirements, setup, commands, environment variables and troubleshooting.Verification
make check: lint, format, typecheck, tests with coverage, quality tests and the TypeScript quality gate all pass. 78 tests, including the Chromium render smoke tests run outside the sandbox. Coverage is 94% lines in total.tests/fixtures), with scenes resized to the narration'stiming.json(2.9 s and 6.9 s):narrate,bgmandsfxall succeeded.sfxmade 3 effects and the preview. A secondsfxrun made 0 and called no API.Two films were built from the same audio, one with
sfxand one without. The output saysnarration + BGM + 3 sound effects.The difference between the two audio tracks, in 50 ms windows, starts at 1.00 s, 3.40 s and 5.45 s. The intended times were 1.0, 3.4 and 5.4 s. The chime's attack is soft, so it crosses the threshold one window late.
Effect level against the narration and BGM in the same window (RMS), after the leveling fix. These match the +6 dB mix the listener picked to within 0.1 dB:
Stereo peak is -0.17 dBFS with effects. No sample is over 0 dBFS.
Not verified
aton the right animation moments, followingSKILL.md, is untested.docs/elevenlabs-key.md("Add credits", "Usage"). They are inferred from the Japanese UI and the ElevenLabs docs.