Skip to content

Instantly share code, notes, and snippets.

@thesadabc
Last active January 10, 2023 02:59
Show Gist options
  • Save thesadabc/5ddff453ef504102f854e52c8106da14 to your computer and use it in GitHub Desktop.
Save thesadabc/5ddff453ef504102f854e52c8106da14 to your computer and use it in GitHub Desktop.
使用ASS字幕+视频模板,实现图文转视频,实现标题样式,翻页,控制行高,渐出动画。中文无法自动换行。
[Script Info]
Title: Test
Synch Point: 0
ScriptType: v4.00+
Timer: 100.0000
ScaledBorderAndShadow: no
YCbCr Matrix: TV.601
WrapStyle: 0
PlayResX: 720
PlayResY: 1280
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, Outline,OutlineColour, Bold, ScaleX, ScaleY, Alignment, MarginL, MarginV, Encoding
Style: TITLE,Microsoft YaHei,55,&H00FFFFFF,0,&H80FFFFFF,1,100,100,1,60,950,134
Style: BODY,Microsoft YaHei,55,&H11FFFFFF,0,&H80000000,1,100,100,7,110,450,134
[Events]
Format: Layer, Start, End, Style, Text
Dialogue: 0,0:00:00.50,0:00:30.00, *TITLE,农业农村部:全国秋粮收获\N{\fs10}\h{\r}\N超六成、近八亿亩
Dialogue: 0,0:00:01.00,0:00:04.50, *BODY,{\clip(0,0,720,500)\t(0,2000,\clip(0,0,720,1280))}据央视新闻客户端,农业农\N{\fs10}\h{\r}\N村部最新农情调度显示,全\N{\fs10}\h{\r}\N国秋粮已收获7.85亿亩、\N{\fs10}\h{\r}\N完成60.1%,进度同比快\N{\fs10}\h{\r}\N2.4个百分点。分地区看,\N{\fs10}\h{\r}\N西北地区收获近八成,西南\N{\fs10}\h{\r}\N地区过七成半,黄淮海地区
Dialogue: 0,0:00:04.75,0:00:08.25, *BODY,{\clip(0,0,720,500)\t(0,2000,\clip(0,0,720,1280))}过六成半,东北地区近六成,\N{\fs10}\h{\r}\N长江中下游及华南地区过五\N{\fs10}\h{\r}\N成。
# 切分新闻稿件与分页脚本
# 因为ass中文不能自动换行,需要手动分页与换行,一个使用例子
import re
word_end = set(",,。.、\\;;::…%~??!!”’)】》 !),.:;?]}¨·ˇˉ―‖’”…∶、。〃々〉》」』】〕〗!"'),.:;?]`|}~¢")
word_start = set("([{·‘“〈《「『【〔〖(.[{£¥")
word_size_in_line = 12 # 每行字数
line_end_gap = 1 # 每行的长度gap,弹性伸缩
line_size_in_page = 8 # 每页行数
def texture_text(text):
# 计算每行长度,数字英文符号算半个
return len(text) - (len(re.findall(r"[\d.,' \"\[\]\|\\/!?a-z]", text)) // 2)
def split_long_text(text):
if texture_text(text) <= word_size_in_line:
yield text
else:
start = 0
while text[start:start + word_size_in_line]:
yield text[start:start + word_size_in_line]
start = start + word_size_in_line
def get_text_pages(text):
for ch in word_end:
text = text.replace(ch, f"{ch} ")
for ch in word_start:
text = text.replace(ch, f" {ch}")
text_word_list = re.sub(r"\s+", " ", text).split()
text_word_list = [t for wd in text_word_list for t in split_long_text(wd)]
lines = []
newline = ""
for word in text_word_list:
if texture_text(newline + word) <= word_size_in_line - line_end_gap:
# 有多余空格, 则直接加入,继续处理下一个词
if texture_text(word) == 1 and not newline:
# 单个符号,插入上一行
lines[-1] += word
else:
newline += word
elif texture_text(newline + word) <= word_size_in_line:
# 刚好,直接加入,另起一行
lines.append(newline + word)
newline = ""
else:
# 超过一行
line_space_len = word_size_in_line - texture_text(newline)
word_head = word[:line_space_len]
word_tail = word[line_space_len:]
if not newline:
lines.append(word_head)
newline = word_tail
else:
if texture_text(word_tail) == 1:
# 多一个符号,插入上一行
lines.append(newline + word)
newline = ""
else:
# 超过很多,新起一行
lines.append(newline + word_head)
newline = word_tail
if newline:
lines.append(newline)
pages = []
start = 0
while lines[start:start + line_size_in_page]:
pages.append(lines[start:start+line_size_in_page])
start += line_size_in_page
if len(pages) > 1:
if len(pages[-1]) == 1:
# 最后一页只有1行,则合并到前一页
lastpage = pages.pop()
pages[-1] += lastpage
else:
if len(pages[-1]) == 2:
# 最后一页只有2行,则前一页后移一行
pages[-1].insert(0, pages[-2].pop())
return [r"\N{\fs10}\h{\r}\N".join(pg) for pg in pages]
def parse_time(time_in_sec):
hour = int(time_in_sec) // 3600
minute = (int(time_in_sec) % 3600) // 60
second = int(time_in_sec) % 60
microsec = int(time_in_sec * 100) % 100
return f"{hour:02}:{minute:02}:{second:02}.{microsec:02}"
ass_file_head = """[Script Info]
Title: Test
Synch Point: 0
ScriptType: v4.00+
Timer: 100.0000
ScaledBorderAndShadow: no
YCbCr Matrix: TV.601
WrapStyle: 0
PlayResX: 720
PlayResY: 1280
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, Outline,OutlineColour, Bold, ScaleX, ScaleY, Alignment, MarginL, MarginV, Encoding
Style: TITLE,Microsoft YaHei,55,&H00FFFFFF,0,&H80FFFFFF,1,100,100,1,60,950,134
Style: BODY,Microsoft YaHei,55,&H11FFFFFF,0,&H80000000,1,100,100,7,110,450,134
[Events]
Format: Layer, Start, End, Style, Text
"""
def create_video(title, content):
tmp_ass_file = f"./tmp/{title}"
title, = get_text_pages(title)
content_page = get_text_pages(content)
page_show_time = 3.5
page_gap_time = 0.25
video_duration = (page_show_time + page_gap_time) * len(content_page) - page_gap_time
ass_file_body = ass_file_head + "Dialogue: 0,%s,%s, *TITLE,%s\n" % (parse_time(0.5), parse_time(video_duration), title)
page_begin_time = 1
for content in content_page:
ass_file_body += "Dialogue: 0,%s,%s, *BODY,{\\clip(0,0,720,500)\\t(0,2000,\\clip(0,0,720,1280))}%s\n" % \
(parse_time(page_begin_time), parse_time(page_begin_time + page_show_time), content)
page_begin_time += page_show_time + page_gap_time
with open(f"{tmp_ass_file}.ass", "w") as f:
f.write(ass_file_body)
print(f"ffmpeg -i ./background.mp4 -y -t {video_duration} -vf 'subtitles={tmp_ass_file}.ass' {tmp_ass_file}.mp4")
if __name__ == '__main__':
for line in open("./data"):
title, content = line.strip().split("\t")
if len(title) > 40:
print(f"标题过长, {title}")
create_video(title, content)

start a demo

ffmpeg -i ./background.mp4 -y -t 8 -vf "subtitles=./article-to-video-subtitle.ass" out.mp4

a output demo

output-demo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment