muvid.visualize.reactive
Precomputed audio-reactivity: pulse a video filter in time with the music.
Because muvid renders from an audio file, not a live stream, the whole
loudness envelope is knowable up front. This module turns that envelope into an
ffmpeg sendcmd script that modulates a named filter (brightness/saturation)
frame by frame — a beat-reactive “flash” baked deterministically into the
render. No realtime, and no dependency beyond numpy and the ffmpeg
muvid.visualize already requires.
It is a general seam, not spectrum-specific: any visual can attach a flash to a
named filter in its chain (see flash_filter()). The vectorscope reacts to
the music through its own amplitude; the spectrogram uses this.
The whole chain degrades to nothing rather than to an error: a track that will
not decode, or an ffmpeg build without FLASH_FILTERS, yields an empty
fragment, so a visual can append it unconditionally.
- muvid.visualize.reactive.DEFAULT_FLASH_LABEL = 'flash'
Default
sendcmdlabel for the pulsingeq. Distinct per flash, so one filtergraph can carry several without their commands crossing.
- muvid.visualize.reactive.ENVELOPE_SR = 22050
Sample rate the envelope is measured at. Low is fine — we only need a per-frame loudness curve, not audio quality.
- muvid.visualize.reactive.FLASH_BRIGHTNESS = 0.25
Peak brightness boost at a full-strength pulse (ffmpeg
eqbrightness, -1..1). At rest the filter is a no-op; this is how far a beat pushes it.
- muvid.visualize.reactive.FLASH_DECAY = 0.5
Per-frame persistence of a pulse, 0 (no trail) to <1 (longer afterglow), so a beat flashes and fades rather than blinking for a single frame.
- muvid.visualize.reactive.FLASH_FILTERS = ('sendcmd', 'eq')
The ffmpeg filters a flash chain is built from. Both are core filters, but a stripped build can omit either — and the flash is a garnish, so a build that cannot do it should render the visual without the flash rather than fail.
- muvid.visualize.reactive.FLASH_SATURATION = 0.8
Peak saturation boost at a full-strength pulse, added to 1.0 (ffmpeg
eq).
- muvid.visualize.reactive.flash_filter(audio: str | Path, *, fps: int, duration: float | None, workdir: Path, label: str = 'flash', brightness: float = 0.25, saturation: float = 0.8, decay: float = 0.5) str[source]
A filter fragment that makes the stream it follows pulse with the beat.
Computes the envelope, writes the
sendcmdscript intoworkdir, and returns the chain,sendcmd=f=…,eq@<label>=…to append after the visual filter (e.g.showspectrum).Returns
""— a fragment that changes nothing — when the audio yields no envelope or this ffmpeg build lacksFLASH_FILTERS, so a caller can append it unconditionally and still render.The
eqstarts as a no-op (brightness=0:saturation=1); the script drives it.eval=frameso it re-reads every frame.- Parameters:
audio – The track whose beats drive the flash.
fps – The render’s frame rate (one command pair per frame).
duration – Clamp the flash to this many seconds (
None= whole track).workdir – Directory to write the
sendcmdscript into.label –
sendcmdlabel for this flash’seq.brightness – Peak brightness boost on a beat.
saturation – Peak saturation boost on a beat.
decay – Per-frame afterglow of a pulse.
- muvid.visualize.reactive.onset_envelope(audio: str | Path, *, fps: int, duration: float | None = None, sr: int = 22050, decay: float = 0.5) list[float][source]
Per-video-frame onset strength in
[0, 1], with phosphor-style decay.Decodes
audioto mono, measures frame-wise loudness, takes the half-wave-rectified rise in loudness (an onset/transient measure, so sustained loud passages don’t stay lit — only attacks do), scales it robustly to[0, 1], then lets each pulse fade bydecayper frame so a beat flashes and trails off rather than blinking for a single frame.- Parameters:
audio – The track to analyse.
fps – Video frame rate — one envelope value per frame.
duration – Clamp the envelope to this many seconds (defaults to the whole track).
sr – Analysis sample rate.
decay – Per-frame persistence of a pulse, 0 (no trail) to <1 (longer afterglow).
- Returns:
One value per frame. Empty if the audio could not be decoded.