Skip to main content

Forced-alignment library and CLI that timestamps lyrics against an audio file, exporting LRC

Project description

syncalong

PyPI version Python versions Documentation CI License: MIT

A Python library and CLI tool that aligns plain-text lyrics to an audio file and outputs timestamped lyrics in LRC format.

It uses OpenAI's Whisper to transcribe the audio with word-level timestamps, then runs a dynamic-programming sequence alignment to map those words back onto your lyrics.

📖 Full documentation: https://syncalong.readthedocs.io/

Prerequisites

  • Python 3.9+
  • ffmpeg — required by Whisper for audio decoding. Install via your package manager (apt install ffmpeg, brew install ffmpeg, etc.)

Installation

pip install syncalong

Or from a checkout, in editable / development mode:

pip install -e ".[dev,docs]"

Optional: vocal separation

For better results on studio recordings (where background music may confuse the speech model), install with the vocal-separation extra:

pip install "syncalong[vocal-separation]"

This adds Demucs, which isolates the vocal track before transcription.

Usage

syncalong LYRICS_FILE AUDIO_FILE [options]

Examples

Basic usage — outputs LRC to stdout:

syncalong song.txt song.mp3

Save to a file:

syncalong song.txt song.mp3 > song.lrc

Use a larger Whisper model for better accuracy:

syncalong song.txt song.wav -m medium

Isolate vocals first (recommended for studio tracks):

syncalong song.txt song.flac --separate-vocals

Specify language (skips auto-detection):

syncalong lied.txt lied.mp3 -l de

Options

Flag Description Default
-m, --model Whisper model: tiny, base, small, medium, large, turbo, or variants like small.en (English-only) and large-v3. base
-l, --language Language code (e.g. en, de, ja). Auto-detected if omitted. auto
--separate-vocals Run Demucs to isolate vocals before transcription. off
--no-lyrics-prompt Don't feed the lyrics to Whisper as a decoding prompt. off
--threshold Minimum fuzzy-match score (0–100) to accept a word alignment. 55

Lyrics file format

One lyric line per text line. Blank lines are preserved as instrumental breaks. Section headers in brackets (like [Chorus] or (Bridge)) are detected and kept as structural markers.

[Verse 1]
I walk a lonely road
The only one that I have ever known

[Chorus]
My shadow's the only one that walks beside me

Output format

Standard LRC with [mm:ss.xx] timestamps:

[00:12.34] I walk a lonely road
[00:15.67] The only one that I have ever known

Use as a library

syncalong is also an importable package, so you can embed alignment in your own code or batch-process a whole album while loading the Whisper model only once. import syncalong is cheap — the heavy Whisper import is deferred until a model is actually loaded.

Align a single song

import syncalong
from pathlib import Path

tx = syncalong.Transcriber(model_name="base")   # load the model once

res = syncalong.align(Path("lyrics.txt"), "song.mp3", transcriber=tx)

print(res.lrc)            # the LRC document, as a string
res.timed_lines           # [(LyricLine, 12.3), (LyricLine, None), ...]
res.matched, res.total    # e.g. (28, 30) — lines that got a timestamp / all lines

align() accepts lyrics as a pathlib.Path (read from a file), a str of lyrics text, or a pre-parsed list[LyricLine]; audio is a path (str or pathlib.Path). Other keyword arguments: transcriber (pass a pre-loaded Transcriber to reuse across songs), plus the CLI-mirroring model_name, language, use_lyrics_prompt, threshold, and separate_vocals. If you omit transcriber, align() loads a model itself from model_name.

Batch-process an album (reuse the model)

Loading a Whisper model takes seconds, so a long-running job should create one Transcriber and reuse it. align_to_lrc() is a convenience wrapper returning just the LRC string (equivalent to align(...).lrc):

import syncalong
from pathlib import Path

tx = syncalong.Transcriber(model_name="small")   # loaded once for the whole run

