H022329's picture
Upload folder using huggingface_hub
9cf08e9 verified
Raw
History Blame Contribute Delete
6.06 kB
import re
import os
import json
import logging
import subprocess
import tempfile
import librosa
import numpy as np
import requests
from typing import Optional
from dataclasses import dataclass
from dotenv import load_dotenv
load_dotenv()
logger = logging.getLogger(__name__)
@dataclass
class ASRResult:
"""ASR 识别结果"""
text: str
lang: str = ""
emotion: str = ""
event: str = ""
timestamps: list = None
class ASRModel:
"""语音识别模型封装,调用 asr_server 的 /asr 接口"""
def __init__(self, base_url: str = None, language: str = "auto", sample_rate: int = 16000):
"""
初始化 ASR 模型
Args:
base_url: ASR 服务地址,如 "http://localhost:8000"
language: 默认语言代码 (auto, zh, en, ja, ko, yue)
sample_rate: 音频采样率,默认 16000 Hz
"""
self.base_url = (base_url or os.getenv("ASR_API_BASE_URL", "http://localhost:8000")).rstrip('/')
self.language = language or os.getenv("ASR_LANGUAGE", "auto")
self.sample_rate = sample_rate
self.asr_endpoint = f"{self.base_url}/asr"
def _load_audio(self, audio_path: str) -> np.ndarray:
"""
加载音频文件并重采样到目标采样率
Args:
audio_path: 音频文件路径
Returns:
音频数组
"""
try:
waveform, sr = librosa.load(audio_path, sr=None, mono=True)
if sr != self.sample_rate:
waveform = librosa.resample(waveform, orig_sr=sr, target_sr=self.sample_rate)
return waveform
except Exception as e:
logger.error(f"Failed to load audio file {audio_path}: {e}")
raise
def transcribe(self, audio_file_path: str, language: Optional[str] = None) -> ASRResult:
"""
转录音频文件
Args:
audio_file_path: 音频文件路径
language: 语言代码,不提供则使用默认值
Returns:
ASRResult 对象,包含 text 字段
"""
lang = language or self.language
try:
# 验证文件存在
if not os.path.exists(audio_file_path):
raise FileNotFoundError(f"Audio file not found: {audio_file_path}")
# logger.info(f"Transcribing audio file: {audio_file_path}")
# 加载音频文件
waveform = self._load_audio(audio_file_path)
# 转换为列表格式用于 API 调用
audio_data = waveform.tolist()
# logger.info(f"Calling ASR API: {self.asr_endpoint}")
# logger.info(f"Audio length: {len(audio_data)}, Sample rate: {self.sample_rate}, Language: {lang}")
# 调用 asr_server 的 /asr 接口
response = requests.post(
self.asr_endpoint,
json={
"audio_data": audio_data,
"sample_rate": self.sample_rate,
"language": lang
},
timeout=300
)
if response.status_code != 200:
error_detail = response.json().get("detail", response.text) if response.headers.get("content-type") == "application/json" else response.text
logger.error(f"ASR API error: {response.status_code} - {error_detail}")
raise RuntimeError(f"ASR API error: {response.status_code} - {error_detail}")
result_data = response.json()
text = result_data.get("text", "")
# 清理文本中的特殊标记
text = self._clean_text(text)
return ASRResult(text=text)
except FileNotFoundError as e:
logger.error(f"File error: {e}")
raise
except requests.RequestException as e:
logger.error(f"Failed to connect to ASR API: {e}")
raise RuntimeError(f"Failed to connect to ASR API at {self.asr_endpoint}: {e}")
except Exception as e:
logger.error(f"ASR transcription error: {e}")
raise
@staticmethod
def _clean_text(text: str) -> str:
"""清理 SenseVoice 返回的文本中的特殊标记"""
# 移除所有 <|....|> 格式的标记
text = re.sub(r'<\|[^|]*\|>', '', text)
# 清理多余空格
text = re.sub(r'\s+', ' ', text).strip()
return text
class SherpaOnnxASRClient:
"""调用 Sherpa-ONNX FastAPI 服务的客户端"""
def __init__(self, base_url: str = None):
self.base_url = (base_url or os.getenv("SHERPA_ASR_URL", "http://10.126.102.211:8016")).rstrip("/")
def _parse_response(self, response) -> ASRResult:
if response.status_code != 200:
raise RuntimeError(f"Sherpa ASR error: {response.status_code} - {response.text}")
data = response.json()
# print("lang: ", data.get("lang", ""))
return ASRResult(
text=data.get("text", ""),
lang=data.get("lang", ""),
emotion=data.get("emotion", ""),
event=data.get("event", ""),
timestamps=data.get("timestamps", []),
)
def transcribe(self, audio_path: str) -> ASRResult:
"""通过 /asr/file 接口上传音频文件"""
if not os.path.exists(audio_path):
raise FileNotFoundError(f"Audio file not found: {audio_path}")
with open(audio_path, "rb") as f:
response = requests.post(
f"{self.base_url}/asr/file",
files={"file": f},
timeout=120,
)
return self._parse_response(response)
def transcribe_audio_data(self, audio_data: list, sample_rate: int = 16000) -> ASRResult:
"""通过 /asr 接口发送音频数组数据"""
response = requests.post(
f"{self.base_url}/asr",
json={"audio_data": audio_data, "sample_rate": sample_rate},
timeout=120,
)
return self._parse_response(response)