162 lines
3.7 KiB
Python
162 lines
3.7 KiB
Python
"""SRT Parser Module
|
|
|
|
解析 SRT 字幕文件格式,将字幕转换为结构化的 Segment 对象。
|
|
|
|
SRT 格式示例:
|
|
1
|
|
00:00:00,000 --> 00:00:05,000
|
|
第一句字幕内容
|
|
|
|
2
|
|
00:00:05,000 --> 00:00:10,000
|
|
第二句字幕内容
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
from typing import List
|
|
|
|
|
|
@dataclass
|
|
class Segment:
|
|
"""字幕分段数据结构
|
|
|
|
Attributes:
|
|
id: 字幕序号
|
|
start_time: 开始时间(秒)
|
|
end_time: 结束时间(秒)
|
|
text: 字幕文本
|
|
"""
|
|
id: int
|
|
start_time: float
|
|
end_time: float
|
|
text: str
|
|
|
|
|
|
# SRT 块正则表达式
|
|
# 格式: 序号\n时间码\n文本(可能多行)\n\n
|
|
SRT_BLOCK_PATTERN = re.compile(
|
|
r'(\d+)\s*\n'
|
|
r'(\d{2}:\d{2}:\d{2},\d{3})\s*-->\s*(\d{2}:\d{2}:\d{2},\d{3})\s*\n'
|
|
r'(.*?)(?=\n\n|\Z)',
|
|
re.DOTALL
|
|
)
|
|
|
|
|
|
def parse_srt_time(time_str: str) -> float:
|
|
"""解析 SRT 时间格式 (HH:MM:SS,mmm) 为秒数
|
|
|
|
Args:
|
|
time_str: SRT 时间字符串,如 "00:05:30,500"
|
|
|
|
Returns:
|
|
秒数,如 330.5
|
|
|
|
Example:
|
|
>>> parse_srt_time("00:05:30,500")
|
|
330.5
|
|
>>> parse_srt_time("01:02:03,004")
|
|
3723.004
|
|
"""
|
|
# 分割时间和毫秒
|
|
main_part, millis = time_str.rsplit(',', 1)
|
|
hours, minutes, seconds = main_part.split(':')
|
|
|
|
total_seconds = (
|
|
int(hours) * 3600 +
|
|
int(minutes) * 60 +
|
|
int(seconds) +
|
|
int(millis) / 1000
|
|
)
|
|
return total_seconds
|
|
|
|
|
|
def parse_srt(content: str) -> List[Segment]:
|
|
"""解析 SRT 格式字符串
|
|
|
|
将 SRT 格式的字幕内容解析为 Segment 对象列表。
|
|
|
|
Args:
|
|
content: SRT 格式的字符串内容
|
|
|
|
Returns:
|
|
Segment 对象列表
|
|
|
|
Raises:
|
|
ValueError: 内容为空或格式无效
|
|
|
|
Example:
|
|
>>> content = "1\\n00:00:00,000 --> 00:00:05,000\\n第一句\\n\\n"
|
|
>>> segments = parse_srt(content)
|
|
>>> len(segments)
|
|
1
|
|
>>> segments[0].text
|
|
'第一句'
|
|
"""
|
|
# 检查空内容
|
|
if not content or not content.strip():
|
|
raise ValueError("Empty SRT content")
|
|
|
|
segments = []
|
|
|
|
# 查找所有 SRT 块
|
|
matches = SRT_BLOCK_PATTERN.findall(content)
|
|
|
|
if not matches:
|
|
raise ValueError("Invalid SRT format: no valid blocks found")
|
|
|
|
for match in matches:
|
|
try:
|
|
segment_id = int(match[0])
|
|
start_time = parse_srt_time(match[1])
|
|
end_time = parse_srt_time(match[2])
|
|
# 清理文本:移除前后空白,合并多行为单行
|
|
text = ' '.join(match[3].strip().split())
|
|
|
|
segment = Segment(
|
|
id=segment_id,
|
|
start_time=start_time,
|
|
end_time=end_time,
|
|
text=text
|
|
)
|
|
segments.append(segment)
|
|
except (ValueError, IndexError) as e:
|
|
# 跳过解析失败的块
|
|
continue
|
|
|
|
if not segments:
|
|
raise ValueError("Invalid SRT format: no valid segments parsed")
|
|
|
|
return segments
|
|
|
|
|
|
def parse_srt_file(file_path: Path) -> List[Segment]:
|
|
"""从文件解析 SRT 格式
|
|
|
|
读取文件内容并解析为 Segment 对象列表。
|
|
|
|
Args:
|
|
file_path: SRT 文件路径
|
|
|
|
Returns:
|
|
Segment 对象列表
|
|
|
|
Raises:
|
|
FileNotFoundError: 文件不存在
|
|
ValueError: 内容格式无效
|
|
|
|
Example:
|
|
>>> from pathlib import Path
|
|
>>> segments = parse_srt_file(Path("subtitles.srt"))
|
|
>>> len(segments)
|
|
10
|
|
"""
|
|
if not file_path.exists():
|
|
raise FileNotFoundError(f"SRT file not found: {file_path}")
|
|
|
|
content = file_path.read_text(encoding='utf-8')
|
|
return parse_srt(content)
|