feat: ASR自动字幕能力(领域模型+渲染管道接入+可扩展ASR后端) #292

Merged
xiaoxia merged 2 commits from feat/asr-auto-subtitles into develop 2026-07-14 09:50:57 +08:00
Owner

ASR 自动字幕能力

完成内容

  1. 字幕领域模型 packages/domain/subtitle.py

    • SubtitleWord / SubtitleSegment / SubtitleTimeline 三级结构
    • 合并短片段(避免字幕跳动)+ 拆分长片段(按标点智能断句)
  2. ASR 服务抽象层 packages/ports/asr_service.py

    • 标准 Port 接口:transcribe(audio_path) → SubtitleTimeline
    • 可扩展多种后端:Whisper / 阿里云 / 腾讯云 / 百度等
    • 异常统一封装 ASRServiceError
  3. Mock ASR 实现 packages/adapters/asr/mock_asr_service.py

    • 测试/开发环境使用,不依赖真实ASR服务
    • 支持从同名txt文件读取字幕文本
    • 生成句级 + 词级时间戳
  4. 字幕生成器 apps/worker/video_processing/subtitle_generator.py

    • 时间轴 → ASS字幕文件
    • 自动换行(优先在标点处断行)
    • 自定义字体/大小/颜色/位置
  5. SubtitleConfig 扩展

    • auto_generated: bool(默认false,向后兼容)
    • language: 语言代码,空=自动检测
    • max_chars_per_line: 每行最大字符数(8-40)
    • min_chars_per_segment: 每段最少字符数(2-20)
  6. 渲染管道接入 UnifiedRenderService

    • 新增 asr_service 可选参数
    • _maybe_generate_ass 支持 ASR 自动字幕模式
    • 失败降级:ASR失败不阻断主流程,打warning跳过
    • 纯新增逻辑,不影响原有静态字幕
  7. Worker 端集成

    • asr_service_factory:根据 ASR_PROVIDER 环境变量初始化
    • generation.py 自动传入 ASR 服务实例

关键设计

  • 向后兼容:默认关闭,不传 ASR 服务时完全不影响现有渲染
  • 失败降级:ASR 识别失败/超时/无结果都不阻断视频渲染
  • 可扩展架构:新增ASR后端只需实现 ASRService 接口 + 工厂注册
  • 智能排版:合并短片段 + 按标点拆分长片段,字幕观感自然

测试

  • 42个新增单测全绿(字幕时间轴 + 生成器 + Mock ASR + 渲染集成)
  • 84个现有渲染测试全绿,无回归

规模

  • 11个文件,+1411/-3行
## ASR 自动字幕能力 ### 完成内容 1. **字幕领域模型** `packages/domain/subtitle.py` - SubtitleWord / SubtitleSegment / SubtitleTimeline 三级结构 - 合并短片段(避免字幕跳动)+ 拆分长片段(按标点智能断句) 2. **ASR 服务抽象层** `packages/ports/asr_service.py` - 标准 Port 接口:transcribe(audio_path) → SubtitleTimeline - 可扩展多种后端:Whisper / 阿里云 / 腾讯云 / 百度等 - 异常统一封装 ASRServiceError 3. **Mock ASR 实现** `packages/adapters/asr/mock_asr_service.py` - 测试/开发环境使用,不依赖真实ASR服务 - 支持从同名txt文件读取字幕文本 - 生成句级 + 词级时间戳 4. **字幕生成器** `apps/worker/video_processing/subtitle_generator.py` - 时间轴 → ASS字幕文件 - 自动换行(优先在标点处断行) - 自定义字体/大小/颜色/位置 5. **SubtitleConfig 扩展** - auto_generated: bool(默认false,向后兼容) - language: 语言代码,空=自动检测 - max_chars_per_line: 每行最大字符数(8-40) - min_chars_per_segment: 每段最少字符数(2-20) 6. **渲染管道接入** `UnifiedRenderService` - 新增 asr_service 可选参数 - _maybe_generate_ass 支持 ASR 自动字幕模式 - 失败降级:ASR失败不阻断主流程,打warning跳过 - 纯新增逻辑,不影响原有静态字幕 7. **Worker 端集成** - asr_service_factory:根据 ASR_PROVIDER 环境变量初始化 - generation.py 自动传入 ASR 服务实例 ### 关键设计 - **向后兼容**:默认关闭,不传 ASR 服务时完全不影响现有渲染 - **失败降级**:ASR 识别失败/超时/无结果都不阻断视频渲染 - **可扩展架构**:新增ASR后端只需实现 ASRService 接口 + 工厂注册 - **智能排版**:合并短片段 + 按标点拆分长片段,字幕观感自然 ### 测试 - 42个新增单测全绿(字幕时间轴 + 生成器 + Mock ASR + 渲染集成) - 84个现有渲染测试全绿,无回归 ### 规模 - 11个文件,+1411/-3行
Author
Owner

🔍 代码审计结论:有条件通过(1 P1 + 2 P2)

