hyper-animator-codex 0.8.0 → 0.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/README.md +30 -13
  2. package/bin/hyper-animator-codex.mjs +4 -4
  3. package/lib/install-skill.mjs +49 -20
  4. package/lib/minimax-config.mjs +5 -9
  5. package/package.json +10 -2
  6. package/skills/hyper-animator-codex/SKILL.md +33 -25
  7. package/skills/hyper-animator-codex/agents/openai.yaml +2 -2
  8. package/skills/hyper-animator-codex/contracts/shot-plan.schema.json +113 -7
  9. package/skills/hyper-animator-codex/contracts/workflow-policy.json +21 -3
  10. package/skills/hyper-animator-codex/references/examples/shot-plan-dual-catalog.json +41 -5
  11. package/skills/hyper-animator-codex/references/hyperframes-intent-workflow.md +20 -10
  12. package/skills/hyper-animator-codex/references/minimax-music-workflow.md +5 -7
  13. package/skills/hyper-animator-codex/references/narration-audio-workflow.md +20 -14
  14. package/skills/hyper-animator-codex/references/preview-controls-workflow.md +4 -2
  15. package/skills/hyper-animator-codex/references/shotcraft/motion-pack/README.md +32 -0
  16. package/skills/hyper-animator-codex/references/shotcraft/motion-pack/shotcraft-motion-pack.js +178 -0
  17. package/skills/hyper-animator-codex/scripts/generate_minimax_music.mjs +11 -6
  18. package/skills/hyper-animator-codex/scripts/generate_minimax_tts.mjs +20 -8
  19. package/skills/hyper-animator-codex/scripts/git_checkpoint.mjs +29 -3
  20. package/skills/hyper-animator-codex/scripts/inject_preview_controls.mjs +37 -1
  21. package/skills/hyper-animator-codex/scripts/minimax_runtime_config.mjs +5 -9
  22. package/skills/hyper-animator-codex/scripts/query_shotcraft.mjs +10 -1
  23. package/skills/hyper-animator-codex/scripts/shot_plan_schema_validator.mjs +3 -0
  24. package/skills/hyper-animator-codex/scripts/validate_hyperframes_html.py +122 -36
  25. package/skills/hyper-animator-codex/scripts/validate_shot_plan.mjs +110 -17
@@ -32,6 +32,32 @@ const DEFAULT_EXCLUDES = [
32
32
  "tmp/",
33
33
  ".tmp/",
34
34
  ];
35
+ const SENSITIVE_EXCLUDES = [
36
+ ".env",
37
+ ".env.*",
38
+ "**/.env",
39
+ "**/.env.*",
40
+ ".npmrc",
41
+ "**/.npmrc",
42
+ ".pypirc",
43
+ "**/.pypirc",
44
+ ".netrc",
45
+ "**/.netrc",
46
+ "credentials.json",
47
+ "**/credentials.json",
48
+ "secrets.json",
49
+ "**/secrets.json",
50
+ "*.pem",
51
+ "**/*.pem",
52
+ "*.key",
53
+ "**/*.key",
54
+ "*.p12",
55
+ "**/*.p12",
56
+ "id_rsa",
57
+ "**/id_rsa",
58
+ "id_ed25519",
59
+ "**/id_ed25519",
60
+ ];
35
61
 
36
62
  let repositoryRootCache = null;
37
63
  let gitPrefixCache = null;
