muvid.visualize.visuals¶
Visual strategies: how the picture of an audio-driven video is produced.
A strategy is a function from a VisualContext (the audio, the optional
cover, the duration, the canvas) to a VisualPlan (the ffmpeg inputs
and filter chains that yield one video stream). muvid.visualize.video owns the
muxing, encoding, and loudness; a strategy only says what the frames look like.
That split is what keeps this open-closed: the built-ins are registered by name
("still", "ken_burns", "cqt", …), and anything else you can express
as a callable — a librosa/matplotlib animation, a projectM render, a shader —
plugs in through the same seam, either by returning a VisualPlan or by
returning the path of a silent video it rendered itself.
>>> sorted(list_visuals())
['bars', 'cqt', 'ken_burns', 'scope', 'spectrum', 'still', 'waves']
Conventions a strategy must honour:
ffmpeg input 0 is always the audio. Inputs a plan adds are numbered from 1, in the order they appear in
VisualPlan.inputs.To react to the audio, set
uses_audio=Trueand consume the[aviz]label — a dedicated copy of the audio, split off so the output track stays untouched.Emit exactly one video stream, labelled
VisualPlan.video.
Module Attributes
Over a reactive background the cover fills nearly the whole frame (with a little padding), and is made slightly transparent so the visualizer plays on through it rather than being hidden behind an opaque card. |
|
The vectorscope fills the whole frame, so it keeps the same big cover as the other visuals but a touch more transparent, letting the line-work read both through the cover and in the space around it. |
|
The vectorscope's plate is darker still, so its sparse line-work has the least competition of any visual. |
|
Default accent for the line/bar visualizers, applied as a tint. |
|
Reactive backgrounds are pushed dark and near-neutral so the teal accent reads (a bright, saturated blurred cover both tints everything its own colour and, under the screen blend, washes the accent out to white). |
|
context in, plan out. |
Functions
|
Frequency bars (the classic EQ look), via ffmpeg's |
|
Constant-Q transform bars — pitch-aligned, the most musical reactive look. |
|
A slow pan/zoom across the cover, lasting exactly as long as the song. |
The names of every registered visual strategy. |
|
|
Register a visual strategy under |
|
Turn |
|
The stereo Lissajous figure, via ffmpeg's |
|
A scrolling spectrogram, via ffmpeg's |
|
The cover, composed on a 16:9 canvas, held for the whole song. |
|
The waveform, via ffmpeg's |
Classes
|
Everything a visual strategy needs to know about the render. |
|
The ffmpeg fragments that render one strategy's video stream. |
- muvid.visualize.visuals.DEFAULT_TINT = 'colorchannelmixer=rr=0.16:gg=0.80:bb=0.85'¶
Default accent for the line/bar visualizers, applied as a tint. Several ffmpeg visualizers ignore their
colorsoption (showfreqsdraws white whatever you ask for), so rather than fight each filter we render them white and recolour the whole visualization here — one knob, identical accent across every method, so a whole album’s videos read as one release. It is acolorchannelmixermapping white (r=g=b) to a light teal; override per call with thetintoption (an empty string leaves the visualizer’s own colour).
- muvid.visualize.visuals.REACTIVE_BG_SATURATION = 0.18¶
Reactive backgrounds are pushed dark and near-neutral so the teal accent reads (a bright, saturated blurred cover both tints everything its own colour and, under the screen blend, washes the accent out to white). The sharp centred cover still carries the artwork’s colour; only the surround is muted.
The dim is MULTIPLICATIVE (muvid#70), so this value is not comparable with the additive
0.5it replaces — it is the constant that lands the plate’s mean display luma where the offset left it, measured over four photographs (as PNGs; on JPEG covers the OLD chain landed somewhere else, because an additive offset is range-dependent and a scaling is not — seecanvas._DIM_PIVOT).The washout this comment warns about was then measured rather than assumed: under
screen, out = P + V(1 - P), so the accent’s contribution is what a brighter plate would eat. Isolated by rendering the real composed graph twice, once with the visualizer branch removed — which underscreenleaves the plate alone — and differencing: over six covers (three photographs, each as PNG and as JPEG) the accent’s frame-mean added luma moves by at most 0.10/255. State the instrument with any number here: the accent is sparse, so a frame mean is small and a mean over the visualizer’s own pixels is not, and only the ratio is portable. It still reads.
- muvid.visualize.visuals.REACTIVE_COVER_FRACTION = 0.95¶
Over a reactive background the cover fills nearly the whole frame (with a little padding), and is made slightly transparent so the visualizer plays on through it rather than being hidden behind an opaque card.
- muvid.visualize.visuals.SCOPE_BG_DIM = 0.965¶
The vectorscope’s plate is darker still, so its sparse line-work has the least competition of any visual. Named rather than spelled at the call site for the same reason
spectrum_visual’s is: muvid#70 named three constants to retune and there were four sites, the fourth being a bare literal nothing pointed at. Multiplicative like the other two, and measured the same way — the value that lands the plate where the additive0.55left it.
- muvid.visualize.visuals.SCOPE_COVER_ALPHA = 0.72¶
The vectorscope fills the whole frame, so it keeps the same big cover as the other visuals but a touch more transparent, letting the line-work read both through the cover and in the space around it.
- muvid.visualize.visuals.Visual¶
context in, plan out. Returning a path to an already-rendered silent video is also accepted (see
resolve_visual()).- Type:
A strategy
alias of
Callable[[VisualContext],VisualPlan | Path | str]
- class muvid.visualize.visuals.VisualContext(audio, image, duration, size, fps, layout=<factory>, title=None, title_style=None, workdir=<factory>, options=<factory>)[source]¶
Bases:
objectEverything a visual strategy needs to know about the render.
- audio¶
The audio file (ffmpeg input 0).
- image¶
The cover art, if the caller supplied one.
- duration¶
Audio duration in seconds.
- size¶
Canvas size (width, height).
- fps¶
Output frame rate.
- layout¶
How the cover sits on the canvas.
- title¶
Title to burn in, if any.
- title_style¶
How to draw that title.
- workdir¶
A directory the strategy may write intermediate files into.
- options¶
Strategy-specific knobs, passed straight through by the caller.
- require_image(visual)[source]¶
The cover image, or a
ValueErrornaming what to do instead.- Return type:
- class muvid.visualize.visuals.VisualPlan(inputs=<factory>, filters=<factory>, video='vbg', uses_audio=False, has_cover=False, has_title=False, still=None)[source]¶
Bases:
objectThe ffmpeg fragments that render one strategy’s video stream.
- inputs¶
Extra ffmpeg input argument groups (each ends with
-i PATH), numbered from input 1.
- filters¶
filter_complexchains, joined with;by the renderer.
- video¶
Label of the video stream the chains emit.
- uses_audio¶
The plan consumes the
[aviz]audio copy.
- has_cover¶
The plan already placed the cover; the renderer must not overlay it again.
- has_title¶
The plan already burnt in the title; the renderer must not draw it again.
- still¶
When set, the video is this static image — the renderer takes a much cheaper path (encode one short segment, then loop it) and ignores
inputs/filters.
- muvid.visualize.visuals.bars_visual(ctx)[source]¶
Frequency bars (the classic EQ look), via ffmpeg’s
showfreqs.Rendered white; colour comes from the accent
tint(seeDEFAULT_TINT).- Return type:
- muvid.visualize.visuals.cqt_visual(ctx)[source]¶
Constant-Q transform bars — pitch-aligned, the most musical reactive look.
Rendered white; colour comes from the accent
tint(recolour it with thetintoption — not a per-filter colour, which the tint would multiply).showcqtcan draw two panes: the bargraph, and beneath it a sonogram that keeps every past frame and scrolls it downward, so the music leaves a trail of where it has been. The sonogram is off by default (bars only, the cleanest read). Givesono_fractiona value in (0, 1) to hand that share of the frame’s height to the trail — the bargraph keeps the rest.- Return type:
- Options:
sono_fraction: share of the height given to the scrolling sonogram,0 (default, bars only) to just under 1.
sono_v/bar_v: sonogram and bargraph volume (sensitivity).sono_g/bar_g: sonogram and bargraph gamma (contrast). Plus the shared background/cover keys of_reactive_plan().
Examples
>>> ctx = VisualContext(Path("a.wav"), None, 10.0, (1920, 1080), 24) >>> "sono_h=0" in cqt_visual(ctx).filters[0] # bars only, by default True >>> trail = replace(ctx, options={"sono_fraction": 0.6}) >>> f = cqt_visual(trail).filters[0] >>> "bar_h=432" in f and "sono_h=648" in f # 40% bars / 60% trail True
- muvid.visualize.visuals.ken_burns_visual(ctx)[source]¶
A slow pan/zoom across the cover, lasting exactly as long as the song.
Renders through the
burnspackage (viamixing.video). By default it pans across the composed canvas rather than the raw cover, so a square or portrait image still fills a 16:9 frame instead of being letterboxed by the pan.Frames are rendered in Python (Pillow), so this is by far the slowest visual — budget several times the song’s duration. The ffmpeg-native visuals are an order of magnitude faster.
- Return type:
- Options:
source:"canvas"(default) or"image"— what to pan across.
- muvid.visualize.visuals.register_visual(name)[source]¶
Register a visual strategy under
name(the open-closed seam).- Return type:
Callable[[Callable[[VisualContext],VisualPlan|Path|str]],Callable[[VisualContext],VisualPlan|Path|str]]
Examples
>>> @register_visual("black") ... def _black(ctx): ... w, h = ctx.size ... return VisualPlan(filters=[f"color=c=black:s={w}x{h}[vbg]"]) >>> "black" in list_visuals() True >>> _ = _VISUALS.pop("black") # (keep the registry tidy for the next doctest)
- muvid.visualize.visuals.resolve_visual(visual, ctx)[source]¶
Turn
visual(a name, or any callable) into aVisualPlan."auto"picks the cheapest strategy that suits the inputs: a still cover when there is an image, an audio-reactive CQT when there is not.A callable may return a
VisualPlan, or the path of a silent video it rendered itself — the latter is the escape hatch for backends that do not express themselves as an ffmpeg filtergraph (librosa/matplotlib, projectM, a headless-browser capture…).- Raises:
ValueError –
visualnames a strategy that is not registered.- Return type:
- muvid.visualize.visuals.scope_visual(ctx)[source]¶
The stereo Lissajous figure, via ffmpeg’s
avectorscope.Tuned for drama and dynamics. Anti-aliased lines,
mirror=xyto fill all four quadrants symmetrically, and a generouszoomso the figure spills into the space around the cover. Crucially the amplitude scale is linear, notsqrt: a compressive scale keeps a steady mix pinned wide the whole time (a constant scribble), whereas linear lets the figure breathe — wide and full when the music swells, small and calm when it settles. The cover stays the same big size as the other visuals, a touch more transparent so the line-work reads through it too.Options:
zoom,scale(lin/sqrt/cbrt/log),mirror(none/x/y/xy),draw, plus the shared background/cover keys.- Return type:
- muvid.visualize.visuals.spectrum_visual(ctx)[source]¶
A scrolling spectrogram, via ffmpeg’s
showspectrum.A
logfrequency axis spreads out the low-mid range where music lives (a linear axis crams it into the bottom edge), lifted gain makes the detail read, andoverlapquickens the right-to-left scroll. Rendered over a darker background so it stands out. The defaultgreencolormap is tinted to the teal accent; pass acolorto use ffmpeg’s colormap instead.The whole spectrogram also pulses with the beat: a precomputed onset envelope (see
muvid.visualize.reactive) drives asendcmd-controlled brightness/saturation flash, so attacks in the music read as flashes at the live leading edge instead of the display feeling merely synced to playback. The flash is appended last, after the recolour, so it modulates the colours the frame actually shows.Options:
color(colormap — the teal tint applies only to the default),gain,saturation,overlap(scroll speed, 0–1),flash(bool, default on),flash_brightness,flash_saturation, plus the shared background/cover keys.- Return type:
- muvid.visualize.visuals.still_visual(ctx)[source]¶
The cover, composed on a 16:9 canvas, held for the whole song.
- Return type: