davinci-resolve-mcp 2.57.4 → 2.58.0

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 (328) hide show
  1. package/AGENTS.md +20 -0
  2. package/CHANGELOG.md +197 -2
  3. package/LICENSE +1 -1
  4. package/README.md +73 -1
  5. package/bin/davinci-resolve-advanced-mcp.mjs +20 -0
  6. package/docs/README.md +19 -0
  7. package/docs/SKILL.md +62 -1
  8. package/docs/contributing.md +1 -1
  9. package/docs/guides/control-panel.md +9 -0
  10. package/docs/kernels/README.md +21 -0
  11. package/docs/kernels/audio-fairlight-kernel.md +22 -0
  12. package/docs/kernels/color-grade-kernel.md +51 -0
  13. package/docs/kernels/fusion-composition-kernel.md +17 -0
  14. package/docs/kernels/media-pool-ingest-kernel.md +20 -0
  15. package/docs/kernels/render-deliver-kernel.md +32 -0
  16. package/docs/kernels/timeline-conform-interchange-kernel.md +39 -0
  17. package/docs/kernels/timeline-edit-kernel.md +17 -0
  18. package/docs/process/release-process.md +29 -7
  19. package/docs/reference/api-coverage.md +1 -1
  20. package/docs/reference/api-limitations.md +38 -1
  21. package/install.py +27 -4
  22. package/package.json +23 -2
  23. package/resolve-advanced/README.md +194 -0
  24. package/resolve-advanced/package.json +50 -0
  25. package/resolve-advanced/server/black-balance.mjs +92 -0
  26. package/resolve-advanced/server/capabilities.mjs +86 -0
  27. package/resolve-advanced/server/cdl-io.mjs +76 -0
  28. package/resolve-advanced/server/contrast-normalize.mjs +180 -0
  29. package/resolve-advanced/server/db-patch.mjs +91 -0
  30. package/resolve-advanced/server/deliverable-entities.mjs +86 -0
  31. package/resolve-advanced/server/deliverable-qc.mjs +270 -0
  32. package/resolve-advanced/server/editorial.mjs +370 -0
  33. package/resolve-advanced/server/exposure-level.mjs +129 -0
  34. package/resolve-advanced/server/extract-frames.mjs +196 -0
  35. package/resolve-advanced/server/ffprobe-media.mjs +88 -0
  36. package/resolve-advanced/server/gamut-legal.mjs +112 -0
  37. package/resolve-advanced/server/grade-body-patch.mjs +246 -0
  38. package/resolve-advanced/server/grade-transfer.mjs +66 -0
  39. package/resolve-advanced/server/group-grade-read.mjs +137 -0
  40. package/resolve-advanced/server/index.mjs +105 -0
  41. package/resolve-advanced/server/lib.mjs +84 -0
  42. package/resolve-advanced/server/libs.mjs +65 -0
  43. package/resolve-advanced/server/lineage-db.mjs +479 -0
  44. package/resolve-advanced/server/lut-apply.mjs +61 -0
  45. package/resolve-advanced/server/match-to-reference.mjs +173 -0
  46. package/resolve-advanced/server/media-inventory.mjs +169 -0
  47. package/resolve-advanced/server/media-ops.mjs +243 -0
  48. package/resolve-advanced/server/node-meta-db.mjs +62 -0
  49. package/resolve-advanced/server/node-provenance.mjs +102 -0
  50. package/resolve-advanced/server/offline-ref-db.mjs +420 -0
  51. package/resolve-advanced/server/offline-ref.mjs +179 -0
  52. package/resolve-advanced/server/project-db.mjs +233 -0
  53. package/resolve-advanced/server/provenance-audit.mjs +210 -0
  54. package/resolve-advanced/server/qc-frame.mjs +224 -0
  55. package/resolve-advanced/server/qc-sampler.mjs +67 -0
  56. package/resolve-advanced/server/readback.mjs +77 -0
  57. package/resolve-advanced/server/render-manifest.mjs +93 -0
  58. package/resolve-advanced/server/reverse-clip-db.mjs +270 -0
  59. package/resolve-advanced/server/runner-apply-contract.mjs +64 -0
  60. package/resolve-advanced/server/runner.mjs +244 -0
  61. package/resolve-advanced/server/saturation-match.mjs +108 -0
  62. package/resolve-advanced/server/scope-read.mjs +257 -0
  63. package/resolve-advanced/server/season-look.mjs +96 -0
  64. package/resolve-advanced/server/shot-intent.mjs +82 -0
  65. package/resolve-advanced/server/shot-match.mjs +147 -0
  66. package/resolve-advanced/server/skin-match.mjs +205 -0
  67. package/resolve-advanced/server/spec-compile.mjs +217 -0
  68. package/resolve-advanced/server/tone-curve-transfer.mjs +184 -0
  69. package/resolve-advanced/server/tool-catalog.mjs +265 -0
  70. package/resolve-advanced/server/tools/audio.mjs +54 -0
  71. package/resolve-advanced/server/tools/audio_plan.mjs +58 -0
  72. package/resolve-advanced/server/tools/capabilities.mjs +16 -0
  73. package/resolve-advanced/server/tools/color_trace.mjs +165 -0
  74. package/resolve-advanced/server/tools/conform.mjs +291 -0
  75. package/resolve-advanced/server/tools/deliverable.mjs +105 -0
  76. package/resolve-advanced/server/tools/drp.mjs +232 -0
  77. package/resolve-advanced/server/tools/drt.mjs +200 -0
  78. package/resolve-advanced/server/tools/drx.mjs +943 -0
  79. package/resolve-advanced/server/tools/editorial.mjs +69 -0
  80. package/resolve-advanced/server/tools/fairlight.mjs +108 -0
  81. package/resolve-advanced/server/tools/fusion.mjs +64 -0
  82. package/resolve-advanced/server/tools/media.mjs +102 -0
  83. package/resolve-advanced/server/tools/offline_ref.mjs +123 -0
  84. package/resolve-advanced/server/tools/pipeline.mjs +137 -0
  85. package/resolve-advanced/server/tools/project_db.mjs +200 -0
  86. package/resolve-advanced/server/tools/project_read.mjs +212 -0
  87. package/resolve-advanced/server/tools/provenance.mjs +73 -0
  88. package/resolve-advanced/server/verify-grade.mjs +113 -0
  89. package/resolve-advanced/server/white-balance-match.mjs +122 -0
  90. package/resolve-advanced/vendor/audio/format-converter.js +330 -0
  91. package/resolve-advanced/vendor/audio/split.js +173 -0
  92. package/resolve-advanced/vendor/audio/trim.js +188 -0
  93. package/resolve-advanced/vendor/audio-fairlight/index.js +391 -0
  94. package/resolve-advanced/vendor/conform-qc/adapters/frame-sampler.js +56 -0
  95. package/resolve-advanced/vendor/conform-qc/adapters/local-ffmpeg-sampler.js +65 -0
  96. package/resolve-advanced/vendor/conform-qc/adapters/rendernode-ffmpeg-sampler.js +50 -0
  97. package/resolve-advanced/vendor/conform-qc/adapters/resolve/driver.py +176 -0
  98. package/resolve-advanced/vendor/conform-qc/adapters/resolve-driver.js +61 -0
  99. package/resolve-advanced/vendor/conform-qc/adapters/resolve-headless-driver.js +86 -0
  100. package/resolve-advanced/vendor/conform-qc/adapters/vision-validator.js +75 -0
  101. package/resolve-advanced/vendor/conform-qc/cli.js +63 -0
  102. package/resolve-advanced/vendor/conform-qc/compare/advisory.js +106 -0
  103. package/resolve-advanced/vendor/conform-qc/compare/decode.js +36 -0
  104. package/resolve-advanced/vendor/conform-qc/compare/index.js +61 -0
  105. package/resolve-advanced/vendor/conform-qc/compare/locate.js +70 -0
  106. package/resolve-advanced/vendor/conform-qc/compare/metrics.js +409 -0
  107. package/resolve-advanced/vendor/conform-qc/docs/runbook.md +72 -0
  108. package/resolve-advanced/vendor/conform-qc/index.js +49 -0
  109. package/resolve-advanced/vendor/conform-qc/knowledge/index.js +110 -0
  110. package/resolve-advanced/vendor/conform-qc/ops/conform-core.js +137 -0
  111. package/resolve-advanced/vendor/conform-qc/ops/conform-diff.js +109 -0
  112. package/resolve-advanced/vendor/conform-qc/ops/insert.js +108 -0
  113. package/resolve-advanced/vendor/conform-qc/ops/local-conform.js +61 -0
  114. package/resolve-advanced/vendor/conform-qc/ops/patch.js +80 -0
  115. package/resolve-advanced/vendor/conform-qc/ops/trigger.js +32 -0
  116. package/resolve-advanced/vendor/conform-qc/ops/verify.js +105 -0
  117. package/resolve-advanced/vendor/conform-qc/ops/workflow.js +30 -0
  118. package/resolve-advanced/vendor/conform-qc/oracle/emulate.js +155 -0
  119. package/resolve-advanced/vendor/conform-qc/oracle/index.js +38 -0
  120. package/resolve-advanced/vendor/conform-qc/oracle/resolve.js +196 -0
  121. package/resolve-advanced/vendor/conform-qc/package.json +16 -0
  122. package/resolve-advanced/vendor/conform-qc/packaging/emit-aaf.js +30 -0
  123. package/resolve-advanced/vendor/conform-qc/packaging/emit-fcp7.js +64 -0
  124. package/resolve-advanced/vendor/conform-qc/packaging/index.js +169 -0
  125. package/resolve-advanced/vendor/conform-qc/packaging/media-ops.js +56 -0
  126. package/resolve-advanced/vendor/conform-qc/packaging/otio.js +50 -0
  127. package/resolve-advanced/vendor/conform-qc/packaging/relink.js +50 -0
  128. package/resolve-advanced/vendor/conform-qc/packaging/surgical-relink.js +195 -0
  129. package/resolve-advanced/vendor/conform-qc/parse/index.js +54 -0
  130. package/resolve-advanced/vendor/conform-qc/parse/merge-dto.js +27 -0
  131. package/resolve-advanced/vendor/conform-qc/parse/xmeml-geometry.js +256 -0
  132. package/resolve-advanced/vendor/conform-qc/reference/burnin-ocr.js +100 -0
  133. package/resolve-advanced/vendor/conform-qc/reference/sidecar.js +37 -0
  134. package/resolve-advanced/vendor/conform-qc/repair/analysis.js +62 -0
  135. package/resolve-advanced/vendor/conform-qc/repair/index.js +78 -0
  136. package/resolve-advanced/vendor/conform-qc/repair/media-analysis.js +139 -0
  137. package/resolve-advanced/vendor/conform-qc/repair/media-index.js +135 -0
  138. package/resolve-advanced/vendor/conform-qc/repair/strategies.js +250 -0
  139. package/resolve-advanced/vendor/conform-qc/report/index.js +133 -0
  140. package/resolve-advanced/vendor/conform-qc/roles/index.js +96 -0
  141. package/resolve-advanced/vendor/conform-qc/synthetic/generate.js +233 -0
  142. package/resolve-advanced/vendor/conform-qc/test/advisory.test.js +85 -0
  143. package/resolve-advanced/vendor/conform-qc/test/cli.test.js +39 -0
  144. package/resolve-advanced/vendor/conform-qc/test/compare.test.js +152 -0
  145. package/resolve-advanced/vendor/conform-qc/test/conform-diff.test.js +70 -0
  146. package/resolve-advanced/vendor/conform-qc/test/emulate.test.js +51 -0
  147. package/resolve-advanced/vendor/conform-qc/test/frame-sampler.test.js +30 -0
  148. package/resolve-advanced/vendor/conform-qc/test/geometry.test.js +112 -0
  149. package/resolve-advanced/vendor/conform-qc/test/knowledge.test.js +56 -0
  150. package/resolve-advanced/vendor/conform-qc/test/lifecycle.test.js +110 -0
  151. package/resolve-advanced/vendor/conform-qc/test/local-ffmpeg-sampler.test.js +66 -0
  152. package/resolve-advanced/vendor/conform-qc/test/locate.test.js +63 -0
  153. package/resolve-advanced/vendor/conform-qc/test/media-analysis.test.js +63 -0
  154. package/resolve-advanced/vendor/conform-qc/test/media-index-normalized.test.js +65 -0
  155. package/resolve-advanced/vendor/conform-qc/test/media-index-origin.test.js +53 -0
  156. package/resolve-advanced/vendor/conform-qc/test/metrics-fast.test.js +91 -0
  157. package/resolve-advanced/vendor/conform-qc/test/oracle-reverse.test.js +86 -0
  158. package/resolve-advanced/vendor/conform-qc/test/oracle.test.js +136 -0
  159. package/resolve-advanced/vendor/conform-qc/test/p0-acceptance.test.js +60 -0
  160. package/resolve-advanced/vendor/conform-qc/test/p1-acceptance.test.js +75 -0
  161. package/resolve-advanced/vendor/conform-qc/test/p45-patch-acceptance.test.js +102 -0
  162. package/resolve-advanced/vendor/conform-qc/test/packaging.test.js +133 -0
  163. package/resolve-advanced/vendor/conform-qc/test/parse-stubs.test.js +28 -0
  164. package/resolve-advanced/vendor/conform-qc/test/repair-strategies.test.js +128 -0
  165. package/resolve-advanced/vendor/conform-qc/test/repair.test.js +84 -0
  166. package/resolve-advanced/vendor/conform-qc/test/report.test.js +52 -0
  167. package/resolve-advanced/vendor/conform-qc/test/resolve-readback.test.js +71 -0
  168. package/resolve-advanced/vendor/conform-qc/test/roles.test.js +107 -0
  169. package/resolve-advanced/vendor/conform-qc/test/runbook.test.js +30 -0
  170. package/resolve-advanced/vendor/conform-qc/test/scale-residual.test.js +40 -0
  171. package/resolve-advanced/vendor/conform-qc/test/sidecar.test.js +51 -0
  172. package/resolve-advanced/vendor/conform-qc/test/smoke.test.js +87 -0
  173. package/resolve-advanced/vendor/conform-qc/test/surgical-relink.test.js +73 -0
  174. package/resolve-advanced/vendor/conform-qc/test/synthetic.test.js +98 -0
  175. package/resolve-advanced/vendor/conform-qc/test/verify.test.js +93 -0
  176. package/resolve-advanced/vendor/conform-qc/test/vision-validator.test.js +43 -0
  177. package/resolve-advanced/vendor/conform-qc/test/workflow-driver.test.js +46 -0
  178. package/resolve-advanced/vendor/conform-qc/toolset/index.js +53 -0
  179. package/resolve-advanced/vendor/drp-format/README.md +116 -0
  180. package/resolve-advanced/vendor/drp-format/__tests__/_resolve-verify.js +39 -0
  181. package/resolve-advanced/vendor/drp-format/__tests__/add-media-clip.test.js +30 -0
  182. package/resolve-advanced/vendor/drp-format/__tests__/assemble-timeline.test.js +68 -0
  183. package/resolve-advanced/vendor/drp-format/__tests__/author-project.test.js +57 -0
  184. package/resolve-advanced/vendor/drp-format/__tests__/composition-text.test.js +98 -0
  185. package/resolve-advanced/vendor/drp-format/__tests__/diff.test.js +272 -0
  186. package/resolve-advanced/vendor/drp-format/__tests__/effect-filters-compressed.test.js +143 -0
  187. package/resolve-advanced/vendor/drp-format/__tests__/fixtures/p0-3-session30-empty-effectfilters.drp +0 -0
  188. package/resolve-advanced/vendor/drp-format/__tests__/fixtures/retimed-timemap-50pct.hex +1 -0
  189. package/resolve-advanced/vendor/drp-format/__tests__/fixtures/retimed-timemap-dynamic.hex +1 -0
  190. package/resolve-advanced/vendor/drp-format/__tests__/inject-grades.test.js +238 -0
  191. package/resolve-advanced/vendor/drp-format/__tests__/keyed-dict.test.js +85 -0
  192. package/resolve-advanced/vendor/drp-format/__tests__/lut-refs.test.js +119 -0
  193. package/resolve-advanced/vendor/drp-format/__tests__/media-blobs.test.js +25 -0
  194. package/resolve-advanced/vendor/drp-format/__tests__/media-timemap.test.js +119 -0
  195. package/resolve-advanced/vendor/drp-format/__tests__/place-fusion-title.test.js +52 -0
  196. package/resolve-advanced/vendor/drp-format/__tests__/place-generator.test.js +40 -0
  197. package/resolve-advanced/vendor/drp-format/__tests__/place-transition.test.js +48 -0
  198. package/resolve-advanced/vendor/drp-format/__tests__/protobuf-wire.test.js +64 -0
  199. package/resolve-advanced/vendor/drp-format/__tests__/relink-media.test.js +106 -0
  200. package/resolve-advanced/vendor/drp-format/__tests__/splice-clips.test.js +256 -0
  201. package/resolve-advanced/vendor/drp-format/assemble-timeline.js +65 -0
  202. package/resolve-advanced/vendor/drp-format/author-project.js +118 -0
  203. package/resolve-advanced/vendor/drp-format/composition-text.js +213 -0
  204. package/resolve-advanced/vendor/drp-format/curves-encoder.js +763 -0
  205. package/resolve-advanced/vendor/drp-format/diff.js +310 -0
  206. package/resolve-advanced/vendor/drp-format/drp-packager.js +201 -0
  207. package/resolve-advanced/vendor/drp-format/drp-validator.js +809 -0
  208. package/resolve-advanced/vendor/drp-format/effect-encoder.js +640 -0
  209. package/resolve-advanced/vendor/drp-format/extract-lut-refs.js +119 -0
  210. package/resolve-advanced/vendor/drp-format/grade-encoder.example.js +133 -0
  211. package/resolve-advanced/vendor/drp-format/grade-encoder.js +401 -0
  212. package/resolve-advanced/vendor/drp-format/grade-encoder.test.js +171 -0
  213. package/resolve-advanced/vendor/drp-format/grade-encoder.verify.js +249 -0
  214. package/resolve-advanced/vendor/drp-format/grade-node-extractor.js +495 -0
  215. package/resolve-advanced/vendor/drp-format/grade-parameter-decoder.js +774 -0
  216. package/resolve-advanced/vendor/drp-format/index.js +174 -0
  217. package/resolve-advanced/vendor/drp-format/inject-grades.js +219 -0
  218. package/resolve-advanced/vendor/drp-format/keyed-dict.js +227 -0
  219. package/resolve-advanced/vendor/drp-format/marker-encoder.js +532 -0
  220. package/resolve-advanced/vendor/drp-format/media-blobs.js +53 -0
  221. package/resolve-advanced/vendor/drp-format/media-timemap.js +164 -0
  222. package/resolve-advanced/vendor/drp-format/mp-folder-builder.js +293 -0
  223. package/resolve-advanced/vendor/drp-format/node-tree-encoder.js +1246 -0
  224. package/resolve-advanced/vendor/drp-format/package.json +18 -0
  225. package/resolve-advanced/vendor/drp-format/place-fusion-title.js +131 -0
  226. package/resolve-advanced/vendor/drp-format/place-generator.js +77 -0
  227. package/resolve-advanced/vendor/drp-format/place-transition.js +91 -0
  228. package/resolve-advanced/vendor/drp-format/power-window-encoder.js +776 -0
  229. package/resolve-advanced/vendor/drp-format/protobuf-wire.js +111 -0
  230. package/resolve-advanced/vendor/drp-format/qualifier-encoder.js +706 -0
  231. package/resolve-advanced/vendor/drp-format/relink-media.js +218 -0
  232. package/resolve-advanced/vendor/drp-format/rich-title-encoder.js +447 -0
  233. package/resolve-advanced/vendor/drp-format/seq-container-builder.js +572 -0
  234. package/resolve-advanced/vendor/drp-format/seq-surgery.js +155 -0
  235. package/resolve-advanced/vendor/drp-format/splice-clips.js +445 -0
  236. package/resolve-advanced/vendor/drp-format/templates/empty-project.drp +0 -0
  237. package/resolve-advanced/vendor/drp-format/templates/fusion-title.xml +56 -0
  238. package/resolve-advanced/vendor/drp-format/templates/generator-solid-color.xml +21 -0
  239. package/resolve-advanced/vendor/drp-format/templates/media-clip-h264.drp +0 -0
  240. package/resolve-advanced/vendor/drp-format/templates/transition-cross-dissolve.xml +22 -0
  241. package/resolve-advanced/vendor/drp-format/tool-registry.descriptor.js +80 -0
  242. package/resolve-advanced/vendor/drp-format/utils/safe-archive.js +54 -0
  243. package/resolve-advanced/vendor/drp-format/xml-builder.js +759 -0
  244. package/resolve-advanced/vendor/drt-format/__tests__/_resolve-verify.js +21 -0
  245. package/resolve-advanced/vendor/drt-format/__tests__/build.test.js +113 -0
  246. package/resolve-advanced/vendor/drt-format/__tests__/multi-format.test.js +93 -0
  247. package/resolve-advanced/vendor/drt-format/__tests__/parse-real-resolve21.test.js +51 -0
  248. package/resolve-advanced/vendor/drt-format/__tests__/parse.test.js +134 -0
  249. package/resolve-advanced/vendor/drt-format/__tests__/resolve-versions.test.js +120 -0
  250. package/resolve-advanced/vendor/drt-format/__tests__/schema-fingerprint.test.js +62 -0
  251. package/resolve-advanced/vendor/drt-format/__tests__/smoke.test.js +27 -0
  252. package/resolve-advanced/vendor/drt-format/__tests__/validate.test.js +109 -0
  253. package/resolve-advanced/vendor/drt-format/capabilities.js +69 -0
  254. package/resolve-advanced/vendor/drt-format/capability-domains.js +56 -0
  255. package/resolve-advanced/vendor/drt-format/drt-builder.js +42 -0
  256. package/resolve-advanced/vendor/drt-format/drt-parser.js +164 -0
  257. package/resolve-advanced/vendor/drt-format/drt-validator.js +98 -0
  258. package/resolve-advanced/vendor/drt-format/index.js +45 -0
  259. package/resolve-advanced/vendor/drt-format/package.json +17 -0
  260. package/resolve-advanced/vendor/drt-format/resolve-versions.js +153 -0
  261. package/resolve-advanced/vendor/drt-format/schema-fingerprint.js +74 -0
  262. package/resolve-advanced/vendor/drt-format/templates/drt-project-shell.xml +124 -0
  263. package/resolve-advanced/vendor/drx-codec/__tests__/curves-roundtrip.test.js +148 -0
  264. package/resolve-advanced/vendor/drx-codec/__tests__/fixtures/p2-1-face-refinement.drx +58 -0
  265. package/resolve-advanced/vendor/drx-codec/__tests__/fixtures/p2-1-filmgrain.drx +58 -0
  266. package/resolve-advanced/vendor/drx-codec/__tests__/fixtures/p2-1-glow.drx +58 -0
  267. package/resolve-advanced/vendor/drx-codec/__tests__/fixtures/p2-1-lens-flare.drx +58 -0
  268. package/resolve-advanced/vendor/drx-codec/__tests__/fixtures/p2-3-beauty-25.drx +58 -0
  269. package/resolve-advanced/vendor/drx-codec/__tests__/fixtures/p2-3-beauty-75.drx +58 -0
  270. package/resolve-advanced/vendor/drx-codec/__tests__/fixtures/p5-1-node-no-lut.drx +58 -0
  271. package/resolve-advanced/vendor/drx-codec/__tests__/fixtures/p5-1-node-with-lut.drx +58 -0
  272. package/resolve-advanced/vendor/drx-codec/__tests__/hsl-curves-roundtrip.test.js +117 -0
  273. package/resolve-advanced/vendor/drx-codec/__tests__/lut-refs-roundtrip.test.js +112 -0
  274. package/resolve-advanced/vendor/drx-codec/__tests__/matte-roundtrip.test.js +167 -0
  275. package/resolve-advanced/vendor/drx-codec/__tests__/ofx-roundtrip.test.js +215 -0
  276. package/resolve-advanced/vendor/drx-codec/__tests__/ofx-slugs-discovery.test.js +149 -0
  277. package/resolve-advanced/vendor/drx-codec/__tests__/power-window-roundtrip.test.js +230 -0
  278. package/resolve-advanced/vendor/drx-codec/__tests__/qualifier-roundtrip.test.js +226 -0
  279. package/resolve-advanced/vendor/drx-codec/cdl-exporter.js +470 -0
  280. package/resolve-advanced/vendor/drx-codec/color-nlp-parser.js +833 -0
  281. package/resolve-advanced/vendor/drx-codec/data/color-nlp-action-mappings.json +2232 -0
  282. package/resolve-advanced/vendor/drx-codec/drx-generator.js +4123 -0
  283. package/resolve-advanced/vendor/drx-codec/drx-merger.js +320 -0
  284. package/resolve-advanced/vendor/drx-codec/drx-parser.js +1808 -0
  285. package/resolve-advanced/vendor/drx-codec/extract-custom-curves.js +215 -0
  286. package/resolve-advanced/vendor/drx-codec/extract-hsl-curves.js +180 -0
  287. package/resolve-advanced/vendor/drx-codec/extract-lut-refs.js +158 -0
  288. package/resolve-advanced/vendor/drx-codec/extract-matte-finesse.js +83 -0
  289. package/resolve-advanced/vendor/drx-codec/extract-ofx-params.js +226 -0
  290. package/resolve-advanced/vendor/drx-codec/extract-power-window.js +184 -0
  291. package/resolve-advanced/vendor/drx-codec/extract-qualifier.js +99 -0
  292. package/resolve-advanced/vendor/drx-codec/index.js +109 -0
  293. package/resolve-advanced/vendor/drx-codec/node-layout.js +249 -0
  294. package/resolve-advanced/vendor/drx-codec/resolved-drx-generator.js +393 -0
  295. package/resolve-advanced/vendor/drx-codec/tool-resolver.js +974 -0
  296. package/resolve-advanced/vendor/drx-codec/training-examples.js +548 -0
  297. package/resolve-advanced/vendor/drx-codec/vocabulary-intents.js +539 -0
  298. package/resolve-advanced/vendor/drx-parameters/CALIBRATION-STATUS.md +319 -0
  299. package/resolve-advanced/vendor/drx-parameters/DRX-VALUE-SCALING.md +251 -0
  300. package/resolve-advanced/vendor/drx-parameters/calibrated-ranges.json +17737 -0
  301. package/resolve-advanced/vendor/drx-parameters/corrector-types.js +120 -0
  302. package/resolve-advanced/vendor/drx-parameters/index.js +193 -0
  303. package/resolve-advanced/vendor/drx-parameters/parameter-codec.js +451 -0
  304. package/resolve-advanced/vendor/drx-parameters/parameter-ids.js +1292 -0
  305. package/resolve-advanced/vendor/drx-parameters/parameter-ranges.js +570 -0
  306. package/resolve-advanced/vendor/drx-parameters/parameter-validator.js +392 -0
  307. package/resolve-advanced/vendor/drx-parameters/resolvefx-registry.json +14973 -0
  308. package/resolve-advanced/vendor/drx-parameters/tool-registry.descriptor.js +38 -0
  309. package/resolve-advanced/vendor/drx-parameters/tools/drx-analyzer.js +371 -0
  310. package/resolve-advanced/vendor/fairlight/index.js +833 -0
  311. package/resolve-advanced/vendor/fusion-codec/composition-generator.js +401 -0
  312. package/resolve-advanced/vendor/fusion-codec/templates/blur-region.js +103 -0
  313. package/resolve-advanced/vendor/fusion-codec/templates/color-correct.js +87 -0
  314. package/resolve-advanced/vendor/fusion-codec/templates/film-grain.js +106 -0
  315. package/resolve-advanced/vendor/fusion-codec/templates/lower-third.js +241 -0
  316. package/resolve-advanced/vendor/fusion-codec/templates/picture-in-picture.js +188 -0
  317. package/resolve-advanced/vendor/fusion-codec/templates/text-overlay.js +161 -0
  318. package/resolve-advanced/vendor/fusion-codec/templates/title-card.js +184 -0
  319. package/resolve-advanced/vendor/fusion-codec/templates/vignette.js +107 -0
  320. package/resolve-advanced/vendor/fusion-codec/templates/watermark.js +101 -0
  321. package/scripts/agent-rules/README.md +68 -0
  322. package/src/analysis_dashboard.py +233 -1
  323. package/src/granular/common.py +1 -1
  324. package/src/server.py +479 -29
  325. package/src/utils/api_truth.py +117 -0
  326. package/src/utils/cut_ir.py +1 -1
  327. package/src/utils/failure_tracker.py +0 -2
  328. package/src/utils/timeline_xml.py +405 -0
