No description
  • Python 63.1%
  • JavaScript 15.1%
  • kvlang 7%
  • CSS 7%
  • HTML 3.8%
  • Other 4%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Florian Egger 8edfbaf670 Add web admin server (Docker) and automatic library sync
- FastAPI server + browser UI in server/, run via docker-compose.yml
  (port 8080, library in ./data/library). Reuses the player's own
  library scanner so both sides share one library format.
- Web UI: browse albums/tracks, edit tags (album/track title, artist,
  colour, emoji icon, cover with live player-tile preview), upload
  music into new or existing albums, preview audio.
- Tag edits write both metadata.json (artist + per-track overrides)
  and real audio tags via mutagen (ID3/FLAC/MP4/Vorbis), covers are
  embedded too; the player displays the new fields.
- Pull-based sync for the Pi: scripts/sync_client.py (stdlib only)
  fetches /api/manifest, downloads new/changed files and removes
  deleted ones; install.sh can set up a systemd timer, and the player
  app rescans its library periodically so changes appear live.
2026-09-20 16:04:49 +02:00
library Add web admin server (Docker) and automatic library sync 2026-09-20 16:04:49 +02:00
scripts Add web admin server (Docker) and automatic library sync 2026-09-20 16:04:49 +02:00
server Add web admin server (Docker) and automatic library sync 2026-09-20 16:04:49 +02:00
simon_player Add web admin server (Docker) and automatic library sync 2026-09-20 16:04:49 +02:00
tests Add web admin server (Docker) and automatic library sync 2026-09-20 16:04:49 +02:00
.dockerignore Add web admin server (Docker) and automatic library sync 2026-09-20 16:04:49 +02:00
.gitignore Add web admin server (Docker) and automatic library sync 2026-09-20 16:04:49 +02:00
conftest.py Add web admin server (Docker) and automatic library sync 2026-09-20 16:04:49 +02:00
docker-compose.yml Add web admin server (Docker) and automatic library sync 2026-09-20 16:04:49 +02:00
README.md Add web admin server (Docker) and automatic library sync 2026-09-20 16:04:49 +02:00
requirements-pi.txt Target Pi Zero 2 W + Waveshare 2.8" DPI LCD (480x640 portrait) 2026-09-18 15:23:00 +02:00
requirements.txt Initial commit: Simon media player 2026-09-18 12:43:16 +02:00

Simon Player

A kid-friendly, full-screen touch audio player for a Raspberry Pi 5 with a 4.3" touchscreen. Big colourful buttons, no reading required, no menus to get lost in - just tap a story to play it.

  • Touch home screen: a shelf of big cover-art tiles for each story.
  • Player screen: huge play/pause, skip, a chunky drag-to-seek bar, volume, and an optional sleep timer (15/30/45 min).
  • Optional Toniebox-style tags: place an RFID/NFC figurine on a reader to start a story, in addition to touch browsing.
  • Remembers where you left off: pausing/leaving a story resumes from the same spot next time.
  • Self-contained UI - all icons/buttons are hand-drawn vector shapes, no external icon fonts or image assets required.

Hardware assumptions

  • Raspberry Pi Zero 2 W, Raspberry Pi OS (Bookworm), preferably booted straight to the console (see Headless kiosk below).
  • A Waveshare 2.8" DPI LCD (480x640 IPS, capacitive touch) mounted on the 40-pin header - setup in Waveshare 2.8" DPI LCD below. Simon Player reads its resolution from ~/.simon_player/config.json (screen_width/screen_height, defaults match this panel).
  • Audio: the Zero 2 W has no audio jack - use a USB sound card or a speaker with a built-in DAC; VLC plays through ALSA automatically.
  • Optional: an MFRC522 RFID reader wired over SPI, for the Toniebox-style tag feature. Pi 5 note: the classic RPi.GPIO package does not work on the Pi 5's RP1 I/O chip - install rpi-lgpio instead (already handled by requirements-pi.txt). Panel note: the 2.8" DPI LCD uses every SPI0 pin, so an SPI-wired reader cannot coexist with it (see RFID section).

Install

git clone <this repo> ~/simon_player
cd ~/simon_player
./scripts/install.sh