@@ -162,7 +188,7 @@ function resolveGitDirPath(value) {
162
188
  function isDefaultExcluded(path) {
163
189
  const normalized = normalizeGitPath(path);
164
190
 
165
- return DEFAULT_EXCLUDES.some((entry) => {
191
+ return isExplicitlyExcluded(normalized, SENSITIVE_EXCLUDES) || DEFAULT_EXCLUDES.some((entry) => {
166
192
  const cleaned = entry.endsWith("/") ? entry.slice(0, -1) : entry;
167
193
  return normalized === cleaned || normalized.startsWith(`${cleaned}/`);
168
194
  });
@@ -193,7 +219,7 @@ function parsePorcelainLine(line) {
193
219
  }
194
220
 
195
221
  function porcelainLines(pathspecs = []) {
196
- const args = ["status", "--porcelain=v1", "--untracked-files=all"];
222
+ const args = ["-c", "core.quotePath=false", "status", "--porcelain=v1", "--untracked-files=all"];
197
223
 
198
224
  if (pathspecs.length > 0) {
199
225
  args.push("--", ...pathspecs);
@@ -230,7 +256,7 @@ function trackedOrUntrackedMatching(pathspecs) {
230
256
  }
231
257
 
232
258
  function stagedFiles() {
233
- return git(["diff", "--cached", "--name-only", "--diff-filter=ACDMR"], { check: true }).stdout
259
+ return git(["-c", "core.quotePath=false", "diff", "--cached", "--name-only", "--diff-filter=ACDMR"], { check: true }).stdout
234
260
  .split("\n")
235
261
  .map((line) => line.trim())
236
262
  .filter(Boolean)
@@ -166,7 +166,7 @@ function injectPreviewControls(html, componentId) {
166
166
  bottom: 0;
167
167
  z-index: 2147483647;
168
168
  display: grid;
169
- grid-template-columns: auto minmax(0, 1fr);
169
+ grid-template-columns: auto auto minmax(0, 1fr);
170
170
  align-items: center;
171
171
  gap: 10px;
172
172
  padding: 6px 10px 8px;
@@ -184,6 +184,17 @@ function injectPreviewControls(html, componentId) {
184
184
  font-variant-numeric: tabular-nums lining-nums;
185
185
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.45);
186
186
  }
187
+ .hyper-preview-play {
188
+ min-width: 54px;
189
+ padding: 5px 9px;
190
+ border: 1px solid rgba(255, 255, 255, 0.35);
191
+ border-radius: 999px;
192
+ color: #fff;
193
+ background: rgba(3, 8, 20, 0.7);
194
+ font: 600 12px/1 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
195
+ cursor: pointer;
196
+ pointer-events: auto;
197
+ }
187
198
  .hyper-preview-progress {
188
199
  width: 100%;
189
200
  height: 3px;
@@ -193,6 +204,7 @@ function injectPreviewControls(html, componentId) {
193
204
  }
194
205
  </style>
195
206
  <div class="hyper-preview-ui" data-hyper-preview-ui hidden aria-label="Animation preview controls">
207
+ <button class="hyper-preview-play" data-hyper-preview-play type="button" aria-label="Play">播放</button>
196
208
  <div class="hyper-preview-page" data-hyper-preview-page aria-live="polite">1 / 1</div>
197
209
  <input class="hyper-preview-progress" data-hyper-preview-progress type="range" min="0" max="1000" step="1" value="0" aria-label="Preview progress" />
198
210
  </div>
@@ -210,6 +222,7 @@ function injectPreviewControls(html, componentId) {
210
222
  }
211
223
 
212
224
  const pageLabel = root.querySelector("[data-hyper-preview-page]");
225
+ const playButton = root.querySelector("[data-hyper-preview-play]");
213
226
  const progress = root.querySelector("[data-hyper-preview-progress]");
214
227
  const composition = findComposition();
215
228
  const duration = readDuration(composition);
@@ -220,6 +233,21 @@ function injectPreviewControls(html, componentId) {
220
233
  root.style.display = "";
221
234
  updateForTime(0);
222
235
 
236
+ if (playButton) {
237
+ playButton.addEventListener("click", function () {
238
+ const timeline = getTimeline();
239
+ if (!timeline) return;
240
+ const atEnd = typeof timeline.progress === "function" && timeline.progress() >= 1;
241
+ if (typeof timeline.paused === "function" && (timeline.paused() || atEnd)) timeline.play(atEnd ? 0 : undefined);
242
+ else if (typeof timeline.pause === "function") timeline.pause();
243
+ syncPlayButton();
244
+ });
245
+ syncPlayButton();
246
+ const timeline = getTimeline();
247
+ if (timeline && typeof timeline.play === "function") timeline.play(0);
248
+ syncPlayButton();
249
+ }
250
+
223
251
  progress.addEventListener("input", function () {
224
252
  const seconds = (Number(progress.value) / Number(progress.max)) * duration;
225
253
  seek(seconds);
@@ -280,6 +308,14 @@ function injectPreviewControls(html, componentId) {
280
308
  }
281
309
  }
282
310
 
311
+ function syncPlayButton() {
312
+ const timeline = getTimeline();
313
+ if (!playButton || !timeline || typeof timeline.paused !== "function") return;
314
+ const paused = timeline.paused();
315
+ playButton.textContent = paused ? "播放" : "暂停";
316
+ playButton.setAttribute("aria-label", paused ? "Play" : "Pause");
317
+ }
318
+
283
319
  function goToPage(nextIndex) {
284
320
  pageIndex = nextIndex;
285
321
  const seconds = pages[pageIndex] || 0;
@@ -1,8 +1,8 @@
1
1
  import { readFile, stat } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
 
4
- export const DEFAULT_MINIMAX_MODEL = "music-2.6";
5
- export const TEXT_MUSIC_MODELS = new Set(["music-3.0", "music-2.6", "music-2.6-free"]);
4
+ export const DEFAULT_MINIMAX_MODEL = "music-3.0";
5
+ export const TEXT_MUSIC_MODELS = new Set(["music-3.0", "music-3.0-free", "music-2.6", "music-2.6-free"]);
6
6
 
7
7
  async function pathExists(path) {
8
8
  try {
@@ -42,7 +42,7 @@ export function redactMinimaxConfig(config = {}) {
42
42
 
43
43
  return {
44
44
  api_key: normalized.api_key ? "[redacted]" : undefined,
45
- group_id: normalized.group_id,
45
+ ...(normalized.group_id ? { group_id: normalized.group_id } : {}),
46
46
  model: normalized.model || DEFAULT_MINIMAX_MODEL,
47
47
  };
48
48
  }
@@ -54,19 +54,15 @@ export function validateMinimaxConfig(config = {}) {
54
54
  throw new Error("MiniMax api_key is required");
55
55
  }
56
56
 
57
- if (!normalized.group_id) {
58
- throw new Error("MiniMax group_id is required");
59
- }
60
-
61
57
  const model = normalized.model || DEFAULT_MINIMAX_MODEL;
62
58
 
63
59
  if (!TEXT_MUSIC_MODELS.has(model)) {
64
- throw new Error(`Unsupported MiniMax text music model: ${model}. Use music-3.0, music-2.6, or music-2.6-free.`);
60
+ throw new Error(`Unsupported MiniMax text music model: ${model}. Use music-3.0, music-3.0-free, music-2.6, or music-2.6-free.`);
65
61
  }
66
62
 
67
63
  return {
68
64
  api_key: normalized.api_key,
69
- group_id: normalized.group_id,
65
+ ...(normalized.group_id ? { group_id: normalized.group_id } : {}),
70
66
  model,
71
67
  };
72
68
  }
@@ -36,11 +36,16 @@ function parseArgs(args) {
36
36
  }
37
37
 
38
38
  function tokens(value) {
39
- return String(value)
39
+ const raw = String(value)
40
40
  .toLowerCase()
41
41
  .split(/[\s,,。::;;/|()[\]{}"'`_-]+/)
42
42
  .map((token) => token.trim())
43
43
  .filter(Boolean);
44
+ const expanded = raw.flatMap((token) => {
45
+ if (!/^[\p{Script=Han}]+$/u.test(token) || token.length < 3) return [token];
46
+ return [token, ...Array.from({ length: token.length - 1 }, (_, index) => token.slice(index, index + 2))];
47
+ });
48
+ return [...new Set(expanded)];
44
49
  }
45
50
 
46
51
  function scoreText(queryTokens, fields) {
@@ -121,6 +126,10 @@ async function main() {
121
126
  .sort((left, right) => right.score - left.score || left.card.localeCompare(right.card))
122
127
  .slice(0, args.limit);
123
128
 
129
+ if (results.length === 0) {
130
+ throw new Error(`No ShotCraft references matched: ${args.query}`);
131
+ }
132
+
124
133
  if (args.json) {
125
134
  console.log(JSON.stringify({
126
135
  ok: true,
@@ -0,0 +1,3 @@
1
+ // Generated by tools/build-shot-plan-validator.mjs; do not edit.
2
+ export const schemaSha256 = "78d808af826b34473732b407d825a89ef1103e04c8cc3b56a5fe4eb013f54261";
3
+ "use strict";export const validate = validate20;export default validate20;const schema31 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://hyper-animator-codex.local/contracts/shot-plan.schema.json","title":"Hyper Animator dual-catalog shot plan","type":"object","additionalProperties":false,"required":["schema_version","workflow_stage","generation_mode","catalog_strategy","dimensions","duration_seconds","audio","model_routing","shots"],"properties":{"schema_version":{"const":"1.1.0"},"workflow_stage":{"enum":["visual_draft","audio_integrated","render_ready"]},"generation_mode":{"enum":["assemble_existing_catalog_items","generate_new_hyperframes_html"]},"catalog_strategy":{"const":"dual_catalog"},"dimensions":{"type":"object","additionalProperties":false,"required":["width","height"],"properties":{"width":{"type":"integer","minimum":1},"height":{"type":"integer","minimum":1}}},"duration_seconds":{"type":"number","exclusiveMinimum":0},"audio":{"type":"object","additionalProperties":false,"required":["narration","bgm"],"properties":{"narration":{"$ref":"#/$defs/audioTrack"},"bgm":{"$ref":"#/$defs/audioTrack"}}},"model_routing":{"type":"object","additionalProperties":false,"required":["page_animation_authoring","planning_and_validation"],"properties":{"page_animation_authoring":{"allOf":[{"$ref":"#/$defs/modelRoute"},{"properties":{"tier":{"const":"high_quality"}}}]},"planning_and_validation":{"allOf":[{"$ref":"#/$defs/modelRoute"},{"properties":{"tier":{"const":"fast"}}}]}}},"shots":{"type":"array","minItems":1,"items":{"$ref":"#/$defs/shot"}},"assumptions":{"type":"array","items":{"type":"string"}}},"$defs":{"modelRoute":{"type":"object","additionalProperties":false,"required":["tier","actual_model"],"properties":{"tier":{"enum":["high_quality","fast"]},"actual_model":{"type":"string","minLength":1}}},"audioTrack":{"type":"object","additionalProperties":false,"required":["mode"],"properties":{"mode":{"enum":["generated","user_local_file","none","deferred"]},"provider":{"enum":["minimax_cn"]},"path":{"type":"string","minLength":1}},"allOf":[{"if":{"properties":{"mode":{"const":"generated"}}},"then":{"required":["provider"],"properties":{"provider":{"const":"minimax_cn"}}}},{"if":{"properties":{"mode":{"const":"user_local_file"}}},"then":{"required":["path"]}}]},"shot":{"type":"object","additionalProperties":false,"required":["id","start_seconds","end_seconds","purpose","hyperframes_refs","shotcraft_refs","motion_contract"],"properties":{"id":{"type":"string","pattern":"^[a-z][a-z0-9-]*$"},"start_seconds":{"type":"number","minimum":0},"end_seconds":{"type":"number","exclusiveMinimum":0},"purpose":{"type":"string","minLength":1},"hyperframes_refs":{"type":"array","minItems":1,"items":{"type":"string","minLength":1},"uniqueItems":true},"shotcraft_refs":{"type":"array","minItems":1,"items":{"type":"string","minLength":1},"uniqueItems":true},"narration_scene":{"type":["string","null"]},"motion_contract":{"type":"object","additionalProperties":false,"required":["motion_preset","energy","camera","transition","shotcraft_commitments","readable_rest_seconds"],"properties":{"motion_preset":{"enum":["none","slam-entrance","whip-transition","crash-zoom","push-stack-wipe","dataviz-landscape-open","panel-grid-reflow","brand-resolve"]},"energy":{"enum":["quiet","balanced","high","cinematic"]},"camera":{"type":"string","minLength":1},"transition":{"type":"string","minLength":1},"shotcraft_commitments":{"type":"array","minItems":1,"items":{"type":"string","minLength":1}},"readable_rest_seconds":{"type":"number","minimum":0}}}}}}};const schema32 = {"type":"object","additionalProperties":false,"required":["mode"],"properties":{"mode":{"enum":["generated","user_local_file","none","deferred"]},"provider":{"enum":["minimax_cn"]},"path":{"type":"string","minLength":1}},"allOf":[{"if":{"properties":{"mode":{"const":"generated"}}},"then":{"required":["provider"],"properties":{"provider":{"const":"minimax_cn"}}}},{"if":{"properties":{"mode":{"const":"user_local_file"}}},"then":{"required":["path"]}}]};const schema34 = {"type":"object","additionalProperties":false,"required":["tier","actual_model"],"properties":{"tier":{"enum":["high_quality","fast"]},"actual_model":{"type":"string","minLength":1}}};const schema36 = {"type":"object","additionalProperties":false,"required":["id","start_seconds","end_seconds","purpose","hyperframes_refs","shotcraft_refs","motion_contract"],"properties":{"id":{"type":"string","pattern":"^[a-z][a-z0-9-]*$"},"start_seconds":{"type":"number","minimum":0},"end_seconds":{"type":"number","exclusiveMinimum":0},"purpose":{"type":"string","minLength":1},"hyperframes_refs":{"type":"array","minItems":1,"items":{"type":"string","minLength":1},"uniqueItems":true},"shotcraft_refs":{"type":"array","minItems":1,"items":{"type":"string","minLength":1},"uniqueItems":true},"narration_scene":{"type":["string","null"]},"motion_contract":{"type":"object","additionalProperties":false,"required":["motion_preset","energy","camera","transition","shotcraft_commitments","readable_rest_seconds"],"properties":{"motion_preset":{"enum":["none","slam-entrance","whip-transition","crash-zoom","push-stack-wipe","dataviz-landscape-open","panel-grid-reflow","brand-resolve"]},"energy":{"enum":["quiet","balanced","high","cinematic"]},"camera":{"type":"string","minLength":1},"transition":{"type":"string","minLength":1},"shotcraft_commitments":{"type":"array","minItems":1,"items":{"type":"string","minLength":1}},"readable_rest_seconds":{"type":"number","minimum":0}}}}};const func1 = Object.prototype.hasOwnProperty;const func2 = (value) => [...value].length;const pattern4 = new RegExp("^[a-z][a-z0-9-]*$", "u");function validate20(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){/*# sourceURL="https://hyper-animator-codex.local/contracts/shot-plan.schema.json" */;let vErrors = null;let errors = 0;const evaluated0 = validate20.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema_version === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema_version"},message:"must have required property '"+"schema_version"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.workflow_stage === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "workflow_stage"},message:"must have required property '"+"workflow_stage"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.generation_mode === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "generation_mode"},message:"must have required property '"+"generation_mode"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.catalog_strategy === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "catalog_strategy"},message:"must have required property '"+"catalog_strategy"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.dimensions === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "dimensions"},message:"must have required property '"+"dimensions"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.duration_seconds === undefined){const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "duration_seconds"},message:"must have required property '"+"duration_seconds"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}if(data.audio === undefined){const err6 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "audio"},message:"must have required property '"+"audio"+"'"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}if(data.model_routing === undefined){const err7 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "model_routing"},message:"must have required property '"+"model_routing"+"'"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}if(data.shots === undefined){const err8 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "shots"},message:"must have required property '"+"shots"+"'"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}for(const key0 in data){if(!(func1.call(schema31.properties, key0))){const err9 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}if(data.schema_version !== undefined){if("1.1.0" !== data.schema_version){const err10 = {instancePath:instancePath+"/schema_version",schemaPath:"#/properties/schema_version/const",keyword:"const",params:{allowedValue: "1.1.0"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}if(data.workflow_stage !== undefined){let data1 = data.workflow_stage;if(!(((data1 === "visual_draft") || (data1 === "audio_integrated")) || (data1 === "render_ready"))){const err11 = {instancePath:instancePath+"/workflow_stage",schemaPath:"#/properties/workflow_stage/enum",keyword:"enum",params:{allowedValues: schema31.properties.workflow_stage.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}if(data.generation_mode !== undefined){let data2 = data.generation_mode;if(!((data2 === "assemble_existing_catalog_items") || (data2 === "generate_new_hyperframes_html"))){const err12 = {instancePath:instancePath+"/generation_mode",schemaPath:"#/properties/generation_mode/enum",keyword:"enum",params:{allowedValues: schema31.properties.generation_mode.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}if(data.catalog_strategy !== undefined){if("dual_catalog" !== data.catalog_strategy){const err13 = {instancePath:instancePath+"/catalog_strategy",schemaPath:"#/properties/catalog_strategy/const",keyword:"const",params:{allowedValue: "dual_catalog"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}if(data.dimensions !== undefined){let data4 = data.dimensions;if(data4 && typeof data4 == "object" && !Array.isArray(data4)){if(data4.width === undefined){const err14 = {instancePath:instancePath+"/dimensions",schemaPath:"#/properties/dimensions/required",keyword:"required",params:{missingProperty: "width"},message:"must have required property '"+"width"+"'"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}if(data4.height === undefined){const err15 = {instancePath:instancePath+"/dimensions",schemaPath:"#/properties/dimensions/required",keyword:"required",params:{missingProperty: "height"},message:"must have required property '"+"height"+"'"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}for(const key1 in data4){if(!((key1 === "width") || (key1 === "height"))){const err16 = {instancePath:instancePath+"/dimensions",schemaPath:"#/properties/dimensions/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key1},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}if(data4.width !== undefined){let data5 = data4.width;if(!((typeof data5 == "number") && (!(data5 % 1) && !isNaN(data5)))){const err17 = {instancePath:instancePath+"/dimensions/width",schemaPath:"#/properties/dimensions/properties/width/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}if(typeof data5 == "number"){if(data5 < 1 || isNaN(data5)){const err18 = {instancePath:instancePath+"/dimensions/width",schemaPath:"#/properties/dimensions/properties/width/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}}if(data4.height !== undefined){let data6 = data4.height;if(!((typeof data6 == "number") && (!(data6 % 1) && !isNaN(data6)))){const err19 = {instancePath:instancePath+"/dimensions/height",schemaPath:"#/properties/dimensions/properties/height/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}if(typeof data6 == "number"){if(data6 < 1 || isNaN(data6)){const err20 = {instancePath:instancePath+"/dimensions/height",schemaPath:"#/properties/dimensions/properties/height/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}}}else {const err21 = {instancePath:instancePath+"/dimensions",schemaPath:"#/properties/dimensions/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}}if(data.duration_seconds !== undefined){let data7 = data.duration_seconds;if(typeof data7 == "number"){if(data7 <= 0 || isNaN(data7)){const err22 = {instancePath:instancePath+"/duration_seconds",schemaPath:"#/properties/duration_seconds/exclusiveMinimum",keyword:"exclusiveMinimum",params:{comparison: ">", limit: 0},message:"must be > 0"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}}else {const err23 = {instancePath:instancePath+"/duration_seconds",schemaPath:"#/properties/duration_seconds/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}}if(data.audio !== undefined){let data8 = data.audio;if(data8 && typeof data8 == "object" && !Array.isArray(data8)){if(data8.narration === undefined){const err24 = {instancePath:instancePath+"/audio",schemaPath:"#/properties/audio/required",keyword:"required",params:{missingProperty: "narration"},message:"must have required property '"+"narration"+"'"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}if(data8.bgm === undefined){const err25 = {instancePath:instancePath+"/audio",schemaPath:"#/properties/audio/required",keyword:"required",params:{missingProperty: "bgm"},message:"must have required property '"+"bgm"+"'"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}for(const key2 in data8){if(!((key2 === "narration") || (key2 === "bgm"))){const err26 = {instancePath:instancePath+"/audio",schemaPath:"#/properties/audio/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key2},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}}if(data8.narration !== undefined){let data9 = data8.narration;const _errs22 = errors;let valid5 = true;const _errs23 = errors;if(data9 && typeof data9 == "object" && !Array.isArray(data9)){if(data9.mode !== undefined){if("generated" !== data9.mode){const err27 = {};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}}}var _valid0 = _errs23 === errors;errors = _errs22;if(vErrors !== null){if(_errs22){vErrors.length = _errs22;}else {vErrors = null;}}if(_valid0){const _errs25 = errors;if(data9 && typeof data9 == "object" && !Array.isArray(data9)){if(data9.provider === undefined){const err28 = {instancePath:instancePath+"/audio/narration",schemaPath:"#/$defs/audioTrack/allOf/0/then/required",keyword:"required",params:{missingProperty: "provider"},message:"must have required property '"+"provider"+"'"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}if(data9.provider !== undefined){if("minimax_cn" !== data9.provider){const err29 = {instancePath:instancePath+"/audio/narration/provider",schemaPath:"#/$defs/audioTrack/allOf/0/then/properties/provider/const",keyword:"const",params:{allowedValue: "minimax_cn"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}}}var _valid0 = _errs25 === errors;valid5 = _valid0;if(valid5){var props0 = {};props0.provider = true;props0.mode = true;}}if(!valid5){const err30 = {instancePath:instancePath+"/audio/narration",schemaPath:"#/$defs/audioTrack/allOf/0/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}const _errs28 = errors;let valid8 = true;const _errs29 = errors;if(data9 && typeof data9 == "object" && !Array.isArray(data9)){if(data9.mode !== undefined){if("user_local_file" !== data9.mode){const err31 = {};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}}}var _valid1 = _errs29 === errors;errors = _errs28;if(vErrors !== null){if(_errs28){vErrors.length = _errs28;}else {vErrors = null;}}if(_valid1){const _errs31 = errors;if(data9 && typeof data9 == "object" && !Array.isArray(data9)){if(data9.path === undefined){const err32 = {instancePath:instancePath+"/audio/narration",schemaPath:"#/$defs/audioTrack/allOf/1/then/required",keyword:"required",params:{missingProperty: "path"},message:"must have required property '"+"path"+"'"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}}var _valid1 = _errs31 === errors;valid8 = _valid1;}if(!valid8){const err33 = {instancePath:instancePath+"/audio/narration",schemaPath:"#/$defs/audioTrack/allOf/1/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}if(props0 !== true){props0 = props0 || {};props0.mode = true;}if(data9 && typeof data9 == "object" && !Array.isArray(data9)){if(data9.mode === undefined){const err34 = {instancePath:instancePath+"/audio/narration",schemaPath:"#/$defs/audioTrack/required",keyword:"required",params:{missingProperty: "mode"},message:"must have required property '"+"mode"+"'"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}for(const key3 in data9){if(!(((key3 === "mode") || (key3 === "provider")) || (key3 === "path"))){const err35 = {instancePath:instancePath+"/audio/narration",schemaPath:"#/$defs/audioTrack/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key3},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}}if(data9.mode !== undefined){let data13 = data9.mode;if(!((((data13 === "generated") || (data13 === "user_local_file")) || (data13 === "none")) || (data13 === "deferred"))){const err36 = {instancePath:instancePath+"/audio/narration/mode",schemaPath:"#/$defs/audioTrack/properties/mode/enum",keyword:"enum",params:{allowedValues: schema32.properties.mode.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}}if(data9.provider !== undefined){if(!(data9.provider === "minimax_cn")){const err37 = {instancePath:instancePath+"/audio/narration/provider",schemaPath:"#/$defs/audioTrack/properties/provider/enum",keyword:"enum",params:{allowedValues: schema32.properties.provider.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}}if(data9.path !== undefined){let data15 = data9.path;if(typeof data15 === "string"){if(func2(data15) < 1){const err38 = {instancePath:instancePath+"/audio/narration/path",schemaPath:"#/$defs/audioTrack/properties/path/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}}else {const err39 = {instancePath:instancePath+"/audio/narration/path",schemaPath:"#/$defs/audioTrack/properties/path/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}}}else {const err40 = {instancePath:instancePath+"/audio/narration",schemaPath:"#/$defs/audioTrack/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err40];}else {vErrors.push(err40);}errors++;}}if(data8.bgm !== undefined){let data16 = data8.bgm;const _errs41 = errors;let valid13 = true;const _errs42 = errors;if(data16 && typeof data16 == "object" && !Array.isArray(data16)){if(data16.mode !== undefined){if("generated" !== data16.mode){const err41 = {};if(vErrors === null){vErrors = [err41];}else {vErrors.push(err41);}errors++;}}}var _valid2 = _errs42 === errors;errors = _errs41;if(vErrors !== null){if(_errs41){vErrors.length = _errs41;}else {vErrors = null;}}if(_valid2){const _errs44 = errors;if(data16 && typeof data16 == "object" && !Array.isArray(data16)){if(data16.provider === undefined){const err42 = {instancePath:instancePath+"/audio/bgm",schemaPath:"#/$defs/audioTrack/allOf/0/then/required",keyword:"required",params:{missingProperty: "provider"},message:"must have required property '"+"provider"+"'"};if(vErrors === null){vErrors = [err42];}else {vErrors.push(err42);}errors++;}if(data16.provider !== undefined){if("minimax_cn" !== data16.provider){const err43 = {instancePath:instancePath+"/audio/bgm/provider",schemaPath:"#/$defs/audioTrack/allOf/0/then/properties/provider/const",keyword:"const",params:{allowedValue: "minimax_cn"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err43];}else {vErrors.push(err43);}errors++;}}}var _valid2 = _errs44 === errors;valid13 = _valid2;if(valid13){var props1 = {};props1.provider = true;props1.mode = true;}}if(!valid13){const err44 = {instancePath:instancePath+"/audio/bgm",schemaPath:"#/$defs/audioTrack/allOf/0/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err44];}else {vErrors.push(err44);}errors++;}const _errs47 = errors;let valid16 = true;const _errs48 = errors;if(data16 && typeof data16 == "object" && !Array.isArray(data16)){if(data16.mode !== undefined){if("user_local_file" !== data16.mode){const err45 = {};if(vErrors === null){vErrors = [err45];}else {vErrors.push(err45);}errors++;}}}var _valid3 = _errs48 === errors;errors = _errs47;if(vErrors !== null){if(_errs47){vErrors.length = _errs47;}else {vErrors = null;}}if(_valid3){const _errs50 = errors;if(data16 && typeof data16 == "object" && !Array.isArray(data16)){if(data16.path === undefined){const err46 = {instancePath:instancePath+"/audio/bgm",schemaPath:"#/$defs/audioTrack/allOf/1/then/required",keyword:"required",params:{missingProperty: "path"},message:"must have required property '"+"path"+"'"};if(vErrors === null){vErrors = [err46];}else {vErrors.push(err46);}errors++;}}var _valid3 = _errs50 === errors;valid16 = _valid3;}if(!valid16){const err47 = {instancePath:instancePath+"/audio/bgm",schemaPath:"#/$defs/audioTrack/allOf/1/if",keyword:"if",params:{failingKeyword: "then"},message:"must match \"then\" schema"};if(vErrors === null){vErrors = [err47];}else {vErrors.push(err47);}errors++;}if(props1 !== true){props1 = props1 || {};props1.mode = true;}if(data16 && typeof data16 == "object" && !Array.isArray(data16)){if(data16.mode === undefined){const err48 = {instancePath:instancePath+"/audio/bgm",schemaPath:"#/$defs/audioTrack/required",keyword:"required",params:{missingProperty: "mode"},message:"must have required property '"+"mode"+"'"};if(vErrors === null){vErrors = [err48];}else {vErrors.push(err48);}errors++;}for(const key4 in data16){if(!(((key4 === "mode") || (key4 === "provider")) || (key4 === "path"))){const err49 = {instancePath:instancePath+"/audio/bgm",schemaPath:"#/$defs/audioTrack/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key4},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err49];}else {vErrors.push(err49);}errors++;}}if(data16.mode !== undefined){let data20 = data16.mode;if(!((((data20 === "generated") || (data20 === "user_local_file")) || (data20 === "none")) || (data20 === "deferred"))){const err50 = {instancePath:instancePath+"/audio/bgm/mode",schemaPath:"#/$defs/audioTrack/properties/mode/enum",keyword:"enum",params:{allowedValues: schema32.properties.mode.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err50];}else {vErrors.push(err50);}errors++;}}if(data16.provider !== undefined){if(!(data16.provider === "minimax_cn")){const err51 = {instancePath:instancePath+"/audio/bgm/provider",schemaPath:"#/$defs/audioTrack/properties/provider/enum",keyword:"enum",params:{allowedValues: schema32.properties.provider.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err51];}else {vErrors.push(err51);}errors++;}}if(data16.path !== undefined){let data22 = data16.path;if(typeof data22 === "string"){if(func2(data22) < 1){const err52 = {instancePath:instancePath+"/audio/bgm/path",schemaPath:"#/$defs/audioTrack/properties/path/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err52];}else {vErrors.push(err52);}errors++;}}else {const err53 = {instancePath:instancePath+"/audio/bgm/path",schemaPath:"#/$defs/audioTrack/properties/path/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err53];}else {vErrors.push(err53);}errors++;}}}else {const err54 = {instancePath:instancePath+"/audio/bgm",schemaPath:"#/$defs/audioTrack/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err54];}else {vErrors.push(err54);}errors++;}}}else {const err55 = {instancePath:instancePath+"/audio",schemaPath:"#/properties/audio/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err55];}else {vErrors.push(err55);}errors++;}}if(data.model_routing !== undefined){let data23 = data.model_routing;if(data23 && typeof data23 == "object" && !Array.isArray(data23)){if(data23.page_animation_authoring === undefined){const err56 = {instancePath:instancePath+"/model_routing",schemaPath:"#/properties/model_routing/required",keyword:"required",params:{missingProperty: "page_animation_authoring"},message:"must have required property '"+"page_animation_authoring"+"'"};if(vErrors === null){vErrors = [err56];}else {vErrors.push(err56);}errors++;}if(data23.planning_and_validation === undefined){const err57 = {instancePath:instancePath+"/model_routing",schemaPath:"#/properties/model_routing/required",keyword:"required",params:{missingProperty: "planning_and_validation"},message:"must have required property '"+"planning_and_validation"+"'"};if(vErrors === null){vErrors = [err57];}else {vErrors.push(err57);}errors++;}for(const key5 in data23){if(!((key5 === "page_animation_authoring") || (key5 === "planning_and_validation"))){const err58 = {instancePath:instancePath+"/model_routing",schemaPath:"#/properties/model_routing/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key5},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err58];}else {vErrors.push(err58);}errors++;}}if(data23.page_animation_authoring !== undefined){let data24 = data23.page_animation_authoring;if(data24 && typeof data24 == "object" && !Array.isArray(data24)){if(data24.tier === undefined){const err59 = {instancePath:instancePath+"/model_routing/page_animation_authoring",schemaPath:"#/$defs/modelRoute/required",keyword:"required",params:{missingProperty: "tier"},message:"must have required property '"+"tier"+"'"};if(vErrors === null){vErrors = [err59];}else {vErrors.push(err59);}errors++;}if(data24.actual_model === undefined){const err60 = {instancePath:instancePath+"/model_routing/page_animation_authoring",schemaPath:"#/$defs/modelRoute/required",keyword:"required",params:{missingProperty: "actual_model"},message:"must have required property '"+"actual_model"+"'"};if(vErrors === null){vErrors = [err60];}else {vErrors.push(err60);}errors++;}for(const key6 in data24){if(!((key6 === "tier") || (key6 === "actual_model"))){const err61 = {instancePath:instancePath+"/model_routing/page_animation_authoring",schemaPath:"#/$defs/modelRoute/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key6},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err61];}else {vErrors.push(err61);}errors++;}}if(data24.tier !== undefined){let data25 = data24.tier;if(!((data25 === "high_quality") || (data25 === "fast"))){const err62 = {instancePath:instancePath+"/model_routing/page_animation_authoring/tier",schemaPath:"#/$defs/modelRoute/properties/tier/enum",keyword:"enum",params:{allowedValues: schema34.properties.tier.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err62];}else {vErrors.push(err62);}errors++;}}if(data24.actual_model !== undefined){let data26 = data24.actual_model;if(typeof data26 === "string"){if(func2(data26) < 1){const err63 = {instancePath:instancePath+"/model_routing/page_animation_authoring/actual_model",schemaPath:"#/$defs/modelRoute/properties/actual_model/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err63];}else {vErrors.push(err63);}errors++;}}else {const err64 = {instancePath:instancePath+"/model_routing/page_animation_authoring/actual_model",schemaPath:"#/$defs/modelRoute/properties/actual_model/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err64];}else {vErrors.push(err64);}errors++;}}}else {const err65 = {instancePath:instancePath+"/model_routing/page_animation_authoring",schemaPath:"#/$defs/modelRoute/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err65];}else {vErrors.push(err65);}errors++;}if(data24 && typeof data24 == "object" && !Array.isArray(data24)){if(data24.tier !== undefined){if("high_quality" !== data24.tier){const err66 = {instancePath:instancePath+"/model_routing/page_animation_authoring/tier",schemaPath:"#/properties/model_routing/properties/page_animation_authoring/allOf/1/properties/tier/const",keyword:"const",params:{allowedValue: "high_quality"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err66];}else {vErrors.push(err66);}errors++;}}}}if(data23.planning_and_validation !== undefined){let data28 = data23.planning_and_validation;if(data28 && typeof data28 == "object" && !Array.isArray(data28)){if(data28.tier === undefined){const err67 = {instancePath:instancePath+"/model_routing/planning_and_validation",schemaPath:"#/$defs/modelRoute/required",keyword:"required",params:{missingProperty: "tier"},message:"must have required property '"+"tier"+"'"};if(vErrors === null){vErrors = [err67];}else {vErrors.push(err67);}errors++;}if(data28.actual_model === undefined){const err68 = {instancePath:instancePath+"/model_routing/planning_and_validation",schemaPath:"#/$defs/modelRoute/required",keyword:"required",params:{missingProperty: "actual_model"},message:"must have required property '"+"actual_model"+"'"};if(vErrors === null){vErrors = [err68];}else {vErrors.push(err68);}errors++;}for(const key7 in data28){if(!((key7 === "tier") || (key7 === "actual_model"))){const err69 = {instancePath:instancePath+"/model_routing/planning_and_validation",schemaPath:"#/$defs/modelRoute/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key7},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err69];}else {vErrors.push(err69);}errors++;}}if(data28.tier !== undefined){let data29 = data28.tier;if(!((data29 === "high_quality") || (data29 === "fast"))){const err70 = {instancePath:instancePath+"/model_routing/planning_and_validation/tier",schemaPath:"#/$defs/modelRoute/properties/tier/enum",keyword:"enum",params:{allowedValues: schema34.properties.tier.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err70];}else {vErrors.push(err70);}errors++;}}if(data28.actual_model !== undefined){let data30 = data28.actual_model;if(typeof data30 === "string"){if(func2(data30) < 1){const err71 = {instancePath:instancePath+"/model_routing/planning_and_validation/actual_model",schemaPath:"#/$defs/modelRoute/properties/actual_model/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err71];}else {vErrors.push(err71);}errors++;}}else {const err72 = {instancePath:instancePath+"/model_routing/planning_and_validation/actual_model",schemaPath:"#/$defs/modelRoute/properties/actual_model/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err72];}else {vErrors.push(err72);}errors++;}}}else {const err73 = {instancePath:instancePath+"/model_routing/planning_and_validation",schemaPath:"#/$defs/modelRoute/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err73];}else {vErrors.push(err73);}errors++;}if(data28 && typeof data28 == "object" && !Array.isArray(data28)){if(data28.tier !== undefined){if("fast" !== data28.tier){const err74 = {instancePath:instancePath+"/model_routing/planning_and_validation/tier",schemaPath:"#/properties/model_routing/properties/planning_and_validation/allOf/1/properties/tier/const",keyword:"const",params:{allowedValue: "fast"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err74];}else {vErrors.push(err74);}errors++;}}}}}else {const err75 = {instancePath:instancePath+"/model_routing",schemaPath:"#/properties/model_routing/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err75];}else {vErrors.push(err75);}errors++;}}if(data.shots !== undefined){let data32 = data.shots;if(Array.isArray(data32)){if(data32.length < 1){const err76 = {instancePath:instancePath+"/shots",schemaPath:"#/properties/shots/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err76];}else {vErrors.push(err76);}errors++;}const len0 = data32.length;for(let i0=0; i0<len0; i0++){let data33 = data32[i0];if(data33 && typeof data33 == "object" && !Array.isArray(data33)){if(data33.id === undefined){const err77 = {instancePath:instancePath+"/shots/" + i0,schemaPath:"#/$defs/shot/required",keyword:"required",params:{missingProperty: "id"},message:"must have required property '"+"id"+"'"};if(vErrors === null){vErrors = [err77];}else {vErrors.push(err77);}errors++;}if(data33.start_seconds === undefined){const err78 = {instancePath:instancePath+"/shots/" + i0,schemaPath:"#/$defs/shot/required",keyword:"required",params:{missingProperty: "start_seconds"},message:"must have required property '"+"start_seconds"+"'"};if(vErrors === null){vErrors = [err78];}else {vErrors.push(err78);}errors++;}if(data33.end_seconds === undefined){const err79 = {instancePath:instancePath+"/shots/" + i0,schemaPath:"#/$defs/shot/required",keyword:"required",params:{missingProperty: "end_seconds"},message:"must have required property '"+"end_seconds"+"'"};if(vErrors === null){vErrors = [err79];}else {vErrors.push(err79);}errors++;}if(data33.purpose === undefined){const err80 = {instancePath:instancePath+"/shots/" + i0,schemaPath:"#/$defs/shot/required",keyword:"required",params:{missingProperty: "purpose"},message:"must have required property '"+"purpose"+"'"};if(vErrors === null){vErrors = [err80];}else {vErrors.push(err80);}errors++;}if(data33.hyperframes_refs === undefined){const err81 = {instancePath:instancePath+"/shots/" + i0,schemaPath:"#/$defs/shot/required",keyword:"required",params:{missingProperty: "hyperframes_refs"},message:"must have required property '"+"hyperframes_refs"+"'"};if(vErrors === null){vErrors = [err81];}else {vErrors.push(err81);}errors++;}if(data33.shotcraft_refs === undefined){const err82 = {instancePath:instancePath+"/shots/" + i0,schemaPath:"#/$defs/shot/required",keyword:"required",params:{missingProperty: "shotcraft_refs"},message:"must have required property '"+"shotcraft_refs"+"'"};if(vErrors === null){vErrors = [err82];}else {vErrors.push(err82);}errors++;}if(data33.motion_contract === undefined){const err83 = {instancePath:instancePath+"/shots/" + i0,schemaPath:"#/$defs/shot/required",keyword:"required",params:{missingProperty: "motion_contract"},message:"must have required property '"+"motion_contract"+"'"};if(vErrors === null){vErrors = [err83];}else {vErrors.push(err83);}errors++;}for(const key8 in data33){if(!((((((((key8 === "id") || (key8 === "start_seconds")) || (key8 === "end_seconds")) || (key8 === "purpose")) || (key8 === "hyperframes_refs")) || (key8 === "shotcraft_refs")) || (key8 === "narration_scene")) || (key8 === "motion_contract"))){const err84 = {instancePath:instancePath+"/shots/" + i0,schemaPath:"#/$defs/shot/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key8},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err84];}else {vErrors.push(err84);}errors++;}}if(data33.id !== undefined){let data34 = data33.id;if(typeof data34 === "string"){if(!pattern4.test(data34)){const err85 = {instancePath:instancePath+"/shots/" + i0+"/id",schemaPath:"#/$defs/shot/properties/id/pattern",keyword:"pattern",params:{pattern: "^[a-z][a-z0-9-]*$"},message:"must match pattern \""+"^[a-z][a-z0-9-]*$"+"\""};if(vErrors === null){vErrors = [err85];}else {vErrors.push(err85);}errors++;}}else {const err86 = {instancePath:instancePath+"/shots/" + i0+"/id",schemaPath:"#/$defs/shot/properties/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err86];}else {vErrors.push(err86);}errors++;}}if(data33.start_seconds !== undefined){let data35 = data33.start_seconds;if(typeof data35 == "number"){if(data35 < 0 || isNaN(data35)){const err87 = {instancePath:instancePath+"/shots/" + i0+"/start_seconds",schemaPath:"#/$defs/shot/properties/start_seconds/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err87];}else {vErrors.push(err87);}errors++;}}else {const err88 = {instancePath:instancePath+"/shots/" + i0+"/start_seconds",schemaPath:"#/$defs/shot/properties/start_seconds/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err88];}else {vErrors.push(err88);}errors++;}}if(data33.end_seconds !== undefined){let data36 = data33.end_seconds;if(typeof data36 == "number"){if(data36 <= 0 || isNaN(data36)){const err89 = {instancePath:instancePath+"/shots/" + i0+"/end_seconds",schemaPath:"#/$defs/shot/properties/end_seconds/exclusiveMinimum",keyword:"exclusiveMinimum",params:{comparison: ">", limit: 0},message:"must be > 0"};if(vErrors === null){vErrors = [err89];}else {vErrors.push(err89);}errors++;}}else {const err90 = {instancePath:instancePath+"/shots/" + i0+"/end_seconds",schemaPath:"#/$defs/shot/properties/end_seconds/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err90];}else {vErrors.push(err90);}errors++;}}if(data33.purpose !== undefined){let data37 = data33.purpose;if(typeof data37 === "string"){if(func2(data37) < 1){const err91 = {instancePath:instancePath+"/shots/" + i0+"/purpose",schemaPath:"#/$defs/shot/properties/purpose/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err91];}else {vErrors.push(err91);}errors++;}}else {const err92 = {instancePath:instancePath+"/shots/" + i0+"/purpose",schemaPath:"#/$defs/shot/properties/purpose/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err92];}else {vErrors.push(err92);}errors++;}}if(data33.hyperframes_refs !== undefined){let data38 = data33.hyperframes_refs;if(Array.isArray(data38)){if(data38.length < 1){const err93 = {instancePath:instancePath+"/shots/" + i0+"/hyperframes_refs",schemaPath:"#/$defs/shot/properties/hyperframes_refs/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err93];}else {vErrors.push(err93);}errors++;}const len1 = data38.length;for(let i1=0; i1<len1; i1++){let data39 = data38[i1];if(typeof data39 === "string"){if(func2(data39) < 1){const err94 = {instancePath:instancePath+"/shots/" + i0+"/hyperframes_refs/" + i1,schemaPath:"#/$defs/shot/properties/hyperframes_refs/items/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err94];}else {vErrors.push(err94);}errors++;}}else {const err95 = {instancePath:instancePath+"/shots/" + i0+"/hyperframes_refs/" + i1,schemaPath:"#/$defs/shot/properties/hyperframes_refs/items/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err95];}else {vErrors.push(err95);}errors++;}}let i2 = data38.length;let j0;if(i2 > 1){const indices0 = {};for(;i2--;){let item0 = data38[i2];if(typeof item0 !== "string"){continue;}if(typeof indices0[item0] == "number"){j0 = indices0[item0];const err96 = {instancePath:instancePath+"/shots/" + i0+"/hyperframes_refs",schemaPath:"#/$defs/shot/properties/hyperframes_refs/uniqueItems",keyword:"uniqueItems",params:{i: i2, j: j0},message:"must NOT have duplicate items (items ## "+j0+" and "+i2+" are identical)"};if(vErrors === null){vErrors = [err96];}else {vErrors.push(err96);}errors++;break;}indices0[item0] = i2;}}}else {const err97 = {instancePath:instancePath+"/shots/" + i0+"/hyperframes_refs",schemaPath:"#/$defs/shot/properties/hyperframes_refs/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err97];}else {vErrors.push(err97);}errors++;}}if(data33.shotcraft_refs !== undefined){let data40 = data33.shotcraft_refs;if(Array.isArray(data40)){if(data40.length < 1){const err98 = {instancePath:instancePath+"/shots/" + i0+"/shotcraft_refs",schemaPath:"#/$defs/shot/properties/shotcraft_refs/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err98];}else {vErrors.push(err98);}errors++;}const len2 = data40.length;for(let i3=0; i3<len2; i3++){let data41 = data40[i3];if(typeof data41 === "string"){if(func2(data41) < 1){const err99 = {instancePath:instancePath+"/shots/" + i0+"/shotcraft_refs/" + i3,schemaPath:"#/$defs/shot/properties/shotcraft_refs/items/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err99];}else {vErrors.push(err99);}errors++;}}else {const err100 = {instancePath:instancePath+"/shots/" + i0+"/shotcraft_refs/" + i3,schemaPath:"#/$defs/shot/properties/shotcraft_refs/items/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err100];}else {vErrors.push(err100);}errors++;}}let i4 = data40.length;let j1;if(i4 > 1){const indices1 = {};for(;i4--;){let item1 = data40[i4];if(typeof item1 !== "string"){continue;}if(typeof indices1[item1] == "number"){j1 = indices1[item1];const err101 = {instancePath:instancePath+"/shots/" + i0+"/shotcraft_refs",schemaPath:"#/$defs/shot/properties/shotcraft_refs/uniqueItems",keyword:"uniqueItems",params:{i: i4, j: j1},message:"must NOT have duplicate items (items ## "+j1+" and "+i4+" are identical)"};if(vErrors === null){vErrors = [err101];}else {vErrors.push(err101);}errors++;break;}indices1[item1] = i4;}}}else {const err102 = {instancePath:instancePath+"/shots/" + i0+"/shotcraft_refs",schemaPath:"#/$defs/shot/properties/shotcraft_refs/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err102];}else {vErrors.push(err102);}errors++;}}if(data33.narration_scene !== undefined){let data42 = data33.narration_scene;if((typeof data42 !== "string") && (data42 !== null)){const err103 = {instancePath:instancePath+"/shots/" + i0+"/narration_scene",schemaPath:"#/$defs/shot/properties/narration_scene/type",keyword:"type",params:{type: schema36.properties.narration_scene.type},message:"must be string,null"};if(vErrors === null){vErrors = [err103];}else {vErrors.push(err103);}errors++;}}if(data33.motion_contract !== undefined){let data43 = data33.motion_contract;if(data43 && typeof data43 == "object" && !Array.isArray(data43)){if(data43.motion_preset === undefined){const err104 = {instancePath:instancePath+"/shots/" + i0+"/motion_contract",schemaPath:"#/$defs/shot/properties/motion_contract/required",keyword:"required",params:{missingProperty: "motion_preset"},message:"must have required property '"+"motion_preset"+"'"};if(vErrors === null){vErrors = [err104];}else {vErrors.push(err104);}errors++;}if(data43.energy === undefined){const err105 = {instancePath:instancePath+"/shots/" + i0+"/motion_contract",schemaPath:"#/$defs/shot/properties/motion_contract/required",keyword:"required",params:{missingProperty: "energy"},message:"must have required property '"+"energy"+"'"};if(vErrors === null){vErrors = [err105];}else {vErrors.push(err105);}errors++;}if(data43.camera === undefined){const err106 = {instancePath:instancePath+"/shots/" + i0+"/motion_contract",schemaPath:"#/$defs/shot/properties/motion_contract/required",keyword:"required",params:{missingProperty: "camera"},message:"must have required property '"+"camera"+"'"};if(vErrors === null){vErrors = [err106];}else {vErrors.push(err106);}errors++;}if(data43.transition === undefined){const err107 = {instancePath:instancePath+"/shots/" + i0+"/motion_contract",schemaPath:"#/$defs/shot/properties/motion_contract/required",keyword:"required",params:{missingProperty: "transition"},message:"must have required property '"+"transition"+"'"};if(vErrors === null){vErrors = [err107];}else {vErrors.push(err107);}errors++;}if(data43.shotcraft_commitments === undefined){const err108 = {instancePath:instancePath+"/shots/" + i0+"/motion_contract",schemaPath:"#/$defs/shot/properties/motion_contract/required",keyword:"required",params:{missingProperty: "shotcraft_commitments"},message:"must have required property '"+"shotcraft_commitments"+"'"};if(vErrors === null){vErrors = [err108];}else {vErrors.push(err108);}errors++;}if(data43.readable_rest_seconds === undefined){const err109 = {instancePath:instancePath+"/shots/" + i0+"/motion_contract",schemaPath:"#/$defs/shot/properties/motion_contract/required",keyword:"required",params:{missingProperty: "readable_rest_seconds"},message:"must have required property '"+"readable_rest_seconds"+"'"};if(vErrors === null){vErrors = [err109];}else {vErrors.push(err109);}errors++;}for(const key9 in data43){if(!((((((key9 === "motion_preset") || (key9 === "energy")) || (key9 === "camera")) || (key9 === "transition")) || (key9 === "shotcraft_commitments")) || (key9 === "readable_rest_seconds"))){const err110 = {instancePath:instancePath+"/shots/" + i0+"/motion_contract",schemaPath:"#/$defs/shot/properties/motion_contract/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key9},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err110];}else {vErrors.push(err110);}errors++;}}if(data43.motion_preset !== undefined){let data44 = data43.motion_preset;if(!((((((((data44 === "none") || (data44 === "slam-entrance")) || (data44 === "whip-transition")) || (data44 === "crash-zoom")) || (data44 === "push-stack-wipe")) || (data44 === "dataviz-landscape-open")) || (data44 === "panel-grid-reflow")) || (data44 === "brand-resolve"))){const err111 = {instancePath:instancePath+"/shots/" + i0+"/motion_contract/motion_preset",schemaPath:"#/$defs/shot/properties/motion_contract/properties/motion_preset/enum",keyword:"enum",params:{allowedValues: schema36.properties.motion_contract.properties.motion_preset.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err111];}else {vErrors.push(err111);}errors++;}}if(data43.energy !== undefined){let data45 = data43.energy;if(!((((data45 === "quiet") || (data45 === "balanced")) || (data45 === "high")) || (data45 === "cinematic"))){const err112 = {instancePath:instancePath+"/shots/" + i0+"/motion_contract/energy",schemaPath:"#/$defs/shot/properties/motion_contract/properties/energy/enum",keyword:"enum",params:{allowedValues: schema36.properties.motion_contract.properties.energy.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err112];}else {vErrors.push(err112);}errors++;}}if(data43.camera !== undefined){let data46 = data43.camera;if(typeof data46 === "string"){if(func2(data46) < 1){const err113 = {instancePath:instancePath+"/shots/" + i0+"/motion_contract/camera",schemaPath:"#/$defs/shot/properties/motion_contract/properties/camera/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err113];}else {vErrors.push(err113);}errors++;}}else {const err114 = {instancePath:instancePath+"/shots/" + i0+"/motion_contract/camera",schemaPath:"#/$defs/shot/properties/motion_contract/properties/camera/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err114];}else {vErrors.push(err114);}errors++;}}if(data43.transition !== undefined){let data47 = data43.transition;if(typeof data47 === "string"){if(func2(data47) < 1){const err115 = {instancePath:instancePath+"/shots/" + i0+"/motion_contract/transition",schemaPath:"#/$defs/shot/properties/motion_contract/properties/transition/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err115];}else {vErrors.push(err115);}errors++;}}else {const err116 = {instancePath:instancePath+"/shots/" + i0+"/motion_contract/transition",schemaPath:"#/$defs/shot/properties/motion_contract/properties/transition/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err116];}else {vErrors.push(err116);}errors++;}}if(data43.shotcraft_commitments !== undefined){let data48 = data43.shotcraft_commitments;if(Array.isArray(data48)){if(data48.length < 1){const err117 = {instancePath:instancePath+"/shots/" + i0+"/motion_contract/shotcraft_commitments",schemaPath:"#/$defs/shot/properties/motion_contract/properties/shotcraft_commitments/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err117];}else {vErrors.push(err117);}errors++;}const len3 = data48.length;for(let i5=0; i5<len3; i5++){let data49 = data48[i5];if(typeof data49 === "string"){if(func2(data49) < 1){const err118 = {instancePath:instancePath+"/shots/" + i0+"/motion_contract/shotcraft_commitments/" + i5,schemaPath:"#/$defs/shot/properties/motion_contract/properties/shotcraft_commitments/items/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err118];}else {vErrors.push(err118);}errors++;}}else {const err119 = {instancePath:instancePath+"/shots/" + i0+"/motion_contract/shotcraft_commitments/" + i5,schemaPath:"#/$defs/shot/properties/motion_contract/properties/shotcraft_commitments/items/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err119];}else {vErrors.push(err119);}errors++;}}}else {const err120 = {instancePath:instancePath+"/shots/" + i0+"/motion_contract/shotcraft_commitments",schemaPath:"#/$defs/shot/properties/motion_contract/properties/shotcraft_commitments/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err120];}else {vErrors.push(err120);}errors++;}}if(data43.readable_rest_seconds !== undefined){let data50 = data43.readable_rest_seconds;if(typeof data50 == "number"){if(data50 < 0 || isNaN(data50)){const err121 = {instancePath:instancePath+"/shots/" + i0+"/motion_contract/readable_rest_seconds",schemaPath:"#/$defs/shot/properties/motion_contract/properties/readable_rest_seconds/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err121];}else {vErrors.push(err121);}errors++;}}else {const err122 = {instancePath:instancePath+"/shots/" + i0+"/motion_contract/readable_rest_seconds",schemaPath:"#/$defs/shot/properties/motion_contract/properties/readable_rest_seconds/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err122];}else {vErrors.push(err122);}errors++;}}}else {const err123 = {instancePath:instancePath+"/shots/" + i0+"/motion_contract",schemaPath:"#/$defs/shot/properties/motion_contract/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err123];}else {vErrors.push(err123);}errors++;}}}else {const err124 = {instancePath:instancePath+"/shots/" + i0,schemaPath:"#/$defs/shot/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err124];}else {vErrors.push(err124);}errors++;}}}else {const err125 = {instancePath:instancePath+"/shots",schemaPath:"#/properties/shots/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err125];}else {vErrors.push(err125);}errors++;}}if(data.assumptions !== undefined){let data51 = data.assumptions;if(Array.isArray(data51)){const len4 = data51.length;for(let i6=0; i6<len4; i6++){if(typeof data51[i6] !== "string"){const err126 = {instancePath:instancePath+"/assumptions/" + i6,schemaPath:"#/properties/assumptions/items/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err126];}else {vErrors.push(err126);}errors++;}}}else {const err127 = {instancePath:instancePath+"/assumptions",schemaPath:"#/properties/assumptions/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err127];}else {vErrors.push(err127);}errors++;}}}else {const err128 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err128];}else {vErrors.push(err128);}errors++;}validate20.errors = vErrors;return errors === 0;}validate20.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
@@ -4,27 +4,72 @@
4
4
  from __future__ import annotations
5
5
 
6
6
  import argparse
7
+ import json
7
8
  import re
8
9
  import sys
10
+ from html.parser import HTMLParser
9
11
  from pathlib import Path
10
12
 
11
13
 
12
- def has_attr(html: str, name: str) -> bool:
13
- return re.search(rf"\b{name}\s*=\s*['\"]?[^'\"\s>]+", html, re.IGNORECASE) is not None
14
-
15
-
16
- def numeric_attr(html: str, name: str) -> float | None:
17
- match = re.search(
18
- rf"\b{name}\s*=\s*(?:['\"]([^'\"]+)['\"]|([^\s>]+))",
19
- html,
20
- re.IGNORECASE,
21
- )
22
- if not match:
23
- return None
14
+ MOTION_HELPERS = {
15
+ "slam-entrance": "slamEntrance",
16
+ "whip-transition": "whipTransition",
17
+ "crash-zoom": "crashZoom",
18
+ "push-stack-wipe": "pushStackWipe",
19
+ "dataviz-landscape-open": "datavizLandscapeOpen",
20
+ "panel-grid-reflow": "panelGridReflow",
21
+ "brand-resolve": "brandResolve",
22
+ }
23
+
24
+
25
+ class CompositionParser(HTMLParser):
26
+ def __init__(self) -> None:
27
+ super().__init__(convert_charrefs=True)
28
+ self.elements: list[tuple[str, dict[str, str | None]]] = []
29
+ self.scripts: list[str] = []
30
+ self.styles: list[str] = []
31
+ self.comments: list[str] = []
32
+ self._raw_tag: str | None = None
33
+ self._raw_text: list[str] = []
34
+
35
+ def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
36
+ self.elements.append((tag.lower(), {name.lower(): value for name, value in attrs}))
37
+ if tag.lower() in ("script", "style"):
38
+ self._raw_tag = tag.lower()
39
+ self._raw_text = []
40
+
41
+ def handle_startendtag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
42
+ self.handle_starttag(tag, attrs)
43
+ self.handle_endtag(tag)
44
+
45
+ def handle_data(self, data: str) -> None:
46
+ if self._raw_tag:
47
+ self._raw_text.append(data)
48
+
49
+ def handle_endtag(self, tag: str) -> None:
50
+ if tag.lower() != self._raw_tag:
51
+ return
52
+ target = self.scripts if self._raw_tag == "script" else self.styles
53
+ target.append("".join(self._raw_text))
54
+ self._raw_tag = None
55
+ self._raw_text = []
56
+
57
+ def handle_comment(self, data: str) -> None:
58
+ self.comments.append(data)
59
+
60
+
61
+ def parse_html(html: str) -> CompositionParser:
62
+ parser = CompositionParser()
63
+ parser.feed(html)
64
+ parser.close()
65
+ return parser
66
+
67
+
68
+ def positive_number(value: str | None) -> bool:
24
69
  try:
25
- return float(match.group(1) or match.group(2))
70
+ return value is not None and float(value) > 0
26
71
  except ValueError:
27
- return None
72
+ return False
28
73
 
29
74
 
30
75
  def check_html(
@@ -32,70 +77,98 @@ def check_html(
32
77
  *,
33
78
  component: bool = False,
34
79
  preview_controls: bool = False,
80
+ shot_plan: dict | None = None,
35
81
  ) -> tuple[list[str], list[str]]:
36
82
  failures: list[str] = []
37
83
  warnings: list[str] = []
84
+ document = parse_html(html)
85
+ script = "\n".join(document.scripts)
86
+ style = "\n".join(document.styles)
87
+ composition_elements = [
88
+ attrs for _, attrs in document.elements if attrs.get("data-composition-id")
89
+ ]
38
90
 
39
91
  if not component:
92
+ composition = composition_elements[0] if composition_elements else {}
40
93
  for attr in ("data-width", "data-height", "data-duration", "data-composition-id"):
41
- if not has_attr(html, attr):
94
+ if not composition.get(attr):
42
95
  failures.append(f"missing {attr}")
43
96
  for attr in ("data-width", "data-height", "data-duration"):
44
- if has_attr(html, attr):
45
- value = numeric_attr(html, attr)
46
- if value is None or value <= 0:
47
- failures.append(f"{attr} must be a positive number")
97
+ if attr in composition and not positive_number(composition.get(attr)):
98
+ failures.append(f"{attr} must be a positive number")
48
99
 
49
- if "<!-- paste from " in html:
100
+ if any("paste from " in comment.lower() for comment in document.comments):
50
101
  failures.append("unresolved component paste placeholder")
51
102
 
52
103
  paused_timeline = re.search(
53
104
  r"gsap\s*\.\s*timeline\s*\(\s*\{[^}]*paused\s*:\s*true",
54
- html,
105
+ script,
55
106
  re.IGNORECASE | re.DOTALL,
56
107
  )
57
108
  if not paused_timeline:
58
109
  failures.append("missing gsap.timeline({ paused: true })")
59
110
 
60
- if "window.__timelines" not in html:
111
+ if "window.__timelines" not in script:
61
112
  failures.append("missing window.__timelines registration")
62
113
 
63
- if re.search(r"\bDate\s*\.\s*now\s*\(", html):
114
+ if re.search(r"\bDate\s*\.\s*now\s*\(", script):
64
115
  failures.append("uses Date.now(); render progress must be timeline-driven")
65
116
 
66
- if re.search(r"\bsetInterval\s*\(", html):
117
+ if re.search(r"\bsetInterval\s*\(", script):
67
118
  failures.append("uses setInterval(); primary render progress must be timeline-driven")
68
119
 
69
- if re.search(r"\bMath\s*\.\s*random\s*\(", html):
120
+ if re.search(r"\bMath\s*\.\s*random\s*\(", script):
70
121
  failures.append("uses Math.random(); visual generation must use a fixed-seed deterministic PRNG")
71
122
 
72
- if re.search(r"\brequestAnimationFrame\s*\(", html) and "fallback" not in html.lower():
123
+ if re.search(r"\brequestAnimationFrame\s*\(", script) and "fallback" not in script.lower():
73
124
  warnings.append("requestAnimationFrame found; ensure it is not the primary render clock and has render fallback")
74
125
 
75
- if "<style" in html.lower() and "[data-composition-id" not in html:
126
+ if document.styles and "[data-composition-id" not in style:
76
127
  warnings.append("style block found without [data-composition-id] scoped selector")
77
128
 
78
- if len(re.findall(r"data-duration\s*=\s*['\"]?(\d+(?:\.\d+)?)", html)) > 1:
129
+ if sum("data-duration" in attrs for _, attrs in document.elements) > 1:
79
130
  warnings.append("multiple data-duration values found; verify the intended render duration")
80
131
 
81
132
  if preview_controls:
82
- if "data-hyper-preview-ui" not in html:
133
+ attrs = [attributes for _, attributes in document.elements]
134
+ if not any("data-hyper-preview-ui" in attributes for attributes in attrs):
83
135
  failures.append("missing preview controls: data-hyper-preview-ui")
84
- if "data-hyper-preview-page" not in html:
136
+ if not any("data-hyper-preview-page" in attributes for attributes in attrs):
85
137
  failures.append("missing preview controls: page indicator")
86
- if "data-hyper-preview-progress" not in html:
138
+ if not any("data-hyper-preview-progress" in attributes for attributes in attrs):
87
139
  failures.append("missing preview controls: progress input")
88
- if not re.search(r"<input[^>]+type=[\"']range[\"']", html, re.IGNORECASE | re.DOTALL):
140
+ if not any(tag == "input" and attributes.get("type") == "range" for tag, attributes in document.elements):
89
141
  failures.append("missing preview controls: range input")
90
- if "ArrowLeft" not in html or "ArrowRight" not in html:
142
+ if "ArrowLeft" not in script or "ArrowRight" not in script:
91
143
  failures.append("missing preview controls: left/right keyboard handlers")
92
- if 'params.get("render") === "1"' not in html:
144
+ if 'params.get("render") === "1"' not in script:
93
145
  failures.append("missing preview controls: ?render=1 hidden-mode check")
94
- if 'params.get("preview") === "0"' not in html:
146
+ if 'params.get("preview") === "0"' not in script:
95
147
  failures.append("missing preview controls: ?preview=0 hidden-mode check")
96
- if 'dataset.renderMode === "video"' not in html:
148
+ if 'dataset.renderMode === "video"' not in script:
97
149
  failures.append("missing preview controls: data-render-mode video hidden-mode check")
98
150
 
151
+ if shot_plan is not None:
152
+ shot_elements = {
153
+ attrs.get("data-shot-id"): attrs
154
+ for _, attrs in document.elements
155
+ if attrs.get("data-shot-id")
156
+ }
157
+ for shot in shot_plan.get("shots", []):
158
+ shot_id = shot.get("id")
159
+ preset = shot.get("motion_contract", {}).get("motion_preset")
160
+ attrs = shot_elements.get(shot_id)
161
+ if attrs is None:
162
+ failures.append(f"missing data-shot-id element for shot: {shot_id}")
163
+ continue
164
+ if attrs.get("data-motion-preset") != preset:
165
+ failures.append(f"shot {shot_id} data-motion-preset must be {preset}")
166
+ helper = MOTION_HELPERS.get(preset)
167
+ if helper:
168
+ calls_only = re.sub(rf"\bfunction\s+{re.escape(helper)}\s*\(", "", script)
169
+ if not re.search(rf"\b{re.escape(helper)}\s*\(", calls_only):
170
+ failures.append(f"shot {shot_id} must call motion-pack helper {helper}")
171
+
99
172
  return failures, warnings
100
173
 
101
174
 
@@ -112,6 +185,10 @@ def main() -> int:
112
185
  action="store_true",
113
186
  help="Require injected Hyper Animator preview controls",
114
187
  )
188
+ parser.add_argument(
189
+ "--shot-plan",
190
+ help="Cross-check data-shot-id and motion-pack usage against a shot-plan JSON file",
191
+ )
115
192
  args = parser.parse_args()
116
193
 
117
194
  path = Path(args.html_file)
@@ -120,10 +197,19 @@ def main() -> int:
120
197
  return 2
121
198
 
122
199
  html = path.read_text(encoding="utf-8")
200
+ shot_plan = None
201
+ if args.shot_plan:
202
+ try:
203
+ shot_plan = json.loads(Path(args.shot_plan).read_text(encoding="utf-8"))
204
+ except (OSError, json.JSONDecodeError) as error:
205
+ print(f"FAIL: cannot read shot plan: {error}", file=sys.stderr)
206
+ return 2
207
+
123
208
  failures, warnings = check_html(
124
209
  html,
125
210
  component=args.component,
126
211
  preview_controls=args.preview_controls,
212
+ shot_plan=shot_plan,
127
213
  )
128
214
 
129
215
  for warning in warnings: