ref: clean forced-alignment
This commit is contained in:
parent
65123d1b39
commit
c4ca9c7d4f
4
.gitignore
vendored
4
.gitignore
vendored
@ -17,4 +17,6 @@ dataset/raw
|
||||
translate/special-spiders
|
||||
ugNMT/BPE/output*
|
||||
ugNMT/BPE/codes
|
||||
forced-alignment/segments
|
||||
forced-alignment/segments
|
||||
forced-alignment/data
|
||||
forced-alignment/output.ttml
|
@ -1,82 +0,0 @@
|
||||
[0]: 25.462-27.102
|
||||
[1]: 27.202-28.382
|
||||
[2]: 28.582-30.262
|
||||
[3]: 30.622-32.303
|
||||
[4]: 32.403-33.643
|
||||
[5]: 33.703-35.483
|
||||
[6]: 35.843-37.503
|
||||
[7]: 37.643-38.803
|
||||
[8]: 38.903-40.563
|
||||
[9]: 41.023-42.683
|
||||
[10]: 42.803-43.883
|
||||
[11]: 44.063-45.884
|
||||
[12]: 56.584-58.245
|
||||
[13]: 58.345-59.585
|
||||
[14]: 59.685-61.345
|
||||
[15]: 61.785-63.405
|
||||
[16]: 63.525-64.785
|
||||
[17]: 64.825-66.565
|
||||
[18]: 66.925-68.665
|
||||
[19]: 68.785-69.826
|
||||
[20]: 70.005-71.686
|
||||
[21]: 72.106-73.846
|
||||
[22]: 73.946-75.086
|
||||
[23]: 75.226-76.986
|
||||
[24]: 77.346-79.526
|
||||
[25]: 79.646-81.426
|
||||
[26]: 81.566-83.527
|
||||
[27]: 84.307-86.847
|
||||
[28]: 86.927-89.487
|
||||
[29]: 89.527-91.947
|
||||
[30]: 92.107-94.667
|
||||
[31]: 94.707-97.128
|
||||
[32]: 97.288-99.848
|
||||
[33]: 99.908-102.328
|
||||
[34]: 102.448-116.229
|
||||
[35]: 116.249-117.949
|
||||
[36]: 118.029-119.129
|
||||
[37]: 119.369-121.07
|
||||
[38]: 121.45-123.13
|
||||
[39]: 123.23-124.47
|
||||
[40]: 124.53-126.27
|
||||
[41]: 126.67-128.29
|
||||
[42]: 128.41-129.59
|
||||
[43]: 129.71-131.51
|
||||
[44]: 131.81-133.511
|
||||
[45]: 133.611-134.671
|
||||
[46]: 134.891-136.651
|
||||
[47]: 137.011-138.671
|
||||
[48]: 138.791-139.991
|
||||
[49]: 140.091-141.811
|
||||
[50]: 142.191-143.871
|
||||
[51]: 143.971-145.071
|
||||
[52]: 145.271-146.952
|
||||
[53]: 147.372-149.052
|
||||
[54]: 149.132-150.412
|
||||
[55]: 150.492-152.212
|
||||
[56]: 152.552-154.272
|
||||
[57]: 154.372-155.492
|
||||
[58]: 155.652-157.412
|
||||
[59]: 157.792-159.953
|
||||
[60]: 160.093-161.873
|
||||
[61]: 162.013-163.953
|
||||
[62]: 164.733-167.293
|
||||
[63]: 167.353-169.913
|
||||
[64]: 169.953-172.394
|
||||
[65]: 172.534-175.114
|
||||
[66]: 175.154-177.594
|
||||
[67]: 177.714-180.294
|
||||
[68]: 180.314-182.854
|
||||
[69]: 182.934-185.575
|
||||
[70]: 195.875-198.436
|
||||
[71]: 198.476-201.056
|
||||
[72]: 201.096-203.516
|
||||
[73]: 203.656-206.196
|
||||
[74]: 206.276-208.796
|
||||
[75]: 208.857-211.417
|
||||
[76]: 211.457-213.917
|
||||
[77]: 214.057-216.617
|
||||
[78]: 216.677-219.077
|
||||
[79]: 219.217-221.817
|
||||
[80]: 221.817-224.378
|
||||
[81]: 224.458-227.098
|
@ -1,86 +0,0 @@
|
||||
[00:25.513] 东汉末狼烟不休
|
||||
[00:27.178] 常侍乱 朝野陷
|
||||
[00:28.499] 阿瞒挟天子 令诸侯
|
||||
[00:30.838] 踞江东志在九州
|
||||
[00:32.457] 继祖业 承父兄
|
||||
[00:33.793] 既冕主吴越 万兜鍪
|
||||
[00:35.878] 纵天下几变春秋
|
||||
[00:37.536] 稳东南 面中原
|
||||
[00:38.839] 水师锁长江 抗曹刘
|
||||
[00:40.978] 镇赤壁雄风赳赳
|
||||
[00:42.872] 夺荆楚 抚山越
|
||||
[00:44.043] 驱金戈铁马 灭仇雠
|
||||
[00:46.740]
|
||||
[00:56.690] 紫发髯碧色眼眸
|
||||
[00:58.324] 射猛虎 倚黄龙
|
||||
[00:59.694] 胆识过凡人 谁敌手
|
||||
[01:01.723] 御天下半百之久
|
||||
[01:03.624] 选贤臣 任能将
|
||||
[01:04.865] 覆江东云雨 尽风流
|
||||
[01:07.034] 千秋过再难回首
|
||||
[01:08.819] 问古今 兴亡事
|
||||
[01:10.059] 几人耀青史 芳名留
|
||||
[01:12.075] 笑谈间云烟已旧
|
||||
[01:13.915] 终留下 万古叹
|
||||
[01:15.278] 生子该当如 孙仲谋
|
||||
[01:17.458] 运帷幄 英雄几拂袖
|
||||
[01:19.644] 阴谋 阳谋 明仇 暗斗
|
||||
[01:21.637] 化作一江浊浪东流
|
||||
[01:24.443] 君不见军赤壁纵野火铁索连环
|
||||
[01:26.739] 也不见御北敌联西蜀长江上鏖战
|
||||
[01:29.596] 继遗志领江东屹立于神州东南
|
||||
[01:32.188] 尽心力洒英血展伟业剑气指苍天
|
||||
[01:34.840] 军帐内公瑾智张昭谋奇策频献
|
||||
[01:37.241] 沙场上太史勇甘宁霸一骑当十千
|
||||
[01:39.965] 纵使有千万种寂寞和孤单相伴
|
||||
[01:42.425] 既受终冠帝冕龙椅上成败也笑看
|
||||
[01:46.351]
|
||||
[01:56.184] 铁瓮城难攻易守
|
||||
[01:58.069] 旌旗立 苍空蔽
|
||||
[01:59.438] 逾百千雄师 万蒙舟
|
||||
[02:01.462] 善制衡眼光独秀
|
||||
[02:03.301] 擢鲁肃 劝阿蒙
|
||||
[02:04.557] 聚贤成霸业 名利收
|
||||
[02:06.500] 固疆土施德恩厚
|
||||
[02:08.302] 军心定 百姓安
|
||||
[02:09.712] 富国又强兵 重耕耨
|
||||
[02:11.783] 交远好未雨绸缪
|
||||
[02:13.642] 联南洋 合林邑
|
||||
[02:14.914] 行军远渡海 驻夷洲
|
||||
[02:16.944] 残垣下枯木雕朽
|
||||
[02:18.784] 想当年 麦城边
|
||||
[02:20.076] 截兵缚关羽 终其寿
|
||||
[02:22.263] 凭栏倚横看吴钩
|
||||
[02:23.993] 叹乱世 几时了
|
||||
[02:25.398] 天下归一统 没其咎
|
||||
[02:27.462] 称帝王壮心仍稠
|
||||
[02:29.061] 却无奈 自孤傲
|
||||
[02:30.438] 同室亦操戈 子嗣斗
|
||||
[02:32.512] 千年后恚恨徒留
|
||||
[02:34.291] 再何寻 军帐里
|
||||
[02:35.698] 将士聚欢饮 赏箜篌
|
||||
[02:37.763] 运帷幄 英雄几拂袖
|
||||
[02:40.029] 阴谋 阳谋 明仇 暗斗
|
||||
[02:42.146] 化作一江浊浪东流
|
||||
[02:44.641] 君不见吕子明踏轻舟白衣渡川
|
||||
[02:47.318] 也不见陆伯言烧联营火光上冲天
|
||||
[02:49.870] 善制衡选贤臣任能将共谋江山
|
||||
[02:52.410] 听忠言摒逆语树威严宝剑斫书案
|
||||
[02:55.117] 夺荆州抗刘备合曹操共克襄樊
|
||||
[02:57.692] 守夷陵任陆逊剿敌军 火计破蜀胆
|
||||
[03:00.391] 固江河成帝业立国家终归于乱
|
||||
[03:02.864] 光阴逝千载过功成者都付笑谈间
|
||||
[03:06.250]
|
||||
[03:15.700] 君不见军赤壁纵野火铁索连环
|
||||
[03:18.545] 也不见御北敌联西蜀长江上鏖战
|
||||
[03:21.080] 继遗志领江东屹立于神州东南
|
||||
[03:23.649] 尽心力洒英血展伟业剑指苍天
|
||||
[03:26.263] 君不见吕子明踏轻舟白衣渡川
|
||||
[03:28.746] 也不见陆伯言烧联营火光上冲天
|
||||
[03:31.395] 善制衡选贤臣任能将共谋江山
|
||||
[03:34.021] 听忠言摒逆语树威严宝剑斫书案
|
||||
[03:36.589] 纵使有千万种寂寞和孤单相伴
|
||||
[03:39.103] 既受终冠帝冕龙椅上成败也笑看
|
||||
[03:41.916] 固江河成帝业立国家终归于乱
|
||||
[03:44.470] 光阴逝千载过功成者都付笑谈间
|
||||
[03:48.436]
|
Binary file not shown.
@ -1,82 +0,0 @@
|
||||
东汉末狼烟不休
|
||||
常侍乱朝野陷
|
||||
阿瞒挟天子令诸侯
|
||||
踞江东志在九州
|
||||
继祖业承父兄
|
||||
既冕主吴越万兜鍪
|
||||
纵天下几变春秋
|
||||
稳东南面中原
|
||||
水师锁长江抗曹刘
|
||||
镇赤壁雄风赳赳
|
||||
夺荆楚抚山越
|
||||
驱金戈铁马灭仇雠
|
||||
紫发髯碧色眼眸
|
||||
射猛虎倚黄龙
|
||||
胆识过凡人谁敌手
|
||||
御天下半百之久
|
||||
选贤臣任能将
|
||||
覆江东云雨尽风流
|
||||
千秋过再难回首
|
||||
问古今兴亡事
|
||||
几人耀青史芳名留
|
||||
笑谈间云烟已旧
|
||||
终留下万古叹
|
||||
生子该当如孙仲谋
|
||||
运帷幄英雄几拂袖
|
||||
阴谋阳谋明仇暗斗
|
||||
化作一江浊浪东流
|
||||
君不见军赤壁纵野火铁索连环
|
||||
也不见御北敌联西蜀长江上鏖战
|
||||
继遗志领江东屹立于神州东南
|
||||
尽心力洒英血展伟业剑气指苍天
|
||||
军帐内公瑾智张昭谋奇策频献
|
||||
沙场上太史勇甘宁霸一骑当十千
|
||||
纵使有千万种寂寞和孤单相伴
|
||||
既受终冠帝冕龙椅上成败也笑看
|
||||
铁瓮城难攻易守
|
||||
旌旗立苍空蔽
|
||||
逾百千雄师万蒙舟
|
||||
善制衡眼光独秀
|
||||
擢鲁肃劝阿蒙
|
||||
聚贤成霸业名利收
|
||||
固疆土施德恩厚
|
||||
军心定百姓安
|
||||
富国又强兵重耕耨
|
||||
交远好未雨绸缪
|
||||
联南洋合林邑
|
||||
行军远渡海驻夷洲
|
||||
残垣下枯木雕朽
|
||||
想当年麦城边
|
||||
截兵缚关羽终其寿
|
||||
凭栏倚横看吴钩
|
||||
叹乱世几时了
|
||||
天下归一统没其咎
|
||||
称帝王壮心仍稠
|
||||
却无奈自孤傲
|
||||
同室亦操戈子嗣斗
|
||||
千年后恚恨徒留
|
||||
再何寻军帐里
|
||||
将士聚欢饮赏箜篌
|
||||
运帷幄英雄几拂袖
|
||||
阴谋阳谋明仇暗斗
|
||||
化作一江浊浪东流
|
||||
君不见吕子明踏轻舟白衣渡川
|
||||
也不见陆伯言烧联营火光上冲天
|
||||
善制衡选贤臣任能将共谋江山
|
||||
听忠言摒逆语树威严宝剑斫书案
|
||||
夺荆州抗刘备合曹操共克襄樊
|
||||
守夷陵任陆逊剿敌军火计破蜀胆
|
||||
固江河成帝业立国家终归于乱
|
||||
光阴逝千载过功成者都付笑谈间
|
||||
君不见军赤壁纵野火铁索连环
|
||||
也不见御北敌联西蜀长江上鏖战
|
||||
继遗志领江东屹立于神州东南
|
||||
尽心力洒英血展伟业剑指苍天
|
||||
君不见吕子明踏轻舟白衣渡川
|
||||
也不见陆伯言烧联营火光上冲天
|
||||
善制衡选贤臣任能将共谋江山
|
||||
听忠言摒逆语树威严宝剑斫书案
|
||||
纵使有千万种寂寞和孤单相伴
|
||||
既受终冠帝冕龙椅上成败也笑看
|
||||
固江河成帝业立国家终归于乱
|
||||
光阴逝千载过功成者都付笑谈间
|
7
forced-alignment/README.md
Normal file
7
forced-alignment/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
# 强制对齐在歌词逐字对齐上的应用
|
||||
|
||||
这个子项目是为了给[AquaVox](https://github.com/alikia2x/aquavox)提供AI加持的逐字歌词功能所诞生的。
|
||||
|
||||
## 规划
|
||||
|
||||
对于给定歌词和
|
File diff suppressed because one or more lines are too long
191
forced-alignment/probs_distribution.ipynb
Normal file
191
forced-alignment/probs_distribution.ipynb
Normal file
File diff suppressed because one or more lines are too long
@ -58,6 +58,24 @@ def timestamp(seconds):
|
||||
|
||||
return f"{hours:02}:{minutes:02}:{seconds:02}.{milliseconds:03}"
|
||||
|
||||
def timestamp_inverse(ttml_timestamp):
|
||||
"""
|
||||
将TTML的时间戳格式字符串(HH:MM:SS.sss)转换为浮点数的秒钟。
|
||||
|
||||
:param ttml_timestamp: TTML时间戳格式字符串
|
||||
:return: 浮点数的秒钟
|
||||
"""
|
||||
parts = ttml_timestamp.split(':')
|
||||
hours = int(parts[0])
|
||||
minutes = int(parts[1])
|
||||
seconds_and_milliseconds = parts[2].split('.')
|
||||
seconds = int(seconds_and_milliseconds[0])
|
||||
milliseconds = int(seconds_and_milliseconds[1])
|
||||
|
||||
total_seconds = hours * 3600 + minutes * 60 + seconds + milliseconds / 1000
|
||||
|
||||
return total_seconds
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
import os
|
||||
@ -113,13 +131,13 @@ class TTMLGenerator:
|
||||
tree = ET.ElementTree(self.tt)
|
||||
tree.write(filename, encoding="utf-8", xml_declaration=True)
|
||||
|
||||
duration = get_audio_duration("霜雪千年_vocal.mp3")
|
||||
duration = get_audio_duration("./data/谷雨.mp3")
|
||||
|
||||
# 示例使用
|
||||
ttml_generator = TTMLGenerator(duration=timestamp(duration))
|
||||
|
||||
|
||||
def process_line(line_idx, start_time):
|
||||
def process_line(line_idx, start_time, total_lines):
|
||||
with open(f"./segments/line-{line_idx}.txt", "r") as f:
|
||||
text = f.read()
|
||||
|
||||
@ -153,6 +171,12 @@ def process_line(line_idx, start_time):
|
||||
break
|
||||
item["end"] = words[idx + 1]["start"]
|
||||
idx+=1
|
||||
if line_idx == total_lines:
|
||||
words = words[1:]
|
||||
elif line_idx == 1:
|
||||
words = words[:-1]
|
||||
else:
|
||||
words = words[1:-1]
|
||||
result = []
|
||||
for word in words:
|
||||
result.append((word["word"], timestamp(word["start"]), timestamp(word["end"])))
|
||||
@ -161,19 +185,53 @@ def process_line(line_idx, start_time):
|
||||
|
||||
lines_to_process = sorted(extract_numbers_from_files("segments"))
|
||||
|
||||
i=1
|
||||
def parse_lrc(lrc_file, audio_len):
|
||||
"""解析LRC文件,返回一个包含时间戳和歌词的列表"""
|
||||
with open(lrc_file, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
lrc_data = []
|
||||
for line in lines:
|
||||
# 使用正则表达式匹配时间戳和歌词
|
||||
match = re.match(r'\[(\d+):(\d+\.\d+)\](.*)', line)
|
||||
if match:
|
||||
minutes = int(match.group(1))
|
||||
seconds = float(match.group(2))
|
||||
lyric = match.group(3).strip()
|
||||
lyric = lyric.replace(" ", "")
|
||||
timestamp = minutes * 60 + seconds
|
||||
lrc_data.append((lyric, timestamp))
|
||||
|
||||
for i, (lyric, start_time) in enumerate(lrc_data):
|
||||
# Skip empty line
|
||||
if lyric.strip() == "":
|
||||
continue
|
||||
if i < len(lrc_data) - 1:
|
||||
end_time = lrc_data[i + 1][1]
|
||||
else:
|
||||
end_time = audio_len
|
||||
lrc_data[i] = (lyric, start_time, end_time)
|
||||
|
||||
# Filter empty lines again
|
||||
lrc_data = [line for line in lrc_data if line[0].strip() != ""]
|
||||
|
||||
return lrc_data
|
||||
|
||||
lrc_data = parse_lrc("./data/谷雨_raw.lrc", duration)
|
||||
|
||||
i=0
|
||||
for line_num in tqdm(lines_to_process):
|
||||
with open(f"./segments/line-{line_num}.time", "r") as f:
|
||||
a = f.read()
|
||||
b = a.split(",")
|
||||
start_time = float(b[0])
|
||||
end_time = float(b[1])
|
||||
result = process_line(line_num, start_time)
|
||||
start_time = lrc_data[i][1]
|
||||
result = process_line(line_num, start_time, len(lines_to_process))
|
||||
end_time = lrc_data[i][2]
|
||||
if timestamp_inverse(result[-1][2]) > end_time:
|
||||
end_time = timestamp_inverse(result[-1][2])
|
||||
ttml_generator.add_lyrics(
|
||||
begin=timestamp(start_time), end=timestamp(end_time), agent="v1", itunes_key=f"L{i}",
|
||||
begin=timestamp(start_time), end=timestamp(end_time), agent="v1", itunes_key=f"L{i+1}",
|
||||
words=result
|
||||
)
|
||||
i+=1
|
||||
ttml_generator.save("output.ttml")
|
||||
|
||||
# 保存文件
|
||||
ttml_generator.save("output.ttml")
|
@ -49,8 +49,8 @@ def split_audio_by_lrc(audio_file, lrc_data, output_prefix):
|
||||
print(f"Saved {output_file}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
lrc_file = "霜雪千年.lrc" # LRC文件路径
|
||||
audio_file = "霜雪千年.mp3" # 音频文件路径
|
||||
lrc_file = "./data/谷雨.lrc" # LRC文件路径
|
||||
audio_file = "./data/谷雨.mp3" # 音频文件路径
|
||||
output_prefix = "segments/line" # 输出文件名的前缀
|
||||
|
||||
lrc_data = parse_lrc(lrc_file)
|
||||
|
@ -5,6 +5,7 @@ from pypinyin import lazy_pinyin
|
||||
from pypinyin_dict.phrase_pinyin_data import cc_cedict
|
||||
from torchaudio.transforms import Resample
|
||||
import xml.etree.ElementTree as ET
|
||||
import argparse
|
||||
|
||||
from pydub import AudioSegment
|
||||
|
||||
@ -25,10 +26,10 @@ def get_audio_duration(file_path):
|
||||
|
||||
def timestamp(seconds):
|
||||
"""
|
||||
将浮点数的秒钟转换为TTML的时间戳格式(HH:MM:SS.sss)。
|
||||
将浮点数的秒钟转换为SRT的时间戳格式(HH:MM:SS,sss)。
|
||||
|
||||
:param seconds: 浮点数的秒钟
|
||||
:return: TTML时间戳格式字符串
|
||||
:return: SRT时间戳格式字符串
|
||||
"""
|
||||
hours = int(seconds // 3600)
|
||||
minutes = int((seconds % 3600) // 60)
|
||||
@ -36,35 +37,7 @@ def timestamp(seconds):
|
||||
milliseconds = int((seconds % 1) * 1000)
|
||||
seconds = int(seconds)
|
||||
|
||||
return f"{hours:02}:{minutes:02}:{seconds:02}.{milliseconds:03}"
|
||||
|
||||
class TTMLGenerator:
|
||||
def __init__(self, duration, xmlns="http://www.w3.org/ns/ttml", xmlns_ttm="http://www.w3.org/ns/ttml#metadata", xmlns_amll="http://www.example.com/ns/amll", xmlns_itunes="http://music.apple.com/lyric-ttml-internal"):
|
||||
self.tt = ET.Element("tt", attrib={
|
||||
"xmlns": xmlns,
|
||||
"xmlns:ttm": xmlns_ttm,
|
||||
"xmlns:amll": xmlns_amll,
|
||||
"xmlns:itunes": xmlns_itunes
|
||||
})
|
||||
self.head = ET.SubElement(self.tt, "head")
|
||||
self.metadata = ET.SubElement(self.head, "metadata")
|
||||
self.body = ET.SubElement(self.tt, "body", attrib={"dur": duration})
|
||||
self.div = ET.SubElement(self.body, "div")
|
||||
|
||||
def add_lyrics(self, begin, end, agent, itunes_key, words):
|
||||
p = ET.SubElement(self.div, "p", attrib={
|
||||
"begin": begin,
|
||||
"end": end,
|
||||
"ttm:agent": agent,
|
||||
"itunes:key": itunes_key
|
||||
})
|
||||
for word, start, stop in words:
|
||||
span = ET.SubElement(p, "span", attrib={"begin": start, "end": stop})
|
||||
span.text = word
|
||||
|
||||
def save(self, filename):
|
||||
tree = ET.ElementTree(self.tt)
|
||||
tree.write(filename, encoding="utf-8", xml_declaration=True)
|
||||
return f"{hours:02}:{minutes:02}:{seconds:02},{milliseconds:03}"
|
||||
|
||||
|
||||
def compute_alignments(waveform: torch.Tensor, transcript: List[str]):
|
||||
@ -84,21 +57,13 @@ aligner = bundle.get_aligner()
|
||||
|
||||
cc_cedict.load()
|
||||
|
||||
add_spaces = lambda s: ' '.join(s)
|
||||
|
||||
with open("./1.txt", "r") as f:
|
||||
text_lines = f.readlines()
|
||||
|
||||
text_pinyin = []
|
||||
|
||||
for line in text_lines:
|
||||
text_pinyin.append("".join(lazy_pinyin(line.strip())))
|
||||
with open("./data/扬旗鸣鼓.txt", "r") as f:
|
||||
text = f.read()
|
||||
|
||||
text_pinyin = lazy_pinyin(text)
|
||||
text_normalized = " ".join(text_pinyin)
|
||||
|
||||
print(text_normalized)
|
||||
|
||||
waveform, sample_rate = torchaudio.load("./权御天下 [vocals].mp3")
|
||||
waveform, sample_rate = torchaudio.load("./data/扬旗鸣鼓.wav")
|
||||
|
||||
waveform = waveform[0:1]
|
||||
resampler = Resample(orig_freq=sample_rate, new_freq=16000)
|
||||
@ -110,13 +75,14 @@ num_frames = emission.size(1)
|
||||
|
||||
ratio = waveform.size(1) / num_frames
|
||||
|
||||
duration = get_audio_duration("权御天下 [vocals].mp3")
|
||||
|
||||
ttml_generator = TTMLGenerator(duration=timestamp(duration))
|
||||
duration = get_audio_duration("./data/扬旗鸣鼓.wav")
|
||||
|
||||
for i in range(len(token_spans)):
|
||||
spans = token_spans[i]
|
||||
x0 = round(int(ratio * spans[0].start) / 16000, 3)
|
||||
x1 = round(int(ratio * spans[-1].end) / 16000, 3)
|
||||
with open("1.fff", "a") as f:
|
||||
f.write(f"{[i]}: {x0}-{x1}\n")
|
||||
with open("1.srt", "a") as f:
|
||||
f.write(f"{i+1}\n")
|
||||
f.write(f"{timestamp(x0)} --> {timestamp(x1)}\n")
|
||||
f.write(f"{transcript[i]}\n\n")
|
||||
f.flush()
|
84
forced-alignment/srt2lrc.py
Normal file
84
forced-alignment/srt2lrc.py
Normal file
@ -0,0 +1,84 @@
|
||||
import re
|
||||
|
||||
def srt_to_lrc(srt_text):
|
||||
# 使用正则表达式匹配时间戳和内容
|
||||
# who the fuck knows this
|
||||
srt_text+='\n\n'
|
||||
pattern = re.compile(r'(\d{2}:\d{2}:\d{2},\d{3}) --> (\d{2}:\d{2}:\d{2},\d{3})\n(.+?)\n\n', re.DOTALL)
|
||||
matches = pattern.findall(srt_text)
|
||||
lrc_lines = []
|
||||
|
||||
for start_time, end_time, content in matches:
|
||||
# 提取开始时间的高亮字符
|
||||
highlight_char = re.search(r'<font color="#00ff00">(.+?)</font>', content)
|
||||
if highlight_char:
|
||||
highlight_char = highlight_char.group(1)
|
||||
else:
|
||||
continue
|
||||
|
||||
# 将时间戳转换为LRC格式
|
||||
f,start_minutes, start_seconds, start_milliseconds = map(int, start_time.replace(',', ':').split(':'))
|
||||
f,end_minutes, end_seconds, end_milliseconds = map(int, end_time.replace(',', ':').split(':'))
|
||||
|
||||
start_time_lrc = f"{start_minutes:02d}:{start_seconds:02d}.{start_milliseconds:02d}"
|
||||
end_time_lrc = f"{end_minutes:02d}:{end_seconds:02d}.{end_milliseconds:02d}"
|
||||
|
||||
# 构建LRC行
|
||||
lrc_line = f"{highlight_char}|{start_time_lrc},{end_time_lrc}"
|
||||
lrc_lines.append(lrc_line)
|
||||
|
||||
# 如果内容中有换行符,将其替换为空格
|
||||
lrc_line = lrc_line.replace('\n', ' ')
|
||||
|
||||
return '\n'.join(lrc_lines)
|
||||
|
||||
with open('./data/谷雨.srt', 'r') as f:
|
||||
srt_text = f.read()
|
||||
|
||||
whole = srt_text.splitlines()[2].replace('<font color="#00ff00">','').replace('</font>','')
|
||||
whole = whole.replace(' ','\n')
|
||||
lines = whole.splitlines()
|
||||
|
||||
lyric_text = ""
|
||||
raw_text = srt_to_lrc(srt_text)
|
||||
raw_lines = raw_text.splitlines()
|
||||
for line in raw_lines:
|
||||
lyric_text += line.split('|')[0]
|
||||
|
||||
raw_idx=0
|
||||
lines_start_chr_idx=[]
|
||||
for line in lines:
|
||||
start = line[0]
|
||||
end = line[-1]
|
||||
while raw_idx < len(raw_lines) and not line.startswith(raw_lines[raw_idx].split("|")[0]):
|
||||
raw_idx += 1
|
||||
lines_start_chr_idx.append(raw_idx)
|
||||
lines_start_chr_idx.append(len(raw_lines)-1)
|
||||
|
||||
raw_idx=0
|
||||
lines_end_chr_idx=[]
|
||||
for line in lines:
|
||||
start = line[0]
|
||||
end = line[-1]
|
||||
while raw_idx < len(raw_lines) and not line.endswith(raw_lines[raw_idx].split("|")[0]):
|
||||
raw_idx += 1
|
||||
lines_end_chr_idx.append(raw_idx)
|
||||
lines_end_chr_idx.append(len(raw_lines)-1)
|
||||
|
||||
lrc_text = ""
|
||||
for i in range(len(lines_start_chr_idx)-1):
|
||||
start = lines_start_chr_idx[i]
|
||||
end = lines_end_chr_idx[i]
|
||||
time_start = raw_lines[start].split("|")[1].split(',')[0]
|
||||
time_end = raw_lines[end].split("|")[1].split(',')[0]
|
||||
lrc_text += f"[{time_start}]{lyric_text[start:end+1]}\n[{time_end}]\n"
|
||||
print(lrc_text)
|
||||
|
||||
lyric_len = len(lyric_text)
|
||||
for i in range(len(lines_start_chr_idx)-1):
|
||||
start = max(0,lines_start_chr_idx[i]-1)
|
||||
end = min(lyric_len-1, lines_end_chr_idx[i]+1)
|
||||
time_start = raw_lines[start].split("|")[1].split(',')[0]
|
||||
time_end = raw_lines[end].split("|")[1].split(',')[0]
|
||||
lrc_text += f"[{time_start}]{lyric_text[start:end+1]}\n[{time_end}]\n"
|
||||
print(lrc_text)
|
File diff suppressed because one or more lines are too long
30
forced-alignment/ttml.py
Normal file
30
forced-alignment/ttml.py
Normal file
@ -0,0 +1,30 @@
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
class TTMLGenerator:
|
||||
def __init__(self, duration, xmlns="http://www.w3.org/ns/ttml", xmlns_ttm="http://www.w3.org/ns/ttml#metadata", xmlns_amll="http://www.example.com/ns/amll", xmlns_itunes="http://music.apple.com/lyric-ttml-internal"):
|
||||
self.tt = ET.Element("tt", attrib={
|
||||
"xmlns": xmlns,
|
||||
"xmlns:ttm": xmlns_ttm,
|
||||
"xmlns:amll": xmlns_amll,
|
||||
"xmlns:itunes": xmlns_itunes
|
||||
})
|
||||
self.head = ET.SubElement(self.tt, "head")
|
||||
self.metadata = ET.SubElement(self.head, "metadata")
|
||||
self.body = ET.SubElement(self.tt, "body", attrib={"dur": duration})
|
||||
self.div = ET.SubElement(self.body, "div")
|
||||
|
||||
def add_lyrics(self, begin, end, agent, itunes_key, words):
|
||||
p = ET.SubElement(self.div, "p", attrib={
|
||||
"begin": begin,
|
||||
"end": end,
|
||||
"ttm:agent": agent,
|
||||
"itunes:key": itunes_key
|
||||
})
|
||||
for word, start, stop in words:
|
||||
span = ET.SubElement(p, "span", attrib={"begin": start, "end": stop})
|
||||
span.text = word
|
||||
|
||||
def save(self, filename):
|
||||
tree = ET.ElementTree(self.tt)
|
||||
tree.write(filename, encoding="utf-8", xml_declaration=True)
|
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
@ -1,55 +0,0 @@
|
||||
[tool: 歌词滚动姬 https://lrc-maker.github.io]
|
||||
[ti: 霜雪千年]
|
||||
[ar: COP]
|
||||
[00:13.876] 梨花香缠着衣角掠过熙攘
|
||||
[00:19.385] 复悄入红帘深帐
|
||||
[00:22.775] 听枝头黄鹂逗趣儿细风绕指淌
|
||||
[00:27.437] 坐船舫兰桨拨开雾霭迷茫
|
||||
[00:33.000] 不觉已一日过半
|
||||
[00:36.361] 过眼的葱郁风光悉数泛了黄
|
||||
[00:41.590] 褪尽温度的风无言牵引中
|
||||
[00:44.898] 便清晰了在此的眉目
|
||||
[00:48.721] 暮色的消融隐约了晦朔葱茏
|
||||
[00:53.888] 在这老街回眸烟云中追溯我是谁
|
||||
[00:58.582] 只消暮雨点滴便足以粉饰这是非
|
||||
[01:02.158] 待这月色涌起谁人轻叩这门扉
|
||||
[01:09.000] 苔绿青石板街
|
||||
[01:10.444] 斑驳了流水般岁月
|
||||
[01:12.389] 小酌三盏两杯理不清缠绕的情结
|
||||
[01:15.868] 在你淡漠眉间
|
||||
[01:19.277] 瞥见离人的喜悲霜雪
|
||||
[01:25.766]
|
||||
[01:36.176] 楼阁现尘飞雾散荧光蹁跹
|
||||
[01:41.500] 显露出斑驳石阶
|
||||
[01:44.865] 入眼是落英纷然芳草入深院
|
||||
[01:49.571] 凭栏杆小桌上置琼觞两盏
|
||||
[01:55.277] 阖眼听清风疏叶
|
||||
[01:58.603] 似曾有欢声笑言萦绕这高轩
|
||||
[02:03.691] 云动寂静鸣蝉雨坠激漪涟
|
||||
[02:07.298] 皴擦点染勾勒这世间
|
||||
[02:11.084] 缘起的一眼定格了三生千年
|
||||
[02:16.245] 在这老街回眸烟云中追溯我是谁
|
||||
[02:20.988] 只消暮雨点滴便足以粉饰这是非
|
||||
[02:24.463] 待这月色涌起谁人轻叩这门扉
|
||||
[02:31.390] 苔绿青石板街
|
||||
[02:32.752] 斑驳了流水般岁月
|
||||
[02:34.687] 小酌三盏两杯理不清缠绕的情结
|
||||
[02:38.162] 在你淡漠眉间
|
||||
[02:41.495] 瞥见离人的喜悲霜雪
|
||||
[02:48.066]
|
||||
[02:58.863] 三月梨花雪几载开了又败
|
||||
[03:02.206] 笔锋走黑白丹青中穿插无奈
|
||||
[03:05.594] 彼时那弯月何时初现于江畔
|
||||
[03:08.966] 而我又在待何人
|
||||
[03:11.201] 在这亭台回眸千年后
|
||||
[03:14.508] 忆起你是谁
|
||||
[03:15.816] 只消月色隐约便足以勾勒这是非
|
||||
[03:19.323] 待这回忆涌起恍惚之间已下泪
|
||||
[03:26.171] 枫红十里长街
|
||||
[03:27.662] 红帘后谁人蹙着眉
|
||||
[03:29.608] 遥梦桑竹桃源
|
||||
[03:31.188] 轮回中曾道别的地点
|
||||
[03:32.832] 愿今生再相见
|
||||
[03:36.478] 消融你眉间
|
||||
[03:38.585] 悲戚霜雪
|
||||
[03:44.184]
|
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Loading…
Reference in New Issue
Block a user