The script installs system packages (vlc for audio playback, plus the SDL2 display libraries needed for headless rendering), adds your user to the video/render/input groups, creates a venv, installs Python dependencies, optionally installs the RFID extras, and optionally enables a boot-time systemd kiosk service so the player starts full-screen on the panel at every boot - with or without a desktop session.

Then copy story folders into ~/SimonPlayerLibrary - or manage the library from any browser with the web admin UI below.

Web admin & library sync (Docker)

A web server with a browser-based admin UI ships in this repo. Run it anywhere on your network (a NAS, another Linux box, or the Pi itself with more RAM-heavy Docker workloads in mind):

docker compose up -d --build
# -> http://<host>:8080

The web UI lets you:

  • browse everything that is (or will be) on the player - albums, tracks, covers - styled like the player's home screen;
  • edit tags: album title, artist, per-track title/artist (written into real ID3/FLAC/MP4 tags and the metadata.json the player reads);
  • upload covers - saved as cover.* and embedded into the audio files;
  • add music by uploading files into new or existing albums;
  • customise the player UI per album: tile colour, emoji icon, cover and titles, with a live preview of the resulting player tile.

The library lives in ./data/library next to docker-compose.yml (bind-mounted to /library in the container). Set API_TOKEN=... in the compose environment if others can reach the server; the UI asks for the token once.

Syncing to the player

The player pulls from the server with a small stdlib-only client:

  1. Run ./scripts/install.sh and answer y to the sync question, or set it up by hand:

    .venv/bin/python scripts/sync_client.py \
        --server http://<server>:8080 [--token <token>]   # one-off test
    
  2. The systemd timer (scripts/simon-player-sync.timer) runs the client every 5 minutes: new/changed files are downloaded into ~/SimonPlayerLibrary, files deleted on the server are removed. State is kept in ~/.simon_player/sync_state.json; use --dry-run or --no-delete to preview/soften the behaviour.

  3. The player app rescans its library every 30 seconds, so changes show up without a restart. Sync is one-way (server → player): treat the web UI as the source of truth.

Headless kiosk (no desktop session)

The Pi does not need a desktop session at all. Kivy draws through SDL2, and SDL2's kmsdrm video driver renders straight to the display hardware

  • no X server and no Wayland compositor involved:
  1. Run ./scripts/install.sh - it adds your user to the video/render/input groups and can enable the simon-player kiosk service.
  2. Answer "y" to booting to the console (equivalent to sudo systemctl set-default multi-user.target or, via sudo raspi-config, System Options -> Boot / Auto Login -> Console).
  3. Reboot. The player fills the panel and is restarted automatically if it crashes.

Useful commands:

sudo systemctl start simon-player   # also stop / restart
journalctl -u simon-player -f       # follow the logs

If you keep booting into the desktop instead, disable the kiosk service (sudo systemctl disable --now simon-player) - a desktop session owns the display, and starting the kiosk unit takes the display away from it.

Running manually from the console works too: python -m simon_player.main auto-selects SDL_VIDEODRIVER=kmsdrm when no desktop session is running.

Fallback if the Kivy build's bundled SDL2 ever refuses kmsdrm on your image: run the app inside a minimal Wayland kiosk compositor - sudo apt install cage, then start cage -- .venv/bin/python -m simon_player.main from a console (or use it as the service's ExecStart).

Waveshare 2.8" DPI LCD

The player targets a Pi Zero 2 W with this panel: a 480x640 IPS capacitive touchscreen that hangs off the 40-pin header and appears as the DPI-1 KMS display - no desktop session needed.

  1. Follow the Waveshare wiki: download its DTBO bundle and copy the .dtbo files into /boot/overlays/, then add to /boot/firmware/config.txt:

    dtoverlay=vc4-kms-v3d
    dtoverlay=waveshare-28dpi-3b-4b
    dtoverlay=waveshare-28dpi-3b
    dtoverlay=waveshare-28dpi-4b
    dtoverlay=waveshare-touch-28dpi
    dtoverlay=vc4-kms-dpi-2inch8
    
  2. Reboot - the console shows on the panel at 480x640 (native portrait), which is what Simon Player defaults to. For landscape instead: add video=DPI-1:480x640M@60,rotate=90 at the start of /boot/firmware/cmdline.txt, rotate touch via a /etc/udev/rules.d/99-waveshare-touch.rules libinput calibration matrix (wiki: Touch Rotation), and set screen_width/screen_height to 640/480 in ~/.simon_player/config.json.

  3. Backlight is wired to GPIO18; the wiki's "Adjust Backlight" section covers dimming and on/off.

  4. Zero 2 W quirk: if some grey pixels render greenish, the wiki FAQ has a replacement overlay file for the Zero 2 W.

