537 lines
20 KiB
Python
537 lines
20 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
统一配置管理
|
||
ASR语音识别配置选项
|
||
"""
|
||
|
||
import os
|
||
from typing import Optional
|
||
from pathlib import Path
|
||
|
||
|
||
class Settings:
|
||
"""统一应用配置类"""
|
||
|
||
# 应用信息
|
||
APP_NAME: str = "Qwen3-ASR Server"
|
||
APP_VERSION: str = "1.0.1"
|
||
APP_DESCRIPTION: str = "Qwen3-ASR speech recognition API service"
|
||
|
||
# 服务器配置
|
||
HOST: str = "0.0.0.0"
|
||
PORT: int = 8000
|
||
DEBUG: bool = False
|
||
|
||
# 鉴权配置
|
||
API_KEY: Optional[str] = None # 从环境变量API_KEY读取,如果为None则鉴权可选
|
||
|
||
# 设备配置
|
||
ACCELERATOR: str = "auto" # auto, cpu, nvidia, metax, iluvatar, mthreads
|
||
DEVICE: str = "auto" # auto, cpu, cuda:0
|
||
ASR_DEPLOY_TOPOLOGY: str = "isolated" # isolated, sharded, auto
|
||
|
||
# 路径配置
|
||
BASE_DIR: Path = Path(__file__).parent.parent.parent
|
||
DATA_DIR: str = str(BASE_DIR / "data")
|
||
TEMP_DIR: str = str(BASE_DIR / "data" / "temp")
|
||
# 项目总模型目录。实际模型目录直接扁平化到:
|
||
# /models/{Qwen,iic,damo,...}
|
||
MODELS_DIR: str = str(BASE_DIR / "models")
|
||
# ModelScope 会在 MODELSCOPE_CACHE 下创建 models/{publisher}/{model_name}。
|
||
# 因此 cache 根目录应指向 models 的上一级,实际运行模型根目录仍由 MODELSCOPE_PATH 指定。
|
||
MODELSCOPE_CACHE: str = str(BASE_DIR)
|
||
MODELSCOPE_PATH: str = str(BASE_DIR / "models")
|
||
|
||
# 日志配置
|
||
LOG_LEVEL: str = "INFO"
|
||
LOG_FILE: Optional[str] = str(BASE_DIR / "data" / "logs" / "qwen3-asr.log")
|
||
LOG_MAX_BYTES: int = 20 * 1024 * 1024 # 20MB
|
||
LOG_BACKUP_COUNT: int = 50 # 保留50个备份文件
|
||
|
||
# ASR模型配置
|
||
WS_MAX_BUFFER_SIZE: int = 10 * 16000 # WebSocket音频缓冲区最大大小(10秒@16kHz)
|
||
|
||
FUNASR_AUTOMODEL_KWARGS = {
|
||
"trust_remote_code": False,
|
||
"disable_update": True,
|
||
"disable_pbar": True,
|
||
"disable_log": True, # 禁用FunASR的tables输出
|
||
"local_files_only": True, # 强制使用本地模型,禁止联网下载
|
||
}
|
||
ASR_MODELS_CONFIG: str = str(BASE_DIR / "app/services/asr/models.json")
|
||
ASR_ENABLE_REALTIME_PUNC: bool = True # 是否启用实时标点模型(用于中间结果展示)
|
||
VAD_MODEL: str = "damo/speech_fsmn_vad_zh-cn-16k-common-pytorch"
|
||
PUNC_MODEL: str = "iic/punc_ct-transformer_zh-cn-common-vocab272727-pytorch"
|
||
PUNC_REALTIME_MODEL: str = (
|
||
"iic/punc_ct-transformer_zh-cn-common-vad_realtime-vocab272727"
|
||
)
|
||
|
||
# 流式ASR远场过滤配置
|
||
ASR_ENABLE_NEARFIELD_FILTER: bool = True # 是否启用远场声音过滤
|
||
ASR_NEARFIELD_RMS_THRESHOLD: float = 0.01 # RMS能量阈值(宽松模式,适合大多数场景)
|
||
# 音频处理配置
|
||
MAX_AUDIO_SIZE: int = 2048 * 1024 * 1024 # 2GB
|
||
|
||
# 批处理推理配置(GPU 真并行)
|
||
ASR_BATCH_SIZE: int = 4 # ASR 批处理大小(同时推理的片段数),建议 2-8
|
||
ASR_ENABLE_WORD_TIMESTAMPS: bool = False # 全局字词级时间戳开关;关闭时不预热 forced aligner,接口参数也默认隐藏
|
||
|
||
# 音频分段配置
|
||
MAX_SEGMENT_SEC: float = 60.0 # Max offline ASR segment duration in seconds.
|
||
|
||
# Runtime 并发配置(按 backend 独立控制)
|
||
QWEN_VLLM_SHARED_CONCURRENCY: int = 8
|
||
QWEN_VLLM_ENFORCE_EAGER: bool = True
|
||
QWEN_RUST_CPU_WORKERS: int = 4
|
||
QWEN_RUST_ASR_CONCURRENCY: int = 0
|
||
QWEN_RUST_ALIGN_CONCURRENCY: int = 0
|
||
FUNASR_WORKERS: int = 1
|
||
|
||
# 声纹数据库配置(与 Model-Test-New 使用同一套 PostgreSQL/pgvector 表结构)
|
||
SPEAKER_DB_ENABLED: bool = True
|
||
DB_USER: str = "postgres"
|
||
DB_PASSWORD: str = "postgres"
|
||
DB_NAME: str = "asr_db"
|
||
DB_HOST: str = "127.0.0.1"
|
||
DB_PORT: int = 5432
|
||
DB_POOL_MAX_SIZE: int = 5
|
||
SV_MODEL: str = "iic/speech_campplus_sv_zh-cn_16k-common"
|
||
SV_MODEL_REVISION: str = "v2.0.2"
|
||
REALTIME_SV_MODEL: str = "iic/speech_eres2netv2_sv_zh-cn_16k-common"
|
||
REALTIME_SV_MODEL_REVISION: str = ""
|
||
SV_THRESHOLD: float = 0.6
|
||
REALTIME_MAX_SEGMENT_SEC: float = 12.0
|
||
REALTIME_MAX_SEGMENT_TAIL_SEC: float = 1.6
|
||
REALTIME_FORCE_STABLE_SEGMENT_SEC: float = 8.0
|
||
REALTIME_FORCE_STABLE_MIN_CHARS: int = 24
|
||
REALTIME_MIN_PARTIAL_SEC: float = 0.45
|
||
REALTIME_PARTIAL_EMIT_INTERVAL_SEC: float = 0.25
|
||
REALTIME_PARTIAL_WINDOW_SEC: float = 8.0
|
||
REALTIME_STREAM_CHUNK_SEC: float = 1.2
|
||
REALTIME_STREAM_MAX_PENDING_CHUNKS: int = 3
|
||
REALTIME_STREAM_WINDOW_SEC: float = 8.0
|
||
REALTIME_STREAM_STABLE_TAIL_CHARS: int = 8
|
||
REALTIME_STREAM_STABLE_MIN_GROW_CHARS: int = 2
|
||
REALTIME_STREAM_DIVERGENCE_TOLERANCE_CHARS: int = 2
|
||
REALTIME_PARTIAL_HOLDBACK_CHARS: int = 6
|
||
REALTIME_LONGFORM_MIN_SEC: float = 8.0
|
||
REALTIME_LONGFORM_CHUNK_SEC: float = 6.0
|
||
REALTIME_LONGFORM_OVERLAP_SEC: float = 1.2
|
||
REALTIME_VAD_CHECK_INTERVAL_SEC: float = 0.8
|
||
REALTIME_VAD_FINALIZE_SILENCE_SEC: float = 0.6
|
||
REALTIME_FINAL_SEGMENT_SOFT_LIMIT_SEC: float = 8.0
|
||
REALTIME_FINAL_SEGMENT_HARD_LIMIT_SEC: float = 12.0
|
||
REALTIME_ENABLE_DIARIZATION: bool = True
|
||
REALTIME_ENABLE_SEGMENT_REFINE: bool = False
|
||
REALTIME_DIARIZATION_MIN_SEC: float = 3.0
|
||
REALTIME_DIARIZATION_LOOKBACK_SEC: float = 3.0
|
||
REALTIME_DIARIZATION_WINDOW_SEC: float = 15.0
|
||
REALTIME_SPEAKER_MIN_SEC: float = 1.2
|
||
REALTIME_SPEAKER_CLUSTER_THRESHOLD: float = 0.75
|
||
REALTIME_UNKNOWN_SPK_CLUSTER_THRESHOLD: float = 0.58
|
||
REALTIME_RECENT_UNKNOWN_SPK_THRESHOLD: float = 0.50
|
||
REALTIME_SPEAKER_CONFIRM_THRESHOLD: float = 0.62
|
||
REALTIME_REGISTRY_MIN_CLUSTER_CONFIDENCE: float = 0.72
|
||
REALTIME_SPEAKER_MAX_SLOTS: int = 8
|
||
REALTIME_SESSION_RESUME_TTL_SEC: int = 120 # WebSocket 断线后保留会话上下文的秒数;TTL 内同 session_id 可恢复
|
||
API_PREFIX: str = "/api/v1"
|
||
TASK_STATE_DIR: str = str(BASE_DIR / "data" / "tasks")
|
||
TASK_RETENTION_HOURS: int = 24
|
||
|
||
def __init__(self):
|
||
"""从环境变量读取配置"""
|
||
self._load_from_env()
|
||
self._ensure_directories()
|
||
|
||
def _load_from_env(self):
|
||
"""从环境变量加载配置"""
|
||
# 服务器配置
|
||
self.HOST = os.getenv("HOST", self.HOST)
|
||
self.PORT = int(os.getenv("PORT", str(self.PORT)))
|
||
self.DEBUG = os.getenv("DEBUG", "false").lower() == "true"
|
||
|
||
self.DATA_DIR = os.getenv("DATA_DIR", self.DATA_DIR)
|
||
self.TEMP_DIR = os.getenv("TEMP_DIR", self.TEMP_DIR)
|
||
|
||
# 日志配置
|
||
self.LOG_LEVEL = os.getenv("LOG_LEVEL", self.LOG_LEVEL)
|
||
self.LOG_FILE = os.getenv("LOG_FILE", self.LOG_FILE)
|
||
self.LOG_MAX_BYTES = int(os.getenv("LOG_MAX_BYTES", str(self.LOG_MAX_BYTES)))
|
||
self.LOG_BACKUP_COUNT = int(
|
||
os.getenv("LOG_BACKUP_COUNT", str(self.LOG_BACKUP_COUNT))
|
||
)
|
||
|
||
# 鉴权配置:空值/空白统一视为未配置
|
||
self.API_KEY = (os.getenv("API_KEY") or "").strip() or None
|
||
|
||
# 设备配置
|
||
self.ACCELERATOR = os.getenv("ACCELERATOR", self.ACCELERATOR)
|
||
self.DEVICE = os.getenv("DEVICE", self.DEVICE)
|
||
self.ASR_DEPLOY_TOPOLOGY = os.getenv(
|
||
"ASR_DEPLOY_TOPOLOGY",
|
||
self.ASR_DEPLOY_TOPOLOGY,
|
||
).strip().lower()
|
||
|
||
# 模型缓存路径
|
||
self.MODELS_DIR = os.getenv("MODELS_DIR", self.MODELS_DIR)
|
||
self.MODELSCOPE_CACHE = os.getenv("MODELSCOPE_CACHE", self.MODELSCOPE_CACHE)
|
||
self.MODELSCOPE_PATH = os.getenv("MODELSCOPE_PATH", self.MODELSCOPE_PATH)
|
||
|
||
# 给第三方库补齐默认缓存环境变量,允许用户自行覆盖
|
||
os.environ.setdefault("MODELS_DIR", self.MODELS_DIR)
|
||
os.environ.setdefault("MODELSCOPE_CACHE", self.MODELSCOPE_CACHE)
|
||
os.environ.setdefault("MODELSCOPE_PATH", self.MODELSCOPE_PATH)
|
||
|
||
# ASR模型配置
|
||
self.ASR_ENABLE_REALTIME_PUNC = (
|
||
os.getenv("ASR_ENABLE_REALTIME_PUNC", "true").lower() == "true"
|
||
)
|
||
|
||
# WebSocket缓冲区配置
|
||
self.WS_MAX_BUFFER_SIZE = int(
|
||
os.getenv("WS_MAX_BUFFER_SIZE", str(self.WS_MAX_BUFFER_SIZE))
|
||
)
|
||
|
||
# 远场过滤配置
|
||
self.ASR_ENABLE_NEARFIELD_FILTER = (
|
||
os.getenv("ASR_ENABLE_NEARFIELD_FILTER", "true").lower() == "true"
|
||
)
|
||
self.ASR_NEARFIELD_RMS_THRESHOLD = float(
|
||
os.getenv(
|
||
"ASR_NEARFIELD_RMS_THRESHOLD", str(self.ASR_NEARFIELD_RMS_THRESHOLD)
|
||
)
|
||
)
|
||
|
||
# 音频处理配置
|
||
# 支持简化格式:纯数字表示MB,或带单位(如 2048MB, 2GB)
|
||
max_audio_size_str = os.getenv("MAX_AUDIO_SIZE")
|
||
if max_audio_size_str:
|
||
self.MAX_AUDIO_SIZE = self._parse_size(max_audio_size_str)
|
||
|
||
self.ASR_BATCH_SIZE = int(
|
||
os.getenv("ASR_BATCH_SIZE", str(self.ASR_BATCH_SIZE))
|
||
)
|
||
self.ASR_ENABLE_WORD_TIMESTAMPS = (
|
||
os.getenv(
|
||
"ASR_ENABLE_WORD_TIMESTAMPS",
|
||
str(self.ASR_ENABLE_WORD_TIMESTAMPS),
|
||
).lower()
|
||
== "true"
|
||
)
|
||
|
||
self.MAX_SEGMENT_SEC = float(
|
||
os.getenv("MAX_SEGMENT_SEC", str(self.MAX_SEGMENT_SEC))
|
||
)
|
||
|
||
self.QWEN_VLLM_SHARED_CONCURRENCY = int(
|
||
os.getenv(
|
||
"QWEN_VLLM_SHARED_CONCURRENCY",
|
||
str(self.QWEN_VLLM_SHARED_CONCURRENCY),
|
||
)
|
||
)
|
||
self.QWEN_VLLM_ENFORCE_EAGER = (
|
||
os.getenv(
|
||
"QWEN_VLLM_ENFORCE_EAGER",
|
||
str(self.QWEN_VLLM_ENFORCE_EAGER),
|
||
).lower()
|
||
== "true"
|
||
)
|
||
self.QWEN_RUST_CPU_WORKERS = int(
|
||
os.getenv("QWEN_RUST_CPU_WORKERS", str(self.QWEN_RUST_CPU_WORKERS))
|
||
)
|
||
self.QWEN_RUST_ASR_CONCURRENCY = int(
|
||
os.getenv("QWEN_RUST_ASR_CONCURRENCY", str(self.QWEN_RUST_ASR_CONCURRENCY))
|
||
)
|
||
self.QWEN_RUST_ALIGN_CONCURRENCY = int(
|
||
os.getenv("QWEN_RUST_ALIGN_CONCURRENCY", str(self.QWEN_RUST_ALIGN_CONCURRENCY))
|
||
)
|
||
self.FUNASR_WORKERS = int(
|
||
os.getenv("FUNASR_WORKERS", str(self.FUNASR_WORKERS))
|
||
)
|
||
|
||
self.SPEAKER_DB_ENABLED = (
|
||
os.getenv("SPEAKER_DB_ENABLED", str(self.SPEAKER_DB_ENABLED)).lower()
|
||
== "true"
|
||
)
|
||
self.DB_USER = os.getenv("DB_USER", self.DB_USER)
|
||
self.DB_PASSWORD = os.getenv("DB_PASSWORD", self.DB_PASSWORD)
|
||
self.DB_NAME = os.getenv("DB_NAME", self.DB_NAME)
|
||
self.DB_HOST = os.getenv("DB_HOST", self.DB_HOST)
|
||
self.DB_PORT = int(os.getenv("DB_PORT", str(self.DB_PORT)))
|
||
self.DB_POOL_MAX_SIZE = int(
|
||
os.getenv("DB_POOL_MAX_SIZE", str(self.DB_POOL_MAX_SIZE))
|
||
)
|
||
self.SV_MODEL = os.getenv("SV_MODEL", self.SV_MODEL)
|
||
self.SV_MODEL_REVISION = os.getenv("SV_MODEL_REVISION", self.SV_MODEL_REVISION)
|
||
self.REALTIME_SV_MODEL = os.getenv("REALTIME_SV_MODEL", self.REALTIME_SV_MODEL)
|
||
self.REALTIME_SV_MODEL_REVISION = os.getenv(
|
||
"REALTIME_SV_MODEL_REVISION",
|
||
self.REALTIME_SV_MODEL_REVISION,
|
||
)
|
||
self.SV_THRESHOLD = float(os.getenv("SV_THRESHOLD", str(self.SV_THRESHOLD)))
|
||
self.REALTIME_MAX_SEGMENT_SEC = float(
|
||
os.getenv(
|
||
"REALTIME_MAX_SEGMENT_SEC",
|
||
str(self.REALTIME_MAX_SEGMENT_SEC),
|
||
)
|
||
)
|
||
self.REALTIME_MAX_SEGMENT_TAIL_SEC = float(
|
||
os.getenv(
|
||
"REALTIME_MAX_SEGMENT_TAIL_SEC",
|
||
str(self.REALTIME_MAX_SEGMENT_TAIL_SEC),
|
||
)
|
||
)
|
||
self.REALTIME_FORCE_STABLE_SEGMENT_SEC = float(
|
||
os.getenv(
|
||
"REALTIME_FORCE_STABLE_SEGMENT_SEC",
|
||
str(self.REALTIME_FORCE_STABLE_SEGMENT_SEC),
|
||
)
|
||
)
|
||
self.REALTIME_FORCE_STABLE_MIN_CHARS = int(
|
||
os.getenv(
|
||
"REALTIME_FORCE_STABLE_MIN_CHARS",
|
||
str(self.REALTIME_FORCE_STABLE_MIN_CHARS),
|
||
)
|
||
)
|
||
self.REALTIME_MIN_PARTIAL_SEC = float(
|
||
os.getenv(
|
||
"REALTIME_MIN_PARTIAL_SEC",
|
||
str(self.REALTIME_MIN_PARTIAL_SEC),
|
||
)
|
||
)
|
||
self.REALTIME_PARTIAL_EMIT_INTERVAL_SEC = float(
|
||
os.getenv(
|
||
"REALTIME_PARTIAL_EMIT_INTERVAL_SEC",
|
||
str(self.REALTIME_PARTIAL_EMIT_INTERVAL_SEC),
|
||
)
|
||
)
|
||
self.REALTIME_PARTIAL_WINDOW_SEC = float(
|
||
os.getenv(
|
||
"REALTIME_PARTIAL_WINDOW_SEC",
|
||
str(self.REALTIME_PARTIAL_WINDOW_SEC),
|
||
)
|
||
)
|
||
self.REALTIME_STREAM_CHUNK_SEC = float(
|
||
os.getenv(
|
||
"REALTIME_STREAM_CHUNK_SEC",
|
||
str(self.REALTIME_STREAM_CHUNK_SEC),
|
||
)
|
||
)
|
||
self.REALTIME_STREAM_MAX_PENDING_CHUNKS = int(
|
||
os.getenv(
|
||
"REALTIME_STREAM_MAX_PENDING_CHUNKS",
|
||
str(self.REALTIME_STREAM_MAX_PENDING_CHUNKS),
|
||
)
|
||
)
|
||
self.REALTIME_STREAM_WINDOW_SEC = float(
|
||
os.getenv(
|
||
"REALTIME_STREAM_WINDOW_SEC",
|
||
str(self.REALTIME_STREAM_WINDOW_SEC),
|
||
)
|
||
)
|
||
self.REALTIME_STREAM_STABLE_TAIL_CHARS = int(
|
||
os.getenv(
|
||
"REALTIME_STREAM_STABLE_TAIL_CHARS",
|
||
str(self.REALTIME_STREAM_STABLE_TAIL_CHARS),
|
||
)
|
||
)
|
||
self.REALTIME_STREAM_STABLE_MIN_GROW_CHARS = int(
|
||
os.getenv(
|
||
"REALTIME_STREAM_STABLE_MIN_GROW_CHARS",
|
||
str(self.REALTIME_STREAM_STABLE_MIN_GROW_CHARS),
|
||
)
|
||
)
|
||
self.REALTIME_STREAM_DIVERGENCE_TOLERANCE_CHARS = int(
|
||
os.getenv(
|
||
"REALTIME_STREAM_DIVERGENCE_TOLERANCE_CHARS",
|
||
str(self.REALTIME_STREAM_DIVERGENCE_TOLERANCE_CHARS),
|
||
)
|
||
)
|
||
self.REALTIME_PARTIAL_HOLDBACK_CHARS = int(
|
||
os.getenv(
|
||
"REALTIME_PARTIAL_HOLDBACK_CHARS",
|
||
str(self.REALTIME_PARTIAL_HOLDBACK_CHARS),
|
||
)
|
||
)
|
||
self.REALTIME_LONGFORM_MIN_SEC = float(
|
||
os.getenv(
|
||
"REALTIME_LONGFORM_MIN_SEC",
|
||
str(self.REALTIME_LONGFORM_MIN_SEC),
|
||
)
|
||
)
|
||
self.REALTIME_LONGFORM_CHUNK_SEC = float(
|
||
os.getenv(
|
||
"REALTIME_LONGFORM_CHUNK_SEC",
|
||
str(self.REALTIME_LONGFORM_CHUNK_SEC),
|
||
)
|
||
)
|
||
self.REALTIME_LONGFORM_OVERLAP_SEC = float(
|
||
os.getenv(
|
||
"REALTIME_LONGFORM_OVERLAP_SEC",
|
||
str(self.REALTIME_LONGFORM_OVERLAP_SEC),
|
||
)
|
||
)
|
||
self.REALTIME_VAD_CHECK_INTERVAL_SEC = float(
|
||
os.getenv(
|
||
"REALTIME_VAD_CHECK_INTERVAL_SEC",
|
||
str(self.REALTIME_VAD_CHECK_INTERVAL_SEC),
|
||
)
|
||
)
|
||
self.REALTIME_VAD_FINALIZE_SILENCE_SEC = float(
|
||
os.getenv(
|
||
"REALTIME_VAD_FINALIZE_SILENCE_SEC",
|
||
str(self.REALTIME_VAD_FINALIZE_SILENCE_SEC),
|
||
)
|
||
)
|
||
self.REALTIME_FINAL_SEGMENT_SOFT_LIMIT_SEC = float(
|
||
os.getenv(
|
||
"REALTIME_FINAL_SEGMENT_SOFT_LIMIT_SEC",
|
||
str(self.REALTIME_FINAL_SEGMENT_SOFT_LIMIT_SEC),
|
||
)
|
||
)
|
||
self.REALTIME_FINAL_SEGMENT_HARD_LIMIT_SEC = float(
|
||
os.getenv(
|
||
"REALTIME_FINAL_SEGMENT_HARD_LIMIT_SEC",
|
||
str(self.REALTIME_FINAL_SEGMENT_HARD_LIMIT_SEC),
|
||
)
|
||
)
|
||
self.REALTIME_ENABLE_DIARIZATION = (
|
||
os.getenv(
|
||
"REALTIME_ENABLE_DIARIZATION",
|
||
str(self.REALTIME_ENABLE_DIARIZATION),
|
||
).lower()
|
||
== "true"
|
||
)
|
||
self.REALTIME_ENABLE_SEGMENT_REFINE = (
|
||
os.getenv(
|
||
"REALTIME_ENABLE_SEGMENT_REFINE",
|
||
str(self.REALTIME_ENABLE_SEGMENT_REFINE),
|
||
).lower()
|
||
== "true"
|
||
)
|
||
self.REALTIME_DIARIZATION_MIN_SEC = float(
|
||
os.getenv(
|
||
"REALTIME_DIARIZATION_MIN_SEC",
|
||
str(self.REALTIME_DIARIZATION_MIN_SEC),
|
||
)
|
||
)
|
||
self.REALTIME_DIARIZATION_LOOKBACK_SEC = float(
|
||
os.getenv(
|
||
"REALTIME_DIARIZATION_LOOKBACK_SEC",
|
||
str(self.REALTIME_DIARIZATION_LOOKBACK_SEC),
|
||
)
|
||
)
|
||
self.REALTIME_DIARIZATION_WINDOW_SEC = float(
|
||
os.getenv(
|
||
"REALTIME_DIARIZATION_WINDOW_SEC",
|
||
str(self.REALTIME_DIARIZATION_WINDOW_SEC),
|
||
)
|
||
)
|
||
self.REALTIME_SPEAKER_MIN_SEC = float(
|
||
os.getenv(
|
||
"REALTIME_SPEAKER_MIN_SEC",
|
||
str(self.REALTIME_SPEAKER_MIN_SEC),
|
||
)
|
||
)
|
||
self.REALTIME_SPEAKER_CLUSTER_THRESHOLD = float(
|
||
os.getenv(
|
||
"REALTIME_SPEAKER_CLUSTER_THRESHOLD",
|
||
str(self.REALTIME_SPEAKER_CLUSTER_THRESHOLD),
|
||
)
|
||
)
|
||
self.REALTIME_UNKNOWN_SPK_CLUSTER_THRESHOLD = float(
|
||
os.getenv(
|
||
"REALTIME_UNKNOWN_SPK_CLUSTER_THRESHOLD",
|
||
str(self.REALTIME_UNKNOWN_SPK_CLUSTER_THRESHOLD),
|
||
)
|
||
)
|
||
self.REALTIME_RECENT_UNKNOWN_SPK_THRESHOLD = float(
|
||
os.getenv(
|
||
"REALTIME_RECENT_UNKNOWN_SPK_THRESHOLD",
|
||
str(self.REALTIME_RECENT_UNKNOWN_SPK_THRESHOLD),
|
||
)
|
||
)
|
||
self.REALTIME_SPEAKER_CONFIRM_THRESHOLD = float(
|
||
os.getenv(
|
||
"REALTIME_SPEAKER_CONFIRM_THRESHOLD",
|
||
str(self.REALTIME_SPEAKER_CONFIRM_THRESHOLD),
|
||
)
|
||
)
|
||
self.REALTIME_REGISTRY_MIN_CLUSTER_CONFIDENCE = float(
|
||
os.getenv(
|
||
"REALTIME_REGISTRY_MIN_CLUSTER_CONFIDENCE",
|
||
str(self.REALTIME_REGISTRY_MIN_CLUSTER_CONFIDENCE),
|
||
)
|
||
)
|
||
self.REALTIME_SPEAKER_MAX_SLOTS = int(
|
||
os.getenv(
|
||
"REALTIME_SPEAKER_MAX_SLOTS",
|
||
str(self.REALTIME_SPEAKER_MAX_SLOTS),
|
||
)
|
||
)
|
||
self.REALTIME_SESSION_RESUME_TTL_SEC = int(
|
||
os.getenv(
|
||
"REALTIME_SESSION_RESUME_TTL_SEC",
|
||
str(self.REALTIME_SESSION_RESUME_TTL_SEC),
|
||
)
|
||
)
|
||
self.API_PREFIX = os.getenv("API_PREFIX", self.API_PREFIX)
|
||
self.TASK_STATE_DIR = os.getenv("TASK_STATE_DIR", self.TASK_STATE_DIR)
|
||
self.TASK_RETENTION_HOURS = int(
|
||
os.getenv("TASK_RETENTION_HOURS", str(self.TASK_RETENTION_HOURS))
|
||
)
|
||
|
||
def _parse_size(self, size_str: str) -> int:
|
||
"""解析带单位的大小字符串
|
||
|
||
支持格式:
|
||
- 纯数字:视为 MB(如 2048 = 2048MB = 2147483648 bytes)
|
||
- 带单位:如 2GB, 2048MB, 1.5GB
|
||
"""
|
||
size_str = size_str.strip().upper()
|
||
|
||
# 如果纯数字,视为 MB
|
||
if size_str.isdigit():
|
||
return int(size_str) * 1024 * 1024
|
||
|
||
# 带单位的处理
|
||
if size_str.endswith('GB'):
|
||
return int(float(size_str[:-2]) * 1024 * 1024 * 1024)
|
||
elif size_str.endswith('MB'):
|
||
return int(float(size_str[:-2]) * 1024 * 1024)
|
||
elif size_str.endswith('KB'):
|
||
return int(float(size_str[:-2]) * 1024)
|
||
else:
|
||
# 默认视为字节
|
||
return int(size_str)
|
||
|
||
def _ensure_directories(self):
|
||
"""确保必需的目录存在"""
|
||
os.makedirs(self.TEMP_DIR, exist_ok=True)
|
||
if self.LOG_FILE:
|
||
os.makedirs(os.path.dirname(self.LOG_FILE), exist_ok=True)
|
||
os.makedirs(self.MODELS_DIR, exist_ok=True)
|
||
os.makedirs(self.MODELSCOPE_CACHE, exist_ok=True)
|
||
os.makedirs(self.MODELSCOPE_PATH, exist_ok=True)
|
||
os.makedirs(self.DATA_DIR, exist_ok=True)
|
||
os.makedirs(self.TASK_STATE_DIR, exist_ok=True)
|
||
|
||
@property
|
||
def models_config_path(self) -> str:
|
||
"""获取模型配置文件的完整路径"""
|
||
return str(self.BASE_DIR / self.ASR_MODELS_CONFIG)
|
||
|
||
@property
|
||
def docs_url(self) -> Optional[str]:
|
||
"""获取文档URL"""
|
||
return "/docs"
|
||
|
||
@property
|
||
def redoc_url(self) -> Optional[str]:
|
||
"""获取ReDoc URL"""
|
||
return "/redoc"
|
||
|
||
|
||
# 全局配置实例
|
||
settings = Settings()
|