Metadata-Version: 2.4
Name: musicvisualizer
Version: 2.1.0
Summary: A plugin-based audio visualization tool that converts audio files into animated visualizer videos
Author-email: fredjt <61065187+fredjt@users.noreply.github.com>
License: MIT
Keywords: audio,visualizer,ffmpeg,music,video
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Multimedia :: Sound/Audio
Classifier: Topic :: Multimedia :: Video
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: tqdm
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0; extra == "dev"
Requires-Dist: ruff>=0.5; extra == "dev"
Requires-Dist: pyright>=1.1; extra == "dev"

# Audio Visualizer

A plugin-based Python tool for converting audio files into animated visualizer videos.

## Features

- **Plugin Architecture**: Easily create, add, or remove visualizer plugins
- **Multiple Visualizer Modes**: Choose from several visualization styles
- **Hardware Acceleration**: NVENC (NVIDIA), QSV (Intel), VA-API (Linux) support
- **Multiple Output Formats**: MP4, MKV, WebM, AVI, MOV, FLV
- **Batch Processing**: Process entire directories of audio files
- **Progress Tracking**: Real-time progress bar with ETA
- **Customizable**: Adjust resolution, FPS, preset, CRF, and more

## Available Visualizers

- **Spectrum** (default) - Classic frequency spectrum bars
- **Waveform** - Audio waveform visualization
- **Bars** - Animated bar visualization
- **Radial** - Radial/circular spectrum display
- **Spectrogram** - Time-frequency spectrogram
- **Vectorscope** - Psychedelic circular visualization
- **Album Art** - Visualizer with album art overlay

## Requirements

