- scheduler: ffmpeg 异步线程 + GPU 串行调度 + 模型复用(2N→2 次加载) - pipeline: 阶段拆分(extract/asr/translate),中间数据存 Task 字段 - translate_service: 长度排序批处理,padding 浪费减少 91% - model_manager: ASR/翻译不共驻,BatchedInferencePipeline 批量解码 - 日志分级: INFO=任务流转里程碑,DEBUG=进度详情;默认 INFO - 前端: 日志最新在上+滚动感知+退避轮询;24h 时间;上传中状态显示 - /health: 返回完整 Whisper/NLLB 配置 - upload_service: 单事务 complete + 扩展名白名单 - task_router: 合并 UploadSession 虚拟任务到列表 - Dockerfile: CPU/GPU 独立构建链,deps 缓存稳定 - prefetch_models: 安装时预下载模型权重
62 lines
2.3 KiB
Python
62 lines
2.3 KiB
Python
"""分片上传会话 ORM:支撑断点续传。"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
import logging
|
||
from datetime import datetime, timezone
|
||
from typing import Any
|
||
|
||
from sqlalchemy import DateTime, ForeignKey, Integer, String, Text, TypeDecorator
|
||
from sqlalchemy.orm import Mapped, mapped_column
|
||
|
||
from ..database import Base
|
||
|
||
logger = logging.getLogger("audio2text.models")
|
||
|
||
|
||
def _now() -> datetime:
|
||
return datetime.now(timezone.utc)
|
||
|
||
|
||
class _IntList(TypeDecorator):
|
||
"""把 list[int] 存成 JSON 字符串。SQLite 没有 ARRAY 类型。"""
|
||
|
||
impl = Text
|
||
cache_ok = True
|
||
|
||
def process_bind_param(self, value: Any, dialect) -> str | None:
|
||
return json.dumps(value) if value is not None else None
|
||
|
||
def process_result_value(self, value: Any, dialect) -> list[int]:
|
||
if not value:
|
||
return []
|
||
try:
|
||
return json.loads(value)
|
||
except (json.JSONDecodeError, TypeError):
|
||
# DB 脏数据(手工修改/并发写截断)不应让整行读取失败
|
||
logger.warning("uploaded_chunks JSON 解析失败,回退空列表:%r", value[:80])
|
||
return []
|
||
|
||
|
||
class UploadSession(Base):
|
||
__tablename__ = "upload_session"
|
||
|
||
upload_id: Mapped[str] = mapped_column(String(64), primary_key=True)
|
||
filename: Mapped[str] = mapped_column(String(512), nullable=False)
|
||
size_bytes: Mapped[int] = mapped_column(Integer, nullable=False)
|
||
chunk_size: Mapped[int] = mapped_column(Integer, nullable=False)
|
||
total_chunks: Mapped[int] = mapped_column(Integer, nullable=False)
|
||
uploaded_chunks: Mapped[list[int]] = mapped_column(_IntList, default=list)
|
||
# pending → completed(complete 成功)| abandoned(reaper 清理)
|
||
status: Mapped[str] = mapped_column(String(32), nullable=False, default="pending", index=True)
|
||
# 拼接完成后的视频相对 upload_dir 路径
|
||
final_path: Mapped[str | None] = mapped_column(String(1024), nullable=True)
|
||
# complete 后关联的 Task.id。ondelete=SET NULL:Task 被删时 session 保留(task_id 置空)
|
||
task_id: Mapped[int | None] = mapped_column(
|
||
ForeignKey("task.id", ondelete="SET NULL"), nullable=True
|
||
)
|
||
|
||
created_at: Mapped[datetime] = mapped_column(DateTime, default=_now, index=True)
|
||
updated_at: Mapped[datetime] = mapped_column(DateTime, default=_now, onupdate=_now)
|