第23章:字幕添加服务

23.1 概述

字幕添加服务是剪映小助手的核心功能模块,负责将字幕内容添加到剪映草稿中。该服务支持批量字幕添加,提供了完整的字幕处理流程,包括字幕样式设置、关键词高亮、动画效果、轨道管理和时间轴控制等功能。

字幕添加服务采用模块化设计,将复杂的字幕处理逻辑封装成简单的API调用,用户只需要提供字幕文本和时间信息,系统就能自动完成字幕的创建、样式设置和添加操作。

23.2 核心功能

23.2.1 批量字幕添加

add_captions函数是字幕添加服务的主入口,负责处理批量字幕添加的完整流程。

核心实现
def add_captions(
    draft_url: str,
    captions: str,
    text_color: str = "#ffffff",
    border_color: Optional[str] = None,
    alignment: int = 1,
    alpha: float = 1.0,
    font: Optional[str] = None,
    font_size: int = 15,
    letter_spacing: Optional[float] = None,
    line_spacing: Optional[float] = None,
    scale_x: float = 1.0,
    scale_y: float = 1.0,
    transform_x: int = 0,
    transform_y: int = 0,
    style_text: bool = False
) -> Tuple[str, str, List[str], List[str]]:
    """
    批量添加字幕到剪映草稿的业务逻辑
    
    Args:
        draft_url: 草稿URL
        captions: 字幕信息列表的JSON字符串,格式如下:
            [
                {
                    "start": 0,  # 字幕开始时间(微秒)
                    "end": 10000000,  # 字幕结束时间(微秒)
                    "text": "你好,剪映",  # 字幕文本内容
                    "keyword": "好",  # 关键词(用|分隔多个关键词),可选参数
                    "keyword_color": "#457616",  # 关键词颜色,可选参数
                    "keyword_font_size": 15,  # 关键词字体大小,可选参数
                    "font_size": 15,  # 文本字体大小,可选参数
                    "in_animation": None,  # 入场动画,可选参数
                    "out_animation": None,  # 出场动画,可选参数
                    "loop_animation": None,  # 循环动画,可选参数
                    "in_animation_duration": None,  # 入场动画时长,可选参数
                    "out_animation_duration": None,  # 出场动画时长,可选参数
                    "loop_animation_duration": None  # 循环动画时长,可选参数
                }
            ]
        text_color: 文本颜色(十六进制),默认"#ffffff"
        border_color: 边框颜色(十六进制),默认None
        alignment: 文本对齐方式(0-5),默认1
        alpha: 文本透明度(0.0-1.0),默认1.0
        font: 字体名称,默认None
        font_size: 字体大小,默认15
        letter_spacing: 字间距,默认None
        line_spacing: 行间距,默认None
        scale_x: 水平缩放,默认1.0
        scale_y: 垂直缩放,默认1.0
        transform_x: 水平位移,默认0
        transform_y: 垂直位移,默认0
        style_text: 是否使用样式文本,默认False
    
    Returns:
        draft_url: 草稿URL
        track_id: 字幕轨道ID
        text_ids: 字幕ID列表
        segment_ids: 字幕片段ID列表
    
    Raises:
        CustomException: 字幕添加失败
    """
    logger.info(f"add_captions started, draft_url: {draft_url}, captions count: {len(json.loads(captions) if captions else [])}")

    # 1. 提取草稿ID
    draft_id = helper.get_url_param(draft_url, "draft_id")
    if (not draft_id) or (draft_id not in DRAFT_CACHE):
        logger.error(f"Invalid draft_url or draft not found in cache: {draft_url}")
        raise CustomException(CustomError.INVALID_DRAFT_URL)

    # 2. 解析字幕信息
    caption_items = parse_captions_data(json_str=captions)
    if len(caption_items) == 0:
        logger.info(f"No caption info provided, draft_id: {draft_id}")
        raise CustomError.INVALID_CAPTION_INFO

    logger.info(f"Parsed {len(caption_items)} caption items")

    # 3. 从缓存中获取草稿
    script: ScriptFile = DRAFT_CACHE[draft_id]

    # 4. 添加字幕轨道
    track_name = f"caption_track_{helper.gen_unique_id()}"
    script.add_track(track_type=TrackType.text, track_name=track_name)
    logger.info(f"Added caption track: {track_name}")

    # 5. 遍历字幕信息,添加字幕到草稿中的指定轨道,收集片段ID
    segment_ids = []
    text_ids = []
    for i, caption in enumerate(caption_items):
        try:
            logger.info(f"Processing caption {i+1}/{len(caption_items)}, text: {caption['text'][:20]}...")
            
            segment_id, text_id = add_caption_to_draft(
                script, track_name,
                caption=caption,
                text_color=text_color,
                border_color=border_color,
                alignment=alignment,
                alpha=alpha,
                font=font,
                font_size=font_size,
                letter_spacing=letter_spacing,
                line_spacing=line_spacing,
                scale_x=scale_x,
                scale_y=scale_y,
                transform_x=transform_x,
                transform_y=transform_y,
                style_text=style_text
            )
            segment_ids.append(segment_id)
            text_ids.append(text_id)
            logger.info(f"Added caption {i+1}/{len(caption_items)}, segment_id: {segment_id}")
        except Exception as e:
            logger.error(f"Failed to add caption {i+1}/{len(caption_items)}, error: {str(e)}")
            raise

    # 6. 保存草稿
    script.save()
    logger.info(f"Draft saved successfully")

    # 7. 获取当前字幕轨道ID
    track_id = ""
    for key in script.tracks.keys():
        if script.tracks[key].name == track_name:
            track_id = script.tracks[key].track_id
            break
    logger.info(f"Caption track created, draft_id: {draft_id}, track_id: {track_id}")

    logger.info(f"add_captions completed successfully - draft_id: {draft_id}, track_id: {track_id}, captions_added: {len(caption_items)}")
    
    return draft_url, track_id, text_ids, segment_ids
