SideB is a retro cassette-style music player for TrimUI Brick with Spotify Connect, offline favorites, and local MP3 playback.
Latest release: v1.1.0
- Local playback now uses SideB's SDL audio callback with bundled
ffmpeg-litePCM decoding, replacing the oldaplaysubprocess output path. - Downloads now persist pending work, resume automatically on startup, show clearer progress, and surface short failure notices for cookies, network, storage, YouTube challenge, and missing runtime tools.
- Local MP3 import now ignores macOS metadata files, supports album-folder
cover.*/folder.*art, and backfills missing covers for existing managed tracks.
- Turns the TrimUI Brick into a Spotify Connect receiver on your local network
- Shows cover art, playback state, and cassette animation directly on
/dev/fb0 - Supports hardware controls for play, pause, skip, volume, favorites, and list navigation
- Save the current track with
X - Browse and manage favorites in the fullscreen
FAV LIST - Play downloaded favorites locally with shuffle, previous, and next track controls
- Drop MP3 files into
data/imports/ - SideB imports tags and cover art automatically
- Imported tracks are added to
FAV LISTand behave like local favorites
The app consists of two components:
- go-librespot : Spotify Connect backend โ handles authentication, audio streaming, and exposes a local HTTP/WebSocket API on port
3678 - spotify-ui-rs (this repo): Rust framebuffer UI โ reads input from
/dev/input, renders the cassette scene to/dev/fb0, and communicates withgo-librespotvia its local API
When a user marks a track as a favorite, the app searches for multiple matching candidates on YouTube using yt-dlp, scores them by duration match against Spotify metadata, title similarity, and channel quality, then downloads the best match as an MP3 file on the SD card. After download, the actual file duration is validated against the Spotify track length to reject mismatched results. Downloads use a bundled FFmpeg-compatible audio transcoder with MP3 encoder support. Cached audio is decoded by bundled ffmpeg-lite into PCM and played through SideB's SDL audio callback. Cover art is fetched from the Spotify CDN or copied from the local cover cache. Incomplete downloads are automatically resumed on the next app launch.
For manual local playback, users can also drop MP3 files into data/imports/. SideB scans that folder automatically, reads MP3 metadata, moves the file into data/music/, extracts embedded cover art with the device's system ffmpeg when available, and adds the track to FAV LIST as a managed local item.
Important: The app does not intercept, decrypt, or extract audio from Spotify streams. Spotify playback and offline caching use entirely separate audio paths.
This project provides an offline caching mechanism strictly for personal, non-commercial use. By using this feature, you acknowledge and agree to the following:
- No content is hosted or distributed by this project. All audio files are cached locally on the user's own device and are never uploaded, shared, or made available to third parties.
- The project does not circumvent any DRM or copy protection. Spotify audio streams are not intercepted or recorded. Offline caching relies on publicly available content retrieved via yt-dlp from YouTube.
- Users are solely responsible for ensuring their use complies with applicable copyright laws and the terms of service of any third-party platform (including YouTube and Spotify). The authors and contributors of this project assume no liability for how the software is used.
- If you are a rights holder and believe this project facilitates infringement of your rights, please open an issue and we will address it promptly.
This software is provided as-is for educational and personal use. It is not intended to promote or facilitate unauthorized copying or distribution of copyrighted material.
- TrimUI Brick
- NextUI, Stock OS, or CrossMix OS
1.1.1+ - Spotify Premium account
- Wi-Fi on the same network as your Spotify client (for streaming mode)
Stable builds are available for NextUI, Stock OS, and CrossMix OS.
KNULLI and muOS builds are Candidate test packages. Use them only if you can try SideB on real hardware and share logs or screenshots. They are not official support yet.
| Button | Action |
|---|---|
| A | Play / Pause |
| โ / โ | Previous / Next track |
| โ / โ | Volume up / down |
| X | Favorite, or press twice to remove a favorite |
| Y | Open / close playlist |
| B / MENU | Exit app |
- Launch SideB from the TrimUI app menu.
- Open Spotify on your phone, desktop, or tablet.
- Pick TrimUI Brick from Spotify Connect.
- Use the hardware buttons on the device for playback, volume, favorites, and list access.
- Start playback from Spotify Connect.
- Press X to add the current track to favorites.
- Wait for the background download to finish.
- Press A on the device to start local playback from the downloaded library, or press Y to pick a downloaded track from
FAV LIST.
Copy .mp3 files into the data/imports/ folder inside the app directory, either directly or inside subfolders:
NextUI -> /mnt/SDCARD/Tools/tg5040/SideB.pak/data/imports/
Stock -> /mnt/SDCARD/Apps/SideB/data/imports/
CrossMix -> /mnt/SDCARD/Apps/SideB/data/imports/
SideB scans that folder recursively at startup and while running. Imported files are moved into data/music/, then added to FAV LIST and treated like downloaded local tracks. macOS metadata such as __MACOSX/, hidden folders, and ._*.mp3 files is ignored.
For best results, embed cover art in the MP3 before copying it into data/imports/. SideB uses embedded cover art first.
If the MP3 has no embedded cover, place a same-name .jpg, .jpeg, or .png image next to the MP3 in data/imports/, or in the same import subfolder:
data/imports/My Album/
Artist - Song.mp3
Artist - Song.jpg
For album subfolders, cover.jpg, cover.jpeg, cover.png, folder.jpg, folder.jpeg, and folder.png are also supported as folder-level covers:
data/imports/My Album/
01 First Song.mp3
02 Second Song.mp3
cover.jpg
Folder-level covers only apply inside import subfolders such as data/imports/My Album/. data/imports/cover.jpg is not treated as a root import default, and data/music/cover.jpg is not treated as a global library cover.
If a track was already imported without cover art, place the same-name image next to the managed MP3 in data/music/:
data/music/
Artist - Song.mp3
Artist - Song.jpg
SideB scans for these existing-library covers at startup and while running. In data/music/, the image filename still needs to match the MP3 filename stem.
Notes:
- Current manual import support is
MP3only. - Embedded cover art is preferred. If no embedded cover exists, SideB tries a same-name sidecar image, then an album-subfolder
cover.*, thenfolder.*. - Press X once to show the remove confirmation, then press X again to actually remove the track from favorites.
- If you remove the track that is currently playing locally, SideB keeps the managed file until playback moves away from that track. If you favorite it again before switching tracks, the cached file is preserved.
When a background download finally fails, SideB shows a short notice on the device and writes details to /tmp/sideb.log.
| Notice | Common log pattern | Meaning | Action |
|---|---|---|---|
Cookie check failed |
Sign in to confirm you're not a bot, Use --cookies |
YouTube wants valid account cookies | Export Firefox cookies to data/yt-dlp-cookies.txt |
YouTube challenge |
Signature solving failed, n challenge, GVS PO Token, PO Token |
YouTube changed or gated this format/client path | Retry later, update SideB/yt-dlp in the next package, or try another track |
Network error |
DNS, timeout, TLS, SSL, Network is unreachable |
Device network, Wi-Fi, certificate, proxy, or routing issue | Check device Wi-Fi/router/proxy; the device does not inherit your computer proxy automatically |
Storage full |
No space left on device, PyInstaller, failed to extract, /tmp |
Temporary extraction or SD-card storage failed | Free space, restart SideB to clear /tmp/sideb-tmp, then retry |
Missing yt-dlp |
yt-dlp missing or permission denied |
Runtime payload is missing or not executable | Reinstall the full release zip |
Audio tool failed |
ffmpeg, ffprobe, ffmpeg-location, transcoder, libmp3lame, s16le |
Bundled audio transcoder path or codec support is missing or broken | Reinstall the full release zip and verify ffmpeg-lite is present |
No audio match |
Requested format is not available, duration mismatch |
Candidate audio did not match the Spotify track | Try another track or retry later |
Download failed |
Other extractor failure | Unknown download failure | Check /tmp/sideb.log and open an issue with the log snippet |
For cookie-related failures:
Use Firefox (recommended โ Chrome rotates cookies on export, making them expire immediately):
- Install Firefox and log into YouTube
- Install yt-dlp (
brew install yt-dlp/winget install yt-dlp) - Export cookies and copy to the device:
yt-dlp --cookies-from-browser firefox --cookies cookies.txt -s "https://www.youtube.com/watch?v=dQw4w9WgXcQ" - Copy
cookies.txtto the device SD card asdata/yt-dlp-cookies.txt
Restart SideB and pending downloads will resume automatically.
Why not Chrome? Since late 2024, Chrome automatically invalidates exported cookies as a security measure. Firefox does not have this limitation.
git clone https://github.com/CharlexH/SideB.git
cd SideB
rustup target add aarch64-unknown-linux-gnu
brew install zig
cargo install cargo-zigbuild --locked
./scripts/package.shpackage/SideB.pak/go-librespotโ Spotify Connect backend binarypackage/SideB.pak/ffmpeg-liteโ bundled FFmpeg-compatible audio transcoder with MP3 encoding and local playback PCM output support; keep it minimal and audio-focusedpackage/SideB.pak/yt-dlpโ YouTube audio downloader (aarch64 binary)package/SideB.pak/resources/ca-certificates.crtโ TLS root certificates
package/SideB.pak/resources/font_mono.ttf is tracked and should already be present after checkout.
Build Stable packages:
./scripts/package.sh
This produces:
dist/SideB-<version>-nextui.zipdist/SideB-<version>-stock.zipdist/SideB-<version>-crossmix.zip
Build Candidate test packages:
./scripts/package_candidates.sh
This produces:
dist/SideB-<version>-knulli-candidate.zipdist/SideB-<version>-muos-candidate.muxapp
If you test KNULLI or muOS, include firmware version, install path, logs, and
the package hash. The short checklist is in
packaging/candidate-test-checklist.md.
Manual installation:
NextUI -> extract the nextui archive into /mnt/SDCARD/Tools/tg5040/SideB.pak/
Stock -> extract the stock archive at the SD-card root so it creates /mnt/SDCARD/Apps/SideB/
CrossMix -> extract the crossmix archive at the SD-card root so it creates /mnt/SDCARD/Apps/SideB/
KNULLI -> Candidate only: extract the knulli archive into the KNULLI roms/ports folder
muOS -> Candidate only: copy the .muxapp archive to ARCHIVE and install it with Archive Manager
On Stable platforms, launch SideB from the TrimUI app menu, then select TrimUI Brick from Spotify Connect on another device.
On Candidate builds, launch SideB Candidate from Ports/PortMaster on KNULLI or from Applications on muOS.
Public releases attach the three Stable archives:
SideB-<version>-nextui.zipSideB-<version>-stock.zipSideB-<version>-crossmix.zip
They may also include Candidate test packages:
SideB-<version>-knulli-candidate.zipSideB-<version>-muos-candidate.muxappcandidate-test-checklist.md
KNULLI and muOS need real-device reports before they move to Beta or Stable.
The NextUI Pak Store consumes the nextui archive via pak.json.
Before publishing, verify the GitHub release assets after upload:
./scripts/check_github_release_assets.sh v<version>
Current release tag: v1.1.0
spotify-ui-rs/ Rust UI source
package/SideB.pak/ Local runtime staging folder and source assets
package/SideB.pak/data/ Runtime config copied into release packages
package/SideB.pak/resources/ UI images, icons, and fonts
packaging/ Platform wrappers and release metadata
scripts/package.sh Multi-platform release packager
scripts/check_release_metadata.sh Release metadata consistency gate
scripts/check_github_release_assets.sh GitHub release asset gate
Main config: package/SideB.pak/data/config.yml
device_name: "TrimUI Brick" device_type: "speaker" audio_backend: "alsa" audio_device: "default" bitrate: 160 volume_steps: 100 initial_volume: 80 zeroconf_enabled: true
The UI communicates with go-librespot at http://127.0.0.1:3678.
This project builds upon the following open-source projects:
| Project | License | Usage |
|---|---|---|
| go-librespot | GPL-3.0 | Spotify Connect backend |
| yt-dlp | Unlicense | YouTube audio search and download for offline caching |
| FFmpeg | LGPL-2.1+ / GPL-2.0+ | Audio decoding and playback pipeline |
| CrossMix OS | โ | Firmware base for TrimUI devices |
Hardware: TrimUI Brick
Release archives also include:
LICENSES/NOTICE.mdLICENSES/THIRD_PARTY_SOURCES.md- Full upstream license texts for bundled binaries
Before publishing a GitHub release, record the exact upstream versions and checksums of the bundled third-party binaries in LICENSES/THIRD_PARTY_SOURCES.md.
Apache-2.0. See LICENSE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. THE AUTHORS ARE NOT RESPONSIBLE FOR ANY MISUSE OF THIS SOFTWARE OR FOR ANY VIOLATION OF THIRD-PARTY TERMS OF SERVICE OR APPLICABLE LAWS. USE AT YOUR OWN RISK.