for audio in Path("album/").glob("*.flac"):
    lyrics = audio.with_suffix(".txt")
    lrc = syncalong.align_to_lrc(lyrics, audio, transcriber=tx)
    audio.with_suffix(".lrc").write_text(lrc, encoding="utf-8")

Lyrics already in memory? Pass them as a plain string instead of a path:

text = "I walk a lonely road\nThe only one that I have ever known\n"
res = syncalong.align(text, "song.mp3", transcriber=tx)

Note: separate_vocals=True requires the optional vocal-separation extra (pip install syncalong[vocal-separation]). Passing it without demucs installed raises ModuleNotFoundError.

How it works

  1. Transcribe — Whisper runs on the audio file with word_timestamps=True, producing a list of (word, start_time, end_time) tuples. The beginning of your lyrics is passed to the decoder as an initial_prompt to bias it toward the correct words (disable with --no-lyrics-prompt), and conditioning on previously decoded text is turned off to avoid the repetition loops Whisper is prone to on music.

  2. Normalize — Both the lyrics and the transcript are lowercased and stripped of punctuation so that matching is case/punctuation-insensitive.

  3. Align — A Needleman–Wunsch-style dynamic programming algorithm finds the optimal monotonic mapping between lyric words and transcript words. Word similarity is scored with rapidfuzz (Levenshtein ratio), so minor mishearings by Whisper are tolerated.

  4. Map to lines — Each lyric line receives the timestamp of its earliest matched word. Small gaps between matched lines are filled with linear interpolation, and unmatched lines before the first match or after the last one are extrapolated from the transcript's start and end times, so every sung line gets a tag.

  5. Format — The timed lines are printed in LRC format to stdout.

Tips for best results

  • Use --separate-vocals on any track with significant instrumentation.
  • Bump up the model size (-m small or -m medium) if alignment is poor. For English songs the .en variants (-m small.en) are often more accurate at the same speed. The large model is the most accurate but is slow without a GPU; turbo is a good speed/quality compromise.
  • Check your lyrics — extra or missing words relative to the actual performance will reduce alignment quality. The closer the lyrics match what's actually sung, the better.
  • GPU acceleration — if you have a CUDA-capable GPU and PyTorch is installed with CUDA support, Whisper will use it automatically.

Documentation

Full guides and the auto-generated API reference live at https://syncalong.readthedocs.io/:

Contributing

Contributions are welcome. Install the dev tooling with pip install -e ".[dev,docs]" and make sure the quality gate passes before opening a PR:

pytest
ruff check .
black --check .
pyright src

See the contributing guide for the full workflow, including how releases are cut from git tags.

License

Released under the MIT License.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

syncalong-0.1.0.tar.gz (44.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

syncalong-0.1.0-py3-none-any.whl (21.8 kB view details)

Uploaded Python 3

File details

Details for the file syncalong-0.1.0.tar.gz.

File metadata

  • Download URL: syncalong-0.1.0.tar.gz
  • Upload date:
  • Size: 44.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for syncalong-0.1.0.tar.gz
Algorithm Hash digest
SHA256 38deda4c1a544ca0be88547cbf44a45189eb011b15a0968051bc6ed1c84d4696
MD5 16def54f9cf6812ab6cde7b66e18b65e
BLAKE2b-256 d69433b8cb0c0219895160d8933a42d527b892d70c0245ed008813d36a514ebf

See more details on using hashes here.

Provenance

The following attestation bundles were made for syncalong-0.1.0.tar.gz:

Publisher: release.yml on wickeddoc/syncalong

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file syncalong-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: syncalong-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 21.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for syncalong-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 37b40f086889b6ef6abc6eb3f3e44c5c8038a00390caf19e58fe595884b2d91d
MD5 ce1901f1cad405671cf1a3744a983b67
BLAKE2b-256 9b90f50df494086ea86ad682fdf439fcb9134130244f5a2c87a0a2875d4e0d2c

See more details on using hashes here.

Provenance

The following attestation bundles were made for syncalong-0.1.0-py3-none-any.whl:

Publisher: release.yml on wickeddoc/syncalong

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page