Skip to content

GeneralD/lyra

Repository files navigation

lyra

Version macOS Swift License Tests Coverage CodeRabbit Reviews Open Source

Coverage Sunburst

lyra

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.

lyra demo

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.

Install

# via Homebrew
brew tap generald/tap
brew install lyra

# via Mint
mint install GeneralD/lyra

# or build from source
make install

Usage

lyra 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

Auto-start

# 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 uninstall

Note: Both methods use LaunchAgent but with different labels (homebrew.mxcl.lyra vs com.generald.lyra). Use one approach — do not mix them, or the daemon will run twice.

Shell completion

# zsh / bash / fish
eval "$(lyra completion zsh)"

Homebrew installs completions automatically.

Configuration

# 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 path

Or 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

Top-level

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)

[text.default] — base text style

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

[text.title], [text.artist], [text.lyric], [text.highlight]

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

[text.decode_effect]

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

[artwork]

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

[ripple]

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

[ripple.shape]

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

[ai]

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 using includes:

# 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.

Screen selection

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

Wallpaper

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 video-only H.264 at up to 4K
uvx brew install uv Zero-install alternative — runs uvx yt-dlp without global install
ffmpeg brew install ffmpeg Required for auto-loop. Remuxes DASH container to standard MP4

If neither yt-dlp nor uvx is found, lyra will show an error. If ffmpeg is not found, the video plays but 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 letterboxing

Time 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.05
  • cycle plays items in the order written, advancing when each item finishes (wraps around at the end).
  • shuffle advances to a random item each time playback completes, never repeating the current one twice in a row.
  • scale is 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. In shuffle, playback starts with whichever item resolves first.

Full example

config.toml

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.3

This 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.toml

[ai]
endpoint = "https://api.openai.com/v1"
model = "gpt-4o-mini"
api_key = "sk-..."

Requirements

  • macOS 14+
  • Swift 6.0+

License

GPL-3.0

About

Desktop backdrop — lyrics overlay & video wallpaper for macOS

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors