{
 "$schema": "https://json-schema.org/draft/2020-12/schema",
 "$id": "https://seamlessscene.com/schemas/seamless-cast-selection-v1.json",
 "title": "Seamless Scene cast selection (cast-selection.json) v1, draft 3",
 "description": "DRAFT 3 (2026-10-16, not frozen). The hand-off from cast identification to batch swaps. Frames are the truth: every *_ms value is floor(frame * 1000 * fps_den / fps_num) on the source clock. The file describes camera and model bounds only, and holds references, never media. Delivery context is not in this file: a selected shot or contiguous selected group under 1.5 s (warnings CS109/CS110) gets original context in the batch runner's delivery plan, by one deterministic rule: append untouched original frames after the group's Resume until the exact rational duration reaches 1.5 s; if that would pass the end of the file, the origin-adjusted runtime or the next selected shot, prepend original frames before Start instead; if it is still short, refuse that group and keep it original. Context frames never enter the model input, and generation never crosses a camera cut.",
 "type": "object",
 "additionalProperties": false,
 "required": [
  "schema",
  "episode",
  "character",
  "replacement",
  "shots",
  "provenance"
 ],
 "properties": {
  "schema": {
   "const": "seamless-cast-selection-v1"
  },
  "episode": {
   "type": "object",
   "additionalProperties": false,
   "required": [
    "title",
    "fps_num",
    "fps_den",
    "runtime_ms",
    "source_sha256",
    "source_clock"
   ],
   "properties": {
    "title": {
     "type": "string",
     "minLength": 1,
     "maxLength": 300
    },
    "season": {
     "type": "integer",
     "minimum": 0,
     "maximum": 10000
    },
    "episode": {
     "type": "integer",
     "minimum": 0,
     "maximum": 10000
    },
    "gti": {
     "type": "string",
     "pattern": "^amzn1\\.dv\\.gti\\.[A-Za-z0-9-]{8,64}$",
     "description": "Prime Video playback id, when known"
    },
    "fps_num": {
     "type": "integer",
     "minimum": 1,
     "maximum": 240000,
     "description": "frame rate numerator, e.g. 24000"
    },
    "fps_den": {
     "type": "integer",
     "minimum": 1,
     "maximum": 100000,
     "description": "frame rate denominator, e.g. 1001"
    },
    "runtime_ms": {
     "type": "integer",
     "minimum": 1000,
     "maximum": 86400000
    },
    "source_sha256": {
     "type": "string",
     "pattern": "^[0-9a-f]{64}$",
     "description": "sha256 of the recording or file the pipeline analysed"
    },
    "source_clock": {
     "type": "object",
     "additionalProperties": false,
     "required": [
      "kind",
      "cadence_verified"
     ],
     "description": "how source frames relate to episode time",
     "properties": {
      "kind": {
       "enum": [
        "native-conformed",
        "capture"
       ],
       "description": "native-conformed: zero-origin constant frame rate at the episode's native rate, so frames map to episode time; capture: a recording (for example OBS) whose frames are not mapped to episode time"
      },
      "origin_ms": {
       "type": "integer",
       "minimum": 0,
       "maximum": 86400000,
       "description": "episode time of source frame 0; required when kind is native-conformed"
      },
      "cadence_verified": {
       "type": "boolean",
       "description": "true when every source frame was checked to be a new picture at the stated rate"
      }
     }
    }
   }
  },
  "character": {
   "type": "object",
   "additionalProperties": false,
   "required": [
    "group_id",
    "label",
    "reviewed_by_human"
   ],
   "properties": {
    "group_id": {
     "type": "string",
     "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$"
    },
    "label": {
     "type": "string",
     "minLength": 1,
     "maxLength": 120
    },
    "reviewed_by_human": {
     "const": true,
     "description": "only a human-reviewed group may be exported"
    }
   }
  },
  "replacement": {
   "type": "object",
   "additionalProperties": false,
   "required": [
    "reference_images"
   ],
   "properties": {
    "reference_images": {
     "type": "array",
     "maxItems": 8,
     "items": {
      "type": "string",
      "pattern": "^(?![A-Za-z]:)(?!.*(?:^|/)\\.\\.(?:/|$))[A-Za-z0-9_][A-Za-z0-9._ /-]{0,239}$",
      "description": "relative path, no .., no drive letter, forward slashes"
     },
     "description": "filled in by the user before batch mode, never by the pipeline (empty when exported)"
    },
    "notes": {
     "type": "string",
     "maxLength": 4000
    }
   }
  },
  "shots": {
   "type": "array",
   "minItems": 1,
   "maxItems": 500,
   "items": {
    "type": "object",
    "additionalProperties": false,
    "required": [
     "shot_id",
     "start_frame",
     "resume_frame",
     "start_ms",
     "resume_ms",
     "other_people",
     "selected"
    ],
    "properties": {
     "shot_id": {
      "type": "string",
      "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$"
     },
     "start_frame": {
      "type": "integer",
      "minimum": 0,
      "maximum": 100000000,
      "description": "Start: the first frame replaced"
     },
     "resume_frame": {
      "type": "integer",
      "minimum": 1,
      "maximum": 100000000,
      "description": "Resume: the first frame NOT replaced"
     },
     "start_ms": {
      "type": "integer",
      "minimum": 0,
      "maximum": 86400000
     },
     "resume_ms": {
      "type": "integer",
      "minimum": 0,
      "maximum": 86400000
     },
     "person_box": {
      "type": [
       "object",
       "null"
      ],
      "additionalProperties": false,
      "required": [
       "frame",
       "x",
       "y",
       "w",
       "h"
      ],
      "description": "the W3 planning anchor on one observed frame inside the shot, in pixels of the analysed source; required when selected, may be null or omitted when not selected (never invent a box)",
      "properties": {
       "frame": {
        "type": "integer",
        "minimum": 0,
        "maximum": 100000000
       },
       "x": {
        "type": "integer",
        "minimum": 0,
        "maximum": 100000
       },
       "y": {
        "type": "integer",
        "minimum": 0,
        "maximum": 100000
       },
       "w": {
        "type": "integer",
        "minimum": 1,
        "maximum": 100000
       },
       "h": {
        "type": "integer",
        "minimum": 1,
        "maximum": 100000
       }
      }
     },
     "other_people": {
      "type": [
       "integer",
       "null"
      ],
      "minimum": 0,
      "maximum": 100,
      "description": "people other than the recast one seen on the anchor frame; null only when the shot is not selected"
     },
     "selected": {
      "type": "boolean"
     },
     "skip_reason": {
      "enum": [
       "short",
       "no-face",
       "occluded",
       "back-to-camera",
       "on-screen-text",
       "human-rejected",
       "other"
      ],
      "description": "required when selected is false, absent when true"
     }
    }
   }
  },
  "provenance": {
   "type": "object",
   "additionalProperties": false,
   "required": [
    "pipeline_version",
    "model_hashes",
    "created_at"
   ],
   "properties": {
    "pipeline_version": {
     "type": "string",
     "minLength": 1,
     "maxLength": 64
    },
    "model_hashes": {
     "type": "object",
     "maxProperties": 32,
     "additionalProperties": {
      "type": "string",
      "pattern": "^[0-9a-f]{64}$"
     }
    },
    "created_at": {
     "type": "string",
     "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,6})?Z$"
    }
   }
  }
 }
}
