| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697 |
- #!/usr/bin/env python3
- """Whisper-based forced alignment for TTS-generated audio.
- Uses faster-whisper (preferred) or openai-whisper to produce word-level
- timestamps. Outputs JSON to stdout.
- Usage:
- python3 align.py <audio_path> [--text TEXT] [--language LANG] [--model MODEL]
- Output format:
- {"ok": true, "words": [{"word": "...", "start": 0.0, "end": 0.5}, ...]}
- {"ok": false, "error": "error message"}
- """
- import sys
- import json
- import argparse
- def transcribe_with_faster_whisper(audio_path, language=None, model_size="base"):
- """Transcribe using faster-whisper and return word-level timestamps."""
- from faster_whisper import WhisperModel
- model = WhisperModel(model_size, device="cpu", compute_type="int8")
- segments, _info = model.transcribe(
- audio_path,
- word_timestamps=True,
- language=language,
- vad_filter=True,
- )
- words = []
- for segment in segments:
- for word_info in segment.words:
- words.append({
- "word": word_info.word.strip(),
- "start": round(word_info.start, 3),
- "end": round(word_info.end, 3),
- })
- return words
- def transcribe_with_whisper(audio_path, language=None, model_size="base"):
- """Fallback: transcribe using openai-whisper and return word-level timestamps."""
- import whisper
- model = whisper.load_model(model_size)
- result = model.transcribe(
- audio_path,
- word_timestamps=True,
- language=language,
- )
- words = []
- for segment in result.get("segments", []):
- for word_info in segment.get("words", []):
- words.append({
- "word": word_info["word"].strip(),
- "start": round(word_info["start"], 3),
- "end": round(word_info["end"], 3),
- })
- return words
- def main():
- parser = argparse.ArgumentParser(description="Whisper forced alignment")
- parser.add_argument("audio_path", help="Path to audio file")
- parser.add_argument("--text", default=None, help="Reference text (unused, reserved)")
- parser.add_argument("--language", default=None, help="Language hint (e.g. 'zh', 'en')")
- parser.add_argument("--model", default="base", help="Whisper model size (tiny/base/small/medium/large)")
- args = parser.parse_args()
- try:
- try:
- words = transcribe_with_faster_whisper(
- args.audio_path,
- language=args.language,
- model_size=args.model,
- )
- except ImportError:
- words = transcribe_with_whisper(
- args.audio_path,
- language=args.language,
- model_size=args.model,
- )
- print(json.dumps({"ok": True, "words": words}))
- except Exception as e:
- print(json.dumps({"ok": False, "error": str(e)}))
- sys.exit(0)
- if __name__ == "__main__":
- main()
|