VisGen — Audio Visualizer Generator
Generate beautiful, customizable audio visualizer videos with Python. FFT-reactive circular bars, multiple visualizers, rich overlays, post-processing effects, and a plugin system — all composable on a single canvas.
Features
| Feature | Description |
|---|---|
| Circular FFT Visualizer | Animated bars reacting to audio frequencies around a circular image |
| Multiple Visualizers | Render several visualizers on the same canvas via render_multi() |
| Background Effects | Solid colors, images with chained effects (blur, grayscale, sepia), opacity, fit modes |
| Overlays | Text (static & timed), images, and video overlays with time ranges, opacity, anchoring |
| Positioning | Named positions (center, top-left, bottom-right...) or exact (x, y) pixels |
| Frame Effects | Post-processing plugins: glow, vignette, shake, zoom pulse |
| Bar Effects | Pre-processing plugins: bounce, mirror, smooth decay |
| Plugin System | Drop .py files in ~/.visgen/plugins/ or use entry points |
| Quad Modes | One-circle repeated-bar quadrants with single or multi-color schemes |
| Quality Output | H.264 + AAC via FFmpeg |
Installation
Requirements: Python 3.10+, FFmpeg in PATH
From PyPI:
pip install visgen
Or with uv:
uv add visgen
For local development:
git clone https://github.com/yourname/visgen.git
cd visgen
pip install -e ".[dev]"
Runtime dependencies: numpy, opencv-python, Pillow
Quick Start
from visgen import AudioVisualizerVideo
viz = AudioVisualizerVideo(
audio_path="song.wav",
image_path="cover.jpg",
output_path="output.mp4",
)
viz.render_single()
API Reference
AudioVisualizerVideo
AudioVisualizerVideo(
audio_path: str,
image_path: str | None = None, # used if visualizers not given
output_path: str = "output.mp4",
duration: float | None = None, # default = full audio length
fps: int = 30,
circle_radius: int = 150,
bar_count: int = 64,
bar_max_length: int = 200,
colors: ColorScheme | None = None,
smooth_factor: float = 0.3,
background: Background | None = None,
visualizers: list[CircularVisualizer] | None = None,
frame_effects: list[FrameEffect] | None = None,
bar_effects: list[BarEffect] | None = None,
)
Note: Either
image_pathorvisualizersmust be provided.
Render Methods
| Method | Description |
|---|---|
render_single(position=..., overlays=..., visualizer=...) |
One visualizer on canvas |
render_multi(configs=[(viz, pos, angle), ...]) |
Multiple visualizers, same canvas, same audio |
render_quad_repeated_bars(...) |
One circle, full bars repeated per quadrant |
render_quad_repeated_bars_multi_color(...) |
Same with per-quadrant colors |
Backgrounds
from visgen import ColorBackground, ImageBackground
# Solid color
ColorBackground(color=(15, 15, 30))
# Image with single effect
ImageBackground("bg.jpg", effect="blur", blur_radius=10.0, fit_mode="cover")
# Image with chained effects (applied in order)
ImageBackground("bg.jpg", effect=["blur", "grayscale"], blur_radius=12.0)
# Image with opacity
ImageBackground("bg.jpg", effect="sepia", opacity=0.6, fit_mode="cover")
Fit modes: cover | contain | stretch
Effects: blur | grayscale | sepia
Overlays
from visgen import TextOverlay, TimedText, ImageOverlay, VideoOverlay
# Static text
TextOverlay(text="Title", position=(540, 80), font_size=60, anchor="mm")
# Timed lyrics
TimedText(start_time=2.0, end_time=5.0, text="Lyrics", position=(540, 980))
# Static image
ImageOverlay("logo.png", position=(100, 100), size=(120, 120), anchor="mm")
# Timed image
ImageOverlay("badge.png", position=(500, 500), start_time=1.0, end_time=6.0)
# Looping video
VideoOverlay("loop.mp4", position=(900, 900), size=(200, 200), loop=True)
Anchors: mm (center) | lt (top-left) | rb (bottom-right) | lb | rt
Positioning
viz.render_single(position="center") # named position
viz.render_single(position="bottom-right") # corner
viz.render_single(position=(300, 800)) # exact pixels
Named positions: center, top-left, top-right, bottom-left, bottom-right, top-center, bottom-center, left-center, right-center
Frame Effects (Post-Processing)
Applied to the final frame before encoding.
from visgen import GlowEffect, VignetteEffect, ShakeEffect, ZoomPulseEffect
viz = AudioVisualizerVideo(
audio_path="song.wav",
image_path="cover.jpg",
output_path="out.mp4",
frame_effects=[
GlowEffect(strength=0.4, radius=10.0),
VignetteEffect(strength=0.5),
],
)
viz.render_single()
Bar Effects (Pre-Processing)
Modify FFT bar values before rendering.
from visgen import BounceEffect, SmoothDecayEffect
viz = AudioVisualizerVideo(
audio_path="song.wav",
image_path="cover.jpg",
output_path="out.mp4",
bar_effects=[
BounceEffect(speed=0.15, amplitude=0.2),
SmoothDecayEffect(decay=0.9),
],
)
viz.render_single()
Examples
Multiple visualizers side by side
from visgen import AudioVisualizerVideo, CircularVisualizer, ColorScheme
left = CircularVisualizer("cover1.jpg", 140, 64, 180, colors=ColorScheme(bar=(0, 200, 255)))
right = CircularVisualizer("cover2.jpg", 140, 64, 180, colors=ColorScheme(bar=(255, 100, 100)))
video = AudioVisualizerVideo(
audio_path="song.wav",
output_path="duo.mp4",
visualizers=[left, right],
)
video.render_multi([
(left, (300, 540), 0),
(right, (780, 540), np.pi),
])
Custom inline effects
from visgen import FrameEffect, BarEffect
import numpy as np
from PIL import Image
class GreenTint(FrameEffect):
def apply(self, frame, frame_idx, bar_values=None):
arr = np.array(frame, dtype=np.float32)
arr[:, :, 1] *= 1.15
return Image.fromarray(np.clip(arr, 0, 255).astype(np.uint8))
class SquashBars(BarEffect):
def process(self, bar_values, frame_idx, audio_chunk=None):
return np.power(bar_values, 1.5)
viz = AudioVisualizerVideo(
audio_path="song.wav",
image_path="cover.jpg",
output_path="out.mp4",
frame_effects=[GreenTint()],
bar_effects=[SquashBars()],
)
viz.render_single()
Plugin discovery
from visgen.plugins import load_plugins, registry
# Auto-discovers Plugin subclasses in ~/.visgen/plugins/
load_plugins()
# Inspect registered extensions
print(registry.frame_effects)
print(registry.bar_effects)
# Instantiate by name
effect = registry.create_frame_effect("GlowEffect", strength=0.5)
Running the Examples
python -m visgen.main
src/visgen/main.py contains 38 example functions. Control which run by editing the EXAMPLES list at the bottom of the file:
EXAMPLES = [
example_01_minimal,
example_04_image_bg_blur,
example_31_frame_effects,
]
File Structure
src/visgen/
├── __init__.py # Public exports
├── main.py # 38 runnable example functions
├── audio.py # AudioProcessor (FFmpeg extraction + FFT)
├── background.py # Background ABC, ColorBackground, ImageBackground
├── colors.py # ColorScheme dataclass
├── overlay.py # TextOverlay, TimedText, ImageOverlay, VideoOverlay
├── renderer.py # AudioVisualizerVideo (main composer)
├── utils.py # Font loader & position resolver
├── visualizer.py # CircularVisualizer
├── bars/ # Bar-value pre-processing effects
│ ├── base.py # BarEffect ABC
│ ├── builtin.py # BounceEffect, MirrorEffect, SmoothDecayEffect
│ └── __init__.py
├── effects/ # Frame post-processing effects
│ ├── base.py # FrameEffect ABC
│ ├── builtin.py # GlowEffect, VignetteEffect, ShakeEffect, ZoomPulseEffect
│ └── __init__.py
└── plugins/ # Plugin discovery & registry
├── base.py # Plugin, PluginMeta, PluginRegistry
├── loader.py # discover_plugins(), load_plugins()
└── __init__.py
License
MIT
Metadata
Release files for visgen 0.2.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| visgen-0.2.2.tar.gz | 15.6 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| visgen-0.2.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 38.0 kB
Release files / visgen-0.2.2.tar.gz
| Download URL | visgen-0.2.2.tar.gz |
|---|---|
| Size | 15.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
67f3d0e90afd1f6b9367968edf7112a8385ad49ecf72ac560fe981465a11f98f
|
|
BLAKE2b-256 checksum How to use checksums |
e57dcbcb688489a7433d5cb70ce46dc873f68ac8d934bb1e3971c1bf5b9000fd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.11.13 {"installer":{"name":"uv","version":"0.11.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / visgen-0.2.2-py3-none-any.whl
| Download URL | visgen-0.2.2-py3-none-any.whl |
|---|---|
| Size | 22.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a6e6656aebdc03732eae7fbadbc55fe7eb7dd75ecc576d07c816feda7cdbaa7c
|
|
BLAKE2b-256 checksum How to use checksums |
fc331936bf74de1fa1fe733d90b6ef8195088ff4328e12f4f7f6664e316fc832
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.11.13 {"installer":{"name":"uv","version":"0.11.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for visgen 0.2.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.