A first pass over an unanalysed library is hours of librosa, and there was no
reason for a desktop to spend them one core at a time. Analysis now runs in a
process pool sized by `general.library.analysis_workers`, defaulting to one per
core bar one (capped at 8) when the key is absent.
Two consequences worth knowing before touching this: whatever an `Analyzer`
returns has to be picklable, and an analyzer that keeps state on its own
instance - a test double counting calls - only behaves as written with
`analysis_workers=1`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Letters-round lessons only had bubbles and jellyfish as eligible arcade
modes; with jellyfish gone, bubbles is now always used instead of the
two alternating - no lessons need to be dropped.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The three tab buttons now share one frosted glass pill instead of each
floating separately, and BrowseView/RoomView reserve TAB_RAIL_CLEARANCE
of right-side padding so a full-width row or grid never renders under
it - verified against the real library, where dense rows (e.g. Conni's
82 audiobooks) previously butted right up against the rail.
Also gitignore the real (non-.example) tippen-curriculum.yml and
tippen-progress.json, matching config.yml's own privacy treatment,
since the curriculum now contains real reward paths into a personal
library.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Moves tippen from a standalone app into web/ as a third tab (audio player /
smarthome / typing), replacing the old single room-toggle corner button with a
vertical icon tab rail. Curriculum and progress now come from the backend
(musicmouse/tippen/*) instead of a build-time YAML import and localStorage.
Adds reward-driven lock rendering: Cover/BrowseView/AlbumModal show a question
mark for locked albums/tracks with a hint on what unlocks them, and
ResultSheet gets a new unlock-animation block alongside the existing
lesson-unlock and aquarium-creature celebrations.
CSS from the two apps is merged carefully: identical rules (bubble/card/
key-cap/view-enter/backdrop-enter and their keyframes) are shared as-is,
while rules that bake in each app's own hue are kept separate under a
`tp-` prefix and scoped to the typing tab's own .tp-stage wrapper, so
neither app's look bleeds into the other's.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Moves the typing app's lesson plan and progress from client-side YAML/localStorage
into the backend, and adds a reward system that ties passing a lesson to unlocking
part of the music library. Lock state is always recomputed live from curriculum x
progress x the live library, never persisted separately.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Rename all German identifiers, types, mode ids, file names, CSS classes
and data-attributes to English throughout tippen/src; only user-facing
text (lesson titles, word lists, labels, spoken praise) stays German.
- Add a word/nonsense-word list to the "Übung: die Grundstellung" home-row
lesson in the curriculum.
- Remove the unused HandHint component and speech.ts, and carry forward
the in-progress App.tsx/component/generator/progress edits from other
sessions.
- Refresh the regenerated music-library cache index.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
_extract_audio wrote transcoded audio to a .tmp temp path, so ffmpeg
couldn't guess a muxer from the filename and aborted on every video
episode whose source was still reachable. Pass -f mp3 explicitly instead
of relying on the extension.
Separately, a permanently 404'd episode was retried on every startup and
every 6-hour poll forever, since nothing remembered past failures. Track
failed downloads per show folder with a 7-day backoff before retrying.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- podcast_feeds.py: fetch per-episode cover art from a feed's itunes:image
(when it's genuinely distinct from the channel image) or, failing that,
from the og:image on the episode's own linked page; extract audio from
video-only enclosures via ffmpeg; match podcast-dl's filename convention
(illegal characters become "_" instead of being dropped) so enabling
feed.txt on an already-downloaded show doesn't re-download its back catalog
- scanner.py: _cover_for_episode now checks for a same-stem sidecar cover
image before falling back to embedded ID3 art and the shared folder cover
- scanner.py/__init__.py: fixed a bug where an album folder nested one level
deeper than usual (an age-range grouping folder, say) was mistaken for an
empty album and skipped; added periodic progress logging for long scans
and analysis passes
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds a TCP client for lircd's classic protocol: play/pause/next/prev/
volume/mute map to the same intents every other front-end already
emits, and number keys 0-9 play an assigned album/audiobook from the
start or a podcast show's newest episode, resolved fresh on every
press. The mapping is configured in config.yml and editable from the
frontend: a small "Taste zuweisen" button on the play screen (or the
A+digit keyboard shortcut) opens a 10-key picker to assign whatever is
currently playing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A show folder under Kinderpodcasts/ opts in by containing a feed.txt marker
naming its RSS feed. A new PodcastFeedService polls every such feed every 6
hours (and once at startup), downloads any episode not already on disk using
the existing YYYYMMDD - Title.ext convention, and triggers the same
rescan-and-broadcast sequence "Bibliothek neu einlesen" already uses - now
shared via App.rescan_library() instead of duplicated. A show with no
feed.txt is untouched, so there is no new config section for this.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The background worker now runs a real librosa analyzer (tempo, beat grid,
per-second energy/valence curves) instead of only the null baseline, kept
behind build_analyzer() so a plain checkout without the analysis extra still
runs fine. The web player reads that per-track analysis and drives a new
animated "Ambience" background (bubbles, colour, current) that reacts to the
beat and the mood curve as the track plays, plus a debug overlay for tuning
it. Also adds a one-off script to backfill podcast cover art from iTunes.
Implements the room-control page from the design mockup: a scenes row above cards
for shutters, color lamps, and brightness-only lamps, all driven by a new
`general.ha` config section (server URL, token, ordered device/scene lists).
The backend proxies every Home Assistant call server-side (GET/POST /api/ha/...)
rather than the browser calling Home Assistant directly, so the long-lived token
never leaves the LAN device and Home Assistant's own CORS settings don't need to
know about musicmouse at all. Card kind (shutter/color/brightness-only) is
inferred at runtime from what Home Assistant reports about each entity, not
configured explicitly.
Also stops tracking python-backend/config.yml, which had drifted into the repo
despite its own header saying it shouldn't be - it now carries real credentials
locally and needs to stay untracked.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>