{"id":51058419,"url":"https://github.com/cumulus13/mpdpop-v2","last_synced_at":"2026-06-22T23:02:10.405Z","repository":{"id":359689897,"uuid":"1243218261","full_name":"cumulus13/mpdpop-v2","owner":"cumulus13","description":"MPD playlist popup controller — cross-platform","archived":false,"fork":false,"pushed_at":"2026-06-16T01:36:48.000Z","size":2039,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-06-16T03:19:41.385Z","etag":null,"topics":["cross-platform","floating","mpd","popup"],"latest_commit_sha":null,"homepage":"https://github.com/cumulus13/mpdpop-v2","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cumulus13.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-19T06:38:24.000Z","updated_at":"2026-06-16T01:36:45.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/cumulus13/mpdpop-v2","commit_stats":null,"previous_names":["cumulus13/mpdpop-v2"],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/cumulus13/mpdpop-v2","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cumulus13%2Fmpdpop-v2","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cumulus13%2Fmpdpop-v2/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cumulus13%2Fmpdpop-v2/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cumulus13%2Fmpdpop-v2/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cumulus13","download_url":"https://codeload.github.com/cumulus13/mpdpop-v2/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cumulus13%2Fmpdpop-v2/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34668499,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-22T02:00:06.391Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cross-platform","floating","mpd","popup"],"created_at":"2026-06-22T23:02:09.549Z","updated_at":"2026-06-22T23:02:10.399Z","avatar_url":"https://github.com/cumulus13.png","language":"Python","funding_links":["https://www.buymeacoffee.com/cumulus13","https://ko-fi.com/cumulus13","https://www.patreon.com/cumulus13"],"categories":[],"sub_categories":[],"readme":"# mpdpop V2\n\nA cross-platform MPD playlist popup controller with album cover art, artist\nbiography, desktop overlay widget, layered caching, and a built-in command bar.\n\nRuns on **Windows**, **macOS**, and **Linux**. Multi-monitor aware. No mandatory\nthird-party dependencies — everything degrades gracefully when optional packages\nare absent.\n\n---\n\n[![Demo](https://github.com/cumulus13/mpdpop-v2/blob/b4348ca04cdf2c30cb142c2a7b27ef7203880181/mpdpop.webp)](https://github.com/cumulus13/mpdpop-v2/blob/b4348ca04cdf2c30cb142c2a7b27ef7203880181/mpdpop.webp)\n\n[![Overlay 1](https://github.com/cumulus13/mpdpop-v2/blob/99da1d993de2bba38db6524b6630b05053da4816/overlay1.png)](https://github.com/cumulus13/mpdpop-v2/blob/99da1d993de2bba38db6524b6630b05053da4816/overlay1.png)      [![Overlay 2](https://github.com/cumulus13/mpdpop-v2/blob/99da1d993de2bba38db6524b6630b05053da4816/overlay2.png)](https://github.com/cumulus13/mpdpop-v2/blob/99da1d993de2bba38db6524b6630b05053da4816/overlay2.png)      [![Overlay 3](https://github.com/cumulus13/mpdpop-v2/blob/99da1d993de2bba38db6524b6630b05053da4816/overlay3.png)](https://github.com/cumulus13/mpdpop-v2/blob/99da1d993de2bba38db6524b6630b05053da4816/overlay3.png)\n\n---\n\n## Files\n\n| File | Lines | Purpose |\n|---|---|---|\n| `mpdpop.py` | 1366 | Entry point — Tkinter playlist dialog, all platform backends |\n| `mpdpop_artinfo.py` | 1210 | Cover art fetcher, bio fetcher, info panel widgets |\n| `mpdpop_cache.py` | 655 | Layered cache: Redis → pickle → SQLAlchemy/SQLite |\n| `mpdpop_env.py` | 238 | Config loader (`mpdpop.env` + `os.environ`) |\n| `mpdpop_overlay.py` | 1026 | CD Art Display-style always-on-top desktop overlay |\n| `mpdpop.env` | — | Your settings and API keys (edit this) |\n\nAll files must live in the **same directory**.\n\n---\n\n## Quick start\n\n```bash\n# minimum — no extra packages needed\npython3 mpdpop.py\n\n# desktop overlay (runs standalone)\npython3 mpdpop_overlay.py\n\n# recommended — enables JPEG cover art + rounded corners + reflection\npip install pillow\n\n# full feature set\npip install pillow redis sqlalchemy pystray\n```\n\n---\n\n## Requirements\n\n### Mandatory\n- Python 3.10+\n- MPD running and reachable (default `127.0.0.1:6600`)\n- Tkinter (bundled with standard Python on all platforms)\n\n### Optional (install for extra features)\n\n| Package | Feature unlocked |\n|---|---|\n| `pillow` | JPEG/WebP cover art, rounded corners, mirror reflection, aspect-ratio resize |\n| `redis` | Redis cache layer (fastest, in-memory) |\n| `sqlalchemy` | Richer DB support for bio cache (PostgreSQL, MySQL, etc.) |\n| `pystray` | System tray icon in overlay (Windows) |\n| `AppKit` (macOS) | Accurate multi-monitor detection |\n| `xrandr` (Linux) | Accurate multi-monitor detection (usually pre-installed) |\n\nWithout `pillow`, only native PNG cover art renders. JPEG files show a\nplaceholder with an install hint.\n---\n\n## Configuration\n\nEdit `mpdpop.env` in the same directory as the scripts.\n`os.environ` always overrides `.env` values.\n\nThe file is searched in order:\n1. Same directory as `mpdpop_env.py`\n2. `~/.config/mpdpop.env`\n3. `~/.mpdpop.env`\n\n**Important**: leave unused values blank — do not add `# comments` after a value\non the same line, as they are treated as part of the value.\n\n```ini\n# ── MPD Connection ────────────────────────────────────────────────────────────\nMPD_HOST     = 127.0.0.1\nMPD_PORT     = 6600\nMPD_PASSWORD =\nMPD_TIMEOUT  = 5\n\n# ── API Keys ─────────────────────────────────────────────────────────────────\n# Last.fm — free key at https://www.last.fm/api/account/create\nLASTFM_API_KEY   =\n\n# Discogs — personal token at https://www.discogs.com/settings/developers\nDISCOGS_TOKEN    =\n\n# MusicBrainz — no key needed; set your app name for the User-Agent header\nMUSICBRAINZ_APP  = mpdpop/1.0\n\n# ── Cache ─────────────────────────────────────────────────────────────────────\nCACHE_REDIS_URL  =              # redis://[:pass@]host:6379/0\nCACHE_PICKLE     = true\nCACHE_PICKLE_DIR =\nCACHE_DB_URL     =              # blank = ~/.local/share/mpdpop/cache.db\nCACHE_TTL_DAYS   = 30\nCACHE_REVALIDATE_DAYS = 7\n\n# ── Cover Art ─────────────────────────────────────────────────────────────────\nCOVER_SIZE       = 120          # thumbnail px in main dialog\nCOVER_BIG_SIZE   = 480          # big cover popup px (S key / click)\nCOVER_CACHE_DIR  =              # blank = system temp dir\n\n# ── Artist Bio ────────────────────────────────────────────────────────────────\nBIO_MAX_CHARS    = 600\nBIO_LANG         = en           # Wikipedia language code\n\n# ── UI ────────────────────────────────────────────────────────────────────────\nDIALOG_WIDTH     = 780\nDIALOG_HEIGHT    = 620\nINFO_PANEL_BIO_H = 80           # bio text area height px\nPAGE_STEP        = 10           # rows per PgUp/PgDn\nCMD_HISTORY      = 50           # command bar history size\nWINDOW_ICON      =\n\n# ── Overlay ───────────────────────────────────────────────────────────────────\nOVERLAY_SIZE          = 220\nOVERLAY_X             = -1\nOVERLAY_Y             = -1\nOVERLAY_OPACITY       = 0.92\nOVERLAY_REFLECTION    = true\nOVERLAY_POLL_MS       = 1000\nOVERLAY_FONT          =\nOVERLAY_FONT_SIZE     = 10\nOVERLAY_CONTROLS_SIZE = 32\nOVERLAY_ALWAYS_ON_TOP = true\nOVERLAY_AUTO_START    = false\nOVERLAY_CORNER_RADIUS = 12\n```\n\nAll keys can also be set as environment variables:\n\n```bash\nexport MPD_HOST=192.168.1.10\nexport LASTFM_API_KEY=abc123\npython3 mpdpop.py\n```\n\n---\n\n## Playlist dialog (`mpdpop.py`)\n\n### Keyboard shortcuts\n\n| Key | Action |\n|---|---|\n| `↑` / `↓` | Navigate track list (works from any widget) |\n| `PgUp` / `PgDn` | Scroll list by `PAGE_STEP` rows |\n| `Enter` | Play selected track |\n| `F` | Focus filter box |\n| `T` | Focus track number field |\n| `S` | Open big cover art popup |\n| `C` | Toggle command bar |\n| `R` | Toggle MPD **repeat** on/off |\n| `N` | Toggle MPD si**n**gle on/off |\n| `Z` | Toggle MPD random (shuffle) on/off |\n| `O` | Toggle MPD cons**o**me on/off |\n| `Esc` | Close command bar if open, otherwise close dialog |\n| Double-click | Play track immediately |\n\n### Status badges\n\nA row of clickable badges in the top bar shows the current state of repeat,\nsingle, random, and consume. Each badge turns blue when the option is on.\nClicking a badge toggles it. The same options are toggled by `R`, `N`, `Z`, `O`.\n\n### Command bar (`C`)\n\nThe command bar accepts MPC shorthand or any shell command. Output appears in a\nscrollable 4-line terminal below the input. The playlist auto-refreshes after\nevery command using a diff-based update (safe for any playlist size).\n\n**MPC shorthand** — the `mpc` prefix is optional for known sub-commands:\n\n```\nnext           →  mpc next\nplay 3         →  mpc play 3\nvolume +5      →  mpc volume +5\ntoggle         →  mpc toggle\nstatus         →  mpc status\ndel 3-7        →  mpc del 3-7\nmpc next       →  mpc next        (prefix already there, unchanged)\nls /           →  ls /            (not an mpc command, runs as-is)\n```\n\nWhen expansion happens the prompt shows both: `$ next  →  mpc next`\n- `↑` / `↓` inside the command field cycles history (last `CMD_HISTORY` entries)\n- `Esc` inside the command field closes the bar without closing the dialog\n- Commands run in a background thread with a 10-second timeout — the UI never freezes\n\n**Navigation inside the command bar:**\n\n| Key | Action |\n|---|---|\n| `Enter` | Run command |\n| `↑` / `↓` | Cycle command history |\n| `Tab` | Move focus to filter box |\n| `Alt+F` | Move focus to filter box |\n| `Alt+T` | Move focus to track number field |\n| `Esc` | Close command bar |\n\nCommands run in a background thread with a 10-second timeout — the UI never freezes.\n\n### Big cover popup (`S` or click thumbnail)\n\nOpens a floating modal window showing the album art at `COVER_BIG_SIZE` pixels.\nThe image scales to fit while preserving aspect ratio.\nClose with `Esc`, `S`, or clicking the image.\n\n### Window icon\n\nThe icon is resolved in order:\n1. `WINDOW_ICON` from config (absolute path, or relative to script directory)\n2. `mpdpop.ico`, `mpdpop.png`, `mpdpop.gif` in the script directory\n3. Any `.ico` → `.png` → `.gif` found in the script directory (alphabetical)\n\nA missing icon is silently ignored — it never prevents the dialog from opening.\n\n### Multi-monitor\n\nThe dialog opens on whichever monitor contains the mouse cursor, centred within\nthat monitor's work area.\n\n| Platform | Method |\n|---|---|\n| Windows | `MonitorFromPoint` + `GetMonitorInfoW` |\n| Linux | `xrandr --query` parsing |\n| macOS | `AppKit.NSScreen.screens()` |\n| Fallback | Virtual screen size from cursor position |\n\n### Platform dialog backends\n\n| Platform | Primary | Fallback |\n|---|---|---|\n| Windows | Tkinter | terminal input |\n| macOS | Tkinter | terminal input |\n| Linux | Tkinter (if `$DISPLAY` / `$WAYLAND_DISPLAY`) | zenity → kdialog → terminal |\n\nThe zenity/kdialog fallbacks do not include cover art or bio — those require Tkinter.\n\n---\n\n## Info panel\n\nThe panel at the top of the dialog shows cover art, track metadata, and artist bio.\n\n- **Startup** — spinner and progress bar start immediately. Cache hit stops them in\n  under 10 ms. Cache miss runs a background network fetch.\n- **Arrow navigation** — labels update instantly on every keypress. Spinner and\n  progress bar restart immediately. A network fetch fires only after 280 ms of idle\n  time (debounce) so rapid scrolling never floods the network.\n- **Token guard** — each fetch call mints a generation token. Results from\n  superseded fetches (user moved to another track before previous fetch finished)\n  are silently discarded.\n- **Stale-while-revalidate** — cached bios older than `CACHE_REVALIDATE_DAYS`\n  trigger a silent background re-fetch. The UI still shows the cached version\n  instantly; the cache is updated if the bio changed.\n\n---\n\n## Cover art sources\n\nTried in order, first success wins. Result is cached to a file.\n\n1. **MPD embedded art** — `readpicture` command (fastest, no network)\n2. **Local file** — `cover.jpg`, `folder.jpg`, `front.jpg`, `AlbumArt.jpg`, etc.\n   Searches `MPD_MUSIC_DIR`, `~/Music`, `/var/lib/mpd/music`\n3. **Last.fm** `album.getInfo` — requires `LASTFM_API_KEY`\n4. **MusicBrainz Cover Art Archive** — no key needed\n5. **Discogs** — requires `DISCOGS_TOKEN`\n\nCover files are cached at `COVER_CACHE_DIR` (default: system temp).\nSet a persistent directory to avoid re-downloading between sessions:\n\n```ini\nCOVER_CACHE_DIR = ~/.cache/mpdpop/covers   # persistent across reboots\n```\n\n---\n\n## Artist biography sources\n\nTried in order, first non-empty result wins. The source label shows which\nservice provided the data and whether it came from cache:\n\n| Label | Meaning |\n|---|---|\n| `via Last.fm` | Fetched live from Last.fm |\n| `via Last.fm (cached)` | Read from cache; originally from Last.fm |\n| `cached` | Text cached by old code with no source record; backfilling in background |\n| `not found` | All services tried, nothing returned — retried every time |\n\n1. **Last.fm** `artist.getInfo` — requires `LASTFM_API_KEY`\n2. **Discogs** artist profile — requires `DISCOGS_TOKEN`\n3. **MusicBrainz** artist annotation\n4. **Wikipedia** intro paragraph — always available, no key needed\n\nSet `BIO_LANG` for non-English bios (e.g. `id`, `de`, `fr`).\n\n---\n\n## Layered cache\n\nBio text is cached across three layers. Cover art uses plain files (faster than\nany DB for binary blobs).\n\n```\nRead order:   Redis → pickle files → SQLite\nWrite order:  all available layers on every cache miss\n```\n\n| Layer | Speed | Requires |\n|---|---|---|\n| Redis | ~0.1 ms | `pip install redis` + running Redis server |\n| Pickle files | ~1 ms | nothing (stdlib only) |\n| SQLite / SQLAlchemy | ~5 ms | `sqlite3` stdlib, or `pip install sqlalchemy` for other DBs |\n\nOn a cache hit the value is promoted upward so subsequent reads are faster.\n\n**Stale-while-revalidate** — entries older than `CACHE_REVALIDATE_DAYS` (default 7)\ntrigger a silent background refresh. `CACHE_TTL_DAYS` (default 30) is the hard\nexpiry after which the entry is deleted entirely.\n\nNot-found results are **never cached** — all services are retried every time\nuntil one returns data.\n\n### Cache CLI\n\n```bash\npython3 mpdpop_cache.py --stats          # row counts per namespace\npython3 mpdpop_cache.py --evict          # remove expired pickle + SQL entries\npython3 mpdpop_cache.py --flush bio      # wipe all bio entries from all layers\n```\n\n### Alternative databases\n\n```ini\n# PostgreSQL\nCACHE_DB_URL = postgresql://user:pass@localhost/mpdpop\n\n# MySQL\nCACHE_DB_URL = mysql+pymysql://user:pass@localhost/mpdpop\n```\n\nRequires `pip install sqlalchemy` plus the driver (`psycopg2`, `pymysql`, etc.).\n\n---\n\n## Multi-monitor support\n\nThe dialog opens on whichever monitor contains the mouse cursor, centred\nwithin that monitor's work area (taskbar excluded on Windows).\n\n| Platform | Detection method |\n|---|---|\n| Windows | `MonitorFromPoint` + `GetMonitorInfoW` (win32 API) |\n| Linux | `xrandr --query` output parsing |\n| macOS | `AppKit.NSScreen.screens()` (requires `pyobjc`) |\n| Fallback | Virtual screen bounds from cursor position |\n\n---\n\n## Platform dialog backends\n\n| Platform | Primary | Fallback chain |\n|---|---|---|\n| Windows | Tkinter (bundled) | terminal input |\n| macOS | Tkinter | terminal input |\n| Linux | Tkinter (if `$DISPLAY` set) | zenity → kdialog → terminal |\n\nThe Linux fallback chain (`zenity`, `kdialog`) does not include the cover art\nor bio panel — those require Tkinter.\n\n---\n\n## Info panel behaviour\n\nThe panel at the top of the dialog shows cover art, track metadata, and artist bio.\n\n- **Startup**: spinner and progress bar start immediately. If cached data exists,\n  they stop within milliseconds. If not, a network fetch runs in the background.\n- **Arrow navigation**: labels update instantly on each keypress. The spinner and\n  progress bar restart immediately. A fetch fires after 280 ms of idle time\n  (debounce) so rapid scrolling does not flood the network.\n- **Token guard**: each fetch call mints a new generation token. Results from\n  superseded fetches (user moved to a different track before the previous fetch\n  completed) are silently discarded.\n\n---\n\n## Config module CLI\n\n```bash\n# Print current resolved config (API keys redacted)\npython3 mpdpop_env.py\n\n# Write a fresh commented template to ./mpdpop.env\npython3 mpdpop_env.py --write-template\n```\n\n---\n\n## Art info module CLI\n\n```bash\n# Test fetching for a specific artist / album\npython3 mpdpop_artinfo.py \"Radiohead\" \"OK Computer\"\n```\n\nPrints cover art size (bytes) and the first 300 characters of the bio, using\nwhichever services are configured.\n\n## Desktop overlay (`mpdpop_overlay.py`)\n\nA CD Art Display-style always-on-top widget that sits on the desktop showing\nalbum cover art and playback controls.\n\n```bash\npython3 mpdpop_overlay.py\n```\n\nOr set `OVERLAY_AUTO_START = true` to launch it alongside the playlist dialog.\n\n### Layout\n\n```\n┌──────────────────────────────┐  ← OVERLAY_SIZE px (default 220)\n│                              │\n│       Album cover art        │  rounded corners, shared cover cache\n│    (fills entire square)     │\n│                              │\n│  ⏮  ⏹  ⏯  ⏭       50%  │  fades in on hover, hidden at rest\n├──────────────────────────────┤  4px progress bar (click to seek)\n│ Song Title                   │  scrolling if too wide\n│ Artist                       │\n├──────────────────────────────┤\n│  ░░▓▓▓░░░░░░░░░░░░░░░░░░░    │  mirror reflection (toggle with M)\n└──────────────────────────────┘\n```\n\n### Overlay interactions\n\n| Action | Result |\n|---|---|\n| Hover | Controls and status badges fade in smoothly |\n| Mouse wheel | Volume ±5%, shows badge for 1.5 s |\n| Click progress bar | Seek to position |\n| Drag | Reposition anywhere on screen |\n| Double-click | Open `mpdpop.py` playlist popup |\n| Right-click | Context menu |\n\n### Overlay keyboard shortcuts\n\n| Key | Action |\n|---|---|\n| `M` | Toggle mirror/reflection on/off |\n| `Esc` | Quit overlay |\n| `Q` | Quit overlay |\n\n### Status badges (top-left, hover only)\n\nSmall icons show active MPD options: `⟳` repeat, `⤮` random, `①` single, `⌫` consume.\n\n### Context menu (right-click)\n\nPlay/Pause · Previous · Next · Stop · Toggle Repeat · Toggle Random ·\n🪞 Hide/Show Mirror · Open Playlist · Close Overlay\n\n### Overlay config keys\n\n| Key | Default | Description |\n|---|---|---|\n| `OVERLAY_SIZE` | `220` | Cover square size in pixels |\n| `OVERLAY_X` | `-1` | Initial X position (`-1` = right edge) |\n| `OVERLAY_Y` | `-1` | Initial Y position (`-1` = vertically centred) |\n| `OVERLAY_OPACITY` | `0.92` | Window alpha (0.0–1.0) |\n| `OVERLAY_REFLECTION` | `true` | Show mirror below cover at startup |\n| `OVERLAY_POLL_MS` | `1000` | MPD poll interval in milliseconds |\n| `OVERLAY_FONT` | _(auto)_ | Font name (blank = Segoe UI / Helvetica Neue / DejaVu Sans) |\n| `OVERLAY_FONT_SIZE` | `10` | Base font size; all other sizes derived from this |\n| `OVERLAY_CONTROLS_SIZE` | `32` | Control button size in pixels |\n| `OVERLAY_ALWAYS_ON_TOP` | `true` | Pin above other windows |\n| `OVERLAY_AUTO_START` | `false` | Launch with `mpdpop.py` |\n| `OVERLAY_CORNER_RADIUS` | `12` | Cover corner radius in pixels (requires Pillow) |\n\nFont size derivation from `OVERLAY_FONT_SIZE` (base = `N`):\n\n| Element | Size |\n|---|---|\n| Track title | `N` bold |\n| Artist name | `max(7, N-2)` |\n| ♪ placeholder | `max(16, cover÷4)` |\n| Control buttons | `max(10, ctrl_size÷2)` |\n| Volume / badges | `max(7, N-3)` |\n| Progress time tooltip | `max(6, N-4)` |\n\n---\n\n## Environment variable reference\n\n### MPD\n\n| Variable | Default | Description |\n|---|---|---|\n| `MPD_HOST` | `127.0.0.1` | MPD server hostname or IP |\n| `MPD_PORT` | `6600` | MPD server port |\n| `MPD_PASSWORD` | _(blank)_ | MPD password if required |\n| `MPD_TIMEOUT` | `5` | Socket timeout in seconds |\n\n### API keys\n\n| Variable | Default | Description |\n|---|---|---|\n| `LASTFM_API_KEY` | _(blank)_ | Last.fm API key |\n| `DISCOGS_TOKEN` | _(blank)_ | Discogs personal access token |\n| `MUSICBRAINZ_APP` | `mpdpop/1.0` | MusicBrainz User-Agent string |\n\n### Cache\n\n| Variable | Default | Description |\n|---|---|---|\n| `CACHE_REDIS_URL` | _(blank)_ | Redis URL, e.g. `redis://localhost:6379/0` |\n| `CACHE_PICKLE` | `true` | Enable pickle file cache layer |\n| `CACHE_PICKLE_DIR` | _(blank)_ | Directory for pickle files |\n| `CACHE_DB_URL` | _(blank)_ | SQLAlchemy DB URL (blank = SQLite in `~/.local/share/mpdpop/`) |\n| `CACHE_TTL_DAYS` | `30` | Hard expiry — entry deleted after this many days |\n| `CACHE_REVALIDATE_DAYS` | `7` | Soft threshold — silent background re-fetch after this many days |\n\n### Cover art\n\n| Variable | Default | Description |\n|---|---|---|\n| `COVER_SIZE` | `120` | Thumbnail size in pixels (main dialog) |\n| `COVER_BIG_SIZE` | `480` | Big cover popup size in pixels |\n| `COVER_CACHE_DIR` | _(blank)_ | Directory for cover image files (blank = system temp) |\n\n### Artist bio\n\n| Variable | Default | Description |\n|---|---|---|\n| `BIO_MAX_CHARS` | `600` | Truncate bio text to this many characters |\n| `BIO_LANG` | `en` | Language code for Wikipedia / Last.fm |\n\n### UI (playlist dialog)\n\n| Variable | Default | Description |\n|---|---|---|\n| `DIALOG_WIDTH` | `780` | Main dialog width in pixels |\n| `DIALOG_HEIGHT` | `620` | Main dialog height in pixels |\n| `INFO_PANEL_BIO_H` | `80` | Artist bio area height in pixels |\n| `PAGE_STEP` | `10` | Rows scrolled per PgUp / PgDn |\n| `CMD_HISTORY` | `50` | Maximum command bar history entries |\n| `WINDOW_ICON` | _(blank)_ | Path to icon file (`.ico`, `.png`, `.gif`) — auto-detected if blank |\n\n### Overlay\n\n| Variable | Default | Description |\n|---|---|---|\n| `OVERLAY_SIZE` | `220` | Cover square size in pixels |\n| `OVERLAY_X` | `-1` | Initial X (`-1` = right screen edge) |\n| `OVERLAY_Y` | `-1` | Initial Y (`-1` = vertically centred) |\n| `OVERLAY_OPACITY` | `0.92` | Window alpha 0.0–1.0 |\n| `OVERLAY_REFLECTION` | `true` | Show mirror at startup |\n| `OVERLAY_POLL_MS` | `1000` | MPD poll interval in milliseconds |\n| `OVERLAY_FONT` | _(auto)_ | Font name |\n| `OVERLAY_FONT_SIZE` | `10` | Base font size |\n| `OVERLAY_CONTROLS_SIZE` | `32` | Control button size in pixels |\n| `OVERLAY_ALWAYS_ON_TOP` | `true` | Always on top |\n| `OVERLAY_AUTO_START` | `false` | Launch overlay when `mpdpop.py` opens |\n| `OVERLAY_CORNER_RADIUS` | `12` | Cover corner radius in pixels |\n\n---\n\n## CLI tools\n\n```bash\n# Config — print resolved config, write template\npython3 mpdpop_env.py\npython3 mpdpop_env.py --write-template\n\n# Art info — test cover + bio fetch for an artist\npython3 mpdpop_artinfo.py \"Radiohead\" \"OK Computer\"\n\n# Cache — stats, evict, flush\npython3 mpdpop_cache.py --stats\npython3 mpdpop_cache.py --evict\npython3 mpdpop_cache.py --flush bio\n```\n\n---\n\n## LICENSE\n\nHadi Cahyadi \n\n[MIT © Hadi Cahyadi](LICENSE)\n\n## 👤 Author\n        \n[Hadi Cahyadi](mailto:cumulus13@gmail.com)\n    \n\n[![Buy Me a Coffee](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/cumulus13)\n\n[![Donate via Ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/cumulus13)\n \n[Support me on Patreon](https://www.patreon.com/cumulus13)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcumulus13%2Fmpdpop-v2","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcumulus13%2Fmpdpop-v2","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcumulus13%2Fmpdpop-v2/lists"}