#!/usr/bin/env python3 """Transcribe long audio with SenseVoice using bounded, overlapping windows.""" import argparse import json import os import subprocess import tempfile from contextlib import contextmanager from pathlib import Path import numpy as np def create_sensevoice_model(auto_model_class, *, model_name, device): """Build the integrated SenseVoice model without adding a VAD pipeline.""" return auto_model_class( model=model_name, device=device, disable_update=True, ) @contextmanager def ffmpeg_pcm_stream(input_path): """Yield ffmpeg stdout as mono 16 kHz little-endian int16 PCM.""" command = [ "ffmpeg", "-nostdin", "-v", "error", "-i", str(input_path), "-f", "s16le", "-ac", "1", "-ar", "16000", "pipe:1", ] with tempfile.TemporaryFile() as errors: try: decoder = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=errors) except FileNotFoundError as error: raise RuntimeError("ffmpeg is required but was not found on PATH") from error try: yield decoder.stdout except BaseException: decoder.terminate() decoder.wait() raise finally: if decoder.stdout: decoder.stdout.close() return_code = decoder.wait() if return_code: errors.seek(0) detail = errors.read().decode("utf-8", errors="replace").strip() raise RuntimeError(f"ffmpeg failed with exit code {return_code}: {detail}") def iter_overlapping_windows( stream, *, sample_rate=16000, window_seconds=30, overlap_seconds=2, ): """Yield ``(start_sample, pcm)`` from a little-endian int16 stream.""" if sample_rate <= 0 or window_seconds <= 0: raise ValueError("sample_rate and window_seconds must be positive") if not 0 < overlap_seconds < window_seconds: raise ValueError("overlap_seconds must be greater than 0 and below window_seconds") window_samples = int(sample_rate * window_seconds) overlap_samples = int(sample_rate * overlap_seconds) if overlap_samples <= 0 or overlap_samples >= window_samples: raise ValueError("overlap_seconds produces an invalid sample count") stride_samples = window_samples - overlap_samples window_bytes = window_samples * 2 stride_bytes = stride_samples * 2 buffer = bytearray() start_sample = 0 furthest_emitted_sample = 0 eof = False while True: while len(buffer) < window_bytes and not eof: block = stream.read(window_bytes - len(buffer)) if block: buffer.extend(block) else: eof = True if len(buffer) % 2: raise RuntimeError("decoder returned an incomplete int16 PCM sample") available_samples = min(len(buffer) // 2, window_samples) end_sample = start_sample + available_samples if available_samples == 0 or end_sample <= furthest_emitted_sample: break pcm = np.frombuffer(bytes(buffer[: available_samples * 2]), dtype="