Spaces:
Running
Download README.md from VinciTutorial/cadence: direct link, hf CLI and curl.
- Browser
- Download file 13.9 kB
-
https://huggingface.co/spaces/VinciTutorial/cadence/resolve/main/README.md
- Command line
-
hf download hf://spaces/VinciTutorial/cadence/README.md
-
curl -L -o README.md https://huggingface.co/spaces/VinciTutorial/cadence/resolve/main/README.md
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
httpspage needs anhttpsbackend.
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:
- 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).
- Exact title / artist matches lift near-ties β
"song 2"puts Blur's track first. - 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". - 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-dlpthrough a residential VPN/proxy (HTTPS_PROXYenv 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/cadencelocally,/datain 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.