ffmpeg -i ./background.mp4 -y -t 8 -vf "subtitles=./article-to-video-subtitle.ass" out.mp4
Last active
January 10, 2023 02:59
-
-
Save thesadabc/5ddff453ef504102f854e52c8106da14 to your computer and use it in GitHub Desktop.
使用ASS字幕+视频模板,实现图文转视频,实现标题样式,翻页,控制行高,渐出动画。中文无法自动换行。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[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成。 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 切分新闻稿件与分页脚本 | |
# 因为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) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment