Skip to content
Merged
Show file tree
Hide file tree
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
4 changes: 3 additions & 1 deletion .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Focused subpackages, each with a clear dependency footprint:
| Module | Purpose | Heavy deps |
|---|---|---|
| `mixing.audio` | `Audio`/`AudioSamples`, fades, crop, concat, overlay, `loop_audio`, `duck_audio`, `find_audio_offset`, segmentation | `pydub`, `numpy`, `scipy` |
| `mixing.audio.beats` | `beat_grid`/`BeatGrid` — beat/downbeat/onset analysis | `librosa` (extra `beats`, lazy-imported) |
| `mixing.video` | `Video`/`VideoFrames`, crop/loop/speed, `replace_audio`, `normalize_audio`, `overlay_ambient_bed`, `ken_burns_*`, `concatenate_videos`, thumbnails, subtitles | `moviepy`, `opencv`, `pillow`, `looks` (vocabulary only — stdlib) |
| `mixing.video.genai` | Google Vertex AI **Veo** generation | `google-genai` (extra `gen`) |
| `mixing.transcript` | ElevenLabs **Scribe** STT (stdlib HTTP, cached), filler removal, SRT/prose | stdlib only |
Expand Down Expand Up @@ -124,7 +125,8 @@ Use the helpers, don't reinvent them:

`pip install mixing[<extra>]`: `audio` (pydub/ffmpeg-python/soundfile),
`widget` (Jupyter audio widget), `gen` (Veo/`google-genai`), `llm` (`aix`, for
chapter titling + SRT translation), `clipboard` (`pyclip`), `dev`/`testing`.
chapter titling + SRT translation), `clipboard` (`pyclip`), `beats` (`librosa`,
for `mixing.audio.beats.beat_grid`), `dev`/`testing`.
**ffmpeg must be on PATH** for most real work (`mixing.has_ffmpeg()` checks).

## Tests = guardrails
Expand Down
21 changes: 21 additions & 0 deletions .claude/skills/mixing-audio/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,27 @@ own `(AudioSegment, **kwargs) -> list[Segment]` callable as `strategy`. The
`segment_by_silence/energy/self_similarity/speech_music` functions are exported
too if you want to call one directly on a pydub `AudioSegment`.

## Beat / downbeat / onset analysis (`mixing[beats]` extra)

```python
from mixing.audio import beat_grid, BeatGrid

grid = beat_grid("song.mp3") # -> BeatGrid; installs librosa lazily on first call
grid.beat_times # np.ndarray of beat instants, seconds, ascending
grid.downbeat_times # best-effort; empty array for the default (only) "librosa" backend
grid.onset_env # onset-strength envelope, one value per STFT hop
grid.onset_hop_s # seconds between onset_env frames (hop_length / sample_rate)
grid.tempo_bpm # estimated global tempo
grid.to_dict() # JSON-round-trippable summary (arrays -> lists; onset_env omitted)
```

`backend="librosa"` is the only shipped backend (ISC, commercial-clean); a
stronger downbeat tracker can be added later behind the same `BeatGrid` shape.
Needs `pip install mixing[beats]` (`librosa`) — `import mixing.audio` stays
light without it; the import happens lazily inside `beat_grid` itself. First
customer is muvid's footage-scoring layer (computes one grid on the master
song, maps clips onto it via their known offset).

## Common recipes

```python
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,13 +128,14 @@ pip install mixing[widget] # interactive Jupyter audio widget
pip install mixing[gen] # Google Vertex AI Veo generation
pip install mixing[llm] # aix — chapter titling + SRT translation
pip install mixing[clipboard] # get file paths from the clipboard
pip install mixing[beats] # librosa — beat/downbeat/onset analysis
```

## What's inside

| Subpackage | Highlights |
|---|---|
| `mixing.audio` | `Audio`, `fade_in/out`, `crop_audio`, `concatenate_audio`, `overlay_audio`, `loop_audio`, `duck_audio`, `find_audio_offset`, `find_segments`/`extract_segments` |
| `mixing.audio` | `Audio`, `fade_in/out`, `crop_audio`, `concatenate_audio`, `overlay_audio`, `loop_audio`, `duck_audio`, `find_audio_offset`, `find_segments`/`extract_segments`, `beat_grid`/`BeatGrid` (extra `beats`) |
| `mixing.video` | `Video`, `crop_video`, `loop_video`, `change_speed`, `replace_audio`, `normalize_audio`, `overlay_ambient_bed`, `ken_burns_video`/`ken_burns_film`, `concatenate_videos` (+ transitions: `crossfade_transition`, `fade_through_black`, …), `make_thumbnail`, `write_subtitles_in_video`, `SOCIAL_SIZES` |
| `mixing.video.genai` | `generate_video` (Vertex AI Veo) |
| `mixing.transcript` | `transcribe`, `remove_fillers`, `srt_for_media`, `words_to_srt`/`words_to_prose` |
Expand Down
7 changes: 4 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,9 @@ audio = [
"ffmpeg-python",
"soundfile",
]
# Beat / downbeat / onset analysis (mixing.audio.beat_grid). librosa is ISC
# (commercial-clean) and lazy-imported, so `import mixing.audio` stays light.
# Beat / downbeat / onset analysis (mixing.audio.beats: beat_grid, BeatGrid).
# librosa is ISC (commercial-clean) and lazy-imported, so `import mixing.audio`
# stays light.
beats = [
"librosa",
]
Expand Down Expand Up @@ -146,7 +147,7 @@ install.macos = "brew install ffmpeg"
install.windows = "choco install ffmpeg -y --no-progress"

[tool.wads.ci]
project_name = ""
project_name = "mixing"

[tool.wads.ci.commands]
pre_test = []
Expand Down
Loading