- Python 3.10+
- [FFmpeg](https://ffmpeg.org/) - Required for video encoding

## Installation

1. Clone the repository:
   ```bash
   git clone <repository-url>
   cd audio-visualizer
   ```

2. Install FFmpeg:
   - **Ubuntu/Debian**: `sudo apt install ffmpeg`
   - **macOS**: `brew install ffmpeg`
   - **Windows**: Download from [ffmpeg.org](https://ffmpeg.org/download.html)

3. Install the package:
   ```bash
   pip install .
   ```

## Usage

### Basic Usage

```bash
audio-visualizer input.mp3
```

### Specify a Visualizer Mode

```bash
audio-visualizer input.mp3 --mode waveform
audio-visualizer input.mp3 --mode radial

# Compose several visualizers side by side in one frame
audio-visualizer input.mp3 --mode spectrum waveform
```

Combo presets are available as a single `--mode` value — `--mode spectrum+waveform` is equivalent to `--mode spectrum waveform` — and `--list-plugins` shows the built-in presets under `[combos]`. Any combination of loaded modes works; unknown or duplicated names fail with the standard mode error.

Shared options (background, colors, border, codec settings) are resolved once for all selected modes. If two modes declare an option with the same name, the first mode's value (including its default) wins and a warning is printed; there is no per-mode option syntax in v2.1.0.

### Custom Output

```bash
audio-visualizer input.mp3 -o output.mp4 --resolution 1920x1080
```

### Hardware Acceleration

```bash
# Auto-detect (default)
audio-visualizer input.mp3

# Force NVIDIA encoding
audio-visualizer input.mp3 --accel nvenc

# Force Intel Quick Sync
audio-visualizer input.mp3 --accel qsv

# Force Linux VA-API
audio-visualizer input.mp3 --accel vaapi

# Disable hardware acceleration
audio-visualizer input.mp3 --accel disabled
```

### Output Format

```bash
# MP4 (default)
audio-visualizer input.mp3

# MKV
audio-visualizer input.mp3 --format mkv

# WebM (VP9)
audio-visualizer input.mp3 -f webm

# AVI
audio-visualizer input.mp3 -f avi
```

### Color Themes

`--theme` applies a built-in color palette to the visualizer; `--list-themes`
shows the available themes (with color swatches when the output is a tty).

<!-- THEME-TABLE:BEGIN (generated by scripts/generate_flag_reference.py - do not edit by hand) -->
| Theme | Palette |
| --- | --- |
| `neon` | lime,magenta,cyan,yellow |
| `sunset` | red,orange,yellow,white |
| `ocean` | blue,cyan,teal,white |
| `forest` | green,lime,yellow,white |
| `dark` | purple,blue,gray,white |
| `light` | white,gray,blue,green |
<!-- THEME-TABLE:END -->

Precedence: an explicit `--colors` always wins; otherwise CLI `--theme` >
`AUDIO_VISUALIZER_THEME` env var > `theme` in the config file.

### Batch Processing

Process all audio files in a directory:

```bash
# Process all audio files in a directory
audio-visualizer ./my-album/ --batch

# Short form
audio-visualizer ./my-album/ -b

# With custom settings
audio-visualizer ./my-album/ -b --mode waveform --resolution 1920x1080

# Parallel encode (N ffmpeg jobs; 'auto' = CPU count)
audio-visualizer ./my-album/ -b --parallel auto

# Recursive scan + glob filters (both repeatable)
audio-visualizer ./music/ -b --recursive --include "*.mp3" --exclude "*lossless*"

# Custom output naming - placeholders: {name} {ext} {mode} {width}
audio-visualizer ./my-album/ -b --output-template "{name}_{mode}.{ext}"
```

Discovery and naming:

- `--recursive` - scan subdirectories as well
- `--include PATTERN` / `--exclude PATTERN` - repeatable globs matched
  against the path relative to the batch directory; the built-in audio
  extension list is the default include
- `--output-template TEMPLATE` (default `{name}_visualizer.{ext}`)

Every batch run writes `<outdir>/batch_report.json` with per-file status,
output path, size, duration, failure reason, and timestamp. The exit code is
non-zero only when at least one file failed (clean run -> 0, failures -> 1),
so CI can gate on the batch exit code.

Supported formats: MP3, WAV, FLAC, OGG, M4A, AAC, WMA

### Environment Variables

All configuration options can be set via environment variables with the `AUDIO_VISUALIZER_` prefix. This is useful for Docker, CI/CD, or shell profiles.

```bash
# Set resolution and FPS via environment
export AUDIO_VISUALIZER_RESOLUTION=1920x1080
export AUDIO_VISUALIZER_FPS=60
export AUDIO_VISUALIZER_PRESET=fast

audio-visualizer input.mp3
```

Supported environment variables:

| Variable | Description | Example |
|---|---|---|
| `AUDIO_VISUALIZER_RESOLUTION` | Output resolution | `1920x1080` |
| `AUDIO_VISUALIZER_FORMAT` | Output format | `mkv` |
| `AUDIO_VISUALIZER_FPS` | Frame rate | `60` |
| `AUDIO_VISUALIZER_PRESET` | Encoding preset | `fast` |
| `AUDIO_VISUALIZER_CRF` | Quality (0-51) | `18` |
| `AUDIO_VISUALIZER_ACCEL` | Hardware acceleration | `nvenc` |
| `AUDIO_VISUALIZER_MODE` | Visualizer mode | `waveform` |
| `AUDIO_VISUALIZER_COLORS` | Custom colors | `red,blue,green` |
| `AUDIO_VISUALIZER_THEME` | Color theme | `neon` |
| `AUDIO_VISUALIZER_OUTPUT_DIR` | Output directory | `/path/to/output` |

**Precedence**: CLI arguments > environment variables > config file > defaults.

### Configuration File

Options can be persisted to `~/.audio_visualizer/config.yml`. Use `--save-config` to write the current options and `--config` to display the file:

```bash
audio-visualizer input.mp3 --save-config
audio-visualizer --config
```

Per-plugin options can be set in indented sections. Values are validated against each plugin's option schema (type, choices, range):

```yaml
# Global options
resolution: 1920x1080
fps: 60

# Per-plugin options
spectrum:
  mode: rainbow
  opacity: 0.8
waveform:
  mode: line
  splits: 2
```

Unknown plugins/options and values that fail validation are skipped with a warning, so a broken config file never crashes the tool.

### List Available Plugins

```bash
audio-visualizer --list-plugins
```

### Install Custom Plugins

```bash
audio-visualizer --install-plugin my_plugin.py
```

Installs the plugin to `~/.audio_visualizer/plugins`, where it is
auto-discovered on every run.

### Load External Plugins

Plugins can be loaded without installing, either for one run or for all
runs:

```bash
# From the command line (repeatable, file or directory)
audio-visualizer --plugin-path ~/plugins/my_plugin.py --mode myviz input.mp3
audio-visualizer --plugin-path ~/plugins/ --list-plugins

# For all runs, via environment variable (path-separated list)
export AUDIO_VISUALIZER_PLUGIN_PATH=~/plugins/my_plugin.py:~/plugins/other/
```

Third-party distributions can ship plugins too: register them under the
`audio-visualizer.plugins` entry-point group (see *Creating a New Visualizer
Plugin*). Installed plugins and `--plugin-path` plugins shadow built-ins of
the same name, with a warning; `--list-plugins --verbose` shows each
plugin's source (builtin, entrypoint, installed, or path).

### Full Usage

```bash
audio-visualizer input.mp3 [options]
audio-visualizer ./directory/ --batch [options]
```

All options can also be set via environment variables (e.g.,
`AUDIO_VISUALIZER_RESOLUTION=1920x1080`) or in the config file (see
[Configuration File](#configuration-file)).

The tables below are generated from the live CLI parser by
[`scripts/generate_flag_reference.py`](scripts/generate_flag_reference.py) -
run `python scripts/generate_flag_reference.py` after changing any flag (CI
runs `--check` and fails when the tables drift from the parser).

<!-- FLAG-REF:BEGIN (generated by scripts/generate_flag_reference.py - do not edit by hand) -->
#### Positional arguments

| Flag | Takes | Default | Description |
| --- | --- | --- | --- |
| `input` | str | — | Input MP3 (or audio) file |

#### Global options

| Flag | Takes | Default | Description |
| --- | --- | --- | --- |
| -h`, `--help | flag (no value) | — | show this help message and exit |
| -o`, `--output | str | — | Output file (single) or output directory (batch mode) |
| --mode | str (one or more) | ['spectrum'] | Visualization mode/plugin name(s); 2+ are composed into one frame (default: spectrum); combos like 'spectrum+waveform' also work |
| --compose | one of: hstack, vstack, tile, overlay | 'hstack' | Layout for multi-mode composites (default: hstack) |
| --compose-scale | one of: fill, fit | 'fill' | Cell scaling for multi-mode composites: fill stretches, fit preserves aspect ratio (default: fill) |
| --resolution`, `-r | str | '1280x720' | Output resolution WxH or preset 360p/480p/720p/1080p/1440p/4k (default: 1280x720) |
| --fps | int | 30 | Frame rate (default: 30) |
| --preset | one of: ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow | 'medium' | Encoding preset (default: medium) |
| --crf | one of: 0-51 | 23 | Quality (18=lossless, 23=default, 28=max). Lower=better (default: 23) |
| --verbose`, `-v | flag (no value) | False | Show detailed filter and command info |
| --quiet`, `-q | flag (no value) | False | Suppress non-error output |
| --log-file | str | — | Also write log output (debug level) to PATH |
| --debug`, `-d | flag (no value) | False | Show debug-level output |
| --list-plugins | flag (no value) | False | List all available visualizer plugins |
| --category | one of: composite, meter, spectrum, waveform | — | Filter --list-plugins by plugin category |
| --install-plugin | str | — | Install a plugin from a Python file |
| --plugin-path | str | [] | Load external plugin(s) from a .py file or directory (repeatable; also AUDIO_VISUALIZER_PLUGIN_PATH, path-separated) |
| --image | str | — | Image file for image visualizer plugin (album art) |
| --border | flag (no value) | False | Add a colored border around the visualization |
| --border-color | str | 'ffffff' | Border color hex (default: ffffff) |
| --border-width | int | 2 | Border width in pixels (default: 2) |
| --aspect-ratio | one of: 16:9, 4:3, 1:1, 21:9 | — | Aspect ratio (auto-calculates resolution). Overrides --resolution |
| --visualizer-config | str | — | Save/load per-visualizer preset configuration file |
| --format`, `-f | one of: mp4, mkv, webm, avi, mov, flv | 'mp4' | Output format (default: mp4) |
| --accel | one of: auto, nvenc, qsv, vaapi, disabled | 'auto' | Hardware acceleration (default: auto) |
| --batch`, `-b | str | — | Process all audio files in a directory |
| --overwrite | flag (no value) | False | Overwrite existing output files in batch mode |
| --parallel`, `-j | str | '1' | Run N ffmpeg encodes concurrently in batch mode (default: 1 = sequential; 'auto' = CPU count) |
| --output-template | str | '{name}_visualizer.{ext}' | Batch output filename template with placeholders {name} {ext} {mode} {width} (default: '{name}_visualizer.{ext}') |
| --recursive | flag (no value) | False | Batch: also scan subdirectories |
| --include | str | [] | Batch: only process files whose path (relative to the batch dir) matches this glob (repeatable; default: built-in audio extensions) |
| --exclude | str | [] | Batch: skip files whose path matches this glob (repeatable) |
| --colors | str | — | Comma-separated colors for visualizer (e.g., 'blue,green,white') |
| --bg-color | str | — | Background color (default: black). Accepts named colors or hex (e.g., 'navy', '#1a1a2e') |
| --title | str | — | Set the output video's title (overrides the source file's tag) |
| --artist | str | — | Set the output video's artist (overrides the source file's tag) |
| --album | str | — | Set the output video's album (overrides the source file's tag) |
| --comment | str | — | Set the output video's comment (overrides the source file's tag) |
| --no-metadata | flag (no value) | False | Strip all metadata tags from the output video |
| --theme | one of: neon, sunset, ocean, forest, dark, light | — | Predefined color theme |
| --list-themes | flag (no value) | False | List available color themes and exit |
| --config | flag (no value) | False | Show current configuration file path |
| --save-config | flag (no value) | False | Save current options to configuration file |
| --validate-config | flag (no value) | False | Validate configuration and exit |
| --validate | flag (no value) | False | Validate all installed plugins and exit |
| --dry-run | flag (no value) | False | Show what would be done without encoding |
| --json | flag (no value) | False | With --dry-run: print machine-readable JSON instead of text |
| --version | flag (no value) | False | Print package and ffmpeg versions and exit |
| --info | str | — | Show detailed audio file information and exit |
| --retry`, `-R | int | 0 | Number of retries on ffmpeg failure (default: 0) |

