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-1.0.0.tar.gz (44.2 kB view details)

Uploaded Source

Built Distribution

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

syncalong-1.0.0-py3-none-any.whl (20.9 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for syncalong-1.0.0.tar.gz
Algorithm Hash digest
SHA256 6e4412172504ad01e80306f8cb0007a9e0351e3f5c2f9d58e3105500b38c1173
MD5 e3e251e9d977ee9ee9a4b915140a9dfe
BLAKE2b-256 c96e2dbb239702890d25cc0c43ec7e9875ea910b11a047dd8d8dd1131dc35ce6

See more details on using hashes here.

Provenance

The following attestation bundles were made for syncalong-1.0.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-1.0.0-py3-none-any.whl.

File metadata

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

File hashes

Hashes for syncalong-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 35f316442186b4137a5ce9c24f54efec9cceaedbc1e11ddbf4e8e0bdddf6ac63
MD5 b24a4d53cea9cd71539969b55de6e57f
BLAKE2b-256 c7b933190b92dabc9b3b36a3a8bd642f964cdc5127cbd8ce726b6554132e4e06

See more details on using hashes here.

Provenance

The following attestation bundles were made for syncalong-1.0.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