add keyframe and text api

This commit is contained in:
sun-guannan
2025-07-13 16:18:27 +08:00
parent 6ba9d1aab9
commit 4eed1e1487
2 changed files with 133 additions and 133 deletions

View File

@@ -14,108 +14,108 @@ def add_text_impl(
draft_id: str = None,
transform_y: float = -0.8,
transform_x: float = 0,
font: str = "文轩体",
font: str = "文轩体", # Wenxuan Font
font_color: str = "#ffffff",
font_size: float = 8.0,
track_name: str = "text_main",
vertical: bool = False, # 是否竖排显示
font_alpha: float = 1.0, # 透明度,范围0.0-1.0
# 描边参数
vertical: bool = False, # Whether to display vertically
font_alpha: float = 1.0, # Transparency, range 0.0-1.0
# Border parameters
border_alpha: float = 1.0,
border_color: str = "#000000",
border_width: float = 0.0, # 默认不显示描边
# 背景参数
border_width: float = 0.0, # Default no border display
# Background parameters
background_color: str = "#000000",
background_style: int = 1,
background_alpha: float = 0.0, # 默认不显示背景
# 气泡效果
background_alpha: float = 0.0, # Default no background display
# Bubble effect
bubble_effect_id: Optional[str] = None,
bubble_resource_id: Optional[str] = None,
# 文本花字
# Text effect
effect_effect_id: Optional[str] = None,
intro_animation: Optional[str] = None, # 入场动画类型
intro_duration: float = 0.5, # 入场动画持续时间默认0.5秒
outro_animation: Optional[str] = None, # 出场动画类型
outro_duration: float = 0.5, # 出场动画持续时间默认0.5秒
intro_animation: Optional[str] = None, # Intro animation type
intro_duration: float = 0.5, # Intro animation duration (seconds), default 0.5 seconds
outro_animation: Optional[str] = None, # Outro animation type
outro_duration: float = 0.5, # Outro animation duration (seconds), default 0.5 seconds
width: int = 1080,
height: int = 1920,
fixed_width: float = -1, # 文本固定宽度比例,默认-1表示不固定
fixed_height: float = -1, # 文本固定高度比例,默认-1表示不固定
fixed_width: float = -1, # Text fixed width ratio, default -1 means not fixed
fixed_height: float = -1, # Text fixed height ratio, default -1 means not fixed
):
"""
向指定草稿添加文本字幕(参数可配置版本)
:param text: 文本内容
:param start: 开始时间(秒)
:param end: 结束时间(秒)
:param draft_id: 草稿ID可选默认None则创建新草稿
:param transform_y: Y轴位置(默认-0.8,屏幕下方)
:param transform_x: X轴位置默认0屏幕中间
:param font: 字体名称(支持Font_type中的所有字体)
:param font_color: 字体颜色 #FFF0FF
:param font_size: 字体大小(浮点值,默认8.0
:param track_name: 轨道名称
:param vertical: 是否竖排显示(默认False
:param font_alpha: 文字透明度范围0.0-1.0默认1.0,完全不透明)
:param border_alpha: 描边透明度范围0.0-1.0(默认1.0
:param border_color: 描边颜色(默认黑色)
:param border_width: 描边宽度默认0.0,不显示描边)
:param background_color: 背景颜色(默认黑色)
:param background_style: 背景样式默认1
:param background_alpha: 背景透明度默认0.0,不显示背景)
:param bubble_effect_id: 气泡效果ID
:param bubble_resource_id: 气泡资源ID
:param effect_effect_id: 花字效果ID
:param intro_animation: 入场动画类型
:param intro_duration: 入场动画持续时间默认0.5秒
:param outro_animation: 出场动画类型
:param outro_duration: 出场动画持续时间默认0.5秒
:param width: 视频宽度(像素)
:param height: 视频高度(像素)
:param fixed_width: 文本固定宽度比例范围0.0-1.0,默认-1表示不固定
:param fixed_height: 文本固定高度比例范围0.0-1.0,默认-1表示不固定
:return: 更新后的草稿信息
Add text subtitle to the specified draft (configurable parameter version)
:param text: Text content
:param start: Start time (seconds)
:param end: End time (seconds)
:param draft_id: Draft ID (optional, default None creates a new draft)
:param transform_y: Y-axis position (default -0.8, bottom of screen)
:param transform_x: X-axis position (default 0, center of screen)
:param font: Font name (supports all fonts in Font_type)
:param font_color: Font color #FFF0FF
:param font_size: Font size (float value, default 8.0)
:param track_name: Track name
:param vertical: Whether to display vertically (default False)
:param font_alpha: Text transparency, range 0.0-1.0 (default 1.0, completely opaque)
:param border_alpha: Border transparency, range 0.0-1.0 (default 1.0)
:param border_color: Border color (default black)
:param border_width: Border width (default 0.0, no border display)
:param background_color: Background color (default black)
:param background_style: Background style (default 1)
:param background_alpha: Background transparency (default 0.0, no background display)
:param bubble_effect_id: Bubble effect ID
:param bubble_resource_id: Bubble resource ID
:param effect_effect_id: Text effect ID
:param intro_animation: Intro animation type
:param intro_duration: Intro animation duration (seconds), default 0.5 seconds
:param outro_animation: Outro animation type
:param outro_duration: Outro animation duration (seconds), default 0.5 seconds
:param width: Video width (pixels)
:param height: Video height (pixels)
:param fixed_width: Text fixed width ratio, range 0.0-1.0, default -1 means not fixed
:param fixed_height: Text fixed height ratio, range 0.0-1.0, default -1 means not fixed
:return: Updated draft information
"""
# 校验字体是否在Font_type
# Validate if font is in Font_type
try:
font_type = getattr(Font_type, font)
except:
available_fonts = [attr for attr in dir(Font_type) if not attr.startswith('_')]
raise ValueError(f"不支持的字体:{font},请使用Font_type中的字体之一:{available_fonts}")
raise ValueError(f"Unsupported font: {font}, please use one of the fonts in Font_type: {available_fonts}")
# 校验alpha值范围
# Validate alpha value range
if not 0.0 <= font_alpha <= 1.0:
raise ValueError("alpha值必须在0.0到1.0之间")
raise ValueError("alpha value must be between 0.0 and 1.0")
if not 0.0 <= border_alpha <= 1.0:
raise ValueError("border_alpha值必须在0.0到1.0之间")
raise ValueError("border_alpha value must be between 0.0 and 1.0")
if not 0.0 <= background_alpha <= 1.0:
raise ValueError("background_alpha值必须在0.0到1.0之间")
raise ValueError("background_alpha value must be between 0.0 and 1.0")
# 获取或创建草稿
# Get or create draft
draft_id, script = get_or_create_draft(
draft_id=draft_id,
width=width,
height=height
)
# 添加文本轨道
# Add text track
if track_name is not None:
try:
imported_track = script.get_imported_track(draft.Track_type.text, name=track_name)
# 如果没有抛出异常,说明轨道已存在
# If no exception is thrown, the track already exists
except exceptions.TrackNotFound:
# 轨道不存在,创建新轨道
# Track doesn't exist, create a new track
script.add_track(draft.Track_type.text, track_name=track_name)
else:
script.add_track(draft.Track_type.audio)
# 转换十六进制颜色为 RGB 元组
# Convert hexadecimal color to RGB tuple
try:
rgb_color = hex_to_rgb(font_color)
rgb_border_color = hex_to_rgb(border_color)
except ValueError as e:
raise ValueError(f"颜色参数错误: {str(e)}")
raise ValueError(f"Color parameter error: {str(e)}")
# 创建text_border (描边)
# Create text_border
text_border = None
if border_width > 0:
text_border = draft.Text_border(
@@ -124,7 +124,7 @@ def add_text_impl(
width=border_width
)
# 创建text_background (背景)
# Create text_background
text_background = None
if background_alpha > 0:
text_background = draft.Text_background(
@@ -133,7 +133,7 @@ def add_text_impl(
alpha=background_alpha
)
# 创建气泡效果
# Create bubble effect
text_bubble = None
if bubble_effect_id and bubble_resource_id:
text_bubble = TextBubble(
@@ -141,14 +141,14 @@ def add_text_impl(
resource_id=bubble_resource_id
)
# 创建花字效果
# Create text effect
text_effect = None
if effect_effect_id:
text_effect = TextEffect(
effect_id=effect_effect_id
)
# 将比例转换为像素值
# Convert ratio to pixel value
pixel_fixed_width = -1
pixel_fixed_height = -1
if fixed_width > 0:
@@ -156,17 +156,17 @@ def add_text_impl(
if fixed_height > 0:
pixel_fixed_height = int(fixed_height * script.height)
# 创建文本片段(使用可配置参数)
# Create text segment (using configurable parameters)
text_segment = draft.Text_segment(
text,
trange(f"{start}s", f"{end-start}s"),
font=font_type, # 使用Font_type中的字体
font=font_type, # Use font from Font_type
style=draft.Text_style(
color=rgb_color,
size=font_size,
align=1,
vertical=vertical, # 设置是否竖排
alpha=font_alpha # 设置透明度
vertical=vertical, # Set whether to display vertically
alpha=font_alpha # Set transparency
),
clip_settings=draft.Clip_settings(transform_y=transform_y, transform_x=transform_x),
border=text_border,
@@ -180,33 +180,33 @@ def add_text_impl(
if text_effect:
text_segment.add_effect(text_effect.effect_id)
# 添加入场动画
# Add intro animation
if intro_animation:
try:
if IS_CAPCUT_ENV:
animation_type = getattr(draft.CapCut_Text_intro, intro_animation)
else:
animation_type = getattr(draft.Text_intro, intro_animation)
# 将秒转换为微秒
# Convert seconds to microseconds
duration_microseconds = int(intro_duration * 1000000)
text_segment.add_animation(animation_type, duration_microseconds) # 添加入场动画,设置持续时间
text_segment.add_animation(animation_type, duration_microseconds) # Add intro animation, set duration
except:
print(f"警告:不支持的入场动画类型 {intro_animation},将忽略此参数")
print(f"Warning: Unsupported intro animation type {intro_animation}, this parameter will be ignored")
# 添加出场动画
# Add outro animation
if outro_animation:
try:
if IS_CAPCUT_ENV:
animation_type = getattr(draft.CapCut_Text_outro, outro_animation)
else:
animation_type = getattr(draft.Text_outro, outro_animation)
# 将秒转换为微秒
# Convert seconds to microseconds
duration_microseconds = int(outro_duration * 1000000)
text_segment.add_animation(animation_type, duration_microseconds) # 添加出场动画,设置持续时间
text_segment.add_animation(animation_type, duration_microseconds) # Add outro animation, set duration
except:
print(f"警告:不支持的出场动画类型 {outro_animation},将忽略此参数")
print(f"Warning: Unsupported outro animation type {outro_animation}, this parameter will be ignored")
# 添加文本片段到轨道
# Add text segment to track
script.add_segment(text_segment, track_name=track_name)
return {

View File

@@ -16,63 +16,63 @@ def add_video_keyframe_impl(
values: Optional[List[str]] = None
) -> Dict[str, str]:
"""
向指定片段添加关键帧
:param draft_id: 草稿ID如果为None或找不到对应的zip文件则创建新草稿
:param track_name: 轨道名称,默认"main"
:param property_type: 关键帧属性类型,支持以下值:
- position_x: 水平位置,范围[-1,1]0表示居中1表示最右边
- position_y: 垂直位置,范围[-1,1]0表示居中1表示最下边
- rotation: 顺时针旋转角度
- scale_x: X轴缩放比例(1.0为不缩放),与uniform_scale互斥
- scale_y: Y轴缩放比例(1.0为不缩放),与uniform_scale互斥
- uniform_scale: 整体缩放比例(1.0为不缩放),与scale_xscale_y互斥
- alpha: 不透明度1.0为完全不透明
- saturation: 饱和度0.0为原始饱和度,范围为-1.0到1.0
- contrast: 对比度0.0为原始对比度,范围为-1.0到1.0
- brightness: 亮度0.0为原始亮度,范围为-1.0到1.0
- volume: 音量1.0为原始音量
:param time: 关键帧时间点(秒),默认0.0
:param value: 关键帧值,格式根据property_type不同而不同:
- position_x/position_y: "0"表示居中位置,范围[-1,1]
- rotation: "45deg"表示45度
- scale_x/scale_y/uniform_scale: "1.5"表示放大1.5倍
- alpha: "50%"表示50%不透明度
- saturation/contrast/brightness: "+0.5"表示增加0.5"-0.5"表示减少0.5
- volume: "80%"表示原始音量的80%
:param property_types: 批量模式:关键帧属性类型列表,如 ["alpha", "position_x", "rotation"]
:param times: 批量模式:关键帧时间点列表(秒),如 [0.0, 1.0, 2.0]
:param values: 批量模式:关键帧值列表,如 ["1.0", "0.5", "45deg"]
注意:property_typestimesvalues 三个参数必须同时提供且长度相等,如果提供了这些参数,将忽略单个关键帧参数
:return: 更新后的草稿信息
Add keyframes to the specified segment
:param draft_id: Draft ID, if None or corresponding zip file not found, a new draft will be created
:param track_name: Track name, default "main"
:param property_type: Keyframe property type, supports the following values:
- position_x: Horizontal position, range [-1,1], 0 means center, 1 means rightmost
- position_y: Vertical position, range [-1,1], 0 means center, 1 means bottom
- rotation: Clockwise rotation angle
- scale_x: X-axis scale ratio (1.0 means no scaling), mutually exclusive with uniform_scale
- scale_y: Y-axis scale ratio (1.0 means no scaling), mutually exclusive with uniform_scale
- uniform_scale: Overall scale ratio (1.0 means no scaling), mutually exclusive with scale_x and scale_y
- alpha: Opacity, 1.0 means completely opaque
- saturation: Saturation, 0.0 means original saturation, range from -1.0 to 1.0
- contrast: Contrast, 0.0 means original contrast, range from -1.0 to 1.0
- brightness: Brightness, 0.0 means original brightness, range from -1.0 to 1.0
- volume: Volume, 1.0 means original volume
:param time: Keyframe time point (seconds), default 0.0
:param value: Keyframe value, format varies according to property_type:
- position_x/position_y: "0" means center position, range [-1,1]
- rotation: "45deg" means 45 degrees
- scale_x/scale_y/uniform_scale: "1.5" means scale up by 1.5 times
- alpha: "50%" means 50% opacity
- saturation/contrast/brightness: "+0.5" means increase by 0.5, "-0.5" means decrease by 0.5
- volume: "80%" means 80% of original volume
:param property_types: Batch mode: List of keyframe property types, e.g. ["alpha", "position_x", "rotation"]
:param times: Batch mode: List of keyframe time points (seconds), e.g. [0.0, 1.0, 2.0]
:param values: Batch mode: List of keyframe values, e.g. ["1.0", "0.5", "45deg"]
Note: property_types, times, values must be provided together and have equal lengths. If these parameters are provided, single keyframe parameters will be ignored
:return: Updated draft information
"""
# 获取或创建草稿
# Get or create draft
draft_id, script = get_or_create_draft(
draft_id=draft_id
)
try:
# 获取指定轨道
# Get specified track
track = script.get_track(draft.Video_segment, track_name=track_name)
# 获取轨道中的片段
# Get segments in the track
segments = track.segments
if not segments:
raise Exception(f"轨道 {track_name} 中没有片段")
raise Exception(f"No segments in track {track_name}")
# 确定要处理的关键帧列表
# Determine the keyframes list to process
if property_types is not None or times is not None or values is not None:
# 批量模式:使用三个数组参数
# Batch mode: use three array parameters
if property_types is None or times is None or values is None:
raise Exception("批量模式下,property_typestimesvalues 三个参数必须同时提供")
raise Exception("In batch mode, property_types, times, values must be provided together")
if not (isinstance(property_types, list) and isinstance(times, list) and isinstance(values, list)):
raise Exception("property_typestimesvalues 必须都是列表类型")
raise Exception("property_types, times, values must all be list types")
if len(property_types) == 0:
raise Exception("批量模式下,参数列表不能为空")
raise Exception("In batch mode, parameter lists cannot be empty")
if not (len(property_types) == len(times) == len(values)):
raise Exception(f"property_typestimesvalues 的长度必须相等,当前长度分别为:{len(property_types)}, {len(times)}, {len(values)}")
raise Exception(f"property_types, times, values must have equal lengths, current lengths are: {len(property_types)}, {len(times)}, {len(values)}")
keyframes_to_process = [
{
@@ -83,76 +83,76 @@ def add_video_keyframe_impl(
for prop_type, t, val in zip(property_types, times, values)
]
else:
# 单个模式:使用原有参数
# Single mode: use original parameters
keyframes_to_process = [{
"property_type": property_type,
"time": time,
"value": value
}]
# 处理每个关键帧
# Process each keyframe
added_count = 0
for i, kf in enumerate(keyframes_to_process):
try:
_add_single_keyframe(track, kf["property_type"], kf["time"], kf["value"])
added_count += 1
except Exception as e:
raise Exception(f"添加第{i+1}个关键帧失败 (property_type={kf['property_type']}, time={kf['time']}, value={kf['value']}): {str(e)}")
raise Exception(f"Failed to add keyframe #{i+1} (property_type={kf['property_type']}, time={kf['time']}, value={kf['value']}): {str(e)}")
result = {
"draft_id": draft_id,
"draft_url": generate_draft_url(draft_id)
}
# 如果是批量模式,返回添加的关键帧数量
# If in batch mode, return the number of added keyframes
if property_types is not None:
result["added_keyframes_count"] = added_count
return result
except exceptions.TrackNotFound:
raise Exception(f"找不到名为 {track_name} 的轨道")
raise Exception(f"Track named {track_name} not found")
except Exception as e:
raise Exception(f"添加关键帧失败: {str(e)}")
raise Exception(f"Failed to add keyframe: {str(e)}")
def _add_single_keyframe(track, property_type: str, time: float, value: str):
"""
添加单个关键帧的内部函数
Internal function to add a single keyframe
"""
# 将属性类型字符串转换为枚举值,验证属性类型是否有效
# Convert property type string to enum value, validate if property type is valid
try:
property_enum = getattr(draft.Keyframe_property, property_type)
except:
raise Exception(f"不支持的关键帧属性类型: {property_type}")
raise Exception(f"Unsupported keyframe property type: {property_type}")
# 根据属性类型解析value值
# Parse value based on property type
try:
if property_type in ['position_x', 'position_y']:
# 处理位置,范围[0,1]
# Handle position, range [0,1]
float_value = float(value)
if not -10 <= float_value <= 10:
raise ValueError(f"{property_type}的值必须在-10到10之间")
raise ValueError(f"Value for {property_type} must be between -10 and 10")
elif property_type == 'rotation':
# 处理旋转角度
# Handle rotation angle
if value.endswith('deg'):
float_value = float(value[:-3])
else:
float_value = float(value)
elif property_type == 'alpha':
# 处理不透明度
# Handle opacity
if value.endswith('%'):
float_value = float(value[:-1]) / 100
else:
float_value = float(value)
elif property_type == 'volume':
# 处理音量
# Handle volume
if value.endswith('%'):
float_value = float(value[:-1]) / 100
else:
float_value = float(value)
elif property_type in ['saturation', 'contrast', 'brightness']:
# 处理饱和度、对比度、亮度
# Handle saturation, contrast, brightness
if value.startswith('+'):
float_value = float(value[1:])
elif value.startswith('-'):
@@ -160,10 +160,10 @@ def _add_single_keyframe(track, property_type: str, time: float, value: str):
else:
float_value = float(value)
else:
# 其他属性直接转换为浮点数
# Other properties directly convert to float
float_value = float(value)
except ValueError:
raise Exception(f"无效的值格式: {value}")
raise Exception(f"Invalid value format: {value}")
# 如果提供了轨道对象,则使用轨道的add_pending_keyframe方法
# If track object is provided, use the track's add_pending_keyframe method
track.add_pending_keyframe(property_type, time, value)