AirSpark/skills/automation/json-schemas.md
seaislee1209 acbd2e30ad Initial commit: Air Spark project
- frontend/: Next.js 16 app (App Router, React 19, Tailwind v4)
- skills/: project skills (seedance, automation, trae-agents, etc.)
- Docs: PRD, UI-Design-System, DEV-LOG, seedance integration notes
- skills-lock.json: skills version lock

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 16:08:49 +08:00

9.4 KiB
Raw Blame History

Air Spark — JSON Schemas后端数据契约

后端与 AI 技能层之间的数据契约。 Stage 2 输出 → Stage 3/4/5 输入。 Stage 5 输出 → Stage 6/7 输入。


文件列表

文件名 由谁生成 被谁使用
characters.json Stage 2 (storyboard-automation) Stage 3, Stage 5, Stage 6
scenes.json Stage 2 (storyboard-automation) Stage 3, Stage 5, Stage 6
keyshots.json Stage 2 (storyboard-automation) Stage 4, Stage 5, Stage 6
segments.json Stage 5 (segmentation-automation) Stage 6, Stage 7

characters.json

{
  "characters": [
    {
      "id": "char_001",
      "name": "T仔",
      "name_en": "T-Zai",
      "species": "T-Rex",
      "prompt_en": "string (Banana Pro English prompt, narrative paragraph)",
      "distinctive_features": ["string", "string", "string"],
      "asset_filename": "character_char_001.jpg",
      "asset_status": "pending"
    }
  ]
}

字段说明

  • id:全局唯一,格式 char_XXX,三位数
  • asset_filename预定义命名Stage 3 生成后按此名存入资产库
  • asset_statuspendingcompletedfailedStage 3 更新)

scenes.json

{
  "scenes": [
    {
      "id": "scene_001",
      "name": "T仔的单身公寓",
      "environment": "indoor",
      "time_of_day": "morning",
      "estimated_duration_sec": 30,
      "characters_present": ["char_001", "char_002"],
      "prompt_en": "string (Banana Pro English prompt, narrative paragraph, No characters in the scene.)",
      "asset_filename": "scene_scene_001.jpg",
      "asset_status": "pending"
    }
  ]
}

字段说明

  • environmentindooroutdoor
  • time_of_daymorning / day / evening / night
  • characters_present:该场景中出现的角色 id 列表
  • asset_statuspendingcompletedfailedStage 3 更新)

keyshots.json

{
  "video_ratio": "16:9",
  "keyshots": [
    {
      "scene_id": "scene_001",
      "scene_name": "T仔的单身公寓",
      "keyshot_index": 1,
      "grid_type": "4",
      "grid_rows": 2,
      "grid_cols": 2,
      "total_cells": 4,
      "gen_width": 2560,
      "gen_height": 1440,
      "cell_width": 1280,
      "cell_height": 720,
      "scene_duration_sec": 30,
      "coverage_start_sec": 0,
      "coverage_end_sec": 30,
      "cell_duration_sec": 7.5,
      "prompt_en": "string (full Banana Pro prompt for the grid image)",
      "grid_asset_filename": "keyshot_scene_001_1_grid.jpg",
      "asset_status": "pending",
      "cells": [
        {
          "num": 1,
          "row": 1,
          "col": 1,
          "timecode_start": "0:00",
          "timecode_end": "0:08",
          "spatial_description_en": "string",
          "asset_filename": "keyshot_scene_001_1_01.jpg",
          "asset_status": "pending"
        }
      ]
    }
  ]
}

字段说明

  • video_ratio:项目级配置,16:9 / 9:16 / 21:9
  • keyshot_index同场景有两个9宫格时区分1或2其余场景固定为1
  • grid_type"4""9"
  • gen_width / gen_height:调用 Banana Pro 时的目标尺寸
  • cell_width / cell_heightPIL 裁切后每格尺寸固定1280×720
  • coverage_start_sec / coverage_end_sec:该宫格在场景内覆盖的秒数范围
  • cell_duration_sec(coverage_end_sec - coverage_start_sec) / total_cells
  • grid_asset_filename:宫格整图文件名(裁切前)
  • grid_asset_filename 命名:keyshot_{scene_id}_{keyshot_index}_grid.jpg
  • 每格 asset_filenamekeyshot_{scene_id}_{keyshot_index}_{cell_num 两位数}.jpg
  • asset_status 更新Stage 4 生成整图后 → completedPIL 裁切每格后 → 各格 completed

segments.json

{
  "episode_id": "ep01",
  "total_segments": 12,
  "total_duration_sec": 160,
  "segments": [
    {
      "id": "seg_001",
      "index": 1,
      "total": 12,
      "timecode_start": "0:00",
      "timecode_end": "0:15",
      "duration_sec": 15,
      "scene_id": "scene_001",
      "scene_number": "1-1",
      "scene_name": "T仔的单身公寓",
      "environment": "indoor",
      "time_of_day": "day",
      "character_ids": ["char_001"],
      "script_text": "string (原始剧本内容,\\n 换行,一字不改)",
      "reference_images": [
        {"type": "character", "id": "char_001"},
        {"type": "scene", "id": "scene_001"},
        {"type": "prop", "id": "prop_001", "note": "闹钟"},
        {"type": "keyshot", "scene_id": "scene_001", "keyshot_index": 1, "cell_num": 1}
      ],
      "is_action_scene": false,
      "seedance_status": "pending",
      "seedance_job_id": null,
      "seedance_video_url": null,
      "seedance_local_path": null,
      "retry_count": 0
    }
  ],
  "visual_warnings": [
    {
      "segment_id": "seg_003",
      "type": "missing_initial_state",
      "message": "开头缺少角色初始状态"
    }
  ]
}

