videoNote / backend /app /transcriber /transcriber_provider.py
zhoujiaangyao
deploy videomemo backend to HF Space
6cfe55f
Raw
History Blame Contribute Delete
8.75 kB
import importlib.util
import os
import platform
from enum import Enum
from app.transcriber.groq import GroqTranscriber
from app.transcriber.whisper import WhisperTranscriber
from app.transcriber.bcut import BcutTranscriber
from app.transcriber.kuaishou import KuaishouTranscriber
from app.utils.logger import get_logger
logger = get_logger(__name__)
class TranscriberType(str, Enum):
FAST_WHISPER = "fast-whisper"
MLX_WHISPER = "mlx-whisper"
BCUT = "bcut"
KUAISHOU = "kuaishou"
GROQ = "groq"
FUNASR = "funasr"
# FunASR 可选引擎:用 find_spec 探测是否安装,绝不在此 import(import funasr 会连带加载
# torch,拖慢启动且桌面瘦身包没有 torch)。真正用到时才在 FunASRTranscriber 内部 import。
# 桌面冻结包强制不可用:torch 无法在 PyInstaller 冻结运行时初始化(pybind 重复注册崩溃),
# 而且装进插件目录后会被 ctranslate2 的启动链路自动 import,直接把应用打挂。
import sys as _sys
FUNASR_AVAILABLE = (
not getattr(_sys, "frozen", False)
and importlib.util.find_spec("funasr") is not None
)
if FUNASR_AVAILABLE:
logger.info("FunASR 可用(已安装 funasr)")
# 在 Apple 平台尝试导入 MLX Whisper(不再依赖环境变量,支持前端动态切换)
MLX_WHISPER_AVAILABLE = False
if platform.system() == "Darwin":
try:
from app.transcriber.mlx_whisper_transcriber import MLXWhisperTranscriber
MLX_WHISPER_AVAILABLE = True
logger.info("MLX Whisper 可用,已导入")
except ImportError:
logger.warning("MLX Whisper 导入失败,可能未安装 mlx_whisper")
logger.info('初始化转录服务提供器')
# 转录器单例缓存
_transcribers = {
TranscriberType.FAST_WHISPER: None,
TranscriberType.MLX_WHISPER: None,
TranscriberType.BCUT: None,
TranscriberType.KUAISHOU: None,
TranscriberType.GROQ: None,
TranscriberType.FUNASR: None,
}
# 公共实例初始化函数
def _init_transcriber(key: TranscriberType, cls, *args, **kwargs):
if _transcribers[key] is None:
logger.info(f'创建 {cls.__name__} 实例: {key}')
try:
_transcribers[key] = cls(*args, **kwargs)
logger.info(f'{cls.__name__} 创建成功')
except Exception as e:
logger.error(f"{cls.__name__} 创建失败: {e}")
raise
return _transcribers[key]
# 各类型获取方法
def get_groq_transcriber():
return _init_transcriber(TranscriberType.GROQ, GroqTranscriber)
def get_whisper_transcriber(model_size="base", device="cuda"):
# size == "custom":使用用户在「音频转写配置」填的自定义模型(本地目录 / HF 仓库 id)
custom_path = None
if model_size == "custom":
from app.services.transcriber_config_manager import TranscriberConfigManager
custom_path = (TranscriberConfigManager().get_config().get("whisper_custom_model") or "").strip()
if not custom_path:
raise RuntimeError("已选择「自定义」Whisper 模型,但未填写模型路径或仓库 id;请到「音频转写配置」填写。")
# 实例「变化即重建」:自定义时按路径比较,否则按档位比较
target_key = custom_path if model_size == "custom" else model_size
inst = _transcribers[TranscriberType.FAST_WHISPER]
if inst is not None and getattr(inst, "model_size", None) != target_key:
logger.info(f"fast-whisper 模型变更 {getattr(inst, 'model_size', None)} -> {target_key},重建实例")
_transcribers[TranscriberType.FAST_WHISPER] = None
if model_size == "custom":
return _init_transcriber(TranscriberType.FAST_WHISPER, WhisperTranscriber, model_path=custom_path, device=device)
return _init_transcriber(TranscriberType.FAST_WHISPER, WhisperTranscriber, model_size=model_size, device=device)
def get_bcut_transcriber():
return _init_transcriber(TranscriberType.BCUT, BcutTranscriber)
def get_kuaishou_transcriber():
return _init_transcriber(TranscriberType.KUAISHOU, KuaishouTranscriber)
def get_funasr_transcriber(model: str = None):
if not FUNASR_AVAILABLE:
raise RuntimeError(
"FunASR 不可用:请先安装依赖(pip install funasr torch torchaudio),"
"安装后重启后端;或在「音频转写配置」页面切换到其他转写引擎。"
)
# 模型名变更时重建实例(用户可在设置页填自定义 FunASR 模型)
inst = _transcribers[TranscriberType.FUNASR]
if inst is not None and getattr(inst, "model_name", None) != (model or "paraformer-zh"):
logger.info(f"FunASR 模型变更 {getattr(inst, 'model_name', None)} -> {model},重建实例")
_transcribers[TranscriberType.FUNASR] = None
# 延迟 import,避免模块加载阶段触发 torch
from app.transcriber.funasr_transcriber import FunASRTranscriber
return _init_transcriber(TranscriberType.FUNASR, FunASRTranscriber, model=model)
def get_mlx_whisper_transcriber(model_size="base"):
if not MLX_WHISPER_AVAILABLE:
logger.warning("MLX Whisper 不可用,请确保在 Apple 平台且已安装 mlx_whisper")
raise ImportError("MLX Whisper 不可用")
# 模型大小变更时重建实例:单例只按类型缓存,否则设置页切换 size 不生效
inst = _transcribers[TranscriberType.MLX_WHISPER]
if inst is not None and getattr(inst, "model_size", None) != model_size:
logger.info(f"mlx-whisper 模型大小变更 {getattr(inst, 'model_size', None)} -> {model_size},重建实例")
_transcribers[TranscriberType.MLX_WHISPER] = None
return _init_transcriber(TranscriberType.MLX_WHISPER, MLXWhisperTranscriber, model_size=model_size)
# 通用入口
def get_transcriber(transcriber_type="fast-whisper", model_size=None, device="cuda"):
"""
获取指定类型的转录器实例
参数:
transcriber_type: 支持 "fast-whisper", "mlx-whisper", "bcut", "kuaishou", "groq"
model_size: 模型大小,适用于 whisper 类;不传时回退到环境变量 WHISPER_MODEL_SIZE
device: 设备类型(如 cuda / cpu),仅 whisper 使用
返回:
对应类型的转录器实例
"""
logger.info(f'请求转录器类型: {transcriber_type}, 模型大小: {model_size or "(默认)"}')
try:
transcriber_enum = TranscriberType(transcriber_type)
except ValueError:
logger.warning(f'未知转录器类型 "{transcriber_type}",默认使用 fast-whisper')
transcriber_enum = TranscriberType.FAST_WHISPER
# 显式入参优先(来自「音频转写配置」页持久化的配置),环境变量只做未传参时的默认值。
# 旧逻辑是环境变量覆盖入参,导致设置页选的模型大小永远被 .env 里的值顶掉。
whisper_model_size = model_size or os.environ.get("WHISPER_MODEL_SIZE", "base")
if transcriber_enum == TranscriberType.FAST_WHISPER:
return get_whisper_transcriber(whisper_model_size, device=device)
elif transcriber_enum == TranscriberType.MLX_WHISPER:
if not MLX_WHISPER_AVAILABLE:
import sys
if getattr(sys, "frozen", False):
from app.utils.path_helper import get_plugin_packages_dir
hint = (
f'请在终端执行:python3.11 -m pip install --target '
f'"{get_plugin_packages_dir()}" mlx_whisper(需要 Python 3.11),'
"安装后重启应用生效;"
)
else:
hint = "请安装 mlx_whisper 包(pip install mlx_whisper)后重启后端;"
raise RuntimeError(
f"MLX Whisper 不可用:需要 macOS(Apple Silicon)平台。{hint}"
"或在「音频转写配置」页面切换到其他转写引擎。"
)
return get_mlx_whisper_transcriber(whisper_model_size)
elif transcriber_enum == TranscriberType.BCUT:
return get_bcut_transcriber()
elif transcriber_enum == TranscriberType.KUAISHOU:
return get_kuaishou_transcriber()
elif transcriber_enum == TranscriberType.GROQ:
return get_groq_transcriber()
elif transcriber_enum == TranscriberType.FUNASR:
from app.services.transcriber_config_manager import TranscriberConfigManager
funasr_model = TranscriberConfigManager().get_config().get("funasr_model") or "paraformer-zh"
return get_funasr_transcriber(model=funasr_model)
# fallback
logger.warning(f'未识别转录器类型 "{transcriber_type}",使用 fast-whisper 作为默认')
return get_whisper_transcriber(whisper_model_size, device=device)