|
马上注册,结交更多好友,享用更多功能^_^
您需要 登录 才可以下载或查看,没有账号?立即注册
x
求助,代码有问题,请帮着修改下
要求:
1. 解析 a.srt 或 a.ass 字幕文件,
2. 利用 eng-to-ipa 库将每个单词转换为 IPA,若单词属于虚词(比如 a, an, the 等,在 function_words.txt 中定义),则将此虚词的单词及ipa都标记为灰色小号字
3. 对最终ass的要求:
- 显示英文字幕。在每个英文单词的正上方显示此单词对应的ipa(ipa字号比下方小1号,是灰色)
- 下方的单词要与它头顶上的它本身的ipa音标在垂直方向上对齐.标点与单词之间没有空格,单词与标点之间没有空格.单词之间要有正常空格间隙,单词不能重叠
- 生成的ASS字幕中单词和标点之间的空格与间距符合正常书写习惯。
4. 生成最终的 ASS 字幕文件(a_tmp.ass)
代码有以下问题:
1.单词与标点,标点与单词之间的间隙不对
2.一个单词后跟一个长单词时,2个单词就连在一起了
3.虚词表里的词应该在单词行,音标行都变成灰色,小字号
代码:
- #!/usr/bin/env python3
- """
- 该脚本实现了:
- 1. 解析 a.srt 或 a.ass 字幕文件,将每条字幕的文本提取出来
- 2. 利用 eng-to-ipa 库将每个单词转换为 IPA,若单词属于虚词(在 function_words.txt 中定义),则使用预设 IPA 并标记为灰色小号字
- 3. 对每条字幕文本,按单词(包括标点符号)拆分,每个单词生成一对 ASS 对话事件:
- - 上方:显示 IPA(字号比下方小1号)
- - 下方:显示英文原文
- 4. 生成最终的 ASS 字幕文件(a_tmp.ass)
- """
- import re
- import sys
- import eng_to_ipa
- from PIL import ImageFont
- # 加载字体并设置字号
- normal_font = ImageFont.truetype("Arial Unicode MS.ttf", 36) # 普通单词字号
- ipa_font = ImageFont.truetype("Arial Unicode MS.ttf", 30) # IPA音标字号
- func_font = ImageFont.truetype("Arial Unicode MS.ttf", 28) # 虚词字号
- class Token:
- """表示一个文本单元(单词或标点)"""
- def __init__(self, text, is_word=True, is_function=False):
- self.text = text.strip() # 去除可能的空格
- self.is_word = is_word
- self.is_function = is_function
- self.ipa = None
- self.width = 0
- self.ipa_width = 0
- self.x_pos = 0
- self.next_space = True # 是否在此token后添加空格
- def get_text_width(text, font):
- """获取文本在指定字体下的渲染宽度"""
- try:
- width = font.getlength(text.strip())
- return width * 1.1 # 添加10%的安全边距,防止重叠
- except AttributeError:
- bbox = font.getbbox(text.strip())
- return (bbox[2] - bbox[0]) * 1.1
- def load_function_words(filename="function_words.txt"):
- """加载虚词及其 IPA 对照表"""
- func_words = {}
- with open(filename, "r", encoding="utf-8") as f:
- for line in f:
- line = line.strip()
- if not line or line.startswith("#"):
- continue
- parts = line.split("\t")
- if len(parts) >= 2:
- word = parts[0].strip().lower()
- ipa_value = parts[1].strip()
- func_words[word] = ipa_value
- return func_words
- def convert_srt_to_ass_time(srt_time):
- """将 SRT 时间格式转换为 ASS 时间格式"""
- hh, mm, ss_mmm = srt_time.split(":")
- ss, mmm = ss_mmm.split(",")
- cs = int(mmm) // 10 # 毫秒转为百分之一秒
- hh = str(int(hh)) # 去掉多余的前导零
- return f"{hh}:{mm}:{ss}.{cs:02d}"
- def parse_srt(filepath):
- """解析 SRT 文件"""
- blocks = []
- with open(filepath, "r", encoding="utf-8") as f:
- content = f.read()
- entries = re.split(r'\n\s*\n', content)
- for entry in entries:
- lines = entry.strip().splitlines()
- if len(lines) >= 3:
- time_line = lines[1].strip()
- match = re.match(r"(\d{1,2}:\d{1,2}:\d{1,2},\d{3})\s*-->\s*(\d{1,2}:\d{1,2}:\d{1,2},\d{3})", time_line)
- if match:
- start = convert_srt_to_ass_time(match.group(1))
- end = convert_srt_to_ass_time(match.group(2))
- text = " ".join(lines[2:]).replace("\n", " ")
- blocks.append((start, end, text))
- return blocks
- def parse_ass(filepath):
- """解析 ASS 文件"""
- blocks = []
- with open(filepath, "r", encoding="utf-8") as f:
- lines = f.readlines()
- in_events = False
- for line in lines:
- line = line.strip()
- if line.startswith("[Events]"):
- in_events = True
- continue
- if in_events and line.startswith("Dialogue:"):
- parts = line[len("Dialogue:"):].strip().split(",", 9)
- if len(parts) >= 10:
- start = parts[1].strip()
- end = parts[2].strip()
- text = parts[9].strip()
- blocks.append((start, end, text))
- return blocks
- def tokenize_text(text, function_words):
- """将文本分割为Token对象列表"""
- # 使用更精确的分词正则表达式
- raw_tokens = re.findall(r'[\w\']+|[.,!?;:"]', text)
- tokens = []
-
- for i, raw in enumerate(raw_tokens):
- if raw[0].isalnum(): # 单词
- clean = re.sub(r"[^\w']+", '', raw).lower()
- # raw_tokens = re.findall(r"([\w']+|[.,!?;:'"])(?:\s+|$)", text)
- # raw_tokens = re.findall(r"(\b\w+'?\w*\b|[.,!?;:'"])", text)
- is_function = clean in function_words
- token = Token(raw, is_word=True, is_function=is_function)
-
- if is_function:
- token.ipa = function_words[clean]
- else:
- token.ipa = eng_to_ipa.convert(raw)
-
- # 计算宽度
- token.width = get_text_width(raw, normal_font if not is_function else func_font)
- token.ipa_width = get_text_width(token.ipa, ipa_font if not is_function else func_font)
-
- # 检查下一个token是否为标点,决定是否需要添加空格
- token.next_space = (i + 1 >= len(raw_tokens) or raw_tokens[i + 1][0].isalnum())
-
- else: # 标点符号
- token = Token(raw, is_word=False)
- token.ipa = raw
- token.width = get_text_width(raw, normal_font)
- token.ipa_width = get_text_width(raw, ipa_font)
- token.next_space = True # 标点后总是添加空格(除非是句子结尾)
-
- tokens.append(token)
-
- return tokens
- def calculate_positions(tokens, start_x=100, word_spacing=30):
- """计算每个token的位置,确保适当的间距"""
- current_x = start_x
-
- for i, token in enumerate(tokens):
- if not token.is_word and i > 0:
- # 标点紧跟前一个token
- token.x_pos = tokens[i-1].x_pos + tokens[i-1].width
- else:
- token.x_pos = current_x
-
- # 更新下一个token的起始位置
- width = max(token.width, token.ipa_width)
- current_x = token.x_pos + width
-
- # 只在需要的地方添加空格
- if token.next_space and i < len(tokens) - 1:
- current_x += word_spacing
- def generate_ass(sub_blocks, function_words, output_path="a_tmp.ass"):
- """生成ASS字幕文件"""
- header = """[Script Info]
- ScriptType: v4.00+
- Collisions: Normal
- PlayResX: 1920
- PlayResY: 1080
- Timer: 100.0000
- [V4+ Styles]
- Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
- Style: IPA,Arial Unicode MS,30,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,1,0,2,10,10,10,0
- Style: Text,Arial Unicode MS,36,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,1,0,2,10,10,10,0
- Style: FuncIPA,Arial Unicode MS,28,&H00AAAAAA,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,1,0,2,10,10,10,0
- Style: FuncText,Arial Unicode MS,28,&H00AAAAAA,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,1,0,2,10,10,10,0
- [Events]
- Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
- """
- events = []
- ipa_y = 100
- text_y = 140
-
- for block in sub_blocks:
- start_time, end_time, text = block
- tokens = tokenize_text(text, function_words)
- calculate_positions(tokens, start_x=100, word_spacing=30) # 增加了word_spacing
-
- for token in tokens:
- if token.is_function:
- style_ipa = "FuncIPA"
- style_text = "FuncText"
- else:
- style_ipa = "IPA"
- style_text = "Text"
-
- # 去除可能的多余空格
- ipa_text = token.ipa.strip()
- display_text = token.text.strip()
-
- dialogue_ipa = f"Dialogue: 0,{start_time},{end_time},{style_ipa},,0,0,0,,{{\\pos({token.x_pos},{ipa_y})}}{ipa_text}"
- dialogue_text = f"Dialogue: 0,{start_time},{end_time},{style_text},,0,0,0,,{{\\pos({token.x_pos},{text_y})}}{display_text}"
-
- events.append(dialogue_ipa)
- events.append(dialogue_text)
- # 写入文件
- ass_content = header + "\n".join(events)
- with open(output_path, "w", encoding="utf-8") as f:
- f.write(ass_content)
- print(f"ASS文件已生成:{output_path}")
- def main():
- """主函数"""
- if len(sys.argv) < 2:
- print("用法:python script.py <输入字幕文件,支持 .srt 或 .ass>")
- sys.exit(1)
-
- input_file = sys.argv[1]
- if input_file.lower().endswith(".srt"):
- sub_blocks = parse_srt(input_file)
- elif input_file.lower().endswith(".ass"):
- sub_blocks = parse_ass(input_file)
- else:
- print("不支持的文件类型,请输入 .srt 或 .ass 文件")
- sys.exit(1)
-
- # 加载虚词及其 IPA 表
- function_words = load_function_words("function_words.txt")
- generate_ass(sub_blocks, function_words)
- if __name__ == "__main__":
- main()
复制代码
|
|