Skip to main content

Generate VTT subtitles with timestamps snapped to voice onset using WhisperX forced alignment

Project description

vtt-synced-voice

Generate VTT subtitles with timestamps precisely snapped to voice onset using WhisperX forced alignment.

日本語版 README はこちら

Features

  • Word-level timestamp alignment via WhisperX (Whisper + wav2vec2 forced alignment)
  • FCP-style peak normalization for recording-level-independent silence detection
  • Bidirectional onset detection: backward scan when CTC start is inside voice, forward scan when in silence
  • Guaranteed silence gap between cues (100ms minimum)
  • Sentence-level cue merging: over-split cues are merged into natural sentence units
    • Japanese: morphological analysis (Janome) detects sentence-ending verb forms
    • Other languages: period / exclamation mark / question mark detection (with abbreviation exclusions)
  • Voice-only output (voice_only=True): plain .txt without timestamps, for adding subtitles to cut-edited video

Installation

1. Install ffmpeg

macOS

brew install ffmpeg

Windows

winget install ffmpeg

Linux (Debian / Ubuntu)

sudo apt install ffmpeg

2. Install PyTorch

WhisperX runs on PyTorch. GPU (CUDA) is significantly faster than CPU for transcription (roughly 10–20x). Install the build that matches your environment.

macOS — CPU only (no CUDA support on macOS)

pip install torch torchaudio

Windows — CUDA 12.8 (recommended for RTX 30xx / 40xx and newer)

pip install torch torchaudio --index-url https://download.pytorch.org/whl/cu128

Windows — CUDA 11.8 (for older GPUs such as GTX 10xx / 20xx)

pip install torch torchaudio --index-url https://download.pytorch.org/whl/cu118

Windows — CPU only (no NVIDIA GPU)

pip install torch torchaudio --index-url https://download.pytorch.org/whl/cpu

Linux — CUDA 12.8 (recommended for RTX 30xx / 40xx and newer)

pip install torch torchaudio --index-url https://download.pytorch.org/whl/cu128

Linux — CUDA 11.8 (for older GPUs such as GTX 10xx / 20xx)

pip install torch torchaudio --index-url https://download.pytorch.org/whl/cu118

Linux — CPU only (no NVIDIA GPU)

pip install torch torchaudio --index-url https://download.pytorch.org/whl/cpu

To check your CUDA version: nvidia-smi (Windows/Linux). If you don't have an NVIDIA GPU, use the CPU build. For the full list of builds, see the PyTorch installation guide.

3. Install vtt-synced-voice

pip install vtt-synced-voice

Usage

from vtt_synced_voice import transcribe

transcribe(
    audio_file="sample.m4a",
    output_file="output.vtt",
    language="ja",            # "ja" / "en" / etc.
    model="large-v2",         # "small" / "medium" / "large-v2"
    device="cpu",             # "cpu" / "cuda"
    margin_before=0.066,      # seconds to shift start earlier after onset detection
    margin_after=0.0,         # seconds to extend end
    silence_threshold=0.001,  # RMS threshold after peak normalization
    merge_sentences=True,     # merge over-split cues into sentence units (default: True)
    voice_only=False,          # output plain .txt without timestamps (default: False)
    verbose=True,
)

max_gap_seconds

Controls how finely the audio is split into cues before sentence merging. When the silence gap between two words exceeds this value, a new cue begins.

  • Default: 0.4 seconds
  • Slow speaker / many pauses: increase (e.g. 0.40.5)
  • Fast speaker / machine-gun delivery: decrease (e.g. 0.10.2)
  • Recommended range: 0.010.5

After splitting, merge_sentences=True merges the resulting cues back into natural sentence units, so a smaller max_gap_seconds produces more fine-grained intermediate cues that are then merged — it does not make the final output more fragmented.

Intended use: raw audio before cut editing

This package is designed to be applied to raw, unedited audio before cut editing in Final Cut Pro:

Raw audio → vtt-synced-voice → VTT → Import into FCP → Cut edit using VTT timestamps

Do not apply to audio that has already been cut-edited. Cut-edited audio has had silence removed between sentences, which breaks two core assumptions:

  1. Cue splitting by gap: The algorithm splits cues where silence gaps exceed max_gap_seconds (default 0.4s). In cut-edited audio, inter-sentence silences have been removed, so words from different sentences appear adjacent. The gap threshold finds no boundaries and merges unrelated sentences into a single cue.

  2. Voice onset detection: find_onset() scans backward from the CTC timestamp to locate the silence→voice boundary. In cut-edited audio, that silence no longer exists, so the scan finds no boundary and onset detection loses its accuracy.

silence_threshold

