videoNote / backend /app /services /transcriber_config_manager.py
zhoujiaangyao
feat(db): 配置与笔记迁入 Postgres,重启不丢
aa08cd6
Raw
History Blame Contribute Delete
7.69 kB
import os
from typing import Optional, Dict, Any
from app.db.app_config_dao import load_value, set_value
class TranscriberConfigManager:
"""管理转写器配置,持久化在数据库 app_config 表(key="transcriber"),支持前端动态修改。"""
# filepath 仅用于把旧的 config/transcriber.json 一次性导入数据库。
_KEY = "transcriber"
def __init__(self, filepath: str = "config/transcriber.json"):
self._legacy_path = filepath
def _read(self) -> Dict[str, Any]:
return load_value(self._KEY, self._legacy_path, {}) or {}
def _write(self, data: Dict[str, Any]):
set_value(self._KEY, data)
def get_config(self) -> Dict[str, Any]:
"""获取当前转写器配置,fallback 到环境变量默认值。
whisper 默认 size 从 'medium' (~1.5GB) 改为 'tiny' (~75MB):
新装用户没主动设置时不应该被首次下载卡住。想要更高精度可在「音频转写配置」
页主动切换。
"""
data = self._read()
ttype = data.get(
"transcriber_type",
os.getenv("TRANSCRIBER_TYPE", "fast-whisper"),
)
size = data.get(
"whisper_model_size",
os.getenv("WHISPER_MODEL_SIZE", "tiny"),
)
# 防御:存储/环境变量里的值不在可选列表时回退到第一个,
# 避免前端下拉框初始化为空或指向不存在的引擎/模型
if ttype not in ("fast-whisper", "bcut", "kuaishou", "groq", "mlx-whisper", "funasr"):
ttype = "fast-whisper"
# "custom" 表示用户自定义本地/HF whisper 模型(路径见 whisper_custom_model)
if size not in ("tiny", "base", "small", "medium", "large-v3", "large-v3-turbo", "custom"):
size = "tiny"
return {
"transcriber_type": ttype,
"whisper_model_size": size,
# 自定义 whisper 模型:本地 CTranslate2 目录 或 HF 仓库 id
"whisper_custom_model": (data.get("whisper_custom_model") or "").strip(),
# FunASR 模型名/路径(modelscope id 或本地目录),默认中文 paraformer-zh
"funasr_model": (data.get("funasr_model") or "paraformer-zh").strip(),
}
def update_config(
self,
transcriber_type: str,
whisper_model_size: Optional[str] = None,
whisper_custom_model: Optional[str] = None,
funasr_model: Optional[str] = None,
) -> Dict[str, Any]:
"""更新转写器配置并持久化。"""
data = self._read()
data["transcriber_type"] = transcriber_type
if whisper_model_size is not None:
data["whisper_model_size"] = whisper_model_size
if whisper_custom_model is not None:
data["whisper_custom_model"] = whisper_custom_model.strip()
if funasr_model is not None:
data["funasr_model"] = funasr_model.strip()
self._write(data)
return self.get_config()
def get_transcriber_type(self) -> str:
return self.get_config()["transcriber_type"]
def get_whisper_model_size(self) -> str:
return self.get_config()["whisper_model_size"]
def is_model_ready(self) -> Dict[str, Any]:
"""当前转写器是否就绪可用。
返回 {ready, transcriber_type, model_size, downloading, reason}:
- 在线引擎 (groq/bcut/kuaishou):永远 ready(不需要本地模型)
- fast-whisper:检查 whisper-{size}/model.bin 落盘
- mlx-whisper:检查 {repo_id}/config.json 落盘
给 /generate_note 入口做「开始视频前先确认模型下载好」的门禁用。
"""
cfg = self.get_config()
ttype = cfg["transcriber_type"]
size = cfg["whisper_model_size"]
result = {
"ready": True,
"transcriber_type": ttype,
"model_size": size,
"downloading": False,
"reason": "",
}
# FunASR:可选引擎,需安装 funasr+torch。模型经 modelscope 首跑自动下载,
# 不做预下载门禁,只确认引擎可用,否则给安装指引。
if ttype == "funasr":
try:
from app.transcriber.transcriber_provider import FUNASR_AVAILABLE
except Exception:
FUNASR_AVAILABLE = True # 检查不了就放行,交给后续流程报错
if not FUNASR_AVAILABLE:
result["ready"] = False
result["reason"] = (
"FunASR 引擎当前不可用(未安装)。请安装依赖:"
"pip install funasr torch torchaudio,安装后重启后端;或切换到其他转写引擎。"
)
return result
if ttype not in ("fast-whisper", "mlx-whisper"):
return result # 在线引擎无需本地模型
# fast-whisper 自定义模型:路径/仓库 id 由用户自负,本地目录存在即就绪;
# 仓库 id 也放行(首跑联网下载),不进预设档位的下载门禁。
if ttype == "fast-whisper" and size == "custom":
custom = cfg.get("whisper_custom_model") or ""
if not custom:
result["ready"] = False
result["reason"] = "已选「自定义」Whisper 模型,但未填写模型路径或仓库 id。"
return result
# mlx-whisper 还要求引擎本身可用(包已安装且原生库能加载)。
# 配置可能是在引擎可用时保存的,之后换了环境/重装应用就失效了——
# 在这里拦下并给出可行动的指引,而不是让 NoteGenerator 初始化时 500。
if ttype == "mlx-whisper":
try:
from app.transcriber.transcriber_provider import MLX_WHISPER_AVAILABLE
except Exception:
MLX_WHISPER_AVAILABLE = True # 检查不了就放行,交给后续流程报错
if not MLX_WHISPER_AVAILABLE:
result["ready"] = False
result["reason"] = (
"MLX Whisper 引擎当前不可用(未安装或本机不支持)。"
"请到「设置 → 音频转写配置」按页面提示安装 mlx_whisper 后重启应用,"
"或切换到其他转写引擎。"
)
return result
# 延迟 import 避免与 routers.config 的循环依赖;只取纯函数,不触发路由副作用
try:
from app.routers.config import (
_check_whisper_model_exists,
_check_mlx_whisper_model_exists,
_downloading,
)
except Exception as e:
# 拿不到检查函数时保守放行,不要把用户卡死
result["reason"] = f"无法检查模型状态: {e}"
return result
if ttype == "fast-whisper":
downloaded = _check_whisper_model_exists(size, "whisper")
downloading = _downloading.get(size) == "downloading"
else: # mlx-whisper
downloaded = _check_mlx_whisper_model_exists(size)
downloading = _downloading.get(f"mlx-{size}") == "downloading"
result["downloading"] = downloading
if downloaded:
return result
result["ready"] = False
result["reason"] = (
f"转写模型 {ttype} / {size} 尚未下载就绪"
+ (",正在下载中,请稍候" if downloading else ",请先在「设置 → 音频转写配置」页下载")
)
return result