94 lines
4.1 KiB
Markdown
94 lines
4.1 KiB
Markdown
---
|
||
name: lyrics-lookup
|
||
description: Find and inject lyrics for songs saved on worship.hesed.sbs that have none — LRCLIB first (free, often synced), then an agy web search (untimed), then local faster-whisper transcription. Use when the user asks to add/fill/inject lyrics, says a song has no lyrics, or asks to transcribe saved songs. For replacing lyrics that are already there but wrong, use lyrics-regenerate instead.
|
||
---
|
||
|
||
# Add lyrics to songs that don't have any
|
||
|
||
Shared lyrics live on the server (`video_notes`, kind `lyrics`) and every user of
|
||
worship.hesed.sbs sees the same ones. Every save keeps the previous version as a
|
||
revision (`video_note_revs`), so nothing is destroyed — but **never overwrite
|
||
existing lyrics from this skill**; that is `lyrics-regenerate`'s job, and it takes
|
||
a backup first.
|
||
|
||
## Sources, cheapest and best first
|
||
|
||
| Order | Source | Timed? | Cost | Where |
|
||
|---|---|---|---|---|
|
||
| 1 | **LRCLIB** | usually synced | free, no key | server-side: `POST /api/notes/<id>/lyrics/web` |
|
||
| 2 | **agy web search** (Genius/AZLyrics/hymnary…) | no — untimed | flat-rate agy | `web_lyrics.py --agy` |
|
||
| 3 | **faster-whisper** on this machine | synced (word timings) | free, ~0.65× real time | `auto_lyrics.py` |
|
||
|
||
Never reach for ElevenLabs Scribe or any credit-billed ASR — the user asked for
|
||
free transcription only.
|
||
|
||
## Credentials
|
||
|
||
Writing needs admin. Either env var works for every script:
|
||
|
||
```bash
|
||
export YTP_ADMIN_PASSWORD='…' # the /admin password, see the ytplayer-admin-password memory
|
||
export YTP_TOKEN='ytp_…' # or an API token minted at /admin
|
||
```
|
||
|
||
The password is also vault secret `YTPLAYER_ADMIN_PASSWORD`
|
||
(`~/development/.secrets/ytplayer-admin.env`) — use it through
|
||
`vault__secret_exec` when you must not print it.
|
||
|
||
## Run it
|
||
|
||
```bash
|
||
cd ~/development/personal/ytplayer
|
||
|
||
# 1) LRCLIB for everything that has no lyrics, then agy for the leftovers
|
||
YTP_ADMIN_PASSWORD=… python3 scripts/lyrics/web_lyrics.py --missing --agy
|
||
|
||
# just look, change nothing
|
||
YTP_ADMIN_PASSWORD=… python3 scripts/lyrics/web_lyrics.py --missing --dry-run
|
||
|
||
# 2) transcribe what the web doesn't have (needs the venv below)
|
||
YTP_ADMIN_PASSWORD=… ~/.local/share/lyrics-asr/.venv/bin/python \
|
||
scripts/lyrics/auto_lyrics.py --missing
|
||
```
|
||
|
||
`--missing` lists songs from `GET /api/admin/media` and keeps only the ones with
|
||
`lyricsLines == 0`. Use `--ids A,B,C` to aim at specific videos. Ids are YouTube
|
||
ids (11 chars) or uploads (`upl_<12 hex>`).
|
||
|
||
Whisper venv, once:
|
||
|
||
```bash
|
||
uv venv ~/.local/share/lyrics-asr/.venv --python 3.12
|
||
~/.local/share/lyrics-asr/.venv/bin/pip install -r scripts/lyrics/requirements.txt
|
||
```
|
||
|
||
The same transcriber also runs unattended as the `lyrics-worker` container in
|
||
`docker-compose.yml` (`auto_lyrics.py --watch 300 --state …`), which is what gives
|
||
newly downloaded songs lyrics "at their own pace". It authenticates with
|
||
`LYRICS_WORKER_TOKEN`.
|
||
|
||
## What lands in the database
|
||
|
||
```jsonc
|
||
{ "lines": [{ "t": 12.4, "text": "Holy You are", "kind": "line" }], // t null = untimed
|
||
"tags": ["from LRCLIB (synced)"], // provenance — always tag
|
||
"offset": 0 }
|
||
```
|
||
|
||
Tags in use: `from LRCLIB (synced)` / `(plain text)`, `from the web (untimed) — check and Tap-sync`,
|
||
`auto-transcribed`, `from the file (synced)` (embedded in an admin upload).
|
||
The tag is how later runs tell machine lyrics from published ones — keep it accurate.
|
||
|
||
## Things that bite
|
||
|
||
- **LRCLIB 503/429** on bursts. `lrclib_regen.py:http_json` already retries with
|
||
backoff; if you write new code against LRCLIB, copy it. Keep ~0.4 s between calls.
|
||
- **Wrong-artist matches.** A common title ("Still") matches another genre's song.
|
||
`artist_ok()` in `lrclib_regen.py` is the verification — port it rather than
|
||
trusting a title+duration hit.
|
||
- **Karaoke / minus-one tracks have no vocals.** Whisper returns noise; the script
|
||
calls them instrumental below `--min-words 25` and skips them. That's correct.
|
||
- **Untimed lyrics are fine.** The app shows them as a plain scrolling list and the
|
||
user can Tap-sync them in the admin lyric editor.
|
||
- **Never redistribute.** These are third-party lyrics in a private library.
|