ADD_KEYFRAMES API 接口文档
接口信息
POST /api/drafts/add_keyframes
功能描述
向现有草稿中的指定片段添加关键帧动画。关键帧动画是视频编辑中的高级功能,通过在时间轴上设置不同时间点的属性值,系统会自动计算中间过渡效果,创造出平滑的动画变化。支持位置、缩放、旋转、透明度等多种属性的关键帧动画。
请求参数
{
"draft_url": "https://ts.fyshark.com/#/cozeToJianyin?drafId=...",
"keyframes": "[{\"offset\":0,\"property\":\"KFTypePositionX\",\"segment_id\":\"d62994b4-25fe-422a-a123-87ef05038558\",\"value\":-0.1}]"
}
参数说明
参数名 | 类型 | 必填 | 默认值 | 说明 |
---|---|---|---|---|
draft_url | string | ✅ | - | 目标草稿的完整URL |
keyframes | string | ✅ | - | JSON字符串格式的关键帧信息数组 |
keyframes 数组元素说明
参数名 | 类型 | 必填 | 默认值 | 说明 |
---|---|---|---|---|
segment_id | string | ✅ | - | 目标片段的唯一标识ID |
property | string | ✅ | - | 动画属性类型 |
offset | number | ✅ | - | 关键帧在片段中的时间偏移(0-1范围) |
value | number | ✅ | - | 属性在该时间点的值 |
支持的动画属性类型
属性类型 | 描述 | 值范围 | 示例 |
---|---|---|---|
KFTypePositionX | X轴位置 | -1.0 到 1.0 | 0.0 (居中), -0.5 (左移), 0.5 (右移) |
KFTypePositionY | Y轴位置 | -1.0 到 1.0 | 0.0 (居中), -0.5 (上移), 0.5 (下移) |
KFTypeScaleX | X轴缩放 | 0.1 到 10.0 | 1.0 (原始), 0.5 (缩小), 2.0 (放大) |
KFTypeScaleY | Y轴缩放 | 0.1 到 10.0 | 1.0 (原始), 0.5 (缩小), 2.0 (放大) |
KFTypeRotation | 旋转角度 | -360 到 360 | 0 (无旋转), 90 (顺时针90度) |
KFTypeAlpha | 透明度 | 0.0 到 1.0 | 1.0 (不透明), 0.5 (半透明), 0.0 (透明) |
时间偏移说明
offset
值范围为 0-1,表示在片段时长中的相对位置- 0 表示片段开始时刻
- 1 表示片段结束时刻
- 0.5 表示片段中间时刻
响应格式
成功响应 (200)
{
"status": "success",
"message": "关键帧添加成功",
"data": {
"draft_url": "https://ts.fyshark.com/#/cozeToJianyin?drafId=...",
"keyframes_added": 3,
"affected_segments": ["segment_001", "segment_002"]
}
}
错误响应 (4xx/5xx)
{
"status": "error",
"message": "错误信息",
"error": "详细错误描述"
}
使用示例
cURL 示例
1. 基本位置动画
curl -X POST https://jy-api.fyshark.com/api/drafts/add_keyframes \
-H "Content-Type: application/json" \
-d '{
"draft_url": "YOUR_DRAFT_URL",
"keyframes": "[{\"offset\":0,\"property\":\"KFTypePositionX\",\"segment_id\":\"your-segment-id\",\"value\":-0.5},{\"offset\":1,\"property\":\"KFTypePositionX\",\"segment_id\":\"your-segment-id\",\"value\":0.5}]"
}'
2. 缩放动画
curl -X POST https://jy-api.fyshark.com/api/drafts/add_keyframes \
-H "Content-Type: application/json" \
-d '{
"draft_url": "YOUR_DRAFT_URL",
"keyframes": "[{\"offset\":0,\"property\":\"KFTypeScaleX\",\"segment_id\":\"your-segment-id\",\"value\":0.5},{\"offset\":0.5,\"property\":\"KFTypeScaleX\",\"segment_id\":\"your-segment-id\",\"value\":1.5},{\"offset\":1,\"property\":\"KFTypeScaleX\",\"segment_id\":\"your-segment-id\",\"value\":1.0}]"
}'
3. 透明度渐变
curl -X POST https://jy-api.fyshark.com/api/drafts/add_keyframes \
-H "Content-Type: application/json" \
-d '{
"draft_url": "YOUR_DRAFT_URL",
"keyframes": "[{\"offset\":0,\"property\":\"KFTypeAlpha\",\"segment_id\":\"your-segment-id\",\"value\":0.0},{\"offset\":0.2,\"property\":\"KFTypeAlpha\",\"segment_id\":\"your-segment-id\",\"value\":1.0},{\"offset\":0.8,\"property\":\"KFTypeAlpha\",\"segment_id\":\"your-segment-id\",\"value\":1.0},{\"offset\":1,\"property\":\"KFTypeAlpha\",\"segment_id\":\"your-segment-id\",\"value\":0.0}]"
}'
4. 复合动画(多属性)
curl -X POST https://jy-api.fyshark.com/api/drafts/add_keyframes \
-H "Content-Type: application/json" \
-d '{
"draft_url": "YOUR_DRAFT_URL",
"keyframes": "[{\"offset\":0,\"property\":\"KFTypePositionX\",\"segment_id\":\"your-segment-id\",\"value\":-0.3},{\"offset\":0,\"property\":\"KFTypeScaleX\",\"segment_id\":\"your-segment-id\",\"value\":0.8},{\"offset\":1,\"property\":\"KFTypePositionX\",\"segment_id\":\"your-segment-id\",\"value\":0.3},{\"offset\":1,\"property\":\"KFTypeScaleX\",\"segment_id\":\"your-segment-id\",\"value\":1.2}]"
}'
JavaScript 示例
const addKeyframes = async (keyframesData, draftUrl) => {
const response = await fetch('/api/drafts/add_keyframes', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
keyframes: JSON.stringify(keyframesData),
draft_url: draftUrl
})
});
const result = await response.json();
return result;
};
// 使用示例 - 位置移动动画
const positionKeyframes = [
{
segment_id: "your-segment-id",
property: "KFTypePositionX",
offset: 0,
value: -0.5 // 开始位置:左侧
},
{
segment_id: "your-segment-id",
property: "KFTypePositionX",
offset: 1,
value: 0.5 // 结束位置:右侧
}
];
// 使用示例 - 缩放呼吸效果
const scaleKeyframes = [
{
segment_id: "your-segment-id",
property: "KFTypeScaleX",
offset: 0,
value: 1.0
},
{
segment_id: "your-segment-id",
property: "KFTypeScaleX",
offset: 0.5,
value: 1.2 // 中间放大
},
{
segment_id: "your-segment-id",
property: "KFTypeScaleX",
offset: 1,
value: 1.0 // 回到原始大小
}
];
// 使用示例 - 旋转动画
const rotationKeyframes = [
{
segment_id: "your-segment-id",
property: "KFTypeRotation",
offset: 0,
value: 0 // 起始角度
},
{
segment_id: "your-segment-id",
property: "KFTypeRotation",
offset: 1,
value: 360 // 完整旋转一圈
}
];
try {
const result = await addKeyframes(positionKeyframes, draftUrl);
console.log('关键帧添加成功:', result.data);
} catch (error) {
console.error('添加失败:', error);
}
高级JavaScript示例
class KeyframeAnimator {
constructor(baseUrl = 'https://jy-api.fyshark.com') {
this.baseUrl = baseUrl;
}
async addKeyframes(draftUrl, keyframes) {
const response = await fetch(`${this.baseUrl}/api/drafts/add_keyframes`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
keyframes: JSON.stringify(keyframes),
draft_url: draftUrl
})
});
return response.json();
}
// 创建淡入淡出动画
createFadeAnimation(segmentId, duration = 1.0) {
return [
{
segment_id: segmentId,
property: "KFTypeAlpha",
offset: 0,
value: 0.0
},
{
segment_id: segmentId,
property: "KFTypeAlpha",
offset: 0.1,
value: 1.0
},
{
segment_id: segmentId,
property: "KFTypeAlpha",
offset: 0.9,
value: 1.0
},
{
segment_id: segmentId,
property: "KFTypeAlpha",
offset: 1,
value: 0.0
}
];
}
// 创建滑动进入动画
createSlideInAnimation(segmentId, direction = 'left') {
const startPos = direction === 'left' ? -1.0 : 1.0;
return [
{
segment_id: segmentId,
property: "KFTypePositionX",
offset: 0,
value: startPos
},
{
segment_id: segmentId,
property: "KFTypePositionX",
offset: 0.3,
value: 0.0
}
];
}
// 创建缩放弹跳动画
createBounceAnimation(segmentId) {
return [
{
segment_id: segmentId,
property: "KFTypeScaleX",
offset: 0,
value: 0.3
},
{
segment_id: segmentId,
property: "KFTypeScaleY",
offset: 0,
value: 0.3
},
{
segment_id: segmentId,
property: "KFTypeScaleX",
offset: 0.4,
value: 1.1
},
{
segment_id: segmentId,
property: "KFTypeScaleY",
offset: 0.4,
value: 1.1
},
{
segment_id: segmentId,
property: "KFTypeScaleX",
offset: 0.6,
value: 0.9
},
{
segment_id: segmentId,
property: "KFTypeScaleY",
offset: 0.6,
value: 0.9
},
{
segment_id: segmentId,
property: "KFTypeScaleX",
offset: 1,
value: 1.0
},
{
segment_id: segmentId,
property: "KFTypeScaleY",
offset: 1,
value: 1.0
}
];
}
// 创建组合动画
async createComplexAnimation(draftUrl, segmentId, animationType) {
let keyframes = [];
switch (animationType) {
case 'fadeIn':
keyframes = this.createFadeAnimation(segmentId);
break;
case 'slideLeft':
keyframes = this.createSlideInAnimation(segmentId, 'left');
break;
case 'bounce':
keyframes = this.createBounceAnimation(segmentId);
break;
default:
throw new Error('Unknown animation type');
}
return this.addKeyframes(draftUrl, keyframes);
}
}
// 使用示例
const animator = new KeyframeAnimator();
// 创建淡入动画
await animator.createComplexAnimation(draftUrl, segmentId, 'fadeIn');
// 创建滑动进入动画
await animator.createComplexAnimation(draftUrl, segmentId, 'slideLeft');
// 创建弹跳动画
await animator.createComplexAnimation(draftUrl, segmentId, 'bounce');
Python 示例 (可选)
import requests
import json
class KeyframeManager:
def __init__(self, base_url="https://jy-api.fyshark.com"):
self.base_url = base_url
def add_keyframes(self, draft_url, keyframes):
data = {
"draft_url": draft_url,
"keyframes": json.dumps(keyframes)
}
response = requests.post(
f'{self.base_url}/api/drafts/add_keyframes',
headers={'Content-Type': 'application/json'},
json=data
)
return response.json()
def create_position_animation(self, segment_id, start_x=-0.5, end_x=0.5):
return [
{
"segment_id": segment_id,
"property": "KFTypePositionX",
"offset": 0,
"value": start_x
},
{
"segment_id": segment_id,
"property": "KFTypePositionX",
"offset": 1,
"value": end_x
}
]
# 使用示例
manager = KeyframeManager()
keyframes = manager.create_position_animation("your-segment-id", -0.3, 0.3)
result = manager.add_keyframes("YOUR_DRAFT_URL", keyframes)
print(f"结果: {result}")
错误码说明
错误码 | 错误信息 | 说明 | 解决方案 |
---|---|---|---|
400 | draft_url是必填项 | 缺少草稿URL参数 | 提供有效的草稿URL |
400 | keyframes是必填项 | 缺少关键帧信息参数 | 提供关键帧信息数组 |
400 | keyframes格式错误 | JSON解析失败 | 检查JSON格式是否正确 |
400 | 关键帧信息验证失败 | 关键帧数据不符合要求 | 检查属性类型和数值范围 |
400 | offset值超出范围 | offset不在0-1范围内 | 使用0-1之间的数值 |
400 | 属性值超出范围 | value值不在有效范围内 | 检查各属性的值范围限制 |
404 | 草稿不存在 | 指定的草稿URL无效 | 检查草稿URL是否正确 |
404 | 片段不存在 | 指定的segment_id无效 | 检查片段ID是否正确 |
500 | 关键帧处理失败 | 内部处理错误 | 联系技术支持 |
注意事项
- keyframes参数格式: 必须是JSON字符串格式,不是直接的数组对象
- 时间范围: offset值必须在0-1范围内,表示在片段时长中的相对位置
- 属性值范围: 不同属性类型有不同的值范围限制,超出范围可能导致异常效果
- 片段ID有效性: segment_id必须是草稿中实际存在的片段ID
- 关键帧顺序: 建议按时间顺序(offset从小到大)添加关键帧
- 性能考虑: 过密的关键帧可能影响渲染性能
工作流程
- 验证必填参数(draft_url, keyframes)
- 解析keyframes JSON字符串
- 验证关键帧信息格式和数值范围
- 获取并解密草稿内容
- 遍历关键帧数组,逐个处理:
- 根据segment_id获取目标片段
- 验证片段是否存在
- 调用segment.addKeyframe添加关键帧
- 验证属性类型和数值有效性
- 加密并保存更新后的草稿
- 返回处理结果统计
动画原理
关键帧插值
系统会自动在关键帧之间进行插值计算,创造平滑的过渡效果:
- 线性插值: 默认的插值方式,创造匀速变化
- 时间映射: offset值确定关键帧在时间轴上的位置
- 属性混合: 支持多个属性同时进行动画
动画曲线
虽然当前接口使用线性插值,但系统支持多种动画曲线:
- 线性(Linear)
- 缓入(Ease In)
- 缓出(Ease Out)
- 缓入缓出(Ease In Out)
最佳实践
关键帧设计原则
const designPrinciples = {
timing: {
minOffset: 0.0, // 片段开始
maxOffset: 1.0, // 片段结束
recommended: [0, 0.25, 0.5, 0.75, 1.0] // 推荐的关键时间点
},
values: {
position: { min: -1.0, max: 1.0, center: 0.0 },
scale: { min: 0.1, max: 10.0, normal: 1.0 },
rotation: { min: -360, max: 360, none: 0 },
alpha: { min: 0.0, max: 1.0, opaque: 1.0 }
}
};
常用动画模式
const animationPatterns = {
// 淡入淡出
fadeInOut: [
{ offset: 0, property: "KFTypeAlpha", value: 0.0 },
{ offset: 0.2, property: "KFTypeAlpha", value: 1.0 },
{ offset: 0.8, property: "KFTypeAlpha", value: 1.0 },
{ offset: 1, property: "KFTypeAlpha", value: 0.0 }
],
// 缩放弹跳
scaleBounce: [
{ offset: 0, property: "KFTypeScaleX", value: 0.3 },
{ offset: 0.4, property: "KFTypeScaleX", value: 1.1 },
{ offset: 0.6, property: "KFTypeScaleX", value: 0.9 },
{ offset: 1, property: "KFTypeScaleX", value: 1.0 }
],
// 位置滑动
slideLeftToRight: [
{ offset: 0, property: "KFTypePositionX", value: -0.8 },
{ offset: 1, property: "KFTypePositionX", value: 0.8 }
]
};
性能优化建议
- 关键帧密度: 避免过密的关键帧设置
- 属性选择: 优先使用position和scale,rotation和alpha较消耗性能
- 时间分布: 合理分布关键帧时间点,避免突然变化
- 批量处理: 一次请求处理多个关键帧,减少网络开销
错误排查指南
问题: 片段不存在错误
解决: 先获取草稿内容,确认segment_id有效性问题: 动画效果不明显
解决: 检查属性值差异是否足够大问题: 动画不平滑
解决: 增加中间关键帧,优化时间分布
高级用法
多段动画组合
const createComplexAnimation = (segmentId) => {
return [
// 第一段:淡入 + 缩放
{ offset: 0, property: "KFTypeAlpha", segment_id: segmentId, value: 0.0 },
{ offset: 0, property: "KFTypeScaleX", segment_id: segmentId, value: 0.5 },
{ offset: 0.3, property: "KFTypeAlpha", segment_id: segmentId, value: 1.0 },
{ offset: 0.3, property: "KFTypeScaleX", segment_id: segmentId, value: 1.0 },
// 第二段:位置移动
{ offset: 0.3, property: "KFTypePositionX", segment_id: segmentId, value: -0.5 },
{ offset: 0.7, property: "KFTypePositionX", segment_id: segmentId, value: 0.5 },
// 第三段:旋转 + 淡出
{ offset: 0.7, property: "KFTypeRotation", segment_id: segmentId, value: 0 },
{ offset: 1.0, property: "KFTypeRotation", segment_id: segmentId, value: 360 },
{ offset: 1.0, property: "KFTypeAlpha", segment_id: segmentId, value: 0.0 }
];
};
同步动画
const createSyncAnimation = (segmentIds) => {
const keyframes = [];
segmentIds.forEach((segmentId, index) => {
const delay = index * 0.1; // 错开启动时间
keyframes.push(
{ offset: delay, property: "KFTypeAlpha", segment_id: segmentId, value: 0.0 },
{ offset: delay + 0.2, property: "KFTypeAlpha", segment_id: segmentId, value: 1.0 }
);
});
return keyframes;
};
相关接口
- CREATE_DRAFT - 创建新草稿
- ADD_IMAGES - 批量添加图片(提供segment_id)
- ADD_CAPTIONS - 批量添加字幕(提供segment_id)
- ADD_AUDIOS - 批量添加音频(提供segment_id)
- ADD_EFFECTS - 批量添加特效(提供segment_id)
更新日志
- v1.0.0 (2025-08-01): 初始版本发布
- 支持基础关键帧动画功能
- 实现6种主要属性类型支持
- 完善的参数验证机制
- 支持批量关键帧处理
- 提供丰富的动画示例
📖 文档版本: v1.0.0
🔄 最后更新: 2025-08-01
👤 维护者: Developer