Skip to content

syncalong.lyrics

Parse a plain-text lyrics file (or in-memory text) into structured LyricLine objects, and build a Whisper decoding prompt from the lyrics.

lyrics

Parse a plain-text lyrics file into structured lines.

LyricLine dataclass

LyricLine(index: int, raw: str, words: list[str], is_blank: bool = False)

A single line from the lyrics file.

Attributes:

Name Type Description
index int

0-based line position in the file.

raw str

Original text as it appeared.

words list[str]

Normalized, split tokens used for matching.

is_blank bool

True for blank or instrumental/section-header lines.

lyrics_prompt

lyrics_prompt(lines: list[LyricLine], max_chars: int = 600) -> str

Build a Whisper initial_prompt from the original lyric text.

Feeding the known lyrics to the decoder biases transcription toward the correct words. Whisper keeps only a limited number of prompt tokens (and keeps the last ones), so just the beginning of the song is passed, truncated at a word boundary.

Parameters:

Name Type Description Default
lines list[LyricLine]

Parsed lyric lines.

required
max_chars int

Maximum prompt length in characters.

600

Returns:

Type Description
str

The prompt text (possibly truncated at a word boundary).

Source code in src/syncalong/lyrics.py
def lyrics_prompt(lines: list[LyricLine], max_chars: int = 600) -> str:
    """Build a Whisper ``initial_prompt`` from the original lyric text.

    Feeding the known lyrics to the decoder biases transcription toward the
    correct words. Whisper keeps only a limited number of prompt tokens
    (and keeps the *last* ones), so just the beginning of the song is passed,
    truncated at a word boundary.

    Args:
        lines: Parsed lyric lines.
        max_chars: Maximum prompt length in characters.

    Returns:
        The prompt text (possibly truncated at a word boundary).
    """
    text = " ".join(line.raw for line in lines if not line.is_blank)
    if len(text) <= max_chars:
        return text
    cut = text.rfind(" ", 0, max_chars + 1)
    if cut == -1:
        cut = max_chars
    return text[:cut]

parse_lyrics_text

parse_lyrics_text(text: str) -> list[LyricLine]

Parse raw lyrics text into structured lines.

Blank lines are preserved (they represent instrumental breaks) but carry no words to match. Section headers like [Chorus] or (Bridge) are treated as blank lines so spacing is preserved.

Parameters:

Name Type Description Default
text str

The full lyrics as one string, lines separated by newlines.

required

Returns:

Name Type Description
One list[LyricLine]

class:LyricLine per input line, in order.

Source code in src/syncalong/lyrics.py
def parse_lyrics_text(text: str) -> list[LyricLine]:
    """Parse raw lyrics text into structured lines.

    Blank lines are preserved (they represent instrumental breaks) but carry
    no words to match. Section headers like ``[Chorus]`` or ``(Bridge)`` are
    treated as blank lines so spacing is preserved.

    Args:
        text: The full lyrics as one string, lines separated by newlines.

    Returns:
        One :class:`LyricLine` per input line, in order.
    """
    lines: list[LyricLine] = []
    for idx, raw in enumerate(text.splitlines()):
        stripped = raw.strip()

        if re.fullmatch(r"[\[\(].*[\]\)]", stripped):
            lines.append(LyricLine(index=idx, raw=stripped, words=[], is_blank=True))
            continue

        if not stripped:
            lines.append(LyricLine(index=idx, raw="", words=[], is_blank=True))
            continue

        norm = normalize(stripped)
        words = norm.split() if norm else []
        lines.append(
            LyricLine(
                index=idx,
                raw=stripped,
                words=words,
                is_blank=len(words) == 0,
            )
        )
    return lines

parse_lyrics

parse_lyrics(path: Path) -> list[LyricLine]

Read a lyrics text file and parse it into structured lines.

Thin wrapper over :func:parse_lyrics_text that reads the file first.

Parameters:

Name Type Description Default
path Path

Path to a UTF-8 plain-text lyrics file.

required

Returns:

Name Type Description
One list[LyricLine]

class:LyricLine per line in the file, in order.

Source code in src/syncalong/lyrics.py
def parse_lyrics(path: Path) -> list[LyricLine]:
    """Read a lyrics text file and parse it into structured lines.

    Thin wrapper over :func:`parse_lyrics_text` that reads the file first.

    Args:
        path: Path to a UTF-8 plain-text lyrics file.

    Returns:
        One :class:`LyricLine` per line in the file, in order.
    """
    return parse_lyrics_text(path.read_text(encoding="utf-8"))