# JSON Structure and Field Rules

## 1. Top-level structure

The full top-level JSON shape is:

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

Rules:
- `meta` is required
- `tracks` is required and must contain at least one track
- `assets` is optional

Not supported:
- Legacy top-level `video`
- Legacy top-level `audio`

## 2. `meta`

```ts
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`
- Type: `string`
- Required: yes
- Purpose: schema version marker
- Recommended value: `"2.0.0"`

### `title`
- Type: `string`
- Required: no
- Purpose: video title metadata

### `description`
- Type: `string`
- Required: no
- Purpose: video description metadata

### `author`
- Type: `string`
- Required: no
- Purpose: author metadata

### `createdAt`
- Type: `string`
- Required: no
- Purpose: creation-time metadata

### `tags`
- Type: `string[]`
- Required: no
- Purpose: tagging metadata

### `width`
- Type: `number`
- Required: yes
- Constraint: positive integer
- Purpose: canvas width

### `height`
- Type: `number`
- Required: yes
- Constraint: positive integer
- Purpose: canvas height

### `fps`
- Type: `number`
- Required: yes
- Constraint: positive integer
- Purpose: frame rate

### `background`
- Type: `string | "transparent" | Gradient`
- Required: no
- Default: `"#000000"`
- Purpose: global background

Gradient shape:

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

## 3. `assets`

```ts
interface Assets {
  fonts?: FontAsset[];
  images?: ImageAsset[];
  videos?: VideoAsset[];
  audios?: AudioAsset[];
  subtitles?: SubtitleAsset[];
  models?: ModelAsset[];
  svgs?: SvgAsset[];
}
```

### `fonts`

```ts
interface FontAsset {
  id: string;
  src: string;
  family: string;
  weight?: number | string;
  style?: "normal" | "italic";
}
```

The runtime registers fonts through `FontFace`. Use the same `fontFamily` value in text or subtitle styles.

### `images`

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

### `videos`

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

### `audios`

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

### `subtitles`

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

### `models`

```ts
interface ModelAsset {
  id: string;
  src: string;
}
```

Use `models` for GLTF/GLB assets referenced by `three` objects.

### `svgs`

```ts
interface SvgAsset {
  id: string;
  svg?: string;
  src?: string;
}
```

`svg` stores inline SVG markup. `src` points to a remote SVG file.

### `$ref` usage

Resource pools that support `$ref`:
- `images`
- `videos`
- `audios`
- `subtitles`
- `models`
- `svgs`

Example:

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

Or:

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

## 4. `tracks`

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

### `id`
- Type: `string`
- Required: no
- Purpose: track identifier

### `clips`
- Type: `Clip[]`
- Required: yes
- Constraint: at least one clip

## 5. Shared `Clip` shape

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

Shared core fields:

### `id`
- Type: `string`
- Required: no
- Purpose: clip identifier

### `type`
- Type: `ClipType`
- Required: yes

Currently supported:
- `video`
- `image`
- `text`
- `rect`
- `circle`
- `polygon`
- `code`
- `svg`
- `icon`
- `line`
- `path`
- `three`
- `audio`
- `subtitle`
- `layout`
- `template`

### `start`
- Type: `number`
- Required: yes
- Unit: seconds
- Purpose: clip start time

### `duration`
- Type: `number`
- Required: yes
- Unit: seconds
- Purpose: clip duration

### `transform`
- Type: `Transform`
- Required: no

### `zIndex`
- Type: `number`
- Required: no
- Default: `0`

### `opacity`
- Type: `number`
- Required: no
- Default: `1`
- Purpose: shorthand opacity for visible elements
- Compatibility rule: `style.opacity` has higher priority than top-level `opacity`

### `style`
- Type: `Style`
- Required: no

### `animations`
- Type: `Animation[]`
- Required: no

### `keyframes`
- Type: `Keyframe[]`
- Required: no

### `transition`
- Type: `Transition`
- Required: no

## 6. Coordinate and percentage rules

Percentage coordinates are based on the Revideo-centered coordinate system, not a DOM-style top-left origin.

Examples:
- `x: "50%"` means horizontally centered
- `y: "50%"` means vertically centered
- `x: "0%"` means far left
- `x: "100%"` means far right
- `y: "0%"` means top
- `y: "100%"` means bottom

## 7. Minimal valid JSON

```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. Public integration note

For external integrations, use the flow documented in [API and Usage](https://renderingvideo.com/docs/api-and-usage.md):
- Submit your schema through `POST /api/preview`
- Send the full schema JSON as the request body
- Missing top-level `meta` or `tracks` should still be treated as invalid input
- A wrong field name inside a clip may not always fail at request time
- Some mistakes may only become visible during preview, playback, or rendering