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,272 @@
1
+ /**
2
+ * P0.2 — diff(drpA, drpB) unit tests.
3
+ *
4
+ * Strategy: build synthetic DRPs via buildDRP with controlled deltas
5
+ * between them, then assert the resulting DTO captures each delta in
6
+ * the right bucket without false positives.
7
+ *
8
+ * Run: node --test packages/drp-format/__tests__/diff.test.js
9
+ */
10
+
11
+ const test = require('node:test');
12
+ const assert = require('node:assert/strict');
13
+ const fs = require('node:fs/promises');
14
+ const path = require('node:path');
15
+ const os = require('node:os');
16
+ const JSZip = require('jszip');
17
+
18
+ const drpFormat = require('..');
19
+
20
+ const BODY_A = '80aaaaaaaaaaaaaaaa';
21
+ const BODY_B = '80bbbbbbbbbbbbbbbb';
22
+ const BODY_C = '80cccccccccccccccc';
23
+
24
+ async function buildTwoClipDrp(outPath, clipSpecs) {
25
+ const buf = await drpFormat.buildDRP({
26
+ projectName: 'diff-test',
27
+ timelines: [{
28
+ name: 'T1',
29
+ frameRate: 24,
30
+ startTimecode: '01:00:00:00',
31
+ resolution: '1920x1080',
32
+ videoTracks: [{
33
+ clips: clipSpecs,
34
+ }],
35
+ audioTracks: [],
36
+ }],
37
+ });
38
+ await fs.writeFile(outPath, buf);
39
+ }
40
+
41
+ async function readClipIds(drpPath) {
42
+ // Use the diff module's indexer to discover the auto-generated DbIds.
43
+ const idx = await drpFormat.diff && (await drpFormat._diffInternals.indexDrp(drpPath));
44
+ return [...idx.clipsById.keys()];
45
+ }
46
+
47
+ // Expose internals for the tests without polluting the public API.
48
+ drpFormat._diffInternals = require('../diff')._internals;
49
+
50
+ test('diff: identical DRPs report no changes', async () => {
51
+ const tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'drp-diff-'));
52
+ try {
53
+ const drpA = path.join(tmp, 'a.drp');
54
+ const drpB = path.join(tmp, 'b.drp');
55
+ const specs = [
56
+ { start: 0, duration: 24, in: 0, mediaFilePath: '/m/clip1.mov', grade: { body: BODY_A, hasCorrection: true } },
57
+ { start: 24, duration: 24, in: 0, mediaFilePath: '/m/clip2.mov', grade: { body: BODY_B, hasCorrection: true } },
58
+ ];
59
+ await buildTwoClipDrp(drpA, specs);
60
+ // Build B from the same spec object (separate buildDRP run = different
61
+ // auto-generated UUIDs — so identical CONTENT will produce
62
+ // added/removed pairs). Cheat the test: copy A bytes-for-bytes to get
63
+ // identical DbIds. That's how a real "compare X against itself" call
64
+ // would look anyway.
65
+ await fs.copyFile(drpA, drpB);
66
+
67
+ const result = await drpFormat.diff(drpA, drpB);
68
+ assert.equal(result.summary.hasAnyChange, false);
69
+ assert.equal(result.addedClips.length, 0);
70
+ assert.equal(result.removedClips.length, 0);
71
+ assert.equal(result.movedClips.length, 0);
72
+ assert.equal(result.gradeChanges.length, 0);
73
+ assert.equal(result.timelineSettingDeltas.length, 0);
74
+ assert.equal(result.mediaPoolDeltas.added.length, 0);
75
+ assert.equal(result.mediaPoolDeltas.removed.length, 0);
76
+ assert.equal(result.summary.sameClipCount, 2);
77
+ } finally {
78
+ await fs.rm(tmp, { recursive: true, force: true });
79
+ }
80
+ });
81
+
82
+ test('diff: grade change on one clip is detected, other clip unchanged', async () => {
83
+ const tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'drp-diff-'));
84
+ try {
85
+ const drpA = path.join(tmp, 'a.drp');
86
+ const drpB = path.join(tmp, 'b.drp');
87
+ const specs = [
88
+ { start: 0, duration: 24, in: 0, mediaFilePath: '/m/clip1.mov', grade: { body: BODY_A, hasCorrection: true } },
89
+ { start: 24, duration: 24, in: 0, mediaFilePath: '/m/clip2.mov', grade: { body: BODY_B, hasCorrection: true } },
90
+ ];
91
+ await buildTwoClipDrp(drpA, specs);
92
+ await fs.copyFile(drpA, drpB);
93
+
94
+ // Mutate clip 1 in B via injectGrades to BODY_C — preserves DbIds.
95
+ const idsB = await readClipIds(drpB);
96
+ const drxFor = (hex) => `<x><Body>${hex}</Body></x>`;
97
+ await drpFormat.injectGrades(drpB, [
98
+ { clipId: idsB[0], drxContent: drxFor(BODY_C) },
99
+ ]);
100
+
101
+ const result = await drpFormat.diff(drpA, drpB);
102
+ assert.equal(result.summary.hasAnyChange, true);
103
+ assert.equal(result.addedClips.length, 0);
104
+ assert.equal(result.removedClips.length, 0);
105
+ assert.equal(result.movedClips.length, 0);
106
+ assert.equal(result.gradeChanges.length, 1);
107
+ const g = result.gradeChanges[0];
108
+ assert.equal(g.clipId, idsB[0]);
109
+ assert.equal(g.hadGrade, true);
110
+ assert.equal(g.hasGrade, true);
111
+ assert.notEqual(g.beforeBodyHash, g.afterBodyHash);
112
+ assert.ok(g.beforeBodyHash && /^[0-9a-f]{64}$/.test(g.beforeBodyHash));
113
+ assert.ok(g.afterBodyHash && /^[0-9a-f]{64}$/.test(g.afterBodyHash));
114
+ } finally {
115
+ await fs.rm(tmp, { recursive: true, force: true });
116
+ }
117
+ });
118
+
119
+ test('diff: clip added in B surfaces in addedClips with no grade-change false positive', async () => {
120
+ const tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'drp-diff-'));
121
+ try {
122
+ const drpA = path.join(tmp, 'a.drp');
123
+ const drpB = path.join(tmp, 'b.drp');
124
+ await buildTwoClipDrp(drpA, [
125
+ { start: 0, duration: 24, in: 0, mediaFilePath: '/m/clip1.mov', grade: { body: BODY_A, hasCorrection: true } },
126
+ ]);
127
+ await buildTwoClipDrp(drpB, [
128
+ { start: 0, duration: 24, in: 0, mediaFilePath: '/m/clip1.mov', grade: { body: BODY_A, hasCorrection: true } },
129
+ { start: 24, duration: 24, in: 0, mediaFilePath: '/m/clip2.mov', grade: { body: BODY_B, hasCorrection: true } },
130
+ ]);
131
+
132
+ const result = await drpFormat.diff(drpA, drpB);
133
+ // Because A and B were built independently, every DbId differs — A's
134
+ // clip 1 is "removed", B's both clips are "added". That's correct per
135
+ // the schema: identity is DbId, not media path. A more sophisticated
136
+ // similarity layer is future work.
137
+ assert.equal(result.summary.hasAnyChange, true);
138
+ assert.equal(result.removedClips.length, 1);
139
+ assert.equal(result.addedClips.length, 2);
140
+ assert.equal(result.gradeChanges.length, 0);
141
+ // Media-pool diff catches the new file.
142
+ assert.ok(result.mediaPoolDeltas.added.includes('/m/clip2.mov'));
143
+ } finally {
144
+ await fs.rm(tmp, { recursive: true, force: true });
145
+ }
146
+ });
147
+
148
+ test('diff: same DbIds, clip 1 moved in B → movedClips, gradeChanges empty', async () => {
149
+ const tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'drp-diff-'));
150
+ try {
151
+ const drpA = path.join(tmp, 'a.drp');
152
+ const drpB = path.join(tmp, 'b.drp');
153
+ const specs = [
154
+ { start: 0, duration: 24, in: 0, mediaFilePath: '/m/clip1.mov', grade: { body: BODY_A, hasCorrection: true } },
155
+ { start: 24, duration: 24, in: 0, mediaFilePath: '/m/clip2.mov', grade: { body: BODY_B, hasCorrection: true } },
156
+ ];
157
+ await buildTwoClipDrp(drpA, specs);
158
+ await fs.copyFile(drpA, drpB);
159
+
160
+ // Mutate the START frame of clip 1 in B's SeqContainer XML directly
161
+ // (the only way to simulate "moved" while preserving DbIds —
162
+ // injectGrades only touches <Body>).
163
+ await mutateClipStart(drpB, /* clipIndex */ 0, /* newStart */ 100);
164
+
165
+ const result = await drpFormat.diff(drpA, drpB);
166
+ assert.equal(result.movedClips.length, 1);
167
+ const move = result.movedClips[0];
168
+ assert.equal(move.before.start, 0);
169
+ assert.equal(move.after.start, 100);
170
+ assert.equal(result.gradeChanges.length, 0,
171
+ 'a pure move should not falsely report a grade change');
172
+ } finally {
173
+ await fs.rm(tmp, { recursive: true, force: true });
174
+ }
175
+ });
176
+
177
+ test('diff: timeline settings delta surfaces project-level differences', async () => {
178
+ const tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'drp-diff-'));
179
+ try {
180
+ const drpA = path.join(tmp, 'a.drp');
181
+ const drpB = path.join(tmp, 'b.drp');
182
+ await buildTwoClipDrp(drpA, [
183
+ { start: 0, duration: 24, in: 0, mediaFilePath: '/m/c.mov', grade: { body: BODY_A } },
184
+ ]);
185
+ // Mutate B's project.xml to change ProjectName.
186
+ await fs.copyFile(drpA, drpB);
187
+ await mutateProjectName(drpB, 'diff-test-renamed');
188
+
189
+ const result = await drpFormat.diff(drpA, drpB);
190
+ const nameDelta = result.timelineSettingDeltas.find((d) => d.key === 'projectName');
191
+ assert.ok(nameDelta, 'projectName delta should be reported');
192
+ assert.equal(nameDelta.before, 'diff-test');
193
+ assert.equal(nameDelta.after, 'diff-test-renamed');
194
+ } finally {
195
+ await fs.rm(tmp, { recursive: true, force: true });
196
+ }
197
+ });
198
+
199
+ test('diff: internals.extractClips finds clips even when body absent', () => {
200
+ const xml = `
201
+ <Sm2TiVideoTrack DbId="t">
202
+ <Sm2TiVideoClip DbId="clip-1">
203
+ <Start>0</Start>
204
+ <Duration>24</Duration>
205
+ <MediaFilePath>/m/a.mov</MediaFilePath>
206
+ </Sm2TiVideoClip>
207
+ <Sm2TiVideoClip DbId="clip-2">
208
+ <Start>24</Start>
209
+ <Duration>24</Duration>
210
+ <MediaFilePath>/m/b.mov</MediaFilePath>
211
+ <Body>80aabb</Body>
212
+ </Sm2TiVideoClip>
213
+ </Sm2TiVideoTrack>
214
+ `;
215
+ const clips = drpFormat._diffInternals.extractClips(xml);
216
+ assert.equal(clips.length, 2);
217
+ assert.equal(clips[0].clipId, 'clip-1');
218
+ assert.equal(clips[0].bodyHex, null);
219
+ assert.equal(clips[0].start, 0);
220
+ assert.equal(clips[1].clipId, 'clip-2');
221
+ assert.equal(clips[1].bodyHex, '80aabb');
222
+ });
223
+
224
+ test('diff: rejects non-string arguments', async () => {
225
+ await assert.rejects(() => drpFormat.diff(123, '/tmp/x'), /must be strings/);
226
+ await assert.rejects(() => drpFormat.diff('/tmp/x', null), /must be strings/);
227
+ });
228
+
229
+ // ─── helpers that mutate a DRP zip in-place ─────────────────────────────
230
+
231
+ async function mutateClipStart(drpPath, clipIndex, newStart) {
232
+ const buf = await fs.readFile(drpPath);
233
+ const zip = await JSZip.loadAsync(buf);
234
+ let seqEntry = null;
235
+ zip.forEach((p, e) => { if (!e.dir && /(^|\/)SeqContainer\d*\.xml$/.test(p)) seqEntry = p; });
236
+ let xml = await zip.file(seqEntry).async('string');
237
+ // Replace the Nth Sm2TiVideoClip's <Start> value.
238
+ const clipRe = /<Sm2TiVideoClip\b([\s\S]*?)<\/Sm2TiVideoClip>/g;
239
+ let m, i = 0, replaced = false;
240
+ while ((m = clipRe.exec(xml)) !== null) {
241
+ if (i === clipIndex) {
242
+ const clipBlock = m[0];
243
+ const newClipBlock = clipBlock.replace(/<Start>[^<]*<\/Start>/, `<Start>${newStart}</Start>`);
244
+ xml = xml.slice(0, m.index) + newClipBlock + xml.slice(m.index + clipBlock.length);
245
+ replaced = true;
246
+ break;
247
+ }
248
+ i += 1;
249
+ }
250
+ if (!replaced) throw new Error('mutateClipStart: no matching clip');
251
+ zip.file(seqEntry, xml);
252
+ const outBuf = await zip.generateAsync({ type: 'nodebuffer', compression: 'DEFLATE' });
253
+ await fs.writeFile(drpPath, outBuf);
254
+ }
255
+
256
+ async function mutateProjectName(drpPath, newName) {
257
+ const buf = await fs.readFile(drpPath);
258
+ const zip = await JSZip.loadAsync(buf);
259
+ let projEntry = null;
260
+ zip.forEach((p, e) => { if (!e.dir && /(^|\/)project\.xml$/.test(p)) projEntry = p; });
261
+ let xml = await zip.file(projEntry).async('string');
262
+ // tool-authored DRPs use <Name>; Resolve-exported variants may
263
+ // use <ProjectName>. Match either.
264
+ const before = xml;
265
+ xml = xml
266
+ .replace(/<ProjectName>[^<]*<\/ProjectName>/, `<ProjectName>${newName}</ProjectName>`)
267
+ .replace(/<Name>[^<]*<\/Name>/, `<Name>${newName}</Name>`);
268
+ if (xml === before) throw new Error('mutateProjectName: no project-name tag found to mutate');
269
+ zip.file(projEntry, xml);
270
+ const outBuf = await zip.generateAsync({ type: 'nodebuffer', compression: 'DEFLATE' });
271
+ await fs.writeFile(drpPath, outBuf);
272
+ }
@@ -0,0 +1,143 @@
1
+ /**
2
+ * P0.3 — Compressed EffectFiltersBA decoder.
3
+ *
4
+ * Resolve 21 finding (Session 30): Resolve 21's DRP export writes
5
+ * `<EffectFiltersBA/>` empty self-closing even when cached ResolveFX
6
+ * are present. The compressed (0x81) payload path is therefore
7
+ * **not exercised by any Resolve 21 fixture** in our capture set;
8
+ * the decoder is implemented for backward compatibility with older
9
+ * Resolve exports and for any future Resolve version that
10
+ * re-enables DRP-side caching.
11
+ *
12
+ * Synthetic round-trip: take an existing 0x80 (uncompressed) payload
13
+ * the encoder already produces, zlib-compress it, prefix with 0x81,
14
+ * wrap with the standard header, and assert the decoder unwinds it
15
+ * to the same transform parameters. This proves the decoder is
16
+ * symmetrical with the encoder side, without needing a Resolve
17
+ * fixture (which doesn't exist today).
18
+ *
19
+ * Real-fixture safety check: parse the Session 30 captured DRP
20
+ * (`<EffectFiltersBA/>` empty) via the project-level path and assert
21
+ * no throw — the codec must handle the Resolve-21-empty case gracefully.
22
+ */
23
+
24
+ const test = require('node:test');
25
+ const assert = require('node:assert/strict');
26
+ const fs = require('node:fs');
27
+ const path = require('node:path');
28
+ const zlib = require('node:zlib');
29
+
30
+ const {
31
+ encodeEffectFiltersBA,
32
+ decodeEffectFiltersBA,
33
+ } = require('../effect-encoder');
34
+
35
+ const FIXTURES = path.join(__dirname, 'fixtures');
36
+
37
+ function compressedHexFromUncompressed(uncompressedHex) {
38
+ const original = Buffer.from(uncompressedHex, 'hex');
39
+ // The header is 8 bytes (version + payloadSize), payloadType is at
40
+ // offset 8, payload follows at offset 9.
41
+ const header = original.slice(0, 8);
42
+ const payload = original.slice(9);
43
+ const compressedPayload = zlib.deflateSync(payload);
44
+ // Rebuild: header + 0x81 + compressed payload
45
+ // (the payloadSize field in the header technically should reflect
46
+ // the new compressed size; the decoder doesn't read it strictly so
47
+ // we leave the original size for now — equivalent of how Resolve's
48
+ // own files keep the *decompressed* size there).
49
+ return Buffer.concat([header, Buffer.from([0x81]), compressedPayload]).toString('hex');
50
+ }
51
+
52
+ test('decodeEffectFiltersBA: 0x81 compressed payload round-trips a zoom transform', () => {
53
+ const input = { zoomX: 1.25, zoomY: 1.25 };
54
+ const uncompressed = encodeEffectFiltersBA(input);
55
+ const compressed = compressedHexFromUncompressed(uncompressed);
56
+ // Verify the compressed form actually starts with 0x81 at offset 8.
57
+ const compressedBuf = Buffer.from(compressed, 'hex');
58
+ assert.equal(compressedBuf[8], 0x81, 'payload-type byte is 0x81');
59
+
60
+ const decoded = decodeEffectFiltersBA(compressed);
61
+ assert.ok(Math.abs(decoded.zoomX - 1.25) < 1e-9);
62
+ assert.ok(Math.abs(decoded.zoomY - 1.25) < 1e-9);
63
+ });
64
+
65
+ test('decodeEffectFiltersBA: 0x81 compressed payload round-trips rotation + pan', () => {
66
+ const input = { rotation: 12.5, panX: 50, panY: -30 };
67
+ const uncompressed = encodeEffectFiltersBA(input);
68
+ const compressed = compressedHexFromUncompressed(uncompressed);
69
+ const decoded = decodeEffectFiltersBA(compressed);
70
+ assert.ok(Math.abs(decoded.rotation - 12.5) < 1e-9);
71
+ assert.ok(Math.abs(decoded.panX - 50) < 1e-9);
72
+ assert.ok(Math.abs(decoded.panY - (-30)) < 1e-9);
73
+ });
74
+
75
+ test('decodeEffectFiltersBA: 0x80 uncompressed path still works (regression)', () => {
76
+ const input = { zoomX: 1.1, rotation: 7 };
77
+ const encoded = encodeEffectFiltersBA(input);
78
+ const buf = Buffer.from(encoded, 'hex');
79
+ assert.equal(buf[8], 0x80, 'encoder default produces 0x80');
80
+ const decoded = decodeEffectFiltersBA(encoded);
81
+ assert.ok(Math.abs(decoded.zoomX - 1.1) < 1e-9);
82
+ assert.ok(Math.abs(decoded.rotation - 7) < 1e-9);
83
+ });
84
+
85
+ test('decodeEffectFiltersBA: 0x81 with raw DEFLATE (no zlib header) also works', () => {
86
+ // Some Resolve versions may use raw DEFLATE instead of zlib-wrapped.
87
+ // Build a payload prefixed with 0x81 + raw-deflate'd protobuf and
88
+ // assert the decoder falls back correctly.
89
+ const input = { zoomX: 1.5 };
90
+ const uncompressed = Buffer.from(encodeEffectFiltersBA(input), 'hex');
91
+ const header = uncompressed.slice(0, 8);
92
+ const payload = uncompressed.slice(9);
93
+ const rawDeflate = zlib.deflateRawSync(payload);
94
+ // Raw DEFLATE doesn't start with 0x78 — confirm.
95
+ assert.notEqual(rawDeflate[0], 0x78,
96
+ 'raw deflate output should not have zlib header byte 0x78');
97
+ const compressed = Buffer.concat([header, Buffer.from([0x81]), rawDeflate]);
98
+ const decoded = decodeEffectFiltersBA(compressed.toString('hex'));
99
+ assert.ok(Math.abs(decoded.zoomX - 1.5) < 1e-9);
100
+ });
101
+
102
+ test('decodeEffectFiltersBA: 0x81 with malformed payload surfaces a clear error', () => {
103
+ // Non-zlib, non-deflate garbage at the 0x81 payload — must throw.
104
+ const input = { zoomX: 1.0 };
105
+ const uncompressed = Buffer.from(encodeEffectFiltersBA(input), 'hex');
106
+ const header = uncompressed.slice(0, 8);
107
+ const garbage = Buffer.from([0xFF, 0xFE, 0xFD, 0xFC, 0xFB]);
108
+ const malformed = Buffer.concat([header, Buffer.from([0x81]), garbage]);
109
+ assert.throws(
110
+ () => decodeEffectFiltersBA(malformed.toString('hex')),
111
+ /Failed to decompress EffectFiltersBA/,
112
+ );
113
+ });
114
+
115
+ // ─── Resolve 21 reality check ────────────────────────────────────────────
116
+
117
+ test('Session 30 captured DRP carries empty <EffectFiltersBA/> (Resolve 21 finding)', () => {
118
+ const drpPath = path.join(FIXTURES, 'p0-3-session30-empty-effectfilters.drp');
119
+ // The fixture is the actual Session 30 capture — confirm the
120
+ // EffectFiltersBA XML field is empty self-closing, validating the
121
+ // P0.3 finding that Resolve 21 doesn't write this blob anymore.
122
+ if (!fs.existsSync(drpPath)) {
123
+ // Fixture not present in checkout — that's fine; the synthetic
124
+ // round-trip tests above prove the decoder works. Skip without
125
+ // failing.
126
+ return;
127
+ }
128
+ const JSZip = require('jszip');
129
+ return JSZip.loadAsync(fs.readFileSync(drpPath)).then(async (zip) => {
130
+ const seqEntries = [];
131
+ zip.forEach((p) => {
132
+ if (p.startsWith('SeqContainer/') && p.endsWith('.xml')) seqEntries.push(p);
133
+ });
134
+ assert.ok(seqEntries.length > 0, 'fixture has a SeqContainer XML');
135
+ const xml = await zip.file(seqEntries[0]).async('string');
136
+ // Look for the EffectFiltersBA tag — must be self-closing in
137
+ // Resolve 21 exports.
138
+ assert.match(xml, /<EffectFiltersBA\/>/,
139
+ 'Resolve 21 exports EffectFiltersBA as empty self-closing');
140
+ assert.doesNotMatch(xml, /<EffectFiltersBA>[0-9a-f]/i,
141
+ 'Resolve 21 does NOT inline a hex blob in EffectFiltersBA');
142
+ });
143
+ });
@@ -0,0 +1 @@
1
+ 0000000100000007000000080059004d0069006e0000000600bff0000000000000000000080059004d006100780000000600bff0000000000000000000080058004d00610078000000060040731547f3fc2d54000000100055006e0069007100750065004900640000000a000000004800330034003600360032003500610063002d0062003000650033002d0034003700360038002d0038003400310038002d00320037003600340038003300380036003000370030003900000020004c00610073007400560061006c006900640059004f006600660073006500740000000600406314e147ae147b00000016004b00650079006600720061006d00650073004200410000000c0000000017800a000a1209542dfcf34715734011542dfcf3471563400000000c0044006200540079007000650000000a00000000140053006d003200540069006d0065004d00610070
@@ -0,0 +1 @@
1
+ 0000000100000007000000080059004d0069006e0000000600bff0000000000000000000080059004d006100780000000600bff0000000000000000000080058004d00610078000000060040546547f3fc2d54000000100055006e0069007100750065004900640000000a000000004800380064006600360062003300320031002d0064006500660039002d0034006500360033002d0039003400630035002d00640065003900660034006100310034006100300039006400000020004c00610073007400560061006c006900640059004f006600660073006500740000000600406314e147ae147b00000016004b00650079006600720061006d00650073004200410000000c000000002b800a000a1209abaaaaaaaa2a1c4011abaaaaaaaa2a0c400a1209542dfcf34765544011542dfcf3471363400000000c0044006200540079007000650000000a00000000140053006d003200540069006d0065004d00610070
@@ -0,0 +1,238 @@
1
+ /**
2
+ * P0.1 — injectGrades unit tests.
3
+ *
4
+ * Strategy: build a synthetic DRP with two pre-graded clips, scrape their
5
+ * auto-generated DbIds out of the SeqContainer XML, inject a new <Body>
6
+ * blob into one of them, and verify (a) the targeted clip's Body changed
7
+ * to the injected value, (b) the other clip's Body didn't change,
8
+ * (c) the surrounding XML is otherwise byte-identical.
9
+ *
10
+ * Run: node --test packages/drp-format/__tests__/inject-grades.test.js
11
+ */
12
+
13
+ const test = require('node:test');
14
+ const assert = require('node:assert/strict');
15
+ const fs = require('node:fs/promises');
16
+ const path = require('node:path');
17
+ const os = require('node:os');
18
+ const JSZip = require('jszip');
19
+
20
+ const drpFormat = require('..');
21
+ const { resolveVerifyTest } = require('./_resolve-verify');
22
+
23
+ // A plausible-shaped synthetic body. The injector does not parse the bytes,
24
+ // only substitutes them. Hex characters only.
25
+ const BASELINE_BODY = '80aaaaaaaaaaaaaaaa';
26
+ const ORIGINAL_BODY_CLIP_2 = '80bbbbbbbbbbbbbbbb';
27
+ const INJECTED_BODY = '81cafebabe0102030405060708090a0b0c0d0e0f';
28
+
29
+ function makeSyntheticDrx(bodyHex) {
30
+ return `<?xml version="1.0" encoding="UTF-8"?>\n<Resolve_Color_Exchange>\n <Body>${bodyHex}</Body>\n</Resolve_Color_Exchange>\n`;
31
+ }
32
+
33
+ async function buildSyntheticDrp(outPath) {
34
+ const buf = await drpFormat.buildDRP({
35
+ projectName: 'inject-grades-test',
36
+ timelines: [{
37
+ name: 'T1',
38
+ frameRate: 24,
39
+ startTimecode: '01:00:00:00',
40
+ resolution: '1920x1080',
41
+ videoTracks: [{
42
+ clips: [
43
+ {
44
+ start: 0, duration: 24, in: 0,
45
+ mediaFilePath: '/synthetic/clip1.mov',
46
+ grade: { body: BASELINE_BODY, hasCorrection: true, versionName: 'V1' },
47
+ },
48
+ {
49
+ start: 24, duration: 24, in: 0,
50
+ mediaFilePath: '/synthetic/clip2.mov',
51
+ grade: { body: ORIGINAL_BODY_CLIP_2, hasCorrection: true, versionName: 'V1' },
52
+ },
53
+ ],
54
+ }],
55
+ audioTracks: [],
56
+ }],
57
+ });
58
+ await fs.writeFile(outPath, buf);
59
+ return buf;
60
+ }
61
+
62
+ async function readSeqContainer(drpPath) {
63
+ const buf = await fs.readFile(drpPath);
64
+ const zip = await JSZip.loadAsync(buf);
65
+ const seqEntries = [];
66
+ zip.forEach((p, e) => {
67
+ if (!e.dir && /(^|\/)SeqContainer\d*\.xml$/.test(p)) seqEntries.push(p);
68
+ });
69
+ assert(seqEntries.length > 0, 'synthetic DRP should have at least one SeqContainer');
70
+ return zip.file(seqEntries[0]).async('string');
71
+ }
72
+
73
+ /**
74
+ * Scrape (DbId, Body) pairs from a built SeqContainer XML. Pulls them in
75
+ * document order so callers can pick clip 1 / clip 2 deterministically.
76
+ */
77
+ function scrapeClipBodies(xml) {
78
+ const pairs = [];
79
+ const clipRe = /<Sm2TiVideoClip\b[^>]*?DbId="([^"]+)"[\s\S]*?<Body>([0-9a-fA-F]+)<\/Body>[\s\S]*?<\/Sm2TiVideoClip>/g;
80
+ let m;
81
+ while ((m = clipRe.exec(xml)) !== null) {
82
+ pairs.push({ dbId: m[1], body: m[2] });
83
+ }
84
+ return pairs;
85
+ }
86
+
87
+ test('injectGrades: replaces body of the targeted clip only', async () => {
88
+ const tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'inject-grades-'));
89
+ try {
90
+ const srcDrp = path.join(tmp, 'src.drp');
91
+ const outDrp = path.join(tmp, 'out.drp');
92
+ await buildSyntheticDrp(srcDrp);
93
+
94
+ // 1. Discover the auto-generated DbIds + baseline bodies.
95
+ const beforeXml = await readSeqContainer(srcDrp);
96
+ const before = scrapeClipBodies(beforeXml);
97
+ assert.equal(before.length, 2, 'expected exactly two video clips with bodies');
98
+ assert.equal(before[0].body, BASELINE_BODY);
99
+ assert.equal(before[1].body, ORIGINAL_BODY_CLIP_2);
100
+
101
+ // 2. Inject into clip 1.
102
+ const result = await drpFormat.injectGrades(srcDrp, [
103
+ { clipId: before[0].dbId, drxContent: makeSyntheticDrx(INJECTED_BODY) },
104
+ ], { outputPath: outDrp });
105
+
106
+ assert.equal(result.clipsInjected, 1);
107
+ assert.deepEqual(result.misses, []);
108
+ assert.ok(result.bytes > 0);
109
+
110
+ // 3. Verify the injection landed only on clip 1.
111
+ const afterXml = await readSeqContainer(outDrp);
112
+ const after = scrapeClipBodies(afterXml);
113
+ assert.equal(after.length, 2);
114
+ assert.equal(after[0].dbId, before[0].dbId);
115
+ assert.equal(after[1].dbId, before[1].dbId);
116
+ assert.equal(after[0].body, INJECTED_BODY, 'clip 1 body should be replaced');
117
+ assert.equal(after[1].body, ORIGINAL_BODY_CLIP_2, 'clip 2 body must be untouched');
118
+ } finally {
119
+ await fs.rm(tmp, { recursive: true, force: true });
120
+ }
121
+ });
122
+
123
+ test('injectGrades: misses report unmatched DbIds', async () => {
124
+ const tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'inject-grades-'));
125
+ try {
126
+ const srcDrp = path.join(tmp, 'src.drp');
127
+ const outDrp = path.join(tmp, 'out.drp');
128
+ await buildSyntheticDrp(srcDrp);
129
+
130
+ const result = await drpFormat.injectGrades(srcDrp, [
131
+ { clipId: 'definitely-not-a-real-dbid', drxContent: makeSyntheticDrx(INJECTED_BODY) },
132
+ ], { outputPath: outDrp });
133
+
134
+ assert.equal(result.clipsInjected, 0);
135
+ assert.deepEqual(result.misses, ['definitely-not-a-real-dbid']);
136
+ } finally {
137
+ await fs.rm(tmp, { recursive: true, force: true });
138
+ }
139
+ });
140
+
141
+ test('injectGrades: rejects malformed DRX content', async () => {
142
+ const tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'inject-grades-'));
143
+ try {
144
+ const srcDrp = path.join(tmp, 'src.drp');
145
+ await buildSyntheticDrp(srcDrp);
146
+ const beforeXml = await readSeqContainer(srcDrp);
147
+ const before = scrapeClipBodies(beforeXml);
148
+
149
+ await assert.rejects(
150
+ () => drpFormat.injectGrades(srcDrp, [
151
+ { clipId: before[0].dbId, drxContent: '<not-a-drx>nothing here</not-a-drx>' },
152
+ ], { outputPath: path.join(tmp, 'out.drp') }),
153
+ /no <Body>HEX<\/Body> block/i,
154
+ );
155
+ } finally {
156
+ await fs.rm(tmp, { recursive: true, force: true });
157
+ }
158
+ });
159
+
160
+ test('injectGrades: resolveId alias works the same as clipId', async () => {
161
+ const tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'inject-grades-'));
162
+ try {
163
+ const srcDrp = path.join(tmp, 'src.drp');
164
+ const outDrp = path.join(tmp, 'out.drp');
165
+ await buildSyntheticDrp(srcDrp);
166
+
167
+ const beforeXml = await readSeqContainer(srcDrp);
168
+ const before = scrapeClipBodies(beforeXml);
169
+
170
+ const result = await drpFormat.injectGrades(srcDrp, [
171
+ { resolveId: before[1].dbId, drxContent: makeSyntheticDrx(INJECTED_BODY) },
172
+ ], { outputPath: outDrp });
173
+
174
+ assert.equal(result.clipsInjected, 1);
175
+ const afterXml = await readSeqContainer(outDrp);
176
+ const after = scrapeClipBodies(afterXml);
177
+ assert.equal(after[1].body, INJECTED_BODY);
178
+ } finally {
179
+ await fs.rm(tmp, { recursive: true, force: true });
180
+ }
181
+ });
182
+
183
+ test('injectGrades: in-place overwrite atomically replaces the source file', async () => {
184
+ const tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'inject-grades-'));
185
+ try {
186
+ const drpPath = path.join(tmp, 'inplace.drp');
187
+ await buildSyntheticDrp(drpPath);
188
+ const originalBytes = (await fs.stat(drpPath)).size;
189
+
190
+ const beforeXml = await readSeqContainer(drpPath);
191
+ const before = scrapeClipBodies(beforeXml);
192
+
193
+ // No outputPath → in-place.
194
+ await drpFormat.injectGrades(drpPath, [
195
+ { clipId: before[0].dbId, drxContent: makeSyntheticDrx(INJECTED_BODY) },
196
+ ]);
197
+
198
+ const afterBytes = (await fs.stat(drpPath)).size;
199
+ assert.ok(Math.abs(afterBytes - originalBytes) < originalBytes * 0.25,
200
+ 'in-place output size should be in the ballpark of original');
201
+
202
+ const afterXml = await readSeqContainer(drpPath);
203
+ const after = scrapeClipBodies(afterXml);
204
+ assert.equal(after[0].body, INJECTED_BODY);
205
+
206
+ // No leftover tmp file.
207
+ const tmpLeftover = await fs.stat(`${drpPath}.injecting`).catch(() => null);
208
+ assert.equal(tmpLeftover, null);
209
+ } finally {
210
+ await fs.rm(tmp, { recursive: true, force: true });
211
+ }
212
+ });
213
+
214
+ test('injectGrades: internals — extractDrxBodyHex strips non-hex chars', () => {
215
+ const { _internals } = require('../inject-grades');
216
+ const xml = '<x><Body> 81 ca\nfe ba be </Body></x>';
217
+ assert.equal(_internals.extractDrxBodyHex(xml), '81cafebabe');
218
+ });
219
+
220
+ test('injectGrades: internals — extractDrxBodyHex throws on missing Body', () => {
221
+ const { _internals } = require('../inject-grades');
222
+ assert.throws(() => _internals.extractDrxBodyHex('<x>nope</x>'),
223
+ /no <Body>HEX<\/Body> block/i);
224
+ });
225
+
226
+ // ─── Resolve-in-loop verification (RESOLVE_VERIFY=1) ───────────
227
+ //
228
+ // Per P0.1 step 5 (logged deferred in knowledge/resolve-verifications.md),
229
+ // the byte-identical-render check requires Resolve. When the env flag is
230
+ // unset the test skips with a clear marker.
231
+
232
+ resolveVerifyTest('injectGrades: rendered frame matches direct DRX apply', async () => {
233
+ // The harness's resolve-verifications.md has the full recipe. The actual
234
+ // implementation will live here once a captured fixture + a Resolve session
235
+ // is in flight. Until then it's a placeholder that exists to demonstrate
236
+ // the resolveVerifyTest gate and to surface in the skip list as a TODO.
237
+ throw new Error('TODO — implement once fixtures/inject-target.drp + Resolve session land');
238
+ });