reference_images 类型说明

type 必填字段 后端处理方式
character id 查 asset_library → character_{id}.jpg
scene id 查 asset_library → scene_{id}.jpg
prop id, note 查 asset_library → prop_{id}.jpg
keyshot scene_id, keyshot_index, cell_num 查 asset_library → keyshot_{scene_id}_{keyshot_index}_{cell_num:02d}.jpg

不使用 prev_framekeyshot cell 图承担空间位置锚点职责所有片段无顺序依赖Stage 6 全并发提交。

Seedance 状态字段

  • seedance_statuspending / running / completed / failed
  • seedance_job_id:提交 Seedance API 后返回的任务 ID
  • seedance_video_urlSeedance 返回的下载 URL
  • seedance_local_path:下载到本地后的路径
  • retry_count:当前重试次数(最多 3 次)

Asset Library 命名规范

所有资产文件统一存放在 projects/{project_id}/episodes/{episode_id}/assets/

资产类型 文件名格式 示例
角色人设图 character_{id}.jpg character_char_001.jpg
场景图 scene_{id}.jpg scene_scene_001.jpg
道具图 prop_{id}.jpg prop_prop_001.jpg
Keyshot 宫格整图 keyshot_{scene_id}_{keyshot_index}_grid.jpg keyshot_scene_001_1_grid.jpg
Keyshot 裁切格 keyshot_{scene_id}_{keyshot_index}_{cell_num:02d}.jpg keyshot_scene_001_1_01.jpg
片段视频 segment_{seg_id}.mp4 segment_seg_001.mp4
成片 final_{episode_id}.mp4 final_ep01.mp4

PIL 裁切逻辑(参考)

from PIL import Image

def crop_keyshot_cells(grid_image_path, keyshot: dict, output_dir: str):
    """
    精确裁切宫格图为独立格子图。
    keyshot: keyshots.json 中的一个 keyshot 对象
    """
    img = Image.open(grid_image_path)
    rows = keyshot["grid_rows"]
    cols = keyshot["grid_cols"]
    cell_w = keyshot["cell_width"]   # 1280
    cell_h = keyshot["cell_height"]  # 720

    for cell in keyshot["cells"]:
        row = cell["row"] - 1  # 转为0-indexed
        col = cell["col"] - 1
        left = col * cell_w
        top = row * cell_h
        right = left + cell_w
        bottom = top + cell_h
        cropped = img.crop((left, top, right, bottom))
        cropped.save(f"{output_dir}/{cell['asset_filename']}")

Seedance API 调用参考Stage 6

# 模型 IDdoubao-seedance-1-5-pro-251215过渡期/ doubao-seedance-2-0正式
# 端点https://ark.cn-beijing.volces.com/api/v3/contents/generations/tasks

# 参考图拼装顺序(固定,决定 [图N] 编号):
#   1. 角色人设图(按 character_ids 顺序每角色1张
#   2. 场景图1张
#   3. keyshot cell 图1张
#   4. 道具图如有0-2张

# 提示词文本结构(后端自动拼装):
#   {script_text}(剧本原文,一字不改)
#
#   {render_style}[图1]是{char1_name}[图2]是{scene_name}[图3]是{keyshot描述}
#   你是一位专业的动画导演,自行安排分镜设计,切镜充满电影感,画面氛围也有电影感,
#   不要有背景音乐,但要有音效。
#   (动作戏追加:动作戏可以有一点荷兰式倾斜镜头,动作戏的镜头具有视觉张力和空间感。)

# 请求体结构content 数组text 在前,图片按顺序追加):
# {
#   "model": "doubao-seedance-1-5-pro-251215",
#   "content": [
#     {"type": "text", "text": "<上述拼装的完整提示词>"},
#     {"type": "image_url", "image_url": {"url": "<图1 URL>"}, "role": "reference_image"},
#     {"type": "image_url", "image_url": {"url": "<图2 URL>"}, "role": "reference_image"},
#     ...
#   ],
#   "generate_audio": true,
#   "duration": <segment.duration_sec>,
#   "ratio": "<project.video_ratio>",
#   "watermark": false
# }

# 异步轮询POST 创建 → GET 轮询每10秒→ status=="succeeded" → 下载 video_url
# 错误重试:最多 3 次,指数退避 1s/4s/16s
# 429 限速:按响应头 Retry-After 等待

FFmpeg 拼接逻辑Stage 7

# 根据 segments.json 按 index 顺序生成 concat.txt
# 格式:
# file 'projects/xxx/episodes/ep01/assets/segment_seg_001.mp4'
# file 'projects/xxx/episodes/ep01/assets/segment_seg_002.mp4'
# ...

ffmpeg -f concat -safe 0 -i concat.txt -c copy final_ep01.mp4
# 无损拼接,保留 Seedance 原生音画同步
# 3-5 秒完成 30-40 片段