@@ -0,0 +1,809 @@
1
+ /**
2
+ * DaVinci Resolve Project (.drp) Validator
3
+ *
4
+ * Validates DRP files to ensure they will load correctly in DaVinci Resolve.
5
+ * Checks archive structure, XML validity, binary blob integrity, and cross-references.
6
+ *
7
+ * @module drp-validator
8
+ */
9
+
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+ const AdmZip = require('adm-zip');
13
+ const { isSafeArchiveEntryName } = require('./utils/safe-archive');
14
+ const { XMLParser } = require('fast-xml-parser');
15
+
16
+ /**
17
+ * Validation result levels
18
+ */
19
+ const SEVERITY = {
20
+ ERROR: 'error', // Will prevent loading
21
+ WARNING: 'warning', // May cause issues
22
+ INFO: 'info', // Informational only
23
+ };
24
+
25
+ /**
26
+ * Known valid frame rates
27
+ */
28
+ const VALID_FRAME_RATES = [
29
+ 23.976, 23.98, 24, 25, 29.97, 30, 48, 50, 59.94, 60, 120
30
+ ];
31
+
32
+ /**
33
+ * Required files in a DRP archive
34
+ */
35
+ const REQUIRED_FILES = ['project.xml'];
36
+
37
+ /**
38
+ * Valid FieldsBlob type markers
39
+ */
40
+ const VALID_FIELDSBLOB_TYPES = [0x00000001, 0x00000002];
41
+
42
+ /**
43
+ * Main validation result class
44
+ */
45
+ class ValidationResult {
46
+ constructor() {
47
+ this.valid = true;
48
+ this.errors = [];
49
+ this.warnings = [];
50
+ this.info = [];
51
+ this.stats = {
52
+ filesChecked: 0,
53
+ blobsValidated: 0,
54
+ clipsFound: 0,
55
+ groupsFound: 0,
56
+ markersFound: 0,
57
+ };
58
+ }
59
+
60
+ addIssue(severity, category, message, details = null) {
61
+ const issue = { category, message, details };
62
+
63
+ switch (severity) {
64
+ case SEVERITY.ERROR:
65
+ this.valid = false;
66
+ this.errors.push(issue);
67
+ break;
68
+ case SEVERITY.WARNING:
69
+ this.warnings.push(issue);
70
+ break;
71
+ case SEVERITY.INFO:
72
+ this.info.push(issue);
73
+ break;
74
+ }
75
+ }
76
+
77
+ isValid() {
78
+ return this.valid;
79
+ }
80
+
81
+ getSummary() {
82
+ return {
83
+ valid: this.valid,
84
+ errorCount: this.errors.length,
85
+ warningCount: this.warnings.length,
86
+ infoCount: this.info.length,
87
+ stats: this.stats,
88
+ };
89
+ }
90
+
91
+ toJSON() {
92
+ return {
93
+ valid: this.valid,
94
+ summary: this.getSummary(),
95
+ errors: this.errors,
96
+ warnings: this.warnings,
97
+ info: this.info,
98
+ };
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Validate a DRP file
104
+ * @param {string|Buffer} input - File path or buffer
105
+ * @param {Object} options - Validation options
106
+ * @returns {ValidationResult} Validation result
107
+ */
108
+ function validateDRP(input, options = {}) {
109
+ const result = new ValidationResult();
110
+ const opts = {
111
+ checkBlobs: true,
112
+ checkReferences: true,
113
+ checkTiming: true,
114
+ verbose: false,
115
+ ...options,
116
+ };
117
+
118
+ let zip;
119
+ let files = {};
120
+
121
+ // Step 1: Archive Integrity
122
+ try {
123
+ if (typeof input === 'string') {
124
+ if (!fs.existsSync(input)) {
125
+ result.addIssue(SEVERITY.ERROR, 'archive', `File not found: ${input}`);
126
+ return result;
127
+ }
128
+ zip = new AdmZip(input);
129
+ } else if (Buffer.isBuffer(input)) {
130
+ zip = new AdmZip(input);
131
+ } else {
132
+ result.addIssue(SEVERITY.ERROR, 'archive', 'Invalid input: expected file path or buffer');
133
+ return result;
134
+ }
135
+
136
+ // Check ZIP magic bytes
137
+ const zipBuffer = typeof input === 'string' ? fs.readFileSync(input) : input;
138
+ if (zipBuffer[0] !== 0x50 || zipBuffer[1] !== 0x4b ||
139
+ zipBuffer[2] !== 0x03 || zipBuffer[3] !== 0x04) {
140
+ result.addIssue(SEVERITY.ERROR, 'archive', 'Invalid ZIP magic bytes (expected PK..)');
141
+ return result;
142
+ }
143
+
144
+ result.addIssue(SEVERITY.INFO, 'archive', 'ZIP archive structure valid');
145
+
146
+ } catch (err) {
147
+ result.addIssue(SEVERITY.ERROR, 'archive', `Failed to open archive: ${err.message}`);
148
+ return result;
149
+ }
150
+
151
+ // Step 2: Extract and check required files
152
+ try {
153
+ const entries = zip.getEntries();
154
+ result.stats.filesChecked = entries.length;
155
+
156
+ for (const entry of entries) {
157
+ if (!entry.isDirectory) {
158
+ // SECURITY: reject path-traversal entries and entries that would land on
159
+ // JS prototype keys (__proto__, constructor). Even though files[] is
160
+ // memory-only here, these names propagate into downstream Map/Object lookups.
161
+ const name = entry.entryName;
162
+ if (!isSafeArchiveEntryName(name)) {
163
+ result.addIssue(SEVERITY.ERROR, 'archive', `Unsafe archive entry rejected: ${name}`);
164
+ continue;
165
+ }
166
+ try {
167
+ files[name] = zip.readFile(entry);
168
+ } catch (err) {
169
+ result.addIssue(SEVERITY.ERROR, 'archive', `Failed to extract: ${name}`, err.message);
170
+ }
171
+ }
172
+ }
173
+
174
+ // Check required files
175
+ for (const required of REQUIRED_FILES) {
176
+ if (!files[required]) {
177
+ result.addIssue(SEVERITY.ERROR, 'archive', `Missing required file: ${required}`);
178
+ }
179
+ }
180
+
181
+ result.addIssue(SEVERITY.INFO, 'archive', `Extracted ${Object.keys(files).length} files`);
182
+
183
+ } catch (err) {
184
+ result.addIssue(SEVERITY.ERROR, 'archive', `Archive extraction failed: ${err.message}`);
185
+ return result;
186
+ }
187
+
188
+ // Step 3: Parse and validate XML files
189
+ const xmlParser = new XMLParser({
190
+ ignoreAttributes: false,
191
+ attributeNamePrefix: '@_',
192
+ allowBooleanAttributes: true,
193
+ });
194
+
195
+ const parsedXml = {};
196
+ const dbIds = new Set();
197
+ const linkedGroups = new Set();
198
+ const groupDbIds = new Set();
199
+ const mediaPoolIds = new Set();
200
+ const clipMediaRefs = new Set();
201
+
202
+ for (const [filename, content] of Object.entries(files)) {
203
+ if (filename.endsWith('.xml')) {
204
+ try {
205
+ const xmlString = content.toString('utf-8');
206
+ const parsed = xmlParser.parse(xmlString);
207
+ parsedXml[filename] = parsed;
208
+
209
+ // Extract DbIds, groups, and references
210
+ extractReferences(parsed, dbIds, linkedGroups, groupDbIds, mediaPoolIds, clipMediaRefs, result);
211
+
212
+ result.addIssue(SEVERITY.INFO, 'xml', `Parsed: ${filename}`);
213
+
214
+ } catch (err) {
215
+ result.addIssue(SEVERITY.ERROR, 'xml', `Failed to parse ${filename}`, err.message);
216
+ }
217
+ }
218
+ }
219
+
220
+ // Step 4: Validate binary blobs
221
+ if (opts.checkBlobs) {
222
+ validateBlobs(parsedXml, result, opts.verbose);
223
+ }
224
+
225
+ // Step 5: Check reference integrity
226
+ if (opts.checkReferences) {
227
+ validateReferences(linkedGroups, groupDbIds, clipMediaRefs, mediaPoolIds, result);
228
+ }
229
+
230
+ // Step 6: Check timing values
231
+ if (opts.checkTiming) {
232
+ validateTiming(parsedXml, result);
233
+ }
234
+
235
+ return result;
236
+ }
237
+
238
+ /**
239
+ * Extract references from parsed XML
240
+ */
241
+ function extractReferences(obj, dbIds, linkedGroups, groupDbIds, mediaPoolIds, clipMediaRefs, result) {
242
+ if (!obj || typeof obj !== 'object') return;
243
+
244
+ // Handle DbId attributes
245
+ if (obj['@_DbId']) {
246
+ if (dbIds.has(obj['@_DbId'])) {
247
+ result.addIssue(SEVERITY.WARNING, 'reference', `Duplicate DbId found`, obj['@_DbId']);
248
+ }
249
+ dbIds.add(obj['@_DbId']);
250
+ }
251
+
252
+ // Track Sm2Group DbIds
253
+ if (obj['Sm2Group']) {
254
+ const groups = Array.isArray(obj['Sm2Group']) ? obj['Sm2Group'] : [obj['Sm2Group']];
255
+ for (const group of groups) {
256
+ if (group['@_DbId']) {
257
+ groupDbIds.add(group['@_DbId']);
258
+ result.stats.groupsFound++;
259
+ }
260
+ }
261
+ }
262
+
263
+ // Track LinkedGroup references
264
+ if (obj['LinkedGroup']) {
265
+ const refs = Array.isArray(obj['LinkedGroup']) ? obj['LinkedGroup'] : [obj['LinkedGroup']];
266
+ for (const ref of refs) {
267
+ if (typeof ref === 'string') {
268
+ linkedGroups.add(ref);
269
+ }
270
+ }
271
+ }
272
+
273
+ // Track media pool IDs
274
+ if (obj['Sm2MpVideoClip'] || obj['Sm2SourceClip']) {
275
+ const clips = obj['Sm2MpVideoClip'] || obj['Sm2SourceClip'];
276
+ const clipArray = Array.isArray(clips) ? clips : [clips];
277
+ for (const clip of clipArray) {
278
+ if (clip['@_DbId']) {
279
+ mediaPoolIds.add(clip['@_DbId']);
280
+ }
281
+ }
282
+ }
283
+
284
+ // Track clip references
285
+ if (obj['Sm2VideoClip'] || obj['Sm2TiVideoClip']) {
286
+ const clips = obj['Sm2VideoClip'] || obj['Sm2TiVideoClip'];
287
+ const clipArray = Array.isArray(clips) ? clips : [clips];
288
+ for (const clip of clipArray) {
289
+ result.stats.clipsFound++;
290
+ if (clip['MediaRef']) {
291
+ clipMediaRefs.add(clip['MediaRef']);
292
+ }
293
+ }
294
+ }
295
+
296
+ // Recurse
297
+ for (const key of Object.keys(obj)) {
298
+ if (key.startsWith('@_')) continue;
299
+ extractReferences(obj[key], dbIds, linkedGroups, groupDbIds, mediaPoolIds, clipMediaRefs, result);
300
+ }
301
+ }
302
+
303
+ /**
304
+ * Validate binary blob fields
305
+ */
306
+ function validateBlobs(parsedXml, result, verbose) {
307
+ const blobFields = [
308
+ 'FieldsBlob', 'EffectFiltersBA', 'MediaTimemapBA', 'Body',
309
+ 'MediaFrameRate', 'ImportExportMetadataBA', 'TracksBA',
310
+ 'VirtualAudioTrackBA', 'Clip', 'Radiometry', 'PreConformMediaExtents'
311
+ ];
312
+
313
+ function findBlobs(obj, path = '') {
314
+ if (!obj || typeof obj !== 'object') return;
315
+
316
+ for (const key of Object.keys(obj)) {
317
+ if (key.startsWith('@_')) continue;
318
+
319
+ const currentPath = path ? `${path}.${key}` : key;
320
+
321
+ if (blobFields.includes(key)) {
322
+ const value = obj[key];
323
+ if (typeof value === 'string' && value.length > 0) {
324
+ validateBlobHex(key, value, currentPath, result, verbose);
325
+ result.stats.blobsValidated++;
326
+ }
327
+ }
328
+
329
+ if (typeof obj[key] === 'object') {
330
+ findBlobs(obj[key], currentPath);
331
+ }
332
+ }
333
+ }
334
+
335
+ for (const [filename, parsed] of Object.entries(parsedXml)) {
336
+ findBlobs(parsed, filename);
337
+ }
338
+ }
339
+
340
+ /**
341
+ * Validate a hex-encoded blob
342
+ */
343
+ function validateBlobHex(blobType, hexString, path, result, verbose) {
344
+ // Check hex validity
345
+ if (!/^[0-9a-fA-F]*$/.test(hexString)) {
346
+ result.addIssue(SEVERITY.ERROR, 'blob', `Invalid hex characters in ${blobType}`, path);
347
+ return;
348
+ }
349
+
350
+ if (hexString.length % 2 !== 0) {
351
+ result.addIssue(SEVERITY.ERROR, 'blob', `Odd hex length in ${blobType}`, path);
352
+ return;
353
+ }
354
+
355
+ const buffer = Buffer.from(hexString, 'hex');
356
+
357
+ switch (blobType) {
358
+ case 'FieldsBlob':
359
+ validateFieldsBlob(buffer, path, result, verbose);
360
+ break;
361
+
362
+ case 'EffectFiltersBA':
363
+ validateEffectFiltersBA(buffer, path, result, verbose);
364
+ break;
365
+
366
+ case 'MediaFrameRate':
367
+ validateMediaFrameRate(buffer, path, result, verbose);
368
+ break;
369
+
370
+ case 'MediaTimemapBA':
371
+ validateMediaTimemapBA(buffer, path, result, verbose);
372
+ break;
373
+
374
+ case 'Body':
375
+ validateBody(buffer, path, result, verbose);
376
+ break;
377
+
378
+ case 'ImportExportMetadataBA':
379
+ validateFieldsBlob(buffer, path, result, verbose); // Same format
380
+ break;
381
+
382
+ default:
383
+ if (verbose) {
384
+ result.addIssue(SEVERITY.INFO, 'blob', `${blobType}: ${buffer.length} bytes`, path);
385
+ }
386
+ }
387
+ }
388
+
389
+ /**
390
+ * Validate FieldsBlob structure
391
+ */
392
+ function validateFieldsBlob(buffer, path, result, verbose) {
393
+ if (buffer.length < 8) {
394
+ result.addIssue(SEVERITY.ERROR, 'blob', 'FieldsBlob too short (< 8 bytes)', path);
395
+ return;
396
+ }
397
+
398
+ const type = buffer.readUInt32BE(0);
399
+
400
+ if (!VALID_FIELDSBLOB_TYPES.includes(type)) {
401
+ // Check for zlib compression
402
+ if (buffer[0] === 0x78 && buffer[1] === 0x9c) {
403
+ if (verbose) {
404
+ result.addIssue(SEVERITY.INFO, 'blob', 'FieldsBlob: Zlib compressed', path);
405
+ }
406
+ return;
407
+ }
408
+
409
+ result.addIssue(SEVERITY.WARNING, 'blob', `FieldsBlob unknown type: 0x${type.toString(16)}`, path);
410
+ return;
411
+ }
412
+
413
+ if (type === 0x00000001) {
414
+ // Type 1: Key-value format
415
+ // Note: FieldsBlob format can have internal complexity after first field
416
+ // We validate the header and first field, but allow for format variations
417
+ const fieldCount = buffer.readUInt32BE(4);
418
+ let offset = 8;
419
+ let validatedFields = 0;
420
+
421
+ for (let i = 0; i < fieldCount && offset < buffer.length; i++) {
422
+ if (offset + 4 > buffer.length) {
423
+ // Ran out of data - if we validated at least one field, this is OK
424
+ if (validatedFields === 0) {
425
+ result.addIssue(SEVERITY.ERROR, 'blob', `FieldsBlob has no valid fields`, path);
426
+ }
427
+ return;
428
+ }
429
+
430
+ const nameLen = buffer.readUInt32BE(offset);
431
+
432
+ // Sanity check: name length should be reasonable (< 1000 bytes for a field name)
433
+ // If not, the format likely has internal structure we don't fully understand
434
+ if (nameLen > 1000 || nameLen === 0) {
435
+ // After first field, internal format may vary - this is OK
436
+ if (validatedFields > 0) {
437
+ if (verbose) {
438
+ result.addIssue(SEVERITY.INFO, 'blob',
439
+ `FieldsBlob Type 1: ${validatedFields}+ fields (internal structure)`, path);
440
+ }
441
+ return;
442
+ }
443
+ // First field has unreasonable name length
444
+ result.addIssue(SEVERITY.WARNING, 'blob',
445
+ `FieldsBlob unusual name length: ${nameLen}`, path);
446
+ return;
447
+ }
448
+ offset += 4;
449
+
450
+ if (offset + nameLen > buffer.length) {
451
+ if (validatedFields > 0) return; // OK if we got some fields
452
+ result.addIssue(SEVERITY.WARNING, 'blob', `FieldsBlob field ${i} name extends beyond buffer`, path);
453
+ return;
454
+ }
455
+ offset += nameLen;
456
+
457
+ if (offset + 4 > buffer.length) {
458
+ if (validatedFields > 0) return;
459
+ result.addIssue(SEVERITY.WARNING, 'blob', `FieldsBlob field ${i} missing value length`, path);
460
+ return;
461
+ }
462
+
463
+ const valueLen = buffer.readUInt32BE(offset);
464
+ offset += 4;
465
+
466
+ if (offset + valueLen > buffer.length) {
467
+ if (validatedFields > 0) return;
468
+ result.addIssue(SEVERITY.WARNING, 'blob', `FieldsBlob field ${i} value extends beyond buffer`, path);
469
+ return;
470
+ }
471
+ offset += valueLen;
472
+ validatedFields++;
473
+ }
474
+
475
+ if (verbose) {
476
+ result.addIssue(SEVERITY.INFO, 'blob', `FieldsBlob Type 1: ${validatedFields} fields validated`, path);
477
+ }
478
+
479
+ } else if (type === 0x00000002) {
480
+ // Type 2: Protobuf-like
481
+ const payloadSize = buffer.readUInt32BE(4);
482
+ const actualSize = buffer.length - 8;
483
+
484
+ // Allow some tolerance for size mismatch (internal compression/padding)
485
+ if (Math.abs(payloadSize - actualSize) > 100 && payloadSize !== actualSize) {
486
+ result.addIssue(SEVERITY.WARNING, 'blob',
487
+ `FieldsBlob Type 2 size mismatch: header=${payloadSize}, actual=${actualSize}`, path);
488
+ }
489
+
490
+ if (verbose) {
491
+ result.addIssue(SEVERITY.INFO, 'blob', `FieldsBlob Type 2: ${payloadSize} bytes payload`, path);
492
+ }
493
+ }
494
+ }
495
+
496
+ /**
497
+ * Validate EffectFiltersBA structure
498
+ */
499
+ function validateEffectFiltersBA(buffer, path, result, verbose) {
500
+ if (buffer.length < 9) {
501
+ result.addIssue(SEVERITY.ERROR, 'blob', 'EffectFiltersBA too short (< 9 bytes)', path);
502
+ return;
503
+ }
504
+
505
+ const version = buffer.readUInt32BE(0);
506
+ if (version !== 2) {
507
+ result.addIssue(SEVERITY.WARNING, 'blob', `EffectFiltersBA unexpected version: ${version}`, path);
508
+ }
509
+
510
+ const payloadSize = buffer.readUInt32BE(4);
511
+ const compressionMarker = buffer[8];
512
+
513
+ if (compressionMarker !== 0x80 && compressionMarker !== 0x81) {
514
+ result.addIssue(SEVERITY.WARNING, 'blob',
515
+ `EffectFiltersBA unknown compression marker: 0x${compressionMarker.toString(16)}`, path);
516
+ }
517
+
518
+ // Check for ZSTD if compressed
519
+ if (compressionMarker === 0x81) {
520
+ if (buffer.length < 13 ||
521
+ buffer[9] !== 0x28 || buffer[10] !== 0xb5 ||
522
+ buffer[11] !== 0x2f || buffer[12] !== 0xfd) {
523
+ result.addIssue(SEVERITY.WARNING, 'blob', 'EffectFiltersBA marked compressed but no ZSTD magic', path);
524
+ }
525
+ }
526
+
527
+ if (verbose) {
528
+ result.addIssue(SEVERITY.INFO, 'blob',
529
+ `EffectFiltersBA: v${version}, ${compressionMarker === 0x81 ? 'compressed' : 'raw'}, ${payloadSize} bytes`, path);
530
+ }
531
+ }
532
+
533
+ /**
534
+ * Validate MediaFrameRate structure
535
+ */
536
+ function validateMediaFrameRate(buffer, path, result, verbose) {
537
+ if (buffer.length !== 16) {
538
+ result.addIssue(SEVERITY.ERROR, 'blob', `MediaFrameRate wrong size: ${buffer.length} (expected 16)`, path);
539
+ return;
540
+ }
541
+
542
+ const fps = buffer.readDoubleLE(0);
543
+ const reserved = buffer.readDoubleLE(8);
544
+
545
+ if (reserved !== 0) {
546
+ result.addIssue(SEVERITY.WARNING, 'blob', `MediaFrameRate reserved bytes non-zero`, path);
547
+ }
548
+
549
+ // Check for valid frame rate (within tolerance)
550
+ const isValidFps = VALID_FRAME_RATES.some(valid => Math.abs(fps - valid) < 0.01);
551
+ if (!isValidFps && fps > 0) {
552
+ result.addIssue(SEVERITY.WARNING, 'blob', `MediaFrameRate unusual value: ${fps.toFixed(6)} fps`, path);
553
+ }
554
+
555
+ if (fps <= 0 || fps > 240) {
556
+ result.addIssue(SEVERITY.ERROR, 'blob', `MediaFrameRate invalid: ${fps}`, path);
557
+ }
558
+
559
+ if (verbose) {
560
+ result.addIssue(SEVERITY.INFO, 'blob', `MediaFrameRate: ${fps.toFixed(6)} fps`, path);
561
+ }
562
+ }
563
+
564
+ /**
565
+ * Validate MediaTimemapBA structure
566
+ */
567
+ function validateMediaTimemapBA(buffer, path, result, verbose) {
568
+ // Empty timemap (no retime effects) - valid
569
+ if (buffer.length === 0) {
570
+ if (verbose) {
571
+ result.addIssue(SEVERITY.INFO, 'blob', 'MediaTimemapBA: empty (no retime)', path);
572
+ }
573
+ return;
574
+ }
575
+
576
+ // Very short blobs with 0x00 marker indicate no retime - valid
577
+ if (buffer.length < 9) {
578
+ if (buffer[0] === 0x00) {
579
+ if (verbose) {
580
+ result.addIssue(SEVERITY.INFO, 'blob', 'MediaTimemapBA: none/default', path);
581
+ }
582
+ return;
583
+ }
584
+ result.addIssue(SEVERITY.WARNING, 'blob', `MediaTimemapBA unusual size: ${buffer.length} bytes`, path);
585
+ return;
586
+ }
587
+
588
+ const typeMarker = buffer[0];
589
+
590
+ if (typeMarker === 0x00) {
591
+ // No retime effect applied - this is valid
592
+ if (verbose) {
593
+ result.addIssue(SEVERITY.INFO, 'blob', 'MediaTimemapBA: no retime effect', path);
594
+ }
595
+ } else if (typeMarker === 0x02) {
596
+ // Standard timing format
597
+ if (buffer.length === 9) {
598
+ // Simple format
599
+ if (verbose) {
600
+ const duration = buffer.readDoubleBE(1);
601
+ result.addIssue(SEVERITY.INFO, 'blob', `MediaTimemapBA simple: ${duration.toFixed(4)}s`, path);
602
+ }
603
+ } else if (buffer.length === 41) {
604
+ // Standard format
605
+ if (verbose) {
606
+ result.addIssue(SEVERITY.INFO, 'blob', 'MediaTimemapBA standard (41 bytes)', path);
607
+ }
608
+ } else if (buffer.length > 100) {
609
+ // Complex format with keyframes
610
+ if (verbose) {
611
+ result.addIssue(SEVERITY.INFO, 'blob', `MediaTimemapBA complex (${buffer.length} bytes)`, path);
612
+ }
613
+ } else {
614
+ if (verbose) {
615
+ result.addIssue(SEVERITY.INFO, 'blob', `MediaTimemapBA: ${buffer.length} bytes`, path);
616
+ }
617
+ }
618
+ } else {
619
+ // Other type markers may be valid variants
620
+ if (verbose) {
621
+ result.addIssue(SEVERITY.INFO, 'blob',
622
+ `MediaTimemapBA type 0x${typeMarker.toString(16)}: ${buffer.length} bytes`, path);
623
+ }
624
+ }
625
+ }
626
+
627
+ /**
628
+ * Validate Body (grade) structure
629
+ */
630
+ function validateBody(buffer, path, result, verbose) {
631
+ if (buffer.length < 2) {
632
+ result.addIssue(SEVERITY.ERROR, 'blob', 'Body too short', path);
633
+ return;
634
+ }
635
+
636
+ const header = buffer[0];
637
+
638
+ if (header === 0x80 && buffer[1] === 0x0a) {
639
+ // Uncompressed format
640
+ if (verbose) {
641
+ result.addIssue(SEVERITY.INFO, 'blob', `Body uncompressed (${buffer.length} bytes)`, path);
642
+ }
643
+ } else if (header === 0x81) {
644
+ // Check for ZSTD
645
+ if (buffer.length >= 5 &&
646
+ buffer[1] === 0x28 && buffer[2] === 0xb5 &&
647
+ buffer[3] === 0x2f && buffer[4] === 0xfd) {
648
+ if (verbose) {
649
+ result.addIssue(SEVERITY.INFO, 'blob', `Body ZSTD compressed (${buffer.length} bytes)`, path);
650
+ }
651
+ } else {
652
+ result.addIssue(SEVERITY.WARNING, 'blob', 'Body marked compressed but no ZSTD magic', path);
653
+ }
654
+ } else {
655
+ result.addIssue(SEVERITY.WARNING, 'blob',
656
+ `Body unknown header: 0x${header.toString(16)}`, path);
657
+ }
658
+ }
659
+
660
+ /**
661
+ * Validate cross-references
662
+ */
663
+ function validateReferences(linkedGroups, groupDbIds, clipMediaRefs, mediaPoolIds, result) {
664
+ // Check LinkedGroup references
665
+ for (const linkedGroup of linkedGroups) {
666
+ if (!groupDbIds.has(linkedGroup)) {
667
+ result.addIssue(SEVERITY.ERROR, 'reference',
668
+ `LinkedGroup references non-existent group`, linkedGroup);
669
+ }
670
+ }
671
+
672
+ // Check clip media references (if we have any)
673
+ // Note: This is a simplified check; full validation would need more context
674
+ if (clipMediaRefs.size > 0 && mediaPoolIds.size > 0) {
675
+ let orphanedRefs = 0;
676
+ for (const ref of clipMediaRefs) {
677
+ if (!mediaPoolIds.has(ref)) {
678
+ orphanedRefs++;
679
+ }
680
+ }
681
+ if (orphanedRefs > 0) {
682
+ result.addIssue(SEVERITY.WARNING, 'reference',
683
+ `${orphanedRefs} clip(s) reference missing media pool entries`);
684
+ }
685
+ }
686
+
687
+ result.addIssue(SEVERITY.INFO, 'reference',
688
+ `Checked: ${linkedGroups.size} LinkedGroup refs, ${groupDbIds.size} groups, ${mediaPoolIds.size} media pool items`);
689
+ }
690
+
691
+ /**
692
+ * Validate timing values in XML
693
+ */
694
+ function validateTiming(parsedXml, result) {
695
+ function checkTiming(obj, path = '') {
696
+ if (!obj || typeof obj !== 'object') return;
697
+
698
+ // Check StartFrame/EndFrame
699
+ if (obj['StartFrame'] !== undefined && obj['EndFrame'] !== undefined) {
700
+ const start = parseInt(obj['StartFrame'], 10);
701
+ const end = parseInt(obj['EndFrame'], 10);
702
+
703
+ if (isNaN(start) || isNaN(end)) {
704
+ result.addIssue(SEVERITY.ERROR, 'timing', `Invalid frame values at ${path}`);
705
+ } else if (start > end) {
706
+ result.addIssue(SEVERITY.ERROR, 'timing', `StartFrame > EndFrame at ${path}`, { start, end });
707
+ } else if (end - start > 1000000) {
708
+ result.addIssue(SEVERITY.WARNING, 'timing', `Unusually long clip (${end - start} frames) at ${path}`);
709
+ }
710
+ }
711
+
712
+ // Check Duration
713
+ if (obj['Duration'] !== undefined) {
714
+ const duration = parseInt(obj['Duration'], 10);
715
+ if (isNaN(duration) || duration < 0) {
716
+ result.addIssue(SEVERITY.ERROR, 'timing', `Invalid Duration at ${path}`);
717
+ }
718
+ }
719
+
720
+ // Recurse
721
+ for (const key of Object.keys(obj)) {
722
+ if (key.startsWith('@_')) continue;
723
+ if (typeof obj[key] === 'object') {
724
+ checkTiming(obj[key], path ? `${path}.${key}` : key);
725
+ }
726
+ }
727
+ }
728
+
729
+ for (const [filename, parsed] of Object.entries(parsedXml)) {
730
+ checkTiming(parsed, filename);
731
+ }
732
+ }
733
+
734
+ /**
735
+ * Validate a DRP file and print results
736
+ * @param {string} filePath - Path to DRP file
737
+ * @param {Object} options - Validation options
738
+ */
739
+ function validateAndPrint(filePath, options = {}) {
740
+ console.log(`\nValidating: ${filePath}\n`);
741
+ console.log('='.repeat(60));
742
+
743
+ const result = validateDRP(filePath, options);
744
+ const summary = result.getSummary();
745
+
746
+ // Print summary
747
+ console.log(`\nSUMMARY:`);
748
+ console.log(` Status: ${result.valid ? '✓ VALID' : '✗ INVALID'}`);
749
+ console.log(` Files checked: ${summary.stats.filesChecked}`);
750
+ console.log(` Blobs validated: ${summary.stats.blobsValidated}`);
751
+ console.log(` Clips found: ${summary.stats.clipsFound}`);
752
+ console.log(` Groups found: ${summary.stats.groupsFound}`);
753
+ console.log(` Errors: ${summary.errorCount}`);
754
+ console.log(` Warnings: ${summary.warningCount}`);
755
+
756
+ // Print errors
757
+ if (result.errors.length > 0) {
758
+ console.log(`\nERRORS (${result.errors.length}):`);
759
+ for (const err of result.errors) {
760
+ console.log(` ✗ [${err.category}] ${err.message}`);
761
+ if (err.details) console.log(` → ${err.details}`);
762
+ }
763
+ }
764
+
765
+ // Print warnings
766
+ if (result.warnings.length > 0) {
767
+ console.log(`\nWARNINGS (${result.warnings.length}):`);
768
+ for (const warn of result.warnings) {
769
+ console.log(` ⚠ [${warn.category}] ${warn.message}`);
770
+ if (warn.details) console.log(` → ${warn.details}`);
771
+ }
772
+ }
773
+
774
+ // Print info if verbose
775
+ if (options.verbose && result.info.length > 0) {
776
+ console.log(`\nINFO (${result.info.length}):`);
777
+ for (const info of result.info) {
778
+ console.log(` ℹ [${info.category}] ${info.message}`);
779
+ }
780
+ }
781
+
782
+ console.log('\n' + '='.repeat(60));
783
+ console.log(`Validation ${result.valid ? 'PASSED' : 'FAILED'}`);
784
+
785
+ return result;
786
+ }
787
+
788
+ // CLI support
789
+ if (require.main === module) {
790
+ const args = process.argv.slice(2);
791
+
792
+ if (args.length === 0) {
793
+ console.log('Usage: node drp-validator.js <file.drp> [--verbose]');
794
+ process.exit(1);
795
+ }
796
+
797
+ const filePath = args[0];
798
+ const verbose = args.includes('--verbose') || args.includes('-v');
799
+
800
+ const result = validateAndPrint(filePath, { verbose });
801
+ process.exit(result.valid ? 0 : 1);
802
+ }
803
+
804
+ module.exports = {
805
+ validateDRP,
806
+ validateAndPrint,
807
+ ValidationResult,
808
+ SEVERITY,
809
+ };