第二十章:视频添加服务
概述
视频添加服务是剪映小助手的核心功能模块,负责将外部视频文件批量添加到剪映草稿中。该服务通过统一的接口封装了复杂的视频处理流程,包括文件下载、格式验证、轨道创建、片段添加等关键步骤,为上层业务提供了简洁可靠的视频添加能力。
核心功能
1. 批量视频添加
视频添加服务支持批量添加多个视频文件到剪映草稿中,每个视频都可以独立配置各种参数。
核心实现
python
def add_videos(
draft_url: str,
video_infos: str,
alpha: float = 1.0,
scale_x: float = 1.0,
scale_y: float = 1.0,
transform_x: int = 0,
transform_y: int = 0
) -> Tuple[str, str, List[str], List[str]]:
"""
添加视频到剪映草稿的业务逻辑
Args:
draft_url: 草稿URL
video_infos: 视频信息列表的JSON字符串
alpha: 全局透明度[0, 1],默认值为1.0
scale_x: X轴缩放比例,默认值为1.0
scale_y: Y轴缩放比例,默认值为1.0
transform_x: X轴位置偏移(像素),默认值为0
transform_y: Y轴位置偏移(像素),默认值为0
Returns:
draft_url: 草稿URL
track_id: 轨道ID
video_ids: 视频ID列表
segment_ids: 片段ID列表
Raises:
CustomException: 视频批量添加失败
"""
logger.info(f"add_videos, draft_url: {draft_url}, video_infos: {video_infos}, alpha: {alpha}, scale_x: {scale_x}, scale_y: {scale_y}, transform_x: {transform_x}, transform_y: {transform_y}")
# 1. 提取草稿ID
draft_id = helper.get_url_param(draft_url, "draft_id")
if (not draft_id) or (draft_id not in DRAFT_CACHE):
raise CustomException(CustomError.INVALID_DRAFT_URL)
# 2. 创建保存视频资源的目录
draft_dir = os.path.join(config.DRAFT_DIR, draft_id)
draft_video_dir = os.path.join(draft_dir, "assets", "videos")
os.makedirs(name=draft_video_dir, exist_ok=True)
# 3. 解析视频信息
videos = parse_video_data(json_str=video_infos)
if len(videos) == 0:
logger.info(f"No video info, draft_id: {draft_id}")
raise CustomException(CustomError.INVALID_VIDEO_INFO)
# 4. 从缓存中获取草稿
script: ScriptFile = DRAFT_CACHE[draft_id]
# 5. 添加视频轨道
track_name = f"video_track_{helper.gen_unique_id()}"
script.add_track(track_type=draft.TrackType.video, track_name=track_name)
# 6. 遍历视频信息,添加视频到草稿中的指定轨道,收集片段ID
segment_ids = []
for video in videos:
segment_id = add_video_to_draft(script, track_name, draft_video_dir=draft_video_dir, video=video)
segment_ids.append(segment_id)
logger.info(f"segment_ids: {segment_ids}")
# 7. 保存草稿
script.save()
# 获取当前视频轨道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"draft_id: {draft_id}, track_id: {track_id}")
# 获取当前所有视频资源ID(全局唯一ID)
video_ids = [video.material_id for video in script.materials.videos if video.material_type == "video"]
logger.info(f"draft_id: {draft_id}, video_ids: {video_ids}")
return draft_url, track_id, video_ids, segment_ids
处理流程
- 参数验证:验证草稿URL和缓存状态
- 目录创建:创建视频资源存储目录
- 数据解析:解析和验证视频信息JSON
- 轨道创建:添加新的视频轨道
- 批量添加:遍历添加每个视频到轨道
- 草稿保存:持久化草稿更改
- 信息返回:返回轨道ID、视频ID和片段ID列表
2. 单个视频添加
add_video_to_draft函数负责将单个视频添加到指定的轨道中。
核心实现
python
def add_video_to_draft(
script: ScriptFile,
track_name: str,
draft_video_dir: str,
video: dict,
alpha: float = 1.0,
scale_x: float = 1.0,
scale_y: float = 1.0,
transform_x: int = 0,
transform_y: int = 0
) -> str:
"""
向剪映草稿中添加视频
Args:
script: 草稿脚本对象
track_name: 轨道名称
draft_video_dir: 视频资源目录
video: 视频信息字典
alpha: 视频透明度
scale_x: 横向缩放
scale_y: 纵向缩放
transform_x: X轴位置偏移(像素)
transform_y: Y轴位置偏移(像素)
Returns:
material_id: 素材ID
Raises:
CustomException: 视频添加失败
"""
try:
# 0. 下载视频
video_path = helper.download(url=video['video_url'], save_dir=draft_video_dir)
# 1. 创建视频素材并添加到草稿
duration = video['end'] - video['start']
video_segment = draft.VideoSegment(
material=video_path,
target_timerange=trange(start=video['start'], duration=duration),
volume=video['volume']
)
logger.info(f"material_id: {video_segment.material_instance.material_id}")
logger.info(f"video_path: {video_path}, start: {video['start']}, duration: {duration}, volume: {video['volume']}")
# 2. 向指定轨道添加片段
script.add_segment(video_segment, track_name)
return video_segment.material_instance.material_id
except CustomException:
logger.info(f"Add video to draft failed, draft_video_dir: {draft_video_dir}, video: {video}")
raise
except Exception as e:
logger.error(f"Add video to draft failed, error: {str(e)}")
raise CustomException(err=CustomError.VIDEO_ADD_FAILED)
关键步骤
- 文件下载:从URL下载视频文件到本地目录
- 素材创建:创建VideoSegment对象,设置时间范围和音量
- 片段添加:将视频片段添加到指定轨道
- ID返回:返回素材的唯一标识符
3. 视频数据解析
parse_video_data函数负责解析和验证客户端提交的视频信息JSON字符串。
核心实现
python
def parse_video_data(json_str: str) -> List[Dict[str, Any]]:
"""
解析视频数据的JSON字符串,处理可选字段的默认值
Args:
json_str: 包含视频数据的JSON字符串,格式如下:
[
{
"video_url": "https://example.com/video1.mp4", // [必选] 视频文件的URL地址
"width": 1920, // [必选] 视频宽度
"height": 1080, // [必选] 视频高度
"start": 0.0, // [必选] 视频在时间轴上的开始时间
"end": 12000000.0, // [必选] 视频在时间轴上的结束时间
"duration": 12000000.0, // [必选] 视频总时长(微秒)
"mask": "", // 遮罩类型[可选],默认值为None
"transition": "", // 转场效果名称[可选],默认值为None
"transition_duration": 500000.0, // 转场持续时间(微秒)[可选],默认值为500000
"volume": 1.0, // 音量大小[0, 1][可选],默认值为1.0
}
]
Returns:
包含视频对象的数组,每个对象都处理了默认值
Raises:
json.JSONDecodeError: 当JSON格式错误时抛出
KeyError: 当缺少必选字段时抛出
CustomException: 自定义异常
"""
try:
# 解析JSON字符串
data = json.loads(json_str)
except json.JSONDecodeError as e:
raise CustomException(CustomError.INVALID_VIDEO_INFO, f"JSON parse error: {e.msg}")
# 确保输入是列表
if not isinstance(data, list):
raise CustomException(CustomError.INVALID_VIDEO_INFO, "video_infos should be a list")
result = []
for i, item in enumerate(data):
if not isinstance(item, dict):
raise CustomException(CustomError.INVALID_VIDEO_INFO, f"the {i}th item should be a dict")
# 检查必选字段
required_fields = ["video_url", "width", "height", "start", "end", "duration"]
missing_fields = [field for field in required_fields if field not in item]
if missing_fields:
raise CustomException(CustomError.INVALID_VIDEO_INFO, f"the {i}th item is missing required fields: {', '.join(missing_fields)}")
# 创建处理后的对象,设置默认值
processed_item = {
"video_url": item["video_url"],
"width": item["width"],
"height": item["height"],
"start": item["start"],
"end": item["end"],
"duration": item["duration"],
"mask": item.get("mask", None), # 默认值 None
"transition": item.get("transition", None), # 默认值 None
"transition_duration": item.get("transition_duration", 500000), # 默认值 500000
"volume": item.get("volume", 1.0) # 默认值 1.0
}
# 验证数值范围
if processed_item["volume"] < 0 or processed_item["volume"] > 1:
# 音量值必须在[0, 1]范围内,给默认值
processed_item["volume"] = 1.0
if processed_item["transition_duration"] < 0:
# 转场持续时间必须为非负数,给默认值
processed_item["transition_duration"] = 500000
result.append(processed_item)
return result
数据验证
- JSON格式验证:确保输入是有效的JSON字符串
- 数据结构验证:验证输入必须是列表类型
- 字段完整性检查:检查所有必选字段是否存在
- 数据类型验证:确保每个视频项都是字典类型
- 数值范围验证:验证音量和转场持续时间的有效范围
- 默认值处理:为可选字段提供合理的默认值
视频参数配置
基础参数
| 参数名 | 类型 | 必选 | 默认值 | 说明 |
|---|---|---|---|---|
| video_url | string | 是 | - | 视频文件的URL地址 |
| width | int | 是 | - | 视频宽度(像素) |
| height | int | 是 | - | 视频高度(像素) |
| start | float | 是 | - | 视频在时间轴上的开始时间(微秒) |
| end | float | 是 | - | 视频在时间轴上的结束时间(微秒) |
| duration | float | 是 | - | 视频总时长(微秒) |
高级参数
| 参数名 | 类型 | 必选 | 默认值 | 说明 |
|---|---|---|---|---|
| mask | string | 否 | None | 遮罩类型 |
| transition | string | 否 | None | 转场效果名称 |
| transition_duration | float | 否 | 500000 | 转场持续时间(微秒) |
| volume | float | 否 | 1.0 | 音量大小[0, 1] |
变换参数
| 参数名 | 类型 | 必选 | 默认值 | 说明 |
|---|---|---|---|---|
| alpha | float | 否 | 1.0 | 全局透明度[0, 1] |
| scale_x | float | 否 | 1.0 | X轴缩放比例 |
| scale_y | float | 否 | 1.0 | Y轴缩放比例 |
| transform_x | int | 否 | 0 | X轴位置偏移(像素) |
| transform_y | int | 否 | 0 | Y轴位置偏移(像素) |
缓存集成
视频添加服务深度集成了草稿缓存机制,通过DRAFT_CACHE实现高效的草稿数据管理。
缓存使用
python
# 验证草稿缓存状态
if (not draft_id) or (draft_id not in DRAFT_CACHE):
raise CustomException(CustomError.INVALID_DRAFT_URL)
# 从缓存获取草稿对象
script: ScriptFile = DRAFT_CACHE[draft_id]
# 操作完成后自动更新缓存(无需手动更新)
script.save()
缓存优势
- 性能提升:避免重复的文件系统操作
- 数据一致性:确保草稿数据的实时同步
- 内存管理:通过LRU策略防止内存溢出
错误处理
视频添加服务采用统一的异常处理机制,通过CustomException和CustomError定义标准化的错误类型。
异常类型
| 异常类型 | 错误码 | 说明 |
|---|---|---|
| INVALID_DRAFT_URL | 1001 | 无效的草稿URL |
| INVALID_VIDEO_INFO | 1002 | 无效的视频信息 |
| VIDEO_ADD_FAILED | 1003 | 视频添加失败 |
| DOWNLOAD_FAILED | 1004 | 文件下载失败 |
异常处理策略
python
try:
# 核心业务逻辑
video_path = helper.download(url=video['video_url'], save_dir=draft_video_dir)
except CustomException:
# 自定义异常直接抛出
logger.info(f"Add video to draft failed, draft_video_dir: {draft_video_dir}, video: {video}")
raise
except Exception as e:
# 未知异常转换为业务异常
logger.error(f"Add video to draft failed, error: {str(e)}")
raise CustomException(err=CustomError.VIDEO_ADD_FAILED)
日志记录
视频添加服务通过结构化的日志记录机制,详细记录每个关键步骤的执行情况。
日志级别
- INFO:记录关键业务操作信息
- ERROR:记录异常和错误信息
- DEBUG:记录详细的调试信息(开发模式)
日志内容
python
# 请求参数日志
logger.info(f"add_videos, draft_url: {draft_url}, video_infos: {video_infos}, alpha: {alpha}")
# 关键步骤日志
logger.info(f"segment_ids: {segment_ids}")
logger.info(f"draft_id: {draft_id}, track_id: {track_id}")
logger.info(f"draft_id: {draft_id}, video_ids: {video_ids}")
# 错误日志
logger.info(f"No video info, draft_id: {draft_id}")
logger.error(f"Add video to draft failed, error: {str(e)}")
性能优化
批量处理优化
- 批量验证:一次性验证所有视频数据的有效性
- 批量添加:顺序添加视频片段,确保时间轴正确性
- 单次保存:所有操作完成后统一保存草稿
文件管理优化
- 目录复用:重复使用已存在的资源目录
- 文件名优化:使用唯一标识符避免文件名冲突
- 下载优化:支持断点续传和并发下载(底层实现)
内存管理优化
- 流式处理:避免一次性加载大文件到内存
- 缓存控制:通过LRU策略控制缓存大小
- 资源清理:及时释放不再使用的资源
安全性考虑
输入验证
- URL验证:验证视频URL的合法性和可访问性
- 参数验证:严格验证所有输入参数的类型和范围
- 路径检查:防止路径穿越和目录遍历攻击
数据保护
- 异常脱敏:避免在异常信息中暴露敏感数据
- 日志脱敏:在日志中脱敏处理URL和文件路径
- 访问控制:通过权限控制防止未授权访问
扩展性设计
参数扩展
视频参数设计支持灵活的扩展,可以轻松添加新的视频属性:
python
# 向后兼容的参数设计
processed_item = {
# 基础参数(必须)
"video_url": item["video_url"],
"width": item["width"],
"height": item["height"],
# 可选参数(有默认值)
"mask": item.get("mask", None),
"transition": item.get("transition", None),
# 新参数(向后兼容)
"new_feature": item.get("new_feature", default_value),
}
功能扩展
- 特效支持:支持添加视频特效和滤镜
- 转场支持:支持视频间的转场效果
- 动画支持:支持视频动画和关键帧
最佳实践
使用示例
python
# 准备视频信息
video_infos = [
{
"video_url": "https://example.com/video1.mp4",
"width": 1920,
"height": 1080,
"start": 0.0,
"end": 12000000.0,
"duration": 12000000.0,
"volume": 1.0
},
{
"video_url": "https://example.com/video2.mp4",
"width": 1920,
"height": 1080,
"start": 12000000.0,
"end": 24000000.0,
"duration": 12000000.0,
"transition": "fade",
"transition_duration": 1000000.0
}
]
# 调用视频添加服务
draft_url, track_id, video_ids, segment_ids = add_videos(
draft_url="http://localhost:8080/draft?draft_id=123",
video_infos=json.dumps(video_infos),
alpha=1.0,
scale_x=1.0,
scale_y=1.0
)
注意事项
- 时间轴规划:确保视频的时间范围不重叠或按预期重叠
- 格式兼容性:使用剪映支持的视频格式(MP4、MOV、AVI等)
- 性能考虑:批量添加大量视频时考虑分批处理
- 错误处理:妥善处理网络异常和文件下载失败
- 缓存管理:合理使用缓存,避免内存溢出
总结
视频添加服务通过统一的接口封装了复杂的视频处理流程,提供了批量添加、参数验证、错误处理、日志记录等核心功能。服务深度集成了缓存机制和异常处理,确保了数据的一致性、可靠性和可追溯性。通过合理的性能优化、安全性考虑和扩展性设计,视频添加服务为剪映小助手提供了稳定可靠的视频添加能力。
相关资源
- GitHub代码仓库: https://github.com/Hommy-master/capcut-mate
- Gitee代码仓库: https://gitee.com/taohongmin-gitee/capcut-mate
- API文档地址: https://docs.jcaigc.cn