muvid.align¶
Lyric → audio alignment.
We have:
a transcript (Scribe / faster-whisper) with word-level (text, start, end)
a user-edited
LyricsDocwith section labels + line text + optional manual line-start anchors
We want a lacing store with three tiers (sections, lines, words) so
the rest of the system can ask “which lines fall in shot X” without
re-implementing interval math.
Strategy (greedy token-match):
Tokenize each lyric line into normalized words.
Walk the transcript word stream once, assigning each transcript word to the next unmatched lyric word that matches (case- and punctuation-insensitive). Tolerate small mismatches (transcript word missing in lyrics, vice versa) with a small lookahead window.
From the matched words, derive line
[start, end]as(first_matched_word.start, last_matched_word.end). If a line has no matched words, fall back to the user’s manual anchor (if any), then to a linear interpolation between neighboring anchored lines.Sections inherit
[start, end]from the union of their lines; if the user provided explicitstart_s/end_son a section, those win.
The result is written as a lacing.SqliteStore so it round-trips and
can be edited by other tools.
Module Attributes
Built-in aligner names. |
|
|
Functions
|
Align a |
|
Greedy token-match against a word-timestamped transcript. |
|
Singing-specific alignment (STARS / similar). |
|
Use line-level timings the caller has already determined. |
|
Local-only aligner that re-uses |
|
|
Return all registered aligner names, sorted. |
|
|
Register an aligner under |
|
Write alignment to a |
Classes
|
One row in the aligner registry. |
|
|
|
|
|
|
|
One alignment between a lyric token and a transcript word. |
- muvid.align.AlignerName¶
Built-in aligner names.
align_lyrics(aligner=...)accepts these plus any name added later viaregister_aligner().
- class muvid.align.AlignerSpec(*, name, description, fn, requires=())[source]¶
Bases:
objectOne row in the aligner registry.
- class muvid.align.AlignmentResult(*, sections)[source]¶
Bases:
object
- class muvid.align.LineAlignment(*, line_index, section_label, text, start_s, end_s, word_alignments)[source]¶
Bases:
object
- muvid.align.WHISPERX_LITE_MODEL_SIZE = 'small'¶
tinyis fast but, on sung/repetitive audio, produces a transcript so garbled that the greedy matcher walks past whole minutes before finding a plausible match (muvid#101: measured 53 s late on a repeated-word lyric). The lyric-video path is happy to wait, so default to a materially more accurate size; override per call or via this env var.
- class muvid.align.WordAlignment(*, line_index, token_index, text, start_s, end_s, confidence=1.0)[source]¶
Bases:
objectOne alignment between a lyric token and a transcript word.
- muvid.align.align_lyrics(lyrics, transcript, *, duration_s=0.0, aligner='scribe-greedy', **aligner_kwargs)[source]¶
Align a
LyricsDocto a transcript.- Parameters:
lyrics (
LyricsDoc) – User-edited lyrics document (the ground-truth text).transcript (
dict) – Aligner-specific input. For"scribe-greedy"this is a Scribe / faster-whisper response withwords: [...]. For"user"this can be empty if you passuser_line_timings=.... For"whisperx-lite"the transcript is ignored — the aligner runs on the audio directly (path passed viaaudio_path=).duration_s (
float) – Used to extrapolate end times for lines with no matched words and no later anchor.aligner (
str) – Name of a registered aligner. Seelist_aligners().**aligner_kwargs – Forwarded to the aligner.
- Return type:
- Returns:
- muvid.align.align_scribe_greedy(lyrics, transcript, *, duration_s=0.0, lookahead=6)[source]¶
Greedy token-match against a word-timestamped transcript.
Cheap, network-only (assumes the transcript came from Scribe or similar). Tolerates 1-character mishears between sung and written text.
duration_sis used only when extrapolating end times.- Return type:
- muvid.align.align_stars(lyrics, transcript, *, duration_s=0.0, **kwargs)[source]¶
Singing-specific alignment (STARS / similar). Not yet implemented.
See
misc/docs/alignment_references.mdfor the literature motivating this aligner. The slot exists so callers can already writealigner="stars"and get a clear NotImplementedError.- Return type:
- muvid.align.align_user_provided(lyrics, transcript, *, duration_s=0.0, user_line_timings=None)[source]¶
Use line-level timings the caller has already determined.
Useful when the user has hand-anchored every line, or when an external aligner has produced
line_index → (start, end)timings and you don’t want any token-matching.user_line_timingsis a list of{"line_index": int, "start_s": float, "end_s": float}. If omitted, this aligner falls back to the manual anchors already onlyrics(the// 12.5end-of-line markers inlyrics.md).- Return type:
- muvid.align.align_whisperx_lite(lyrics, transcript, *, duration_s=0.0, audio_path=None, model_size='small', lookahead=6)[source]¶
Local-only aligner that re-uses
faster-whisper(no API).The
transcriptargument is ignored whenaudio_pathis given: we re-transcribe locally with faster-whisper and then run the same greedy match used byscribe-greedy. Whenaudio_pathis not given, we fall through and just use the suppliedtranscript.Trade-offs vs
scribe-greedy: free + offline; slower; less accurate on singing; needs torch + faster-whisper installed.model_sizedefaults toWHISPERX_LITE_MODEL_SIZE("small", overridable viaMUVID_WHISPERX_LITE_MODEL_SIZE) rather than faster-whisper’s own"tiny"default — see muvid#101.- Return type:
- muvid.align.register_aligner(name, fn, *, description, requires=())[source]¶
Register an aligner under
name.The function should accept
(lyrics, transcript, *, duration_s, \*\*kw) -> AlignmentResult. Extra keyword arguments forwarded byalign_lyrics()are passed through.- Return type: