- Python 85%
- TypeScript 12.6%
- CSS 1.8%
- JavaScript 0.5%
- HTML 0.1%
Split the track into ~45s windows and force-align each independently (fresh ASR + whisperx per chunk, clock resets, no accumulation), then stitch with one monotonic pass. Fixes both drifting mechanisms: the whole-track ASR stamps that became whisperx's hard crop windows, and the global start>=prev_end clamp cascade that lifted every later line. - plan_chunks: greedy line->window assignment via anchor midpoints (linear-in-word fallback), never splits a lyric line - align_chunked: crop +margin_ms around each window, chunk-local anchors, +offset rebias, weighted quality, dominant-engine report - WhisperXEngine caches the align model across chunk calls (whisperx does not cache load_align_model internally) - PipelineSettings.chunk_ms (default 45000) + chunk_margin_ms (2000); generate.py falls back to single-pass for short songs / user LRC anchors / non-whisperx engines plan_chunks bug: drifted late tail anchors (beyond song duration) combined with linear fallback for unanchored following lines produced non-monotonic centers where lo_b>hi_b, silently dropping the final lyric lines and leaving them as 1000ms-stepped empty phantoms. Fixed by clamping both centers and widening the crop so drifted-tail chunks are always emitted. 12 chunked unit tests; 238 total green, ruff+mypy clean. Verified live: 66/66 lines, final chorus 'It's Halloween.' genuinely at 198s (was 210-212s phantoms). |
||
|---|---|---|
| .forgejo/workflows | ||
| backend | ||
| docker | ||
| docs | ||
| frontend | ||
| .dockerignore | ||
| .gitignore | ||
| AGENTS.md | ||
| LICENSE | ||
| README.md | ||
SingerMaker
Open-source, self-hosted alternative to AutoLyrics: upload a song and its
lyrics, get back an xLights .xtiming singing-face track (phrases,
words and Preston-Blair mouth-shape phonemes, time-aligned to the actual
vocals) that you import straight into your sequence — so your prop's mouth
movements hit the words.
song.mp3 + lyrics.txt ──► Demucs (vocals) ──► forced alignment (WhisperX)
└─► 3-layer timing model ─► snap to 25/50 ms grid ─► out.xtiming / out.pgo
Features
- Automatic word timing from the supplied lyrics — WhisperX forced alignment (primary, ±150 ms class accuracy), faster-whisper word stamps (fallback) and an energy-anchored heuristic that never fails the job.
- Exact mouth shapes: ARPAbet → xLights face labels
(
AI, E, FV, L, MBP, O, U, WQ, etc, rest) via the CMUdict + Papagayo Preston-Blair tables, the same xLights itself uses. - LRC support: paste synced lyrics (
[mm:ss.xx]) and each line's timestamp becomes an alignment anchor — tighter and faster. - Vocal-stem upload to skip separation, per-job engine/grid settings, app-level user pronunciation dictionary for made-up words.
.xtimingand.pgoexports — verified against xLights' own import/export code paths.- CPU-only by default (a GPU profile is available). Docker Compose deployment, single-user, no accounts, your music never leaves the box.
Quick start (Docker)
git clone <repository-url> && cd singermaker
docker compose -f docker/docker-compose.yml up -d --build
# open http://localhost:8844
Create a project → upload audio + paste lyrics → Generate →
Download .xtiming. Then see
docs/xlights-import-guide.md for the 60-second
xLights side.
How it works
ffmpegdecodes your audio to 44.1 kHz WAV.- Demucs separates vocals (skipped if you upload your own stem, or set None).
- A forced-alignment engine times every lyric word against the vocals.
- Words are converted to phonemes: user dictionary → CMUdict → extended dictionary → espeak-ng G2P; unknown words are reported so you can add them.
- ARPAbet phonemes map to the ten xLights mouth shapes; word and phoneme spans are distributed inside each word like xLights' own breakdown.
- Everything snaps to your chosen grid (25/50 ms) and the 3-layer
.xtimingXML is written.
Fine-tuning is expected and easy: xLights' timing tracks are fully editable — drag a word or regenerate a word's phonemes there. (See docs/alignment-tuning.md for making the raw alignment better first.)
Development
cd backend && uv sync --extra dev --extra web --extra ml # CPU torch deps
uv run uvicorn singermaker.api.app:app --reload --port 8000
cd ../frontend && npm install && npm run dev # http://localhost:5173
| Task | Command |
|---|---|
| Backend unit/API tests | cd backend && uv run pytest tests/unit |
| Backend lint / types | uv run ruff check . / uv run mypy singermaker |
| Real-model integration (downloads models once) | uv run pytest tests/real -m real |
| Frontend unit tests / typecheck / build | cd frontend && npm test && npm run typecheck && npm run build |
| Browser end-to-end | npm run test:e2e (needs Playwright browsers) |
| CLI pipeline | uv run singermaker generate song.mp3 lyrics.txt -o out.xtiming --engine heuristic |
Architecture
Full details in docs/architecture.md and docs/installation.md.
nginx (SPA + /api proxy) ── FastAPI ── Redis ── Celery workers (CPU torch)
│ │
SQLite + files model-cache volume
Configuration
| Variable | Default | Meaning |
|---|---|---|
SINGERMAKER_DATA_DIR |
data/ |
SQLite DB, uploads, outputs |
SINGERMAKER_QUEUE |
eager |
eager (threads, dev) or celery (Redis) |
SINGERMAKER_BROKER_URL |
redis://redis:6379/0 |
Celery broker |
HF_HOME / TORCH_HOME |
container: /cache/... |
model download cache |
OMP_NUM_THREADS |
host default | torch CPU thread cap per job |
License
GPL-3.0-or-later. Bundled data: CMUdict (BSD-style, Carnegie Mellon),
ARPAbet→mouth mapping derived from Papagayo's phoneme_mapping (GPL,
Preston Blair shapes); xLights is a trademark of its authors — this project
is unaffiliated, just compatible. See data/README.md for provenance.