Audio: the Zero 2 W has no audio jack - plug in a USB sound card (or a speaker with a built-in DAC); VLC picks it up through ALSA.

Running manually

source .venv/bin/activate
python -m simon_player.main

Press Escape (if you have a keyboard attached) or Alt+F4 to quit during development. On the real kiosk deployment there's no window chrome or keyboard, so quitting is done via sudo systemctl stop simon-player or by power-cycling the Pi.

Configuration

Settings live in ~/.simon_player/config.json (created with defaults on first run):

Key Default Meaning
library_path ~/SimonPlayerLibrary Where story folders live
screen_width / screen_height 480 / 640 Match your touchscreen (panel native: 480x640 portrait)
fullscreen true Full-screen kiosk mode
show_cursor false Hide the mouse cursor (touch-only)
default_volume 80 Starting volume (0-100)
rfid_enabled true Poll for RFID tags (no-ops without a reader)

Playback resume position, current volume, and RFID tag links are stored separately in ~/.simon_player/state.json and ~/.simon_player/tags.json.

Setting up RFID figurines/cards

Heads-up: the Waveshare 2.8" DPI LCD uses all of the SPI0 pins, so an SPI-wired MFRC522 cannot coexist with that panel. To use RFID with it, re-wire the reader for I2C mode or use an I2C reader (e.g. PN532). Without a working reader, the app silently stays in touch-only mode.

  1. Wire an MFRC522 reader to the Pi's SPI pins and enable SPI via sudo raspi-config -> Interface Options -> SPI.
  2. pip install -r requirements-pi.txt (inside the venv).
  3. Link a tag to a story folder:
    python scripts/rfid_enroll.py add "Winnie the Pooh"
    
    Hold the figurine/card on the reader when prompted.
  4. python scripts/rfid_enroll.py list shows all linked tags; remove <uid> unlinks one.

If no reader is wired up (or you're developing on a laptop), RFID support silently disables itself - touch browsing always works.

Project layout

simon_player/
  config.py       Settings (~/.simon_player/config.json)
  state.py        Resume-position / volume / RFID-tag persistence
  library.py      Scans story folders into Album/Track objects
  player.py       python-vlc playback wrapper
  rfid.py         Optional background RFID polling thread
  app.py          The Kivy App - all playback/UI control logic
  ui/
    screens.py    Hand-drawn touch widgets (buttons, seek bar, icons) + screens
    simon_player.kv  Layout/styling for the home & player screens
server/             Web admin server + browser UI (runs in Docker)
  app/main.py     FastAPI routes: browse, edit tags, upload, sync manifest
  app/tags.py     mutagen-based tag/cover writing (mp3/flac/m4a/ogg)
  app/static/     The web frontend (vanilla HTML/CSS/JS)
  tests/          API tests (pytest)
scripts/
  install.sh          Pi setup script
  simon-player.service  systemd kiosk unit template
  simon-player-sync.service/.timer  systemd library-sync units
  sync_client.py      Pulls the library from the web server (stdlib only)
  rfid_enroll.py      Grown-up CLI to link tags to stories
docker-compose.yml  Runs the web server on port 8080 with ./data/library
library/README.md  Story folder format
tests/              Unit tests for the (hardware-free) library/state logic

Running the tests

source .venv/bin/activate
pip install pytest
pytest

(Only library.py and state.py are covered by unit tests - they have no hardware/Kivy dependency. player.py, rfid.py, and the UI need real audio/touch/RFID hardware or a display and are exercised manually.)