cadence / README.md
Vincisteel's picture
Update README.md
21ba26b verified
|
Raw History Blame Contribute Delete
13.9 kB
metadata
title: Cadence
sdk: static
app_port: 7860
pinned: false
app_build_command: npm run build

🎧 Cadence β€” YouTube music dashboard

Three ways to run it:

What Needs
cadence-dashboard.html The whole dashboard in one file β€” double-click it, host it anywhere, no install nothing (demo mode)
This project The same dashboard wired to a real backend: YouTube Music search, radio, audio streaming Python 3 (./run.sh)
cadence-android/ The same dashboard as an installable Android app (PWA): home-screen icon, full-screen, offline any https static host

The single file starts in demo mode (built-in catalog, simulated playback, every panel and interaction live) and can be pointed at the backend from βš™ Settings for real search and audio. To rebuild it after editing the dashboard: python3 standalone/build_standalone.py.


Backend version

Search any song by name, stream it, and let the engine keep the music going with artist radios and similar-song suggestions β€” wrapped in a browser dashboard with live playback, suggestion and cache telemetry.

No API key needed: search/radio/artist/genre data comes from YouTube Music's public data (via ytmusicapi), and the audio is pulled with yt-dlp and served by this server so the browser can play, seek and buffer it like a normal audio file.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Cadence Β· dashboard        [ search songs, artists, albums… ]   CH  Queue  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Overview  β”‚  β–’ now playing  β–’ queue  β–’ buffered  β–’ radio  β”‚  Up next       β”‚
β”‚ Search    β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚  (queue rail)  β”‚
β”‚ Trending  β”‚  β”‚  Now playing     β”‚ β”‚  Suggestion feed    β”‚ β”‚  track 3 of 42 β”‚
β”‚ Genres    β”‚  β”‚  art + controls  β”‚ β”‚  + reason labels    β”‚ β”‚  ...           β”‚
β”‚ Recent    β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚                β”‚
β”‚ ───────   β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚                β”‚
β”‚ Engine    β”‚  β”‚ Trending in CH   β”‚ β”‚ Trending artists    β”‚ β”‚                β”‚
β”‚ cache …   β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚                β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  now playing Β· transport Β· seek Β· volume                                   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Run it

./run.sh                  # installs deps if needed β†’ http://localhost:8000
# or
pip install -r requirements.txt
PORT=8000 python3 app.py

Then open http://localhost:8000 (dashboard) β€” the older single-column player is kept at /classic.

Dashboard views

View What you get
Overview Live tiles (now playing, queue depth, buffered ahead, radio engine), a now-playing hero with one-click Play similar / Artist radio, the suggestion feed with a reason per track ("Up next", "Similar to X", "More from X"), trending tracks + artists, and recently played
Search Any song / artist / album by name, with live suggestions in the search box; artists render as cards, songs as a playable list
Trending YouTube Music charts for 69 regions (defaults to πŸ‡¨πŸ‡­ Switzerland) β€” charting tracks plus trending artists with rank and up/down trend
Genres & moods 38 tiles (moods + genres) β†’ curated playlists β†’ playable track lists, all browsable without typing anything
Artist Top songs, monthly listeners, "Fans also like" similar artists, albums & singles
Album / playlist Full track lists with play-all and per-track similar songs
Recently played Local listening history (stored in your browser)
Queue rail The full upcoming queue, click any row to jump; collapses via the Queue button
Engine panel Server telemetry: cache size vs. 3 GB cap, tracks cached, downloads, average fetch time, in-flight downloads, uptime

