- 音频/视频转双语(英/中)SRT 字幕,Docker 容器化,CPU 开发/GPU 生产同一份代码 - faster-whisper ASR(词级时间戳) + 断句时间戳重算 + NLLB 翻译(模型不共驻) - 分片上传(断点续传) + SQLite 持久化 + 主页/历史/日志页面 - 历史页文件名搜索;缓存定时清理(默认保留7天,可配置) - 双 Dockerfile(cpu/gpu) + setup/start/stop 脚本
137 lines
4.1 KiB
Python
137 lines
4.1 KiB
Python
"""运行时配置:所有参数从 config.yaml 读取,对齐 server/config.py 的风格。
|
||
|
||
CPU dev / GPU prod 仅靠 device / model / compute_type 三项切换,代码完全不变。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import os
|
||
from functools import lru_cache
|
||
from pathlib import Path
|
||
|
||
import yaml
|
||
from pydantic import BaseModel, Field, field_validator
|
||
|
||
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
||
DEFAULT_CONFIG_PATH = PROJECT_ROOT / "config.yaml"
|
||
|
||
|
||
class ServerConfig(BaseModel):
|
||
host: str = "0.0.0.0"
|
||
port: int = 8000
|
||
workers: int = 1
|
||
|
||
|
||
class StorageConfig(BaseModel):
|
||
upload_dir: str = "/data/uploads"
|
||
work_dir: str = "/data/.work"
|
||
output_dir: str = "/data/outputs"
|
||
chunk_bytes: int = 1024 * 1024
|
||
chunk_session_ttl_seconds: int = 300
|
||
# 缓存保留:超期任务产物(输出字幕 / 中间音频 / 保留的原始视频)连同 DB 记录一并清理
|
||
cache_retention_days: int = 7
|
||
# 定时清理间隔(小时):启动时跑一次,之后按此间隔循环
|
||
cache_cleanup_interval_hours: int = 24
|
||
|
||
|
||
class ProcessingConfig(BaseModel):
|
||
delete_original_after_extract: bool = True
|
||
keep_audio: bool = False
|
||
|
||
|
||
class AsrConfig(BaseModel):
|
||
model: str = "small"
|
||
device: str = "cpu" # cpu | cuda
|
||
compute_type: str = "int8" # cpu: int8;gpu: float16
|
||
language: str = "en"
|
||
word_timestamps: bool = True
|
||
vad_filter: bool = True
|
||
|
||
|
||
class TranslationConfig(BaseModel):
|
||
model: str = "facebook/nllb-200-distilled-1.3B"
|
||
device: str = "cpu" # cpu | cuda
|
||
src_lang: str = "eng_Latn"
|
||
tgt_lang: str = "zho_Hans"
|
||
batch_size: int = 16
|
||
max_length: int = 256
|
||
|
||
|
||
class SegmentationConfig(BaseModel):
|
||
max_words_per_line: int = 14
|
||
max_duration_seconds: float = 7.0
|
||
min_duration_seconds: float = 1.0
|
||
max_chars_per_line: int = 42
|
||
|
||
|
||
class LoggingConfig(BaseModel):
|
||
"""日志配置:控制台 + 内存缓冲的最低级别,以及缓冲条数。
|
||
|
||
分层语义:
|
||
- debug:详细(ffmpeg 命令、模型加载/卸载、转写逐段、翻译逐批进度)
|
||
- info:简略(仅任务阶段转换,如 "任务 N [transcribing 55%]")
|
||
- error:详细错误(完整 traceback,由 logger.exception 自带)
|
||
"""
|
||
|
||
level: str = "info" # debug | info | warning | error
|
||
buffer_size: int = 2000 # /logs 页面内存缓冲条数
|
||
|
||
@field_validator("level", mode="before")
|
||
@classmethod
|
||
def _normalize_level(cls, v):
|
||
return str(v).lower() if v else "info"
|
||
|
||
|
||
class DocsConfig(BaseModel):
|
||
"""/docs Basic Auth 凭据(明文,对齐 server)。"""
|
||
|
||
enabled: bool = True
|
||
username: str = "admin"
|
||
password: str = ""
|
||
realm: str = "audio2text docs"
|
||
|
||
@field_validator("password", mode="before")
|
||
@classmethod
|
||
def _coerce(cls, v):
|
||
return "" if v is None else str(v)
|
||
|
||
|
||
class Settings(BaseModel):
|
||
server: ServerConfig = ServerConfig()
|
||
storage: StorageConfig = StorageConfig()
|
||
processing: ProcessingConfig = ProcessingConfig()
|
||
asr: AsrConfig = AsrConfig()
|
||
translation: TranslationConfig = TranslationConfig()
|
||
segmentation: SegmentationConfig = SegmentationConfig()
|
||
logging: LoggingConfig = LoggingConfig()
|
||
docs: DocsConfig = DocsConfig()
|
||
|
||
def upload_dir(self) -> Path:
|
||
return Path(self.storage.upload_dir)
|
||
|
||
def work_dir(self) -> Path:
|
||
return Path(self.storage.work_dir)
|
||
|
||
def output_dir(self) -> Path:
|
||
return Path(self.storage.output_dir)
|
||
|
||
|
||
def _load_yaml(path: Path) -> dict:
|
||
if not path.exists():
|
||
raise FileNotFoundError(
|
||
f"未找到配置文件 {path};请先 cp config.example.yaml config.yaml 并填值。"
|
||
)
|
||
return yaml.safe_load(path.read_text(encoding="utf-8")) or {}
|
||
|
||
|
||
@lru_cache(maxsize=1)
|
||
def get_settings() -> Settings:
|
||
path = Path(os.getenv("CONFIG_PATH", str(DEFAULT_CONFIG_PATH)))
|
||
return Settings.model_validate(_load_yaml(path))
|
||
|
||
|
||
def reload_settings() -> Settings:
|
||
"""清缓存并重新读取,供脚本与测试使用。"""
|
||
get_settings.cache_clear()
|
||
return get_settings()
|