"""SRT 条目解析(纯函数,按秒返回,供参考字幕与产物字幕共用)。 与 `nodes/srt.py` 的区别:`nodes/srt.py` 保留原始毫秒字符串供生产链路 序列化;本模块把时间戳换算为**秒浮点数**,用于测试中的时间对齐计算与 篇幅统计,不参与生产输出。 """ from __future__ import annotations import re # 匹配 SRT 时间轴行(时间戳 --> 时间戳),作为条目边界。 _SRT_TIME_LINE_RE = re.compile( r"(\d{2}:\d{2}:\d{2},\d{3})\s*-->\s*(\d{2}:\d{2}:\d{2},\d{3})", ) def parse_srt_entries(text: str) -> list[dict]: """解析 SRT 为 [{start, end, text}](秒为单位)。 按行分块:一个条目 = 序号行 + 时间轴行 + 若干文本行(可空)。即使文本为 空串(如翻译补空占位、空字幕)也计入一条,时间轴不丢失。正文多行用空格 连接,便于按关键词检索。 """ entries: list[dict] = [] lines = text.splitlines() index = 0 while index < len(lines): line = lines[index].strip() # 跳过序号行与空行,找时间轴行。 if not line or not _SRT_TIME_LINE_RE.search(line): index += 1 continue match = _SRT_TIME_LINE_RE.search(line) start = _ts_to_seconds(match.group(1)) end = _ts_to_seconds(match.group(2)) index += 1 # 收集后续非序号、非时间轴的文本行(可空/多行),直到空行或序号行。 text_parts: list[str] = [] while index < len(lines): nxt = lines[index].strip() if not nxt: break # 空行:条目结束 if _SRT_TIME_LINE_RE.search(nxt): break # 下一个时间轴:条目结束 if nxt.isdigit(): break # 下一个序号:条目结束 text_parts.append(nxt) index += 1 entries.append( { "start": start, "end": end, "text": " ".join(text_parts), } ) index += 1 return entries def _ts_to_seconds(ts: str) -> float: """把 SRT 时间戳(HH:MM:SS,mmm)换算为秒。""" hours, minutes, rest = ts.split(":") seconds, millis = rest.split(",") return int(hours) * 3600 + int(minutes) * 60 + int(seconds) + int(millis) / 1000