After peak normalization, complete silence ≈ 0.0 and voiced speech ≈ 0.05–1.0. The default 0.001 works well for clean recordings with no background noise. Use verbose=True to inspect onset detection results and adjust if needed.

You can also apply sentence merging to an existing VTT file:

from vtt_synced_voice import read_vtt, merge_cues, write_vtt

cues = read_vtt("input.vtt")
merged = merge_cues(cues, language="ja")
write_vtt(merged, "output_merged.vtt")

merge_sentences

When True (default), over-split cues produced by WhisperX are merged into natural sentence units after transcription:

  • Japanese (language="ja"): Uses Janome morphological analysis to detect sentence-ending forms (です, ます, ました, ください, etc.). Adjunctive (e.g. 作成された) is correctly excluded.
  • Other languages: Detects . / ! / ? as sentence boundaries. Common abbreviations (Mr., Dr., U.S., e.g., etc., ...) are excluded to avoid false splits.

Set merge_sentences=False to disable merging and receive the raw WhisperX-aligned cues.

voice_only

When True, outputs a plain .txt file instead of a .vtt file. Timestamps are omitted and trailing punctuation (, , , ., !, ?) is stripped from each line.

This is intended for cut-edited video where VTT timestamps are meaningless. The typical workflow:

Cut-edited audio → vtt-synced-voice (voice_only=True) → .txt → paste into subtitle tool

You can also convert an existing VTT file directly:

from vtt_synced_voice import read_vtt, write_txt

cues = read_vtt("input.vtt")
write_txt(cues, "output.txt")

Note: voice_only=True automatically changes the output file extension to .txt regardless of the output_file argument.

Language support for sentence merging

Language Status Notes
Japanese Supported Janome morphological analysis
English Supported Punctuation-based (. ! ?)
French Supported Punctuation-based
German Supported Punctuation-based
Spanish Supported Punctuation-based (sentence-ending ! ? only; leading ¡ ¿ are ignored)
Italian Supported Punctuation-based
Portuguese Supported Punctuation-based
Dutch Supported Punctuation-based
Other Latin-script languages Likely supported Punctuation-based
Chinese (Simplified / Traditional) Planned Uses as sentence terminator — contributions welcome
Korean Planned Mixed punctuation conventions — contributions welcome
Arabic, Hebrew, Thai, etc. Not supported Different punctuation systems

Requirements

  • Python 3.10+
  • ffmpeg (system)
  • numpy
  • whisperx
  • janome

Development

Setup

macOS / Linux

python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

Windows

python -m venv .venv
.venv\Scripts\activate
pip install -r requirements.txt

Running tests

python -m pytest tests/ -v

59 tests covering vtt_io, onset, cue_builder, and cue_merger modules.

Manual test with a local audio file

Place an audio file in audio_input/, then run:

python test_run.py

Output is written to vtt_output/test_package.vtt.

Project structure

src/vtt_synced_voice/
├── __init__.py       # exports transcribe(), merge_cues()
├── transcriber.py    # transcribe() entry point, ffmpeg conversion, WhisperX calls
├── onset.py          # find_onset() — bidirectional voice onset detection
├── cue_builder.py    # build_cues_from_segments() — WhisperX result → VttCue
├── cue_merger.py     # merge_cues() — sentence-level cue merging (Japanese / other)
└── vtt_io.py         # VttCue dataclass, read_vtt(), write_vtt(), format_timestamp()

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

vtt_synced_voice-0.1.2.tar.gz (14.5 MB view details)

Uploaded Source

Built Distribution

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

vtt_synced_voice-0.1.2-py3-none-any.whl (16.0 kB view details)

Uploaded Python 3

File details

Details for the file vtt_synced_voice-0.1.2.tar.gz.

File metadata

  • Download URL: vtt_synced_voice-0.1.2.tar.gz
  • Upload date:
  • Size: 14.5 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for vtt_synced_voice-0.1.2.tar.gz
Algorithm Hash digest
SHA256 40a9216f82d41d543d5f1fd73d9fc6f46626c4775093ab4c0ef0af5f2f6d8aef
MD5 c65eff8401400aaa2d2a753ebad3d280
BLAKE2b-256 41d8599aa066ef9d9fcfb9dd71f70f44d52e1e3b31772e6972eb08e80addb514

See more details on using hashes here.

File details

Details for the file vtt_synced_voice-0.1.2-py3-none-any.whl.

File metadata

File hashes

Hashes for vtt_synced_voice-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 d373636c5477537f1e17624e110e173c101c5d026ea264010c007594623f7847
MD5 2883cb32b9b4b1f69cd37a65b611915a
BLAKE2b-256 0e45bcbede2ec85870f51c62cbe23e85b31cb93e0628bab0954643a2fb5a367a

See more details on using hashes here.

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