Disclaimer: This is a private hobby project, fully vibe-coded with AI assistance. It is not intended for production use and comes with no guarantees or support.
Python web interface to manage kids audiobooks from folders and upload selected albums to a configured Tonie figure.
- Folder-first library model:
Series/Album - Inbox processing: drop new downloads in
data/inboxand run ingest - Manage GUI at
/manageto send files directly into inbox + trigger ingest - Duplicate detection for existing series/album combinations
- Automatic library sync on startup and
/scan, including DB purge for removed album folders - Album deletion from
/manage(single album or bulk selected albums) - Poster logic:
- use image from album folder if available
- else extract embedded artwork from first audio file
- Album selection with 90-minute cap
- Upload flow with configurable figure id and pluggable
my-toniesAPI client
app/
services/
static/
templates/
data/
inbox/
library/
processed/
rejected/
config/
-
Copy env file:
cp .env.example .env
-
Create required directories:
mkdir -p data/inbox data/library data/processed data/rejected data/posters config
-
Start app:
docker compose up --build
-
Open:
http://localhost:8000
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
mkdir -p data/inbox data/library data/processed data/rejected data/posters config
uvicorn app.main:app --reloadDrop files into:
data/inbox/<Series>/<Album>/*.{mp3,m4a,flac,ogg,wav}
For flat drops directly in data/inbox, ingest now groups files by embedded audio tags first:
- Album from
Album
If tags are missing, filename parsing is used as fallback.
Then press Process Inbox in the UI (or POST /scan).
Alternatively, open /manage, select files, and submit once — the app stores uploads in data/inbox and runs the same process_inbox + sync_library stack automatically.
The client in app/services/my_tonies.py is intentionally minimal. Configure:
MY_TONIES_BASE_URLMY_TONIES_GRAPHQL_URL(default:https://api.prod.tcs.toys/v2/graphql) for figurine fetch- or username/password login via:
MY_TONIES_USERNAMEMY_TONIES_PASSWORD- optional OIDC parameters (
MY_TONIES_AUTH_BASE_URL,MY_TONIES_CLIENT_ID,MY_TONIES_REDIRECT_URI,MY_TONIES_SCOPE,MY_TONIES_UI_LOCALES)
- optional fallback selector values:
FIGURE_OPTIONS=id1:Kitchen Tonie,id2:Bedroom Tonie - optional figure filtering (mutually exclusive – whitelist takes priority if both are set):
FIGURE_WHITELIST=id1,id2– show only the listed figure IDs in the UIFIGURE_BLACKLIST=id3,id4– hide the listed figure IDs from the UI
For username/password auth, the app performs the same OIDC browser login flow (PKCE + login form submit + auth code exchange) and reuses the access token until expiry. Upload flow uses Tonies v2 API endpoints:
PATCH /households/{householdId}/creativetonies/{figureId}with{"chapters": []}to clear existing contentPOST /fileto obtain a signed S3 form andfileId- multipart form upload to the returned S3 URL using returned form fields + file body
- final
PATCH /households/{householdId}/creativetonies/{figureId}to assign uploaded chapters
The final chapter payload shape can vary by backend version; the client currently tries both {"chapters": ["fileId", ...]} and {"chapters": [{"file": "fileId"}, ...]}.
When API credentials are available, the web UI fetches available figures via GraphQL (households -> creativeTonies) and shows a dropdown.
If fetching fails (or credentials are missing), the UI falls back to FIGURE_OPTIONS (or manual figure id input).
The Tonie transfer endpoint is POST /upload-to-tonie (used by the main selector page).