#### [composite] Album Art Visualizer options

| Flag | Takes | Default | Description |
| --- | --- | --- | --- |
| --albumart-effect | one of: glow, pulse, zoom | 'glow' | Background effect (glow, pulse, zoom) |
| --albumart-position | one of: bottom, top, left, right, fullscreen | 'bottom' | Spectrum overlay position (bottom, top, left, right, fullscreen) |
| --albumart-glow-intensity | float | 0.08 | Glow intensity (0.0-1.0) |
| --albumart-fit | one of: cover, contain, stretch | 'cover' | Image fit mode (cover, contain, stretch) |
| --albumart-blur | int | 0 | Gaussian blur radius (0-50) |

#### [meter] Volume Meter options

| Flag | Takes | Default | Description |
| --- | --- | --- | --- |
| --showvolume-mode | one of: peak, rms | 'peak' | Volume mode (peak, rms) |
| --showvolume-orientation | one of: h, v | 'h' | Orientation (h, v) |
| --showvolume-scale | one of: lin, log | 'lin' | Display scale (lin, log) |

#### [meter] Psychedelic Vectorscope options

| Flag | Takes | Default | Description |
| --- | --- | --- | --- |
| --vectorscope-mode | one of: frame, line, dot | 'frame' | Draw mode (frame, line, dot) |
| --vectorscope-scale | one of: lin, sqrt, cbrt, log | 'cbrt' | Amplitude scale (lin, sqrt, cbrt, log) |
| --vectorscope-zoom | float | 1.0 | Zoom level (0.1-10.0) |
| --vectorscope-trails | int | 100 | Trail persistence (0-255) |
| --vectorscope-framerate | int | 0 | Custom frame rate (0 = auto) |
| --vectorscope-border | flag (no value) | False | Enable border around scope |
| --vectorscope-color-index | int | 0 | Palette color index |

