JSON 结构与字段规范

1. 顶层结构

完整 JSON 顶层:

{
  "meta": {},
  "assets": {},
  "tracks": []
}

说明:

  • meta 必填
  • tracks 必填,且至少包含 1 个 track
  • assets 可选

2. meta

interface Meta {
  version: string;
  title?: string;
  description?: string;
  author?: string;
  createdAt?: string;
  tags?: string[];
  width: number;
  height: number;
  fps: number;
  background?: string | "transparent" | Gradient;
}

字段说明:

version

  • 类型:string
  • 必填:是
  • 用途:标识 schema 版本
  • 建议值:"2.0.0"

title

  • 类型:string
  • 必填:否
  • 用途:视频标题元数据

description

  • 类型:string
  • 必填:否
  • 用途:视频描述元数据

author

  • 类型:string
  • 必填:否
  • 用途:作者元数据

createdAt

  • 类型:string
  • 必填:否
  • 用途:创建时间元数据

tags

  • 类型:string[]
  • 必填:否
  • 用途:标签元数据

width

  • 类型:number
  • 必填:是
  • 限制:必须是正整数
  • 用途:画布宽度

height

  • 类型:number
  • 必填:是
  • 限制:必须是正整数
  • 用途:画布高度

fps

  • 类型:number
  • 必填:是
  • 限制:必须是正整数
  • 用途:播放帧率

background

  • 类型:string | "transparent" | Gradient
  • 必填:否
  • 默认值:"#000000"
  • 用途:全局背景

渐变对象:

interface Gradient {
  type: "linear" | "radial" | "conic";
  angle?: number;
  stops: Array<{
    offset: number;
    color: string;
  }>;
}

3. assets

interface Assets {
  fonts?: FontAsset[];
  images?: ImageAsset[];
  videos?: VideoAsset[];
  audios?: AudioAsset[];
  subtitles?: SubtitleAsset[];
}

fonts

interface FontAsset {
  id: string;
  src: string;
  family: string;
}

当前状态:

  • 可以出现在 JSON 里
  • 当前运行时不会自动下载或注册字体
  • 不要把它当成完整字体加载系统

images

interface ImageAsset {
  id: string;
  src: string;
}

videos

interface VideoAsset {
  id: string;
  src: string;
}

audios

interface AudioAsset {
  id: string;
  src: string;
}

subtitles

interface SubtitleAsset {
  id: string;
  words: SubtitleWord[];
}

$ref 用法

支持 $ref 的资源池:

  • images
  • videos
  • audios
  • subtitles

格式:

{
  "src": { "$ref": "video-hero" }
}

或:

{
  "words": { "$ref": "subtitle-main" }
}

4. tracks

interface Track {
  id?: string;
  clips: Clip[];
}

字段说明:

id

  • 类型:string
  • 必填:否
  • 用途:track 标识

clips

  • 类型:Clip[]
  • 必填:是
  • 限制:至少 1 个

5. Clip 总体结构

interface BaseClip {
  id?: string;
  type: ClipType;
  start: number;
  duration: number;
  transform?: Transform;
  zIndex?: number;
  opacity?: number;
  style?: Style;
  animations?: Animation[];
  keyframes?: Keyframe[];
  transition?: Transition;
}

所有 clip 共有的核心字段:

id

  • 类型:string
  • 必填:否
  • 用途:标识 clip

type

  • 类型:ClipType
  • 必填:是

当前支持:

  • video
  • image
  • text
  • rect
  • circle
  • polygon
  • audio
  • subtitle
  • layout
  • template

start

  • 类型:number
  • 必填:是
  • 单位:秒
  • 用途:开始时间

duration

  • 类型:number
  • 必填:是
  • 单位:秒
  • 用途:持续时间

transform

  • 类型:Transform
  • 必填:否

zIndex

  • 类型:number
  • 必填:否
  • 默认值:0

opacity

  • 类型:number
  • 必填:否
  • 默认值:1
  • 用途:所有可见元素的透明度快捷字段
  • 兼容规则:style.opacity 优先级高于 opacity

style

  • 类型:Style
  • 必填:否

animations

  • 类型:Animation[]
  • 必填:否

keyframes

  • 类型:Keyframe[]
  • 必填:否

transition

  • 类型:Transition
  • 必填:否

6. 坐标与百分比规则

百分比并不是 DOM 那种左上角原点,而是基于 Revideo 中心坐标系。

换算规则:

  • x: "50%" 表示水平居中
  • y: "50%" 表示垂直居中
  • x: "0%" 表示最左侧
  • x: "100%" 表示最右侧
  • y: "0%" 表示最上侧
  • y: "100%" 表示最下侧

7. 顶层最小可用 JSON

{
  "meta": {
    "version": "2.0.0",
    "width": 1920,
    "height": 1080,
    "fps": 30,
    "background": "#000000"
  },
  "tracks": [
    {
      "clips": [
        {
          "type": "text",
          "start": 0,
          "duration": 3,
          "text": "Hello, world",
          "transform": {
            "x": "50%",
            "y": "50%"
          },
          "style": {
            "fontSize": 72,
            "fill": "#ffffff",
            "textAlign": "center"
          }
        }
      ]
    }
  ]
}

8. 已知验证边界

POST /api/json 对顶层结构的校验是严格的,但对每个 clip 的字段不是深度 zod 校验。

这意味着:

  • 顶层缺 metatracks 会报错
  • 但 clip 内部写错字段名,不一定会在 API 校验阶段报错
  • 某些错误会在播放或渲染阶段才体现出来

所以写 JSON 时仍然应该以本文档和运行时实现为准。