处理流程
  1. 参数验证:验证草稿URL和缓存状态
  2. 数据解析:解析和验证字幕信息JSON
  3. 轨道创建:添加新的字幕轨道
  4. 批量添加:遍历添加每个字幕到轨道
  5. 草稿保存:持久化草稿更改
  6. 信息返回:返回轨道ID、字幕ID和片段ID列表

23.2.2 单个字幕添加

add_caption_to_draft函数负责将单个字幕添加到指定的轨道中,支持样式设置和动画效果。

核心实现
def add_caption_to_draft(
    script: ScriptFile,
    track_name: str,
    caption: dict,
    text_color: str = "#ffffff",
    border_color: Optional[str] = None,
    alignment: int = 1,
    alpha: float = 1.0,
    font: Optional[str] = None,
    font_size: int = 15,
    letter_spacing: Optional[float] = None,
    line_spacing: Optional[float] = None,
    scale_x: float = 1.0,
    scale_y: float = 1.0,
    transform_x: int = 0,
    transform_y: int = 0,
    style_text: bool = False
) -> Tuple[str, str]:
    """
    向剪映草稿中添加单个字幕
    
    Args:
        script: 草稿文件对象
        track_name: 字幕轨道名称
        caption: 字幕信息字典,包含以下字段:
            start: 字幕开始时间(微秒)
            end: 字幕结束时间(微秒)
            text: 字幕文本内容
            keyword: 关键词(用|分隔多个关键词),可选
            keyword_color: 关键词颜色,可选
            keyword_font_size: 关键词字体大小,可选
            font_size: 文本字体大小,可选
            in_animation: 入场动画,可选
            out_animation: 出场动画,可选
            loop_animation: 循环动画,可选
            in_animation_duration: 入场动画时长,可选
            out_animation_duration: 出场动画时长,可选
            loop_animation_duration: 循环动画时长,可选
        其他参数:字幕样式设置
    
    Returns:
        segment_id: 片段ID
        text_id: 文本ID(material_id)
    
    Raises:
        CustomException: 添加字幕失败
    """
    try:
        # 1. 创建时间范围
        caption_duration = caption['end'] - caption['start']
        timerange = Timerange(start=caption['start'], duration=caption_duration)
        
        # 2. 解析颜色
        rgb_color = hex_to_rgb(text_color)
        
        # 3. 创建文本样式
        align_value: Literal[0, 1, 2] = 0
        if alignment == 1:
            align_value = 1
        elif alignment == 2:
            align_value = 2
        
        text_style = TextStyle(
            size=float(caption.get('font_size', font_size)),
            color=rgb_color,
            alpha=alpha,
            align=align_value,
            letter_spacing=int(letter_spacing) if letter_spacing is not None else 0,
            line_spacing=int(line_spacing) if line_spacing is not None else 0,
            auto_wrapping=True  # 字幕默认开启自动换行
        )
        
        # 4. 创建图像调节设置
        clip_settings = ClipSettings(
            scale_x=scale_x,
            scale_y=scale_y,
            transform_x=float(transform_x) / script.width * 2,  # 转换为半画布宽度单位
            transform_y=float(transform_y) / script.height * 2  # 转换为半画布高度单位
        )
        
        # 5. 创建文本片段
        text_segment = TextSegment(
            text=caption['text'],
            timerange=timerange,
            style=text_style,
            clip_settings=clip_settings
        )
        
        logger.info(f"Created text segment, material_id: {text_segment.material_id}")
        logger.info(f"Text segment details - start: {caption['start']}, duration: {caption_duration}, text: {caption['text'][:50]}")

        # 6. TODO: 处理关键词高亮(这需要更复杂的实现)
        if caption.get('keyword'):
            logger.info(f"Keyword highlighting specified but not implemented yet: {caption['keyword']}")
        
        # 7. TODO: 处理动画效果(需要导入相应的动画类型)
        if caption.get('in_animation'):
            logger.info(f"In animation specified but not implemented yet: {caption['in_animation']}")
        if caption.get('out_animation'):
            logger.info(f"Out animation specified but not implemented yet: {caption['out_animation']}")
        if caption.get('loop_animation'):
            logger.info(f"Loop animation specified but not implemented yet: {caption['loop_animation']}")

        # 8. 向指定轨道添加片段
        script.add_segment(text_segment, track_name)

        return text_segment.segment_id, text_segment.material_id
        
    except CustomException:
        logger.error(f"Add caption to draft failed, caption: {caption}")
        raise
    except Exception as e:
        logger.error(f"Add caption to draft failed, error: {str(e)}")
        raise CustomException(CustomError.CAPTION_ADD_FAILED)

