sound (Full Version)
A complete toolchain for adding sound to videos, with three capability lines:
- Sound Effect Selection — Multi-source search, filtering by human feedback signals, download, and analysis.
- BGM (Background Music) — Two paths: offline synthesis of seamless loopable music, or retrieval of long audio beds from libraries.
- Vocals & Mixing — Noise reduction/EQ/de-esser/compression, sidechain ducking, static mixing, two-pass loudness normalization.
Environment: Python 3 + FFmpeg (ffmpeg / ffprobe in PATH). Pure standard library, no third-party packages, no API keys, no login required.
All capabilities of this skill are open-source (MIT licensed) and implemented in this repository: all commands, rules, parameters are documented below and in references/, freely modifiable and distributable.
After installation, set variables according to the actual location. The examples below use the in-project installation path:
S=.claude/skills/sound/scripts/sfx.py # Search / Download / Analysis
V=.claude/skills/sound/scripts/vocal.py # Vocals / Mixing
C=.claude/skills/sound/scripts/compose.py # Offline BGM synthesis
For global installation, replace .claude/skills/sound/ with ~/.claude/skills/sound/.
1. Sound Effect Selection
Workflow
- Convert visual actions into English keywords ("card flies in" →
short whoosh, "switch toggle" →soft click). allsearches across sources for candidates; use single-source search when necessary;--urlsprovides user audition links.- First judge whether it matches the scene, then check popularity; provide candidates and reasoning—don't treat numbers as "I listened to it."
- Determine license based on usage, download, then use
analyzeto verify duration and energy distribution. - When delivering, retain the source URL, author, and license; for non-CC0, include attribution.
python3 $S all "short whoosh" --license cc0 -n 10 --urls
python3 $S freesound "paper slide" --license by -n 5 --sort short
python3 $S commons "bell" -n 5 --json
python3 $S mixkit "whoosh" -n 5
python3 $S kenney "impact" -n 5
python3 $S get sfx/ fs:167563 wm:131079817 mk:1489
python3 $S analyze sfx/*.mp3 --loudness --silence --json
Selection Rules (Following Human Feedback Signals)
- Rating count > Average rating:
★4.4(380)is stronger evidence than★4.9(6). - Uploader description closest to audition:
- Mentions like "built-in mic" / "made with my mouth" indicate lo-fi quality.
- Named recorders (Zoom, Sound Devices) and specific sources ("cork from a wine bottle") indicate controlled conditions.
- If source doesn't match the scene, discard regardless of popularity.
- Popularity ≠ Fit: The most downloaded "thud" might be a 3-second movie low-frequency rumble. Always check duration, use
analyzeto see where energy falls and tail length, then decide on trimming. - Show candidates before downloading: Add
--urlsfor user audition; don't decide for them. - Valuable signals differ by source:
- Freesound: download count and rating count.
- Commons: real duration and full license fields.
- Mixkit: duration (whole seconds) but no popularity.
- Kenney: entire asset packs.
- Don't treat missing fields as zero scores.
License Rules (Strictly Graded, No Loosening)
| Parameter | Allowed | Typical Use |
|---|---|---|
--license cc0 | CC0 / PDM only | Commercial use, no attribution hassle |
--license by | CC0 / PDM / BY / BY-SA | Attribution acceptable (NC variants excluded) |
--license any | All, but results still labeled | Just want to see candidates |
- BY-NC is never used commercially; scripts already exclude it under
cc0/by; underany, judge yourself. - Mixkit uses its own "Mixkit Free License" (effectively commercial-free, no attribution), but it falls outside the CC0/BY label system, so it's excluded under
cc0/by. When it's needed, use--license anyexplicitly and note the license name in deliverables. - Non-CC0 material:
getprints author and license info; this is just a clue—retain the original source and license text in deliverables. - Kenney: all CC0, but downloads are entire ZIP packs, not individual sounds—extract and pick files manually.
Source Division
| Source | Strength | Limitation |
|---|---|---|
| Freesound | Only source with download/rating counts | Only downloads preview MP3, not original files; ~80 KB/s per connection |
| Wikimedia Commons | Real duration + complete license fields; environmental recordings, bells, long audio | Matches by file description; strange keywords may fail; no popularity data |
| Mixkit | Commercial-free, no attribution; UI sound effects are consistently good | Page structure scraping; no popularity; duration labeled in whole seconds |
| Kenney | CC0 game/UI sound packs | Entire ZIP packs, no individual duration |
Detailed scraping methods and license tag mappings in references/sources.md.
2. BGM: Synthesis or Bed
First determine whether the user wants "music" or "a piece that matches the mood":
| Scenario | Path | Command |
|---|---|---|
| Needs loopable, zero-dependency, controllable mood, no copyright risk | Offline Synthesis | compose.py |
| Needs real instruments/vocals/environmental atmosphere | Retrieve bed | sfx.py music |
| Has a short BGM and wants to fill the scene | Loop/Cut | vocal.py loop / trim |
Offline Synthesis (Preferred, True "BGM Synthesis")
python3 $C bgm.wav --style ambient --duration 30 --bpm 90 --key Am
python3 $C bgm.wav --style lofi --duration 30 --bpm 85 --key C
python3 $C bgm.wav --style upbeat --duration 30 --bpm 120 --key G
ambient: slow pads (bed chords), no drums, ideal for narration/product demos.lofi: detuned chorus + loose kick/hat + tape noise floor.upbeat: bright short chords + four-on-the-floor kick + bass line.- Output: 44.1 kHz / 16-bit / stereo WAV, phase-aligned at start and end + chord loop crossfade, seamlessly loopable.
- Duration rounds to nearest whole bar (e.g., 30s @ 90 BPM actually 29.333s); for exact seconds, use a BPM divisible by 4 (e.g., 120).
- Script prints directly pasteable
--bgm-gainsuggestion (in dB), consistent withvocal.pyparameters. --seedguarantees identical output for the same parameters, useful for version management.
Retrieve Bed
python3 $S music "ambient" --duration long --max-seconds 600 -n 8 --urls --json
python3 $S get bgm/ wm:123456
python3 $V trim bgm/xxx.ogg bed.wav --start 0 --end 30
musicsearches Commons withfiletype:audio;--duration long: defaults to ≥30 seconds, excludes unknown durations (add--allow-unknownto include them).- Commons often returns multi-hour field recordings/audiobooks; always add
--max-seconds 600to avoid downloading hundreds of MB. - After downloading a long audio, typically
trima segment, orloopa short segment to fill the scene length.
3. Vocal Cleaning & Mixing
Standard Chain
# 1. Vocals: noise reduction → high-pass → (de-esser) → compression → two-pass loudness normalization → fade in/out
python3 $V clean voice.wav clean.wav --deesser
# Default target -16 LUFS; add --no-loudnorm to skip normalization
# 2. BGM alignment to scene length (choose one)
python3 $V loop bgm.wav bed.wav --duration 30 # Short material, true crossfade at seam
python3 $V trim bgm.wav bed.wav --start 0 --end 30
# 3. Mix: auto lower BGM when voice is present (sidechain ducking)
python3 $V duck clean.wav bed.wav mixed.wav --bgm-gain -12 --threshold -30 --ratio 8
# For no vocals / pure ambience, use static mix instead
python3 $V mix bed.wav clean.wav mixed.wav --bgm-gain -18
# 4. Final loudness check / normalize
python3 $V report mixed.wav
python3 $V loudnorm mixed.wav final.wav --target-lufs -16
Parameter Rules
| Parameter | Default | When to Adjust |
|---|---|---|
--bgm-gain | duck -12 / mix -18 dB | Use suggestion from compose.py; if BGM overpowers vocals, reduce by another 3-6 dB |
--threshold | -30 dB | Sidechain threshold is level threshold, not voice detection; lower to -36 for very quiet vocals, raise if BGM is very loud |
--ratio | 8 | If ducking is too obvious or breathing is strong, reduce to 4 |
--target-lufs | -16 LUFS | Use -16 for speech/video; -14 for pure music streaming; -16 for podcasts |
--true-peak | -1.5 dBTP | Change only if a platform has an explicit upper limit |
--fade | duck 0.3 / clean 0.05 s | For intro/outro and loop seams |
- Don't mix units:
Iis LUFS (integrated loudness),TPis dBTP (true peak),LRAis LU (loudness range);mean_volumeis dBFS average level.reportclearly labels these—don't treat them as LUFS. duck/mixboth addapad(silence padding) and useduration=longest, so BGM shorter than vocals won't be cut off;--durationcan specify final length.clean's two-pass loudnorm measurement runs after the entire pre-chain, and measured I/TP/LRA are identical between passes; fade in/out comes before loudnorm to ensure measurement signal matches normalized input—otherwisemeasured_*won't align.loudnorm's internal measurement may differ fromebur128by about 1 LU; script performs a closed-loop verification and applies an additional gain after final output, printing "Closed-loop correction ±X.X dB". This is necessary for precise compliance—don't remove it.- For very short/near-silent material where valid LUFS can't be measured, it automatically downgrades to single-pass loudnorm and explicitly writes "Single pass" in output; closed-loop is skipped, so it won't blindly amplify silence.
Filter chain source, two-pass loudnorm theory, and adjustment table in references/mixing.md.
4. Common Recipes
A. 30-second product demo (fully offline, zero assets)
python3 $C bgm.wav --style ambient --duration 30 --bpm 120 --key Am
python3 $V clean voice.wav clean.wav --deesser
python3 $V duck clean.wav bgm.wav mixed.wav --bgm-gain -13
python3 $V loudnorm mixed.wav final.wav --target-lufs -16
python3 $S all "soft whoosh" --license cc0 -n 6 --urls
B. Realistic ambience bed for existing voiceover
python3 $S music "rain ambience" --duration long --max-seconds 600 --license by -n 6 --urls
python3 $S get bgm/ wm:xxx
python3 $V trim bgm/xxx bed.wav --start 10 --end 40
python3 $V clean voice.wav clean.wav
python3 $V duck clean.wav bed.wav mixed.wav --bgm-gain -18
C. Just a transition sound, commercial use allowed
python3 $S all "paper slide" --license cc0 -n 8 --sort short --urls
python3 $S get sfx/ fs:xxx
python3 $S analyze sfx/*.mp3 --loudness --silence --json
D. Volume envelope following the timeline (Remotion rendering)
Mastering uses the FFmpeg chain above; per-frame fade in/out and beat-based volume changes are done in Remotion rendering—see references/remotion-audio.md.