Files
meijiaka-zy/python-api/app/services/ass_generator.py
T
小鱼开发 e9dbf4f5fc refactor: 视频生成流程重构 - concat拼接替代全局音频替换
- 新增 generate_empty_shot_clip 原子命令:截取视频→截取音频→替换音频→自动清理临时文件
- 新增 concat_video_clips 命令:直接拼接已标准化片段,零重新编码
- VideoGeneration 改为先生成各分镜标准化片段,再 concat 拼接,不再使用 replace_audio_track
- segment 对口型视频保留自带同步音频,empty_shot 注入对应配音音频
- 删除未使用的单分镜重新生成功能(handleRegenerateShot、useVideoGeneration hook)
- ScriptShot 新增 clipVideoPath 字段
2026-04-30 00:23:11 +08:00

126 lines
3.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
ASS 字幕生成器
==============
生成带样式的 ASS 字幕文件,使用抖音美好体 (DouyinSans)。
"""
from __future__ import annotations
from app.schemas.caption import CaptionUtterance
def generate_ass(
utterances: list[CaptionUtterance],
video_width: int = 1080,
video_height: int = 1920,
fontname: str = "DouyinSansBold",
fontsize: float = 28.0,
primary_color: str = "&H00FFFFFF", # 白色
outline_color: str = "&H00000000", # 黑色描边
back_color: str = "&H80000000", # 半透明背景
outline: float = 2.0,
shadow: float = 1.0,
margin_v: int = 50,
border_style: int = 1,
) -> str:
"""
生成 ASS 字幕内容
Args:
utterances: 字幕时间轴列表
video_width: 视频宽度
video_height: 视频高度
fontname: 字体名称(默认为 DouyinSansBold
fontsize: 字体大小
primary_color: 主颜色 (&HAABBGGRR 格式)
outline_color: 描边颜色
back_color: 背景颜色
outline: 描边宽度
shadow: 阴影深度
margin_v: 垂直边距
border_style: 边框样式 (1=描边, 3=背景色块)
Returns:
ASS 格式字符串
"""
if border_style not in (1, 3):
border_style = 1
# BorderStyle=3 在当前 FFmpeg 嵌入版 libass 中不渲染,
# 转换为 BorderStyle=1 + 粗描边来模拟背景色效果
if border_style == 3:
border_style = 1
outline = max(6, round(fontsize * 0.12))
outline_color = back_color
# Script Info 部分
script_info = f"""[Script Info]
Title: Generated by Meijiaka AI Video
ScriptType: v4.00+
PlayResX: {video_width}
PlayResY: {video_height}
ScaledBorderAndShadow: yes
YCbCr Matrix: None
"""
# Styles 部分
styles = f"""[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: Default,{fontname},{fontsize:.1f},{primary_color},&H000000FF,{outline_color},{back_color},0,0,0,0,100,100,0,0,{border_style},{outline:.1f},{shadow:.1f},2,20,20,{margin_v},1
"""
# Events 部分
events_header = """[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
"""
events = []
for u in utterances:
start = _ms_to_ass_time(u.start_time)
end = _ms_to_ass_time(u.end_time)
# 转义特殊字符
text = u.text.replace("\\", "\\\\").replace("{", "\\{").replace("}", "\\}")
event = f"Dialogue: 0,{start},{end},Default,,0,0,0,,{text}"
events.append(event)
return script_info + styles + events_header + "\n".join(events)
def _ms_to_ass_time(ms: int) -> str:
"""毫秒转换为 ASS 时间格式 H:MM:SS.cc"""
hours = ms // 3600000
minutes = (ms % 3600000) // 60000
seconds = (ms % 60000) // 1000
centiseconds = (ms % 1000) // 10
return f"{hours}:{minutes:02d}:{seconds:02d}.{centiseconds:02d}"
# 预设样式
def generate_ass_short_video(utterances: list[CaptionUtterance]) -> str:
"""生成短视频风格的 ASS 字幕(竖屏 9:16)"""
return generate_ass(
utterances=utterances,
video_width=1080,
video_height=1920,
fontsize=32.0,
outline=2.5,
shadow=2.0,
margin_v=80,
)
def generate_ass_professional(utterances: list[CaptionUtterance]) -> str:
"""生成专业风格的 ASS 字幕(横屏 16:9)"""
return generate_ass(
utterances=utterances,
video_width=1920,
video_height=1080,
fontsize=24.0,
outline=1.5,
shadow=1.0,
margin_v=60,
)