23.2.3 字幕数据解析

parse_captions_data函数负责解析和验证字幕数据的JSON字符串,处理可选字段的默认值。

核心实现
def parse_captions_data(json_str: str) -> List[Dict[str, Any]]:
    """
    解析字幕数据的JSON字符串,处理可选字段的默认值
    
    Args:
        json_str: 包含字幕数据的JSON字符串,格式如下:
        [
            {
                "start": 0,  # [必选] 字幕开始时间(微秒)
                "end": 10000000,  # [必选] 字幕结束时间(微秒)
                "text": "你好,剪映",  # [必选] 字幕文本内容
                "keyword": "好",  # [可选] 关键词(用|分隔多个关键词)
                "keyword_color": "#457616",  # [可选] 关键词颜色,默认"#ff7100"
                "keyword_font_size": 15,  # [可选] 关键词字体大小,默认15
                "font_size": 15,  # [可选] 文本字体大小,默认15
                "in_animation": None,  # [可选] 入场动画,默认None
                "out_animation": None,  # [可选] 出场动画,默认None
                "loop_animation": None,  # [可选] 循环动画,默认None
                "in_animation_duration": None,  # [可选] 入场动画时长,默认None
                "out_animation_duration": None,  # [可选] 出场动画时长,默认None
                "loop_animation_duration": None  # [可选] 循环动画时长,默认None
            }
        ]
        
    Returns:
        包含字幕对象的数组,每个对象都处理了默认值
        
    Raises:
        CustomException: 当JSON格式错误或缺少必选字段时抛出
    """
    try:
        # 解析JSON字符串
        data = json.loads(json_str)
    except json.JSONDecodeError as e:
        logger.error(f"JSON parse error: {e.msg}")
        raise CustomException(CustomError.INVALID_CAPTION_INFO, f"JSON parse error: {e.msg}")
    
    # 确保输入是列表
    if not isinstance(data, list):
        logger.error("captions should be a list")
        raise CustomException(CustomError.INVALID_CAPTION_INFO, "captions should be a list")
    
    result = []
    
    for i, item in enumerate(data):
        if not isinstance(item, dict):
            logger.error(f"the {i}th item should be a dict")
            raise CustomException(CustomError.INVALID_CAPTION_INFO, f"the {i}th item should be a dict")
        
        # 检查必选字段
        required_fields = ["start", "end", "text"]
        missing_fields = [field for field in required_fields if field not in item]
        
        if missing_fields:
            logger.error(f"the {i}th item is missing required fields: {', '.join(missing_fields)}")
            raise CustomException(CustomError.INVALID_CAPTION_INFO, f"the {i}th item is missing required fields: {', '.join(missing_fields)}")
        
        # 创建处理后的对象,设置默认值
        processed_item = {
            "start": item["start"],
            "end": item["end"],
            "text": item["text"],
            "keyword": item.get("keyword", None),
            "keyword_color": item.get("keyword_color", "#ff7100"),
            "keyword_font_size": item.get("keyword_font_size", 15),
            "font_size": item.get("font_size", 15),
            "in_animation": item.get("in_animation", None),
            "out_animation": item.get("out_animation", None),
            "loop_animation": item.get("loop_animation", None),
            "in_animation_duration": item.get("in_animation_duration", None),
            "out_animation_duration": item.get("out_animation_duration", None),
            "loop_animation_duration": item.get("loop_animation_duration", None)
        }
        
        # 验证数值类型和范围
        if not isinstance(processed_item["start"], (int, float)) or processed_item["start"] < 0:
            logger.error(f"the {i}th item has invalid start time: {processed_item['start']}")
            raise CustomException(CustomError.INVALID_CAPTION_INFO, f"the {i}th item has invalid start time")
        
        if not isinstance(processed_item["end"], (int, float)) or processed_item["end"] <= processed_item["start"]:
            logger.error(f"the {i}th item has invalid end time: {processed_item['end']}")
            raise CustomException(CustomError.INVALID_CAPTION_INFO, f"the {i}th item has invalid end time")
        
        if not isinstance(processed_item["text"], str) or len(processed_item["text"].strip()) == 0:
            logger.error(f"the {i}th item has invalid text: {processed_item['text']}")
            raise CustomException(CustomError.INVALID_CAPTION_INFO, f"the {i}th item has invalid text")
        
        # 验证字体大小
        if not isinstance(processed_item["font_size"], (int, float)) or processed_item["font_size"] <= 0:
            processed_item["font_size"] = 15
        if not isinstance(processed_item["keyword_font_size"], (int, float)) or processed_item["keyword_font_size"] <= 0:
            processed_item["keyword_font_size"] = 15
        
        result.append(processed_item)
    
    logger.info(f"Successfully parsed {len(result)} caption items")
    return result