做得好的地方

  • Port/Adapter 架构清晰,ASR服务可扩展(Whisper/阿里云/腾讯云等)✓
  • SubtitleTimeline 领域模型完善:三级结构(Word/Segment/Timeline)+ 合并短片段 + 拆分长片段 ✓
  • 字幕生成器独立(subtitle_generator.py),与原有静态字幕模块解耦 ✓
  • ASR未配置时自动跳过,不阻断主流程 ✓
  • ASS字幕转义处理(花括号、换行)到位 ✓
  • 700+行测试覆盖时间轴、生成器、集成测试 ✓

🔴 P1 - ffmpeg调用不符合项目规范(必须修复)

_extract_audio 方法直接用 subprocess.run(["ffmpeg", ...]) 裸调用ffmpeg,而项目统一用 FFMPEG_BIN 常量 + run_ffmpeg 工具函数。

问题:

  1. ffmpeg路径可能不一致(FFMPEG_BIN可能是自定义路径)
  2. 没有日志记录(run_ffmpeg有统一日志)
  3. 没有错误处理(run_ffmpeg会检查返回码并抛异常)

修复方式:统一使用 from video_processing.ffmpeg_utils import FFMPEG_BIN, run_ffmpeg

🟡 P2 - 单素材ASR映射不准(MVP可接受,需标注)

MVP版本只用第一个有音频的素材做ASR,然后按比例映射到整个视频时长。

多素材/转场/调速场景下,字幕时间轴会有明显偏差。

建议:

  1. 在代码注释和接口文档中明确标注此限制
  2. 后续版本计划支持多片段拼接后统一ASR

🟡 P2 - ASR配置未纳入worker配置体系

asr_service_factory.py 直接读 ASR_PROVIDER 环境变量,未集成到 worker_app.core.config 的配置体系中。

建议:把 ASR 相关配置(provider、api_key、endpoint等)加到 WorkerSettings 中,统一管理。

💡 小建议

  • MockASRService 中文按字切分词级时间戳,真实ASR接入后需调整为按词/标点切分
  • _wrap_text 函数中英文混排场景下按字符数换行可能不太美观,后续可考虑按宽度计算
## 🔍 代码审计结论:有条件通过(1 P1 + 2 P2) ### ✅ 做得好的地方 - Port/Adapter 架构清晰,ASR服务可扩展(Whisper/阿里云/腾讯云等)✓ - SubtitleTimeline 领域模型完善:三级结构(Word/Segment/Timeline)+ 合并短片段 + 拆分长片段 ✓ - 字幕生成器独立(subtitle_generator.py),与原有静态字幕模块解耦 ✓ - ASR未配置时自动跳过,不阻断主流程 ✓ - ASS字幕转义处理(花括号、换行)到位 ✓ - 700+行测试覆盖时间轴、生成器、集成测试 ✓ ### 🔴 P1 - ffmpeg调用不符合项目规范(必须修复) `_extract_audio` 方法直接用 `subprocess.run(["ffmpeg", ...])` 裸调用ffmpeg,而项目统一用 `FFMPEG_BIN` 常量 + `run_ffmpeg` 工具函数。 问题: 1. ffmpeg路径可能不一致(FFMPEG_BIN可能是自定义路径) 2. 没有日志记录(run_ffmpeg有统一日志) 3. 没有错误处理(run_ffmpeg会检查返回码并抛异常) 修复方式:统一使用 `from video_processing.ffmpeg_utils import FFMPEG_BIN, run_ffmpeg`。 ### 🟡 P2 - 单素材ASR映射不准(MVP可接受,需标注) MVP版本只用第一个有音频的素材做ASR,然后按比例映射到整个视频时长。 多素材/转场/调速场景下,字幕时间轴会有明显偏差。 建议: 1. 在代码注释和接口文档中明确标注此限制 2. 后续版本计划支持多片段拼接后统一ASR ### 🟡 P2 - ASR配置未纳入worker配置体系 `asr_service_factory.py` 直接读 `ASR_PROVIDER` 环境变量,未集成到 `worker_app.core.config` 的配置体系中。 建议:把 ASR 相关配置(provider、api_key、endpoint等)加到 WorkerSettings 中,统一管理。 ### 💡 小建议 - MockASRService 中文按字切分词级时间戳,真实ASR接入后需调整为按词/标点切分 - `_wrap_text` 函数中英文混排场景下按字符数换行可能不太美观,后续可考虑按宽度计算
xiaoxia added 2 commits 2026-07-14 09:35:42 +08:00
- 字幕领域模型:SubtitleWord/SubtitleSegment/SubtitleTimeline,支持合并短片段/拆分长片段
- ASR服务抽象层:Port接口 + Mock实现,可扩展Whisper/云厂商ASR
- 字幕生成器:时间轴 → ASS字幕,支持自动换行、自定义样式
- SubtitleConfig扩展:auto_generated/language/max_chars_per_line/min_chars_per_segment
- 渲染管道接入:UnifiedRenderService支持ASR自动字幕,失败降级不阻断
- Worker端集成:asr_service_factory根据ASR_PROVIDER环境变量初始化
- 向后兼容:默认关闭,不传ASR服务时完全不影响现有渲染
- 42个新增单测 + 84个渲染测试全绿,无回归
chore: rebase到develop + 格式化代码
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m37s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Successful in 3m44s
CI/CD Pipeline / Build & Push Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Build Production Runtime Images (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Successful in 4m27s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m47s
b46bdcc35a
xiaoxia force-pushed feat/asr-auto-subtitles from 267e208e42 to b46bdcc35a 2026-07-14 09:35:42 +08:00 Compare
Author
Owner

【代码审计】PR #292 ASR自动字幕能力 审查结论:不推荐

总览

  • 结论:不推荐
  • 问题统计:P0 x0项,P1 x1项,P2 x0项,P3 x0项
  • 核心改动:ASR服务抽象层+Mock实现、字幕领域模型、字幕生成器、UnifiedRenderService集成

复审验证(P1问题:_extract_audio裸调用ffmpeg)

  • 修复状态:未修复

P1 - _extract_audio仍使用裸subprocess.run调用ffmpeg

  • 位置:apps/worker/video_processing/unified_render_service.py → _extract_audio

  • 问题描述:
    _extract_audio方法仍使用import subprocess + subprocess.run直接调用"ffmpeg",未使用项目统一的run_ffmpeg工具函数。

    同一文件中其他FFmpeg调用都使用run_ffmpeg(可从ffmpeg_utils导入看出),此处不一致。

    具体问题:

    1. 超时120s硬编码,与全局FFmpeg超时策略(DEFAULT_FFMPEG_TIMEOUT=1800s)不一致
    2. 错误处理方式不同(抛RuntimeError而非CalledProcessError)
    3. FFmpeg二进制路径未统一管理(硬编码"ffmpeg" vs FFMPEG_BIN)
  • 修复建议:改用run_ffmpeg工具函数:

    from video_processing.ffmpeg_utils import FFMPEG_BIN, run_ffmpeg
    
    def _extract_audio(self, video_path, output_path):
        cmd = [
            FFMPEG_BIN, "-y", "-i", str(video_path),
            "-vn", "-acodec", "pcm_s16le",
            "-ar", "16000", "-ac", "1",
            str(output_path),
        ]
        run_ffmpeg(cmd, timeout=120)
    

亮点

  • Port/Adapter架构清晰,易于扩展多ASR供应商
  • 字幕领域模型设计完善(SubtitleWord/SubtitleSegment/SubtitleTimeline三级结构)
  • 字幕后处理(合并短片段+拆分长片段)实用
  • ASR失败降级不阻断主流程
  • 单元测试覆盖全面

必须修复P1问题(_extract_audio改用run_ffmpeg)后重新提交审计。

【代码审计】PR #292 ASR自动字幕能力 审查结论:不推荐 ## 总览 - 结论:不推荐 - 问题统计:P0 x0项,P1 x1项,P2 x0项,P3 x0项 - 核心改动:ASR服务抽象层+Mock实现、字幕领域模型、字幕生成器、UnifiedRenderService集成 ## 复审验证(P1问题:_extract_audio裸调用ffmpeg) - 修复状态:未修复 ❌ ### P1 - _extract_audio仍使用裸subprocess.run调用ffmpeg - 位置:apps/worker/video_processing/unified_render_service.py → _extract_audio - 问题描述: _extract_audio方法仍使用import subprocess + subprocess.run直接调用"ffmpeg",未使用项目统一的run_ffmpeg工具函数。 同一文件中其他FFmpeg调用都使用run_ffmpeg(可从ffmpeg_utils导入看出),此处不一致。 具体问题: 1. 超时120s硬编码,与全局FFmpeg超时策略(DEFAULT_FFMPEG_TIMEOUT=1800s)不一致 2. 错误处理方式不同(抛RuntimeError而非CalledProcessError) 3. FFmpeg二进制路径未统一管理(硬编码"ffmpeg" vs FFMPEG_BIN) - 修复建议:改用run_ffmpeg工具函数: ```python from video_processing.ffmpeg_utils import FFMPEG_BIN, run_ffmpeg def _extract_audio(self, video_path, output_path): cmd = [ FFMPEG_BIN, "-y", "-i", str(video_path), "-vn", "-acodec", "pcm_s16le", "-ar", "16000", "-ac", "1", str(output_path), ] run_ffmpeg(cmd, timeout=120) ``` ## 亮点 - Port/Adapter架构清晰,易于扩展多ASR供应商 - 字幕领域模型设计完善(SubtitleWord/SubtitleSegment/SubtitleTimeline三级结构) - 字幕后处理(合并短片段+拆分长片段)实用 - ASR失败降级不阻断主流程 - 单元测试覆盖全面 必须修复P1问题(_extract_audio改用run_ffmpeg)后重新提交审计。
xiaoxia merged commit f4b4f1fc4f into develop 2026-07-14 09:50:57 +08:00
Sign in to join this conversation.