Desktop lyrics overlay and video wallpaper for macOS.
Displays synced lyrics from LRCLIB over your desktop, with optional video wallpaper and mouse-reactive ripple effects. Text appears with a matrix-style decode animation.
If lyra is useful to you, please consider starring the repo. It helps other macOS users find the project and supports future official Homebrew submission.
# via Homebrew
brew tap generald/tap
brew install lyra
# via Mint
mint install GeneralD/lyra
# or build from source
make installlyra start # start as background daemon
lyra stop # stop the daemon
lyra restart # restart
lyra daemon # run in foreground (debug)
lyra version # show version
lyra healthcheck # check API connectivity
lyra config template # print default config to stdout
lyra config init # create config file with defaults
lyra config edit # open config in $EDITOR
lyra config open # open config in GUI app
lyra track # show now-playing info as JSON
lyra track -r # resolve metadata (MusicBrainz/regex)
lyra track -l # include lyrics (LRCLIB)
lyra track -rl # resolve + lyrics
lyra benchmark # measure CPU/memory baselines
lyra benchmark -d 30 # 30s per scenario
lyra benchmark --json # JSON output for CI# via Homebrew (recommended for Homebrew installs)
brew services start lyra
brew services stop lyra
# or manually (Mint / source-build users)
lyra service install # register LaunchAgent directly
lyra service uninstallNote: Both methods use LaunchAgent but with different labels (
homebrew.mxcl.lyravscom.generald.lyra). Use one approach — do not mix them, or the daemon will run twice.
# zsh / bash / fish
eval "$(lyra completion zsh)"Homebrew installs completions automatically.
# Generate a starter config with all defaults
lyra config init # creates ~/.config/lyra/config.toml
lyra config init --format json # JSON variant
lyra config template > custom.toml # pipe to any pathOr create ~/.config/lyra/config.toml (or config.json) manually. All fields are optional — missing values use sensible defaults.
Alternative paths: ~/.lyra/config.toml, $XDG_CONFIG_HOME/lyra/config.toml
| Key | Type | Default | Description |
|---|---|---|---|
screen |
string / int | "main" |
Which display to use (see Screen selection) |
screen_debounce |
number | 5 |
Seconds between re-evaluations in "vacant" mode |
wallpaper |
string | — | Video wallpaper. Local path, HTTP(S) URL, or YouTube URL (see Wallpaper) |
includes |
array | — | TOML-only: list of additional TOML files to merge (ignored for config.json; paths relative to config dir or absolute) |
All text sections inherit from [text.default]. Section-specific values override the base.
| Key | Type | Default | Description |
|---|---|---|---|
font |
string | system font | Font family name (e.g. "Helvetica Neue") |
size |
number | 12 |
Font size in points |
weight |
string | "regular" |
Font weight: "regular", "medium", "bold", etc. |
color |
string / array | "#FFFFFFD9" |
Solid hex "#RRGGBBAA" or gradient ["#AAA", "#BBB"] |
shadow |
string | "#000000E6" |
Shadow color in hex |
spacing |
number | 6 |
Vertical padding around each line |
Each overrides specific properties from [text.default]. Unset properties fall back to the base.
| Section | Built-in overrides |
|---|---|
title |
size = 18, weight = "bold" |
artist |
weight = "medium" |
lyric |
inherits default as-is |
highlight |
color = ["#B8942DFF", "#EDCF73FF", "#FFEB99FF", "#CCA64DFF", "#A68038FF"] (gold gradient). Inherits from lyric, then default |
Controls the matrix-style text reveal animation.
| Key | Type | Default | Description |
|---|---|---|---|
duration |
number | 0.8 |
Animation duration in seconds |
charset |
string / array | all | Character sets for scramble: "latin", "cyrillic", "greek", "symbols", "cjk". Single string or array |
processing_color |
string / array | "#4ADE80FF" (green) |
Title/artist color while the AI extractor is resolving (LLM cache miss). The header scrambles in this color until the API responds, then settles to the resolved text in its normal color. Solid hex or gradient array. Only applies when an [ai] endpoint is configured |
| Key | Type | Default | Description |
|---|---|---|---|
size |
number | 96 |
Album artwork size in points |
opacity |
number | 1.0 |
0 hides artwork (text aligns left), 1 fully visible |
Mouse-reactive ripple effect on the overlay.
| Key | Type | Default | Description |
|---|---|---|---|
enabled |
boolean | true |
Set to false to disable ripple effects entirely |
color |
string | "#AAAAFFFF" |
Ripple color in hex |
radius |
number | 60 |
Ripple radius in points |
duration |
number | 0.6 |
Ripple animation duration in seconds |
idle |
number | 1 |
Seconds before ripple fades after mouse stops |
shape |
string / table | "circle" |
Ripple outline shape. See below |
Polymorphic shape spec. Three accepted forms:
# 1. Omit entirely → defaults to circle
[ripple]
radius = 60
# 2. Bare string for parameterless shapes
[ripple]
shape = "circle"
# 3. Table form for shapes that take parameters
[ripple.shape]
type = "polygon"
sides = 6
angle = 15| Shape | Required keys | Optional keys | Notes |
|---|---|---|---|
circle |
— | — | Same as omitting shape |
polygon |
sides (int 3...256) |
angle (degrees, default 0) |
Out-of-range sides values fail config decoding. angle = 0 orients one vertex straight up |
Optional LLM-based song title and artist extraction via any OpenAI-compatible API. When omitted, lyra uses regex-based parsing only. All three fields are required to enable this feature.
| Key | Type | Default | Description |
|---|---|---|---|
endpoint |
string | — | OpenAI-compatible API base URL (e.g. "https://api.openai.com/v1") |
model |
string | — | Model name (e.g. "gpt-4o-mini") |
api_key |
string | — | API key for authentication |
Tip: Keep your API key out of version control by splitting
[ai]into a separate file and usingincludes:# config.toml includes = ["ai.toml"]# ai.toml (add to .gitignore) [ai] endpoint = "https://api.openai.com/v1" model = "gpt-4o-mini" api_key = "sk-..."Included files are merged into the main config. Values in the main file take precedence over included ones.
| Value | Behavior |
|---|---|
"main" |
Current main display (with focused window) |
"primary" |
Primary display (menu bar screen) |
"smallest" |
Smallest display by area |
"largest" |
Largest display by area |
"vacant" |
Least-occupied display (auto-migrates every screen_debounce seconds) |
0, 1, … |
Display by index |
The wallpaper field accepts three types of values:
# Local file (relative to config dir or absolute)
wallpaper = "loop.mp4"
wallpaper = "/Users/me/Videos/bg.mp4"
# Direct HTTP(S) URL
wallpaper = "https://example.com/background.mp4"
# YouTube URL
wallpaper = "https://www.youtube.com/watch?v=XXXXX"
wallpaper = "https://youtu.be/XXXXX"Remote and YouTube videos are downloaded once and cached in ~/.cache/lyra/wallpapers/. Subsequent launches use the cached file instantly.
YouTube requirements:
| Tool | Install | Notes |
|---|---|---|
yt-dlp |
brew install yt-dlp |
Preferred. Downloads the highest-quality video-only stream, up to 4K |
uvx |
brew install uv |
Zero-install alternative — runs uvx yt-dlp without global install |
ffmpeg |
brew install ffmpeg |
Remuxes DASH to MP4 and adds +faststart for seamless looping (1080p H.264 ceiling) |
ffprobe |
included with brew install ffmpeg |
Unlocks 4K: detects codec and transcodes AV1/VP9 → HEVC for pre-M3 Apple Silicon and Intel |
If neither yt-dlp nor uvx is found, lyra will show an error. ffmpeg alone
enables DASH-to-MP4 remuxing for seamless looping at the H.264 1080p ceiling.
Pair it with ffprobe (included in brew install ffmpeg) to unlock 4K: lyra
then downloads the best VP9/AV1 stream and hardware-transcodes non-natively-playable
codecs to HEVC so every Mac — including pre-M3 Apple Silicon and Intel — can play
it. Without ffmpeg, lyra downloads a direct H.264 stream that may not loop automatically.
Trim playback range (optional):
[wallpaper]
location = "https://www.youtube.com/watch?v=XXXXX"
start = "0:30" # skip intro
end = "3:45" # stop before outro
scale = 1.15 # enlarge this video to hide letterboxingTime format: M:SS, H:MM:SS, or fractional seconds (1:23.5). Both start and end are optional. scale defaults to 1.0; values above 1.0 zoom the current video only. The bare string format (wallpaper = "file.mp4") still works for simple cases.
Multiple wallpapers (optional):
Provide multiple videos with [[wallpaper.items]] and choose between sequential (cycle) and random (shuffle) playback:
[wallpaper]
mode = "cycle" # or "shuffle" — default is "cycle"
[[wallpaper.items]]
location = "loop.mp4"
[[wallpaper.items]]
location = "https://www.youtube.com/watch?v=XXXXX"
start = "0:30"
end = "3:45"
scale = 1.2
[[wallpaper.items]]
location = "https://example.com/bg.mp4"
scale = 1.05cycleplays items in the order written, advancing when each item finishes (wraps around at the end).shuffleadvances to a random item each time playback completes, never repeating the current one twice in a row.scaleis configured per item, so videos with different letterboxing can use different zoom values.- All items are resolved in parallel. In
cycle, playback starts as soon as the first configured item is ready — later items play in configured order regardless of download speed. Inshuffle, playback starts with whichever item resolves first.
includes = ["ai.toml"]
screen = "vacant"
screen_debounce = 5
[wallpaper]
mode = "cycle"
[[wallpaper.items]]
location = "https://www.youtube.com/watch?v=Sn1ieBOLGB0"
start = "0:17"
end = "3:37"
[[wallpaper.items]]
location = "https://www.youtube.com/watch?v=P0az9IS2XQQ"
start = "0:24"
end = "3:15"
scale = 1.325
[text.default]
font = "Zen Maru Gothic"
size = 12
color = "#FFFFFFD9"
shadow = "#000000E6"
spacing = 6
[text.title]
font = "Zen Kaku Gothic New"
size = 18
weight = "bold"
[text.artist]
font = "Zen Kaku Gothic New"
size = 12
weight = "medium"
[text.lyric]
color = "#FFFFFFE6"
[text.highlight]
color = ["#B8942DFF", "#EDCF73FF", "#FFEB99FF", "#CCA64DFF", "#A68038FF"]
[artwork]
size = 96
opacity = 0.8
[ripple]
color = "#AAAAFFFF"
radius = 60
duration = 0.4
idle = 1.3This example uses Zen Maru Gothic and Zen Kaku Gothic New. If those fonts are not installed, install them with Homebrew Cask:
brew install --cask font-zen-maru-gothic font-zen-kaku-gothic-new[ai]
endpoint = "https://api.openai.com/v1"
model = "gpt-4o-mini"
api_key = "sk-..."- macOS 14+
- Swift 6.0+
GPL-3.0

