cang-ying/engines/subtitle-pipeline/srt-tools/script-to-srt-with-timing.py

279 lines
8.8 KiB
Python
Raw Permalink Normal View History

#!/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()