"""Mock ASR 服务 — 用于测试和开发环境。 生成模拟的字幕时间轴,不依赖真实ASR服务。 """ from __future__ import annotations import re from pathlib import Path from typing import Optional from packages.domain.subtitle import ( SubtitleSegment, SubtitleTimeline, SubtitleWord, ) from packages.ports.asr_service import ASRService, ASRServiceError class MockASRService(ASRService): """Mock ASR 服务,生成模拟字幕数据。 如果 audio_path 对应的目录下有同名 .txt 文件, 就读取该文件内容作为字幕文本,按时间均匀分段。 否则生成默认的测试字幕。 """ def __init__(self, mock_text: Optional[str] = None): self._mock_text = mock_text def transcribe( self, audio_path: Path, language: Optional[str] = None, with_word_timestamps: bool = True, ) -> SubtitleTimeline: if not audio_path.exists(): raise ASRServiceError(f"音频文件不存在: {audio_path}", provider="mock") # 尝试读取同名 txt 文件作为字幕文本 text = self._mock_text if text is None: txt_path = audio_path.with_suffix(".txt") if txt_path.exists(): text = txt_path.read_text(encoding="utf-8").strip() else: text = "这是一段测试字幕。它用于验证ASR自动字幕功能是否正常工作。每一句话都会被正确地分段并显示在视频底部。字幕的样式可以根据用户的喜好进行自定义调整。" # 估算音频时长(用ffmpeg probe或者直接假设) # mock模式下按字数估算,每秒4个字 total_duration = max(5.0, len(text) / 4.0) segments = self._text_to_segments(text, total_duration, with_word_timestamps) return SubtitleTimeline( segments=segments, language=language or "zh", total_duration=total_duration, ) def _text_to_segments( self, text: str, total_duration: float, with_word_timestamps: bool, ) -> list[SubtitleSegment]: """将文本按句切分成带时间轴的字幕片段。""" # 按句末标点拆分 sentences = re.split(r"(?<=[。!?!?])", text) sentences = [s.strip() for s in sentences if s.strip()] if not sentences: sentences = [text] total_chars = sum(len(s) for s in sentences) if total_chars == 0: return [] segments = [] current_time = 0.0 for sentence in sentences: char_count = len(sentence) duration = total_duration * (char_count / total_chars) end_time = current_time + duration words: list[SubtitleWord] = [] if with_word_timestamps: # 每个字作为一个词级单元(中文按字,英文按词) word_time = current_time word_duration = duration / char_count for char in sentence: words.append( SubtitleWord( text=char, start=word_time, end=word_time + word_duration, ) ) word_time += word_duration segments.append( SubtitleSegment( text=sentence, start=current_time, end=end_time, words=words, ) ) current_time = end_time return segments