| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071 |
- """faster-whisper 本地 STT 引擎封装"""
- import os
- import logging
- import numpy as np
- import av
- from faster_whisper import WhisperModel
- logger = logging.getLogger(__name__)
- _model = None
- _loaded = False
- def init_model(model_size: str = "base", device: str = "cpu", compute_type: str = "int8"):
- """初始化 faster-whisper 模型(启动时调用)"""
- global _model, _loaded
- if _loaded:
- return
- os.environ.setdefault("HF_ENDPOINT", "https://hf-mirror.com")
- logger.info("加载 faster-whisper 模型 %s (device=%s, compute=%s)...", model_size, device, compute_type)
- _model = WhisperModel(model_size, device=device, compute_type=compute_type)
- _loaded = True
- logger.info("faster-whisper 模型加载完成")
- def transcribe_audio(file_path: str, language: str = "zh") -> dict:
- """转写音频文件,返回 {text, language, language_probability, duration}"""
- if not _model:
- raise RuntimeError("faster-whisper 模型未初始化")
- duration_sec = 0.0
- try:
- with av.open(file_path) as container:
- stream = container.streams.audio[0]
- audio_chunks = []
- sample_cnt = 0
- for frame in container.decode(audio=0):
- arr = frame.to_ndarray()
- sample_cnt += frame.samples
- if arr.ndim > 1 and arr.shape[0] > 1:
- arr = np.mean(arr, axis=0)
- else:
- arr = arr.reshape(-1)
- audio_chunks.append(arr)
- if not audio_chunks:
- raise ValueError("音频文件无有效帧")
- audio = np.concatenate(audio_chunks).astype(np.float32)
- max_val = np.max(np.abs(audio))
- if max_val > 0:
- audio = audio / max_val
- orig_rate = stream.sample_rate if stream.sample_rate else 44100
- if orig_rate != 16000:
- new_len = int(sample_cnt * 16000 / orig_rate)
- audio = np.interp(
- np.linspace(0, sample_cnt - 1, new_len),
- np.arange(sample_cnt),
- audio
- ).astype(np.float32)
- duration_sec = len(audio) / 16000.0
- except Exception as e:
- logger.error("音频解码失败 %s: %s", file_path, e)
- raise
- segments, info = _model.transcribe(audio, language=language, beam_size=1, vad_filter=True)
- text = "".join(seg.text for seg in segments).strip()
- return {
- "text": text,
- "language": info.language,
- "language_probability": round(float(info.language_probability), 4),
- "duration": round(duration_sec, 2),
- }
|