23.3 数据模型设计

23.3.1 请求响应模型

字幕添加服务定义了清晰的数据模型:

class AddCaptionsRequest(BaseModel):
    """批量添加字幕请求参数"""
    draft_url: str = Field(default="", description="草稿URL")
    captions: str = Field(default="", description="字幕信息列表, 用JSON字符串表示")
    text_color: str = Field(default="#ffffff", description="文本颜色(十六进制)")
    border_color: Optional[str] = Field(default=None, description="边框颜色(十六进制)")
    alignment: int = Field(default=1, ge=0, le=5, description="文本对齐方式(0-5)")
    alpha: float = Field(default=1.0, ge=0.0, le=1.0, description="文本透明度(0.0-1.0)")
    font: Optional[str] = Field(default=None, description="字体名称")
    font_size: int = Field(default=15, ge=1, description="字体大小")
    letter_spacing: Optional[float] = Field(default=None, description="字间距")
    line_spacing: Optional[float] = Field(default=None, description="行间距")
    scale_x: float = Field(default=1.0, description="水平缩放")
    scale_y: float = Field(default=1.0, description="垂直缩放")
    transform_x: int = Field(default=0, description="水平位移")
    transform_y: int = Field(default=0, description="垂直位移")
    style_text: bool = Field(default=False, description="是否使用样式文本")

