ADD_KEYFRAMES API 接口文档

📄 API_ADD_KEYFRAMES.md 🕒 8/10/2025, 6:48:24 PM 📏 15KB

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 (透明)

时间偏移说明

响应格式

成功响应 (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 关键帧处理失败 内部处理错误 联系技术支持

注意事项

  1. keyframes参数格式: 必须是JSON字符串格式,不是直接的数组对象
  2. 时间范围: offset值必须在0-1范围内,表示在片段时长中的相对位置
  3. 属性值范围: 不同属性类型有不同的值范围限制,超出范围可能导致异常效果
  4. 片段ID有效性: segment_id必须是草稿中实际存在的片段ID
  5. 关键帧顺序: 建议按时间顺序(offset从小到大)添加关键帧
  6. 性能考虑: 过密的关键帧可能影响渲染性能

工作流程

  1. 验证必填参数(draft_url, keyframes)
  2. 解析keyframes JSON字符串
  3. 验证关键帧信息格式和数值范围
  4. 获取并解密草稿内容
  5. 遍历关键帧数组,逐个处理:
    • 根据segment_id获取目标片段
    • 验证片段是否存在
    • 调用segment.addKeyframe添加关键帧
    • 验证属性类型和数值有效性
  6. 加密并保存更新后的草稿
  7. 返回处理结果统计

动画原理

关键帧插值

系统会自动在关键帧之间进行插值计算,创造平滑的过渡效果:

动画曲线

虽然当前接口使用线性插值,但系统支持多种动画曲线:

最佳实践

关键帧设计原则

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 }
  ]
};

性能优化建议

  1. 关键帧密度: 避免过密的关键帧设置
  2. 属性选择: 优先使用position和scale,rotation和alpha较消耗性能
  3. 时间分布: 合理分布关键帧时间点,避免突然变化
  4. 批量处理: 一次请求处理多个关键帧,减少网络开销

错误排查指南

  1. 问题: 片段不存在错误
    解决: 先获取草稿内容,确认segment_id有效性

  2. 问题: 动画效果不明显
    解决: 检查属性值差异是否足够大

  3. 问题: 动画不平滑
    解决: 增加中间关键帧,优化时间分布

高级用法

多段动画组合

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;
};

相关接口

更新日志


📖 文档版本: v1.0.0
🔄 最后更新: 2025-08-01
👤 维护者: Developer