add examples/whisper.youtube: YouTube → speech-to-text pipeline

- yt_transcribe.py: yt-dlp + ffmpeg + whisper-cli (large-v3) wrapper
- parakeet_transcribe.py: alternative NVIDIA Parakeet ASR backend
- parakeet: shell launcher for parakeet via local venv
- transcriptions/q1CluXct_VI.txt: sample output (8h audiobook)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
nnnet 2026-05-11 20:32:07 +03:00
parent 8452ea1650
commit 7a4ff7ec36
5 changed files with 165 additions and 0 deletions

4
examples/whisper.youtube/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
videos/
.venv-parakeet/
__pycache__/
*.pyc

View File

@ -0,0 +1,8 @@
#!/usr/bin/env bash
# Parakeet V3 transcription launcher.
# Usage:
# ./parakeet <wav-file>
# ./parakeet <youtube-url>
set -euo pipefail
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
exec "$DIR/.venv-parakeet/bin/python" "$DIR/parakeet_transcribe.py" "$@"

View File

@ -0,0 +1,70 @@
#!/usr/bin/env python3
"""Transcribe audio with NVIDIA Parakeet-tdt-0.6b-v3 (multilingual)."""
import os
import subprocess
import sys
import tempfile
from pathlib import Path
MODEL_NAME = "nvidia/parakeet-tdt-0.6b-v3"
def download_audio(url: str, out_path: str):
subprocess.run([
"yt-dlp", "-x", "--audio-format", "wav",
"-o", out_path, url,
], check=True)
def convert_wav(src: str, dst: str):
subprocess.run([
"ffmpeg", "-y", "-i", src,
"-ar", "16000", "-ac", "1", "-f", "wav", dst,
], check=True, capture_output=True)
def transcribe(wav_path: str, long_audio: bool = True) -> str:
import nemo.collections.asr as nemo_asr
print(f"Loading {MODEL_NAME} ...", flush=True)
asr = nemo_asr.models.ASRModel.from_pretrained(MODEL_NAME)
asr = asr.cuda() if __import__("torch").cuda.is_available() else asr
if long_audio:
asr.change_attention_model(
self_attention_model="rel_pos_local_attn",
att_context_size=[256, 256],
)
print(f"Transcribing {wav_path} ...", flush=True)
out = asr.transcribe([wav_path], timestamps=False)
return out[0].text
def main():
if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} <wav-file-or-youtube-url>")
sys.exit(1)
src = sys.argv[1]
if src.startswith(("http://", "https://", "youtu", "www.")):
with tempfile.TemporaryDirectory(prefix="parakeet-") as tmp:
raw = os.path.join(tmp, "audio.%(ext)s")
wav = os.path.join(tmp, "audio_16k.wav")
print(f"Downloading {src} ...", flush=True)
download_audio(src, raw)
downloaded = [f for f in os.listdir(tmp) if f.startswith("audio.")]
print("Converting to 16kHz WAV ...", flush=True)
convert_wav(os.path.join(tmp, downloaded[0]), wav)
text = transcribe(wav)
else:
text = transcribe(src)
print("\n=== Transcription ===")
print(text)
if __name__ == "__main__":
main()

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,82 @@
#!/usr/bin/env python3
"""Download audio from YouTube and transcribe with local whisper-cli."""
import os
import subprocess
import sys
import tempfile
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent.parent
WHISPER_CLI = REPO_ROOT / "build" / "bin" / "whisper-cli"
MODEL = REPO_ROOT / "models" / "ggml-large-v3.bin"
def download_audio(url: str, out_path: str):
"""Download audio from YouTube URL using yt-dlp."""
subprocess.run([
"yt-dlp", "-x", "--audio-format", "wav",
"-o", out_path, url,
], check=True)
def convert_wav(src: str, dst: str):
"""Convert audio to 16kHz mono WAV for whisper."""
subprocess.run([
"ffmpeg", "-y", "-i", src,
"-ar", "16000", "-ac", "1", "-f", "wav", dst,
], check=True, capture_output=True)
def transcribe(wav_path: str) -> str:
"""Run whisper-cli on a WAV file and return text."""
result = subprocess.run([
str(WHISPER_CLI), "-m", str(MODEL),
"-f", wav_path, "-nt", "-np", "-l", "auto",
], capture_output=True, text=True)
if result.returncode != 0:
print(f"whisper-cli error: {result.stderr[:500]}", file=sys.stderr)
sys.exit(1)
return result.stdout.strip().replace("[BLANK_AUDIO]", "").strip()
def main():
if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} <youtube-url>")
sys.exit(1)
url = sys.argv[1]
if not WHISPER_CLI.exists():
print(f"whisper-cli not found: {WHISPER_CLI}", file=sys.stderr)
sys.exit(1)
if not MODEL.exists():
print(f"Model not found: {MODEL}\nRun: bash models/download-ggml-model.sh base", file=sys.stderr)
sys.exit(1)
with tempfile.TemporaryDirectory(prefix="yt-whisper-") as tmpdir:
raw_audio = os.path.join(tmpdir, "audio.%(ext)s")
wav_16k = os.path.join(tmpdir, "audio_16k.wav")
print(f"Downloading audio from {url} ...")
download_audio(url, raw_audio)
# find the downloaded file (yt-dlp replaces %(ext)s)
downloaded = [f for f in os.listdir(tmpdir) if f.startswith("audio.")]
if not downloaded:
print("Download failed: no audio file found", file=sys.stderr)
sys.exit(1)
raw_path = os.path.join(tmpdir, downloaded[0])
print("Converting to 16kHz WAV ...")
convert_wav(raw_path, wav_16k)
print("Transcribing ...")
text = transcribe(wav_16k)
print("\n=== Transcription ===")
print(text)
if __name__ == "__main__":
main()