cang-ying/engines/subtitle-pipeline/srt-tools/script-to-srt-with-timing.py
Guanghu Domestic Migration a2fa7d57d8 chore: import sanitized domestic snapshot for REPO-005
Source snapshot: 23037fa3a2e9b0597162735755e92fc763bf4d94
2026-07-17 15:55:48 +08:00

279 lines
8.8 KiB
Python
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
Script to SRT with Timing · 剧本到 SRT 时间轴生成器
===============================================
用剧本台词和配音时长生成正式 SRT别手写测试时间轴。
功能:
1. 读取剧本台词JSON/MD/TXT
2. 读取配音文件(获取每条台词的实际时长)
3. 自动计算时间轴(考虑角色对话间隔)
4. 生成标准 SRT 字幕文件
5. 支持多角色、旁白、音效标注
依赖:
pip install pysrt
输入格式:
# 剧本 JSON 格式(推荐)
[
{"id": 1, "character": "苏白", "text": "付费才能修仙?", "audio_file": "./audio/subai_001.wav"},
{"id": 2, "character": "旁白", "text": "天道宗门口", "audio_file": "./audio/narrator_001.wav"}
]
# 剧本 MD 格式(兼容)
## E1-SHOT01
**苏白**付费才能修仙audio: subai_001.wav
**旁白**天道宗门口audio: narrator_001.wav
输出 SRT 格式:
1
00:00:01,000 --> 00:00:03,500
付费才能修仙?
2
00:00:03,800 --> 00:00:05,200
天道宗门口
用法:
# 从剧本 JSON + 配音文件生成 SRT
python script-to-srt-with-timing.py --script script.json --output subtitles.srt
# 指定配音目录(自动匹配 audio_file
python script-to-srt-with-timing.py --script script.json --audio-dir ./audio/ --output subtitles.srt
# 从 MD 剧本生成
python script-to-srt-with-timing.py --script script.md --output subtitles.srt
# 自定义对话间隔(秒)
python script-to-srt-with-timing.py --script script.json --gap 0.3 --output subtitles.srt
路径:
video-ai-system/engines/subtitle-pipeline/srt-tools/script-to-srt-with-timing.py
"""
import argparse
import json
import os
import sys
import re
from pathlib import Path
try:
import pysrt
except ImportError:
print("[ERROR] 缺少依赖pysrt")
print("请先安装pip install pysrt")
sys.exit(1)
def parse_script_json(script_path: str) -> list:
"""
解析剧本 JSON 文件
:param script_path: 剧本 JSON 文件路径
:return: 台词列表 [{"id", "character", "text", "audio_file", "start", "end"}]
"""
with open(script_path, "r", encoding="utf-8") as f:
script = json.load(f)
print(f"[INFO] 读取剧本 JSON{len(script)} 条台词")
return script
def parse_script_md(script_path: str) -> list:
"""
解析剧本 MD 文件(兼容格式)
:param script_path: 剧本 MD 文件路径
:return: 台词列表
"""
with open(script_path, "r", encoding="utf-8") as f:
lines = f.readlines()
script = []
current_id = 0
for line in lines:
# 匹配:**角色**台词audio: file.wav
match = re.match(r"\*\*(.+?)\*\*[:]\s*(.+?)\s*\(audio:\s*(.+?)\)", line.strip())
if match:
character = match.group(1).strip()
text = match.group(2).strip()
audio_file = match.group(3).strip()
script.append({
"id": current_id + 1,
"character": character,
"text": text,
"audio_file": audio_file
})
current_id += 1
print(f"[INFO] 读取剧本 MD{len(script)} 条台词")
return script
def get_audio_duration(audio_path: str) -> float:
"""
获取音频文件时长(秒)
:param audio_path: 音频文件路径
:return: 时长(秒)
"""
try:
import ffmpeg
probe = ffmpeg.probe(audio_path)
duration = float(probe['format']['duration'])
return duration
except ImportError:
# 如果没有 ffmpeg-python使用 ffprobe 命令行
try:
import subprocess
cmd = [
"ffprobe", "-v", "quiet", "-print_format", "json",
"-show_format", "-show_streams", audio_path
]
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
probe = json.loads(result.stdout)
duration = float(probe['format']['duration'])
return duration
except Exception as e:
print(f"[WARN] 无法获取音频时长:{audio_path} ({e})")
# 估算:中文普通话约 4-6 字/秒
return 2.0 # 默认 2 秒
except Exception as e:
print(f"[WARN] 无法获取音频时长:{audio_path} ({e})")
return 2.0
def calculate_timings(script: list, audio_dir: str = None, gap: float = 0.2) -> list:
"""
计算时间轴
:param script: 台词列表
:param audio_dir: 配音文件目录(如果 audio_file 是相对路径)
:param gap: 对话间隔(秒)
:return: 带时间轴的台词列表
"""
current_time = 0.0
for item in script:
# 获取配音文件时长
audio_file = item.get("audio_file")
if audio_file and os.path.isfile(audio_file):
duration = get_audio_duration(audio_file)
elif audio_file and audio_dir:
audio_path = os.path.join(audio_dir, audio_file)
if os.path.isfile(audio_path):
duration = get_audio_duration(audio_path)
else:
# 估算时长
text = item.get("text", "")
duration = len(text) / 5.0 # 假设 5 字/秒
else:
# 没有配音文件,估算时长
text = item.get("text", "")
duration = len(text) / 5.0 # 假设 5 字/秒
# 设置开始和结束时间
item["start"] = current_time
item["end"] = current_time + duration
# 更新当前时间(加上间隔)
current_time = item["end"] + gap
print(f"[INFO] 时间轴计算完成:总时长 {current_time:.2f}")
return script
def generate_srt(script: list, output_path: str):
"""
生成 SRT 字幕文件
:param script: 带时间轴的台词列表
:param output_path: 输出 SRT 文件路径
"""
# 创建 pysrt SubRipFile
subs = pysrt.SubRipFile()
for item in script:
# 创建字幕项
sub = pysrt.SubRipItem()
sub.index = item["id"]
sub.text = item["text"]
# 设置时间
start_seconds = item["start"]
end_seconds = item["end"]
sub.start.hours = int(start_seconds // 3600)
sub.start.minutes = int((start_seconds % 3600) // 60)
sub.start.seconds = int(start_seconds % 60)
sub.start.milliseconds = int((start_seconds % 1) * 1000)
sub.end.hours = int(end_seconds // 3600)
sub.end.minutes = int((end_seconds % 3600) // 60)
sub.end.seconds = int(end_seconds % 60)
sub.end.milliseconds = int((end_seconds % 1) * 1000)
subs.append(sub)
# 保存 SRT 文件
os.makedirs(os.path.dirname(os.path.abspath(output_path)), exist_ok=True)
subs.save(output_path, encoding="utf-8")
print(f"[OK] SRT 字幕文件已生成:{output_path}")
print(f"[INFO] 共 {len(script)} 条字幕")
def main():
parser = argparse.ArgumentParser(
description="Script to SRT with Timing · 剧本到 SRT 时间轴生成器",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
示例:
python script-to-srt-with-timing.py --script script.json --output subtitles.srt
python script-to-srt-with-timing.py --script script.md --audio-dir ./audio/ --output subtitles.srt
python script-to-srt-with-timing.py --script script.json --gap 0.3 --output subtitles.srt
"""
)
parser.add_argument("--script", required=True, help="剧本文件路径JSON 或 MD 格式)")
parser.add_argument("--audio-dir", help="配音文件目录(如果 audio_file 是相对路径)")
parser.add_argument("--output", required=True, help="输出 SRT 文件路径")
parser.add_argument("--gap", type=float, default=0.2, help="对话间隔(秒,默认 0.2")
args = parser.parse_args()
# 检查剧本文件是否存在
if not os.path.isfile(args.script):
print(f"[ERROR] 剧本文件不存在:{args.script}")
sys.exit(1)
# 解析剧本
if args.script.lower().endswith(".json"):
script = parse_script_json(args.script)
elif args.script.lower().endswith(".md"):
script = parse_script_md(args.script)
else:
print(f"[ERROR] 不支持的剧本格式:{args.script}")
print("支持格式:.json, .md")
sys.exit(1)
if not script:
print("[ERROR] 剧本为空,请检查格式")
sys.exit(1)
# 计算时间轴
script = calculate_timings(script, args.audio_dir, args.gap)
# 生成 SRT 文件
generate_srt(script, args.output)
print("\n[OK] 处理完成")
if __name__ == "__main__":
main()