Everything is hash-routed (#/artist/…, #/playlist/…, #/genre/…), so views are shareable and the browser back button works.

How the "keep playing" engine works

  • Play any track β†’ the server downloads the audio once into a local cache and serves it with HTTP Range support (seeking/buffering/replays are instant).
  • While you listen, the next track is prefetched β€” the "Buffered ahead" tile polls /api/ready/<id> and flips to Ready βœ“ when it's on disk.
  • When the queue runs low, radio suggestions for the current track are appended automatically (the "Radio" badge appears in the player bar), so playback never dead-ends.
  • ✨ on any row = "play similar songs" β†’ seeds a station from that track.

Keyboard

/ search Β· space play/pause Β· n / p next/prev Β· ← β†’ skip 10 s Β· s shuffle Β· r repeat (off/queue/track) Β· m mute Β· q toggle queue rail. OS media keys work too.

API

Endpoint Purpose
GET /api/search?q=&limit= songs + artists for a query
GET /api/suggest?q= search-box autocomplete
GET /api/radio?videoId=&limit= "similar songs" / up-next radio for a track
GET /api/artist?id=|name= top songs, albums, similar artists, monthly listeners
GET /api/artist/radio?id= continuous artist station
GET /api/trending?country=CH regional chart tracks + trending artists + region list
GET /api/genres Β· GET /api/genre?params= Β· GET /api/playlist?id= browse tree and playlists
GET /api/album?id= album track list
GET /api/stream/<videoId> cached audio with Range support (the <audio> source)
POST /api/prefetch/<videoId> Β· GET /api/ready/<videoId> warm the cache / check if a track is buffered
GET /api/stats engine telemetry for the sidebar panel
GET /healthz health probe ({"status":"ok"}) for Render/Fly/Railway
GET /manifest.webmanifest Β· GET /sw.js Β· /static/pwa/icons/* installable-PWA assets
GET /api/img?u= Β· GET /api/thumb/<videoId> image proxies (allow-listed hosts only)

Deploy it

No credit card: DEPLOY-FREE.md β€” run the backend on your own machine and put a free open-source https tunnel in front of it (bash run.sh + bash tunnel.sh, or a single bash tunnel.sh --with-backend), or host the app as static files. Everything Cadence depends on is open source (Flask, gunicorn, yt-dlp, ytmusicapi, cloudflared); only the hosting was ever proprietary.

Paid/simple clouds: DEPLOY.md β€” Render, Railway and Fly are pre-configured (render.yaml, railway.json, fly.toml). Whichever you pick, the dashboard, the PWA assets and the API are served from the same https origin, so the installed Android app needs no backend URL and there is no CORS or mixed-content setup.

The dashboard is server-backed, so it needs the Flask app running (a static host alone can't stream or search).

Docker

docker build -t cadence .
docker run -p 8000:8000 -v cadence-data:/data cadence

Render / Railway / Fly.io / any PaaS β€” push the repo, it detects the Procfile: gunicorn --bind 0.0.0.0:$PORT --workers 1 --threads 16 --timeout 180 app:app. Set CADENCE_CACHE to a mounted volume (e.g. /data) so the audio cache survives restarts. One worker is deliberate: the cache, download locks and stats live in-process.

Reverse proxy β€” keep response buffering off for /api/stream/ (Nginx: proxy_buffering off;) so seeking stays snappy.

The standalone single-file dashboard

cadence-dashboard.html is generated from these same sources by standalone/build_standalone.py:

  • CSS, icons, dashboard logic and a demo engine are inlined β€” zero external requests, so it works from file://, a USB stick, GitHub Pages/Netlify/S3, or an e-mail attachment.
  • Demo mode synthesises a catalog (12 artists, 48 tracks, 32 regions, moods, genres, playlists), generates artwork as inline SVG, and runs a virtual player so progress, queue growth, radio suggestions and the telemetry panel all behave exactly like the live app.
  • Server mode (βš™ Settings β†’ enter a URL) switches every request to the real backend. The backend sends CORS headers, so the file can be hosted on a different origin. Mixed content applies: an https page needs an https backend.

Android app (installable PWA)

standalone/build_standalone.py also emits cadence-android/ β€” the app shell plus manifest.webmanifest, sw.js and a full icon set. Host that folder on any https static host (Netlify Drop, GitHub Pages, nginx…), open it on the phone and tap Install.

On phones the dashboard switches to a purpose-built layout (≀640 px): 2Γ—2 tile grid, full-width search, icon nav strip, compact 104 px player bar with thumb-reachable transport, and the queue as a full-height sheet. Touch devices also drop the keyboard hints and turn artist names inside track rows into plain text β€” Chrome's touch adjustment would otherwise turn a "play this song" tap into "open this artist".

Install criteria, mixed-content rules and how to get a signed .apk are documented in cadence-android/README.md.

Layout

cadence/
β”œβ”€β”€ app.py                  # Flask: search, radio, artist, trending, genres, stream, images, stats
β”œβ”€β”€ templates/dashboard.html # the dashboard shell (nav Β· panels Β· rail Β· player bar)
β”œβ”€β”€ templates/index.html     # the classic single-column UI (/classic)
β”œβ”€β”€ static/dash.css          # dashboard theme (dark, glassy)
β”œβ”€β”€ static/dash.js           # router, views, player, radio growth, telemetry
β”œβ”€β”€ static/style.css static/app.js   # classic UI
β”œβ”€β”€ standalone/runtime.js    # demo engine + backend connector (injected into the single file)
β”œβ”€β”€ standalone/build_standalone.py  # builds cadence-dashboard.html
β”œβ”€β”€ requirements.txt Β· run.sh Β· Dockerfile Β· Procfile

How search ranks results

/api/search runs three YouTube Music searches in parallel (relevance-ordered, filtered songs, filtered artists) and merges them with de-duplication. Ordering:

  1. YouTube's own relevance position leads β€” it encodes popularity, so the canonical recording beats karaoke and cover versions ("karma police" β†’ Radiohead, not a band called Karma Police).
  2. Exact title / artist matches lift near-ties β€” "song 2" puts Blur's track first.
  3. Artist queries are detected automatically (many results by one artist vs. many songs with that title) and that artist's catalogue is promoted β€” "nirvana", "blur", "drake".
  4. Results sharing nothing with the query are dropped, so a nonsense query returns an honest "no matches" with suggestions instead of random noise.

Typical latency is 0.5–1.5 s (the three lookups run concurrently, not in sequence).

Troubleshooting

"YouTube refused to serve this audio to the server's IP address" (HTTP 503, code youtube_blocked) β€” YouTube rate-limits/blocks media downloads from many datacenter and cloud IPs (VPS, CI, some PaaS egress ranges). The dashboard keeps working (search, charts, genres, suggestions, artist pages); only the audio bytes are refused.

  • Run it on your own machine / home connection β€” this is the normal setup and works.
  • On a VPS, route yt-dlp through a residential VPN/proxy (HTTPS_PROXY env var), or use a local machine as the playback backend.
  • The app tells you this explicitly instead of silently skipping tracks, and stops rather than hammering YouTube with retries.

WARNING: No supported JavaScript runtime could be found β€” harmless for audio-only use. Current yt-dlp prefers the deno runtime for YouTube's JS challenges; most formats still resolve without it. If a specific track returns "Requested format is not available", install deno and re-run.

First play is slow, then instant β€” by design: the track is downloaded once into the cache (β‰ˆ1–2 s), then served locally with Range support.

Port already in use β€” PORT=8010 ./run.sh.

Notes & limits

  • Audio cache: ~/.cache/cadence locally, /data in Docker (max ~3 GB, oldest evicted). Images cached in …/img.
  • Only the first play of a track costs a download (~1–2 s); after that it's local.
  • Regional trending charts are YouTube trending videos, so some titles arrive in video-style formatting rather than clean song metadata. Genre/mood playlists come from YouTube Music proper.
  • Lyrics are not included β€” YouTube Music's lyrics endpoint rejects key-less requests.
  • Built for personal use: it talks to the same public endpoints a browser does, so be sensible with volume.