class CaptionItem(BaseModel):
    """单个字幕信息"""
    start: int = Field(..., description="字幕开始时间(微秒)")
    end: int = Field(..., description="字幕结束时间(微秒)")
    text: str = Field(..., description="字幕文本内容")
    keyword: Optional[str] = Field(default=None, description="关键词(用|分隔多个关键词)")
    keyword_color: str = Field(default="#ff7100", description="关键词颜色")
    keyword_font_size: int = Field(default=15, ge=1, description="关键词字体大小")
    font_size: int = Field(default=15, ge=1, description="文本字体大小")
    in_animation: Optional[str] = Field(default=None, description="入场动画")
    out_animation: Optional[str] = Field(default=None, description="出场动画")
    loop_animation: Optional[str] = Field(default=None, description="循环动画")
    in_animation_duration: Optional[int] = Field(default=None, description="入场动画时长")
    out_animation_duration: Optional[int] = Field(default=None, description="出场动画时长")
    loop_animation_duration: Optional[int] = Field(default=None, description="循环动画时长")

class AddCaptionsResponse(BaseModel):
    """添加字幕响应参数"""
    draft_url: str = Field(default="", description="草稿URL")
    track_id: str = Field(default="", description="字幕轨道ID")
    text_ids: List[str] = Field(default=[], description="字幕ID列表")
    segment_ids: List[str] = Field(default=[], description="字幕片段ID列表")

23.3.2 字幕参数配置

字幕添加服务支持以下参数配置:

参数名 类型 必选 默认值 取值范围 说明
start int - ≥0 字幕开始时间(微秒)
end int - >start 字幕结束时间(微秒)
text string - - 字幕文本内容
keyword string None - 关键词(用
keyword_color string “#ff7100” - 关键词颜色

相关资源

  • GitHub代码仓库: https://github.com/Hommy-master/capcut-mate
  • Gitee代码仓库: https://gitee.com/taohongmin-gitee/capcut-mate
  • API文档地址: https://docs.jcaigc.cn

代码仓库地址

  • GitHub: https://github.com/Hommy-master/capcut-mate
  • Gitee: https://gitee.com/taohongmin-gitee/capcut-mate

接口文档地址

  • API文档地址: https://docs.jcaigc.cn
    | keyword_font_size | int | 否 | 15 | ≥1 | 关键词字体大小 |
    | font_size | int | 否 | 15 | ≥1 | 文本字体大小 |
    | in_animation | string | 否 | None | - | 入场动画 |
    | out_animation | string | 否 | None | - | 出场动画 |
    | loop_animation | string | 否 | None | - | 循环动画 |
    | in_animation_duration | int | 否 | None | - | 入场动画时长(微秒) |
    | out_animation_duration | int | 否 | None | - | 出场动画时长(微秒) |
    | loop_animation_duration | int | 否 | None | - | 循环动画时长(微秒) |

23.4 字幕处理特性

23.4.1 颜色转换系统

字幕添加服务实现了十六进制颜色到RGB的转换:

def hex_to_rgb(hex_color: str) -> tuple:
    """
    将十六进制颜色值转换为RGB三元组(0-1范围)
    
    Args:
        hex_color: 十六进制颜色值,如"#ffffff"或"ffffff"
    
    Returns:
        RGB三元组,取值范围为[0, 1]
    """
    # 移除#号(如果存在)
    hex_color = hex_color.lstrip('#')
    
    # 确保是6位十六进制
    if len(hex_color) != 6:
        logger.warning(f"Invalid hex color format: {hex_color}, using white as default")
        return (1.0, 1.0, 1.0)
    
    try:
        # 转换为RGB值(0-255)
        r = int(hex_color[0:2], 16)
        g = int(hex_color[2:4], 16)
        b = int(hex_color[4:6], 16)
        
        # 转换为0-1范围
        return (r / 255.0, g / 255.0, b / 255.0)
    except ValueError:
        logger.warning(f"Invalid hex color format: {hex_color}, using white as default")
        return (1.0, 1.0, 1.0)

23.4.2 文本样式设置

系统支持丰富的文本样式设置:

# 创建文本样式
text_style = TextStyle(
    size=float(caption.get('font_size', font_size)),
    color=rgb_color,
    alpha=alpha,
    align=align_value,
    letter_spacing=int(letter_spacing) if letter_spacing is not None else 0,
    line_spacing=int(line_spacing) if line_spacing is not None else 0,
    auto_wrapping=True  # 字幕默认开启自动换行
)

23.4.3 坐标转换系统

字幕位置采用智能坐标转换:

# 创建图像调节设置
clip_settings = ClipSettings(
    scale_x=scale_x,
    scale_y=scale_y,
    transform_x=float(transform_x) / script.width * 2,  # 转换为半画布宽度单位
    transform_y=float(transform_y) / script.height * 2  # 转换为半画布高度单位
)

23.4.4 关键词高亮框架

系统预留了关键词高亮功能接口:

# TODO: 处理关键词高亮(这需要更复杂的实现)
if caption.get('keyword'):
    logger.info(f"Keyword highlighting specified but not implemented yet: {caption['keyword']}")

23.4.5 动画效果框架

系统预留了丰富的动画效果接口:

# TODO: 处理动画效果(需要导入相应的动画类型)
if caption.get('in_animation'):
    logger.info(f"In animation specified but not implemented yet: {caption['in_animation']}")
if caption.get('out_animation'):
    logger.info(f"Out animation specified but not implemented yet: {caption['out_animation']}")
if caption.get('loop_animation'):
    logger.info(f"Loop animation specified but not implemented yet: {caption['loop_animation']}")

23.5 缓存集成

字幕添加服务深度集成了草稿缓存机制:

# 从缓存获取草稿对象
script: ScriptFile = DRAFT_CACHE[draft_id]

# 操作完成后更新缓存
script.save()

23.6 错误处理

字幕添加服务实现了完善的错误处理机制:

try:
    # 字幕添加逻辑
    segment_id, text_id = add_caption_to_draft(
        script, track_name, caption=caption,
        text_color=text_color, border_color=border_color,
        alignment=alignment, alpha=alpha, font=font,
        font_size=font_size, letter_spacing=letter_spacing,
        line_spacing=line_spacing, scale_x=scale_x,
        scale_y=scale_y, transform_x=transform_x,
        transform_y=transform_y, style_text=style_text
    )
except CustomException:
    logger.error(f"Add caption to draft failed, caption: {caption}")
    raise
except Exception as e:
    logger.error(f"Add caption to draft failed, error: {str(e)}")
    raise CustomException(CustomError.CAPTION_ADD_FAILED)

23.7 日志记录

字幕添加服务提供了详细的日志记录:

logger.info(f"add_captions started, draft_url: {draft_url}, captions count: {len(json.loads(captions) if captions else [])}")
logger.info(f"Parsed {len(caption_items)} caption items")
logger.info(f"Added caption track: {track_name}")
logger.info(f"Processing caption {i+1}/{len(caption_items)}, text: {caption['text'][:20]}...")
logger.info(f"Added caption {i+1}/{len(caption_items)}, segment_id: {segment_id}")
logger.info(f"Draft saved successfully")
logger.info(f"Caption track created, draft_id: {draft_id}, track_id: {track_id}")
logger.info(f"add_captions completed successfully - draft_id: {draft_id}, track_id: {track_id}, captions_added: {len(caption_items)}")

23.8 性能优化

23.8.1 批量处理

字幕添加服务支持批量处理,减少I/O操作次数:

# 批量添加字幕
for i, caption in enumerate(caption_items):
    segment_id, text_id = add_caption_to_draft(
        script, track_name, caption=caption,
        text_color=text_color, border_color=border_color,
        alignment=alignment, alpha=alpha, font=font,
        font_size=font_size, letter_spacing=letter_spacing,
        line_spacing=line_spacing, scale_x=scale_x,
        scale_y=scale_y, transform_x=transform_x,
        transform_y=transform_y, style_text=style_text
    )
    segment_ids.append(segment_id)
    text_ids.append(text_id)

23.8.2 缓存优化

利用草稿缓存机制,避免重复加载:

# 从缓存获取草稿
script: ScriptFile = DRAFT_CACHE[draft_id]

23.9 安全性考虑

23.9.1 输入验证

对所有输入参数进行严格验证:

# 验证时间范围
if not isinstance(processed_item["start"], (int, float)) or processed_item["start"] < 0:
    logger.error(f"the {i}th item has invalid start time: {processed_item['start']}")
    raise CustomException(CustomError.INVALID_CAPTION_INFO, f"the {i}th item has invalid start time")

if not isinstance(processed_item["end"], (int, float)) or processed_item["end"] <= processed_item["start"]:
    logger.error(f"the {i}th item has invalid end time: {processed_item['end']}")
    raise CustomException(CustomError.INVALID_CAPTION_INFO, f"the {i}th item has invalid end time")

# 验证文本内容
if not isinstance(processed_item["text"], str) or len(processed_item["text"].strip()) == 0:
    logger.error(f"the {i}th item has invalid text: {processed_item['text']}")
    raise CustomException(CustomError.INVALID_CAPTION_INFO, f"the {i}th item has invalid text")

# 验证字体大小
if not isinstance(processed_item["font_size"], (int, float)) or processed_item["font_size"] <= 0:
    processed_item["font_size"] = 15
if not isinstance(processed_item["keyword_font_size"], (int, float)) or processed_item["keyword_font_size"] <= 0:
    processed_item["keyword_font_size"] = 15

23.9.2 颜色安全

颜色转换过程中进行异常处理:

try:
    # 转换为RGB值(0-255)
    r = int(hex_color[0:2], 16)
    g = int(hex_color[2:4], 16)
    b = int(hex_color[4:6], 16)
    
    # 转换为0-1范围
    return (r / 255.0, g / 255.0, b / 255.0)
except ValueError:
    logger.warning(f"Invalid hex color format: {hex_color}, using white as default")
    return (1.0, 1.0, 1.0)

23.10 扩展性设计

23.10.1 关键词高亮扩展

关键词高亮采用预留接口设计,便于扩展:

# TODO: 处理关键词高亮(这需要更复杂的实现)
if caption.get('keyword'):
    logger.info(f"Keyword highlighting specified but not implemented yet: {caption['keyword']}")

23.10.2 动画效果扩展

动画效果支持多种类型,易于扩展:

# TODO: 处理动画效果(需要导入相应的动画类型)
if caption.get('in_animation'):
    logger.info(f"In animation specified but not implemented yet: {caption['in_animation']}")
if caption.get('out_animation'):
    logger.info(f"Out animation specified but not implemented yet: {caption['out_animation']}")
if caption.get('loop_animation'):
    logger.info(f"Loop animation specified but not implemented yet: {caption['loop_animation']}")

23.10.3 样式参数扩展

字幕参数采用字典结构,便于添加新参数:

processed_item = {
    "start": item["start"],
    "end": item["end"],
    "text": item["text"],
    "keyword": item.get("keyword", None),
    "keyword_color": item.get("keyword_color", "#ff7100"),
    "keyword_font_size": item.get("keyword_font_size", 15),
    "font_size": item.get("font_size", 15),
    # 可以轻松添加新参数
}

23.11 总结

字幕添加服务提供了完整的字幕处理解决方案,具有以下特点:

  1. 功能完整:支持批量字幕添加、单个字幕处理、样式设置和动画效果
  2. 样式丰富:支持文本颜色、透明度、字体大小、对齐方式、字间距、行间距等多种样式参数
  3. 关键词支持:预留了关键词高亮功能接口,支持关键词颜色和字体大小设置
  4. 动画框架:预留了入场动画、出场动画、循环动画的效果接口
  5. 坐标智能:实现了像素坐标到画布坐标的智能转换
  6. 颜色转换:支持十六进制颜色到RGB的智能转换
  7. 错误处理:完善的异常处理和错误恢复机制
  8. 性能优化:批量处理、缓存优化和异步处理
  9. 扩展性强:插件式动画效果设计和灵活的参数结构
  10. 安全可靠:输入验证和异常处理保护

该服务为剪映小助手提供了强大的字幕处理能力,是视频编辑功能的重要组成部分,特别是在制作教学视频、解说视频、字幕翻译等场景中发挥重要作用。

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