| 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"),支持前端动态修改。""" |
|
|
| |
| _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" |
| |
| 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_custom_model": (data.get("whisper_custom_model") or "").strip(), |
| |
| "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": "", |
| } |
| |
| |
| 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 |
|
|
| |
| |
| 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 |
|
|
| |
| |
| |
| 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 |
|
|
| |
| 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: |
| 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 |
|
|