#### [spectrum] Classic Bars options

| Flag | Takes | Default | Description |
| --- | --- | --- | --- |
| --bar-spacing | float | 0.5 | Bar spacing (0.0-1.0) |
| --bar-orientation | one of: horizontal, vertical | 'horizontal' | Bar orientation (horizontal, vertical) |

#### [spectrum] Circular Spectrum options

| Flag | Takes | Default | Description |
| --- | --- | --- | --- |
| --circular-spectrum-mode | one of: channel, intensity, rainbow, moreland, nebulae, fire, fiery, fruit, cool, magma, green, viridis, plasma, cividis, terrain, colormapping | 'rainbow' | Colormap mode (rainbow, fire, magma, viridis, etc.) |
| --circular-spectrum-opacity | float | 1.0 | Spectrum opacity (0.0-1.0) |

#### [spectrum] CQT Spectrum options

| Flag | Takes | Default | Description |
| --- | --- | --- | --- |
| --cqt-gamma | float | 3.0 | Sonogram gamma (1.0-7.0) |
| --cqt-basefreq | float | 20.0 | Base frequency in Hz |
| --cqt-endfreq | float | 20495.0 | End frequency in Hz |
| --cqt-attack | float | 0.0 | Attack time (0.0-1.0) |
| --cqt-timeclamp | float | 0.17 | Timeclamp value (0.002-1.0) |

#### [spectrum] Peak-Hold Bars options

| Flag | Takes | Default | Description |
| --- | --- | --- | --- |
| --peakbars-hold | float | 0.1 | Seconds each peak is held at full height (0.0-2.0) |
| --peakbars-decay | float | 0.6 | Seconds the held peak takes to decay away (0.0-2.0) |

#### [spectrum] Mirrored Spectrum options

| Flag | Takes | Default | Description |
| --- | --- | --- | --- |
| --mirror-axis | one of: x, y, xy | 'y' | Mirror axis (x, y, xy) |
| --mirror-gap | int | 0 | Gap pixels between original and mirrored halves |
| --mirror-opacity | float | 1.0 | Opacity of mirrored half (0.0-1.0) |

#### [spectrum] Showfreqs options

| Flag | Takes | Default | Description |
| --- | --- | --- | --- |
| --showfreqs-mode | one of: bar, line, dot | 'bar' | Display mode (bar, line, dot) |
| --showfreqs-ascale | one of: lin, sqrt, cbrt, log | 'log' | Amplitude scale (lin, sqrt, cbrt, log) |
| --showfreqs-fscale | one of: lin, log, rlog | 'log' | Frequency scale (lin, log, rlog) |
| --showfreqs-winfunc | one of: rect, bartlett, hann, hanning, hamming, blackman, welch, flattop, bharris, bnuttall, bhann, sine, nuttall, lanczos, gauss, tukey, dolph, cauchy, parzen, poisson, bohman, kaiser | 'hann' | Window function (hann, hamming, blackman, etc.) |
| --showfreqs-overlap | float | 0.5 | Window overlap (0.0-1.0) |
| --showfreqs-channels | str | 'all' | Channels to draw (all, 0, 1, etc.) |

#### [spectrum] Spectrogram Waterfall options

| Flag | Takes | Default | Description |
| --- | --- | --- | --- |
| --spectrogram-mode | one of: intensity, rain, fire, heat, ice, swirl | 'intensity' | Colormap mode (intensity, rain, fire, heat, ice, swirl) |
| --spectrogram-speed | float | 0.95 | Scroll speed (0.0-1.0, higher = slower) |
| --spectrogram-opacity | float | 1.0 | Waterfall opacity (0.0-1.0) |
| --spectrogram-transpose | flag (no value) | False | Rotate spectrogram 90 degrees |

#### [spectrum] Color Spectrum options

| Flag | Takes | Default | Description |
| --- | --- | --- | --- |
| --spectrum-mode | one of: channel, intensity, rainbow, moreland, nebulae, fire, fiery, fruit, cool, magma, green, viridis, plasma, cividis, terrain, colormapping | 'rainbow' | Colormap mode (rainbow, fire, magma, viridis, etc.) |
| --spectrum-style | one of: replace, scroll, fill | 'scroll' | Slide mode (replace, scroll, fill) |
| --spectrum-opacity | float | 1.0 | Spectrum opacity (0.0-1.0) |
| --spectrum-start | float | 0 | Start frequency for display range (Hz) |
| --spectrum-stop | float | 0 | Stop frequency for display range (Hz) |

#### [waveform] Oscilloscope options

| Flag | Takes | Default | Description |
| --- | --- | --- | --- |
| --oscilloscope-persist | float | 0.5 | Seconds of trace persistence/afterglow (0.0 disables) |
| --oscilloscope-scale | one of: lin, sqrt, cbrt, log, dbl, pwr | 'cbrt' | Amplitude scale (lin, sqrt, cbrt, log, dbl, pwr) |
| --oscilloscope-grid | float | 0.25 | Grid line spacing as a fraction of frame size; 0.0 disables the grid |

#### [waveform] Gradient Waveform options

| Flag | Takes | Default | Description |
| --- | --- | --- | --- |
| --waveform-mode | one of: point, line, p2p, cline, pict | 'line' | Waveform display mode (line, point, p2p, cline, pict) |
| --waveform-splits | int | 1 | Draw channels separately (e.g., stereo L/R) when > 1 |
| --waveform-frame | int | 0 | Audio samples per frame (0 = default) |
| --waveform-colors-per-channel | flag (no value) | False | Assign different colors per channel |
<!-- FLAG-REF:END -->

## Creating a New Visualizer Plugin

1. Create a Python file with a class inheriting from `VisualizerPlugin` in
   `audio_visualizer/visualizers/base.py`
2. Define a `metadata` (`PluginMetadata`) and an `options` list (`PluginOption`)
   — the CLI flags are auto-generated from this schema
3. Implement `build_filter()`
4. Choose how it gets discovered:
   - **Built-in**: place the file in the `visualizers/` directory
   - **Installed**: `audio-visualizer --install-plugin my_plugin.py`
   - **External path**: `--plugin-path` or `AUDIO_VISUALIZER_PLUGIN_PATH`
   - **Distribution**: ship a package registering an entry point, e.g.

     ```toml
     [project.entry-points."audio-visualizer.plugins"]
     myviz = "mymypkg.plugin:MyVizPlugin"
     ```

All discovery paths apply the same schema validation and isolation: a broken
plugin is skipped with a warning instead of crashing the run.

### The `build_filter()` contract

`build_filter()` returns the filter graph for your visualization. It must
produce the plugin's **main output** labeled with `get_output_label()`
(default: `viz`) and must **not** produce the final `[out]` label — the
base class composes that (overlaying your output on the background input,
or relabeling it) and then applies post-processing such as the border.

```python
def build_filter(self, width, height, fps, options, audio_index=0, video_index=1, **kwargs):
    return f"[{audio_index}:a] showspectrum=size={width}x{height}[viz]"
```

For multi-layer visualizations, return a `dict` mapping output names to
filter segments (the first key is the main output):

```python
def build_filter(self, width, height, fps, options, **kwargs):
    return {
        "viz": "[0:a] showvolume=w=1280:h=720[viz]",
        "spectrum": "[0:a] showspectrum=size=1280x720[spectrum]",
    }
```

Plugins that need custom inputs (image files, no background, etc.)
override `get_inputs()`, and plugins with a custom final composition
override `compose_final_filter()`. See `albumart.py`, `vectorscope.py`,
and `radial.py` for examples.

For the full guide — plugin anatomy, a worked example (the built-in
`peakbars`), packaging a plugin as an entry-point distribution, and
testing — see [docs/PLUGIN_DEVELOPMENT.md](docs/PLUGIN_DEVELOPMENT.md).

## Project Structure

```
audio_visualizer/
├── __init__.py
├── __main__.py              # Main application entry point
└── visualizers/
    ├── __init__.py
    ├── base.py              # Base plugin class
    ├── registry.py          # Plugin registry system
    ├── spectrum.py          # Spectrum visualizer
    ├── waveform.py          # Waveform visualizer
    ├── bars.py              # Bars visualizer
    ├── radial.py            # Radial visualizer
    ├── spectrogram.py       # Spectrogram visualizer
    ├── vectorscope.py       # Vectorscope visualizer
    └── albumart.py          # Album art visualizer
```

## License

MIT License
