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,85 @@
1
+ const test = require('node:test');
2
+ const assert = require('node:assert');
3
+ const fs = require('fs');
4
+ const JSZip = require('jszip');
5
+ const {
6
+ decodeKeyedDict, encodeKeyedDict, readKeyedDict, keyedDictType,
7
+ getKeyedValue, setKeyedValue, readAudioTracks,
8
+ } = require('../keyed-dict');
9
+
10
+ const FIXTURE = 'docs/design/drp-drx-drt-closeout-harness/fixtures/canary-resolve21.drp';
11
+
12
+ async function grabAll(tag) {
13
+ const zip = await JSZip.loadAsync(fs.readFileSync(FIXTURE));
14
+ const out = [];
15
+ for (const name of Object.keys(zip.files)) {
16
+ if (!name.endsWith('.xml')) continue;
17
+ const x = await zip.files[name].async('string');
18
+ const re = new RegExp(`<${tag}>([0-9a-f]+)</${tag}>`, 'g');
19
+ let m;
20
+ while ((m = re.exec(x))) out.push(m[1]);
21
+ }
22
+ return out;
23
+ }
24
+
25
+ test('decodeKeyedDict parses Geometry with typed values', async (t) => {
26
+ if (!fs.existsSync(FIXTURE)) { t.skip('fixture missing'); return; }
27
+ const hex = (await grabAll('Geometry'))[0];
28
+ const { count, entries } = decodeKeyedDict(hex);
29
+ assert.strictEqual(count, entries.length, 'count matches entry total');
30
+ const byKey = Object.fromEntries(entries.map((e) => [e.key, e]));
31
+ assert.strictEqual(byKey.DbType.value, 'BtGeometry');
32
+ // Resolution is a BYTES value [u32 0][u32 W][u32 0][u32 H] — W=352 H=262 in the canary template.
33
+ const res = Buffer.from(byKey.Resolution.value, 'hex');
34
+ assert.strictEqual(res.readUInt32BE(4), 352);
35
+ assert.strictEqual(res.readUInt32BE(12), 262);
36
+ });
37
+
38
+ test('decodeKeyedDict parses Time (StartFrame/NumFrames/FrameRate)', async (t) => {
39
+ if (!fs.existsSync(FIXTURE)) { t.skip('fixture missing'); return; }
40
+ const hex = (await grabAll('Time'))[0];
41
+ const byKey = Object.fromEntries(decodeKeyedDict(hex).entries.map((e) => [e.key, e]));
42
+ assert.strictEqual(byKey.StartFrame.value, 0);
43
+ assert.strictEqual(byKey.NumFrames.value, 4576);
44
+ assert.strictEqual(keyedDictType(hex), 'BtVideoTime');
45
+ // FrameRate BYTES value is [double fps LE][double 0] — 29.97 in the canary.
46
+ const fr = Buffer.from(byKey.FrameRate.value, 'hex');
47
+ assert.ok(Math.abs(fr.readDoubleLE(0) - 30000 / 1001) < 1e-6, `fps ${fr.readDoubleLE(0)}`);
48
+ });
49
+
50
+ test('encodeKeyedDict round-trips every keyed-dict blob byte-for-byte', async (t) => {
51
+ if (!fs.existsSync(FIXTURE)) { t.skip('fixture missing'); return; }
52
+ // Includes the nested audio dicts (TracksBA holds a BYTES inner BtAudioTrack dict).
53
+ for (const tag of ['Geometry', 'Time', 'VideoMetadata', 'Proxy', 'TracksBA', 'VirtualAudioTrackBA']) {
54
+ for (const hex of await grabAll(tag)) {
55
+ const re = encodeKeyedDict(decodeKeyedDict(hex)).toString('hex');
56
+ assert.strictEqual(re, hex, `${tag} must round-trip exactly`);
57
+ }
58
+ }
59
+ });
60
+
61
+ test('readAudioTracks decodes BtAudioInfo config from TracksBA', async (t) => {
62
+ if (!fs.existsSync(FIXTURE)) { t.skip('fixture missing'); return; }
63
+ const tracks = readAudioTracks((await grabAll('TracksBA'))[0]);
64
+ assert.strictEqual(tracks.length, 1);
65
+ const tr = tracks[0];
66
+ assert.strictEqual(tr.sampleRate, 44100);
67
+ assert.strictEqual(tr.numChannels, 2);
68
+ assert.strictEqual(tr.codecName, 'AAC');
69
+ // Duration is in audio samples: 6733824 / 44100 == 152.69 s (the clip length).
70
+ assert.strictEqual(Math.round(Number(tr.duration) / tr.sampleRate), 153);
71
+ });
72
+
73
+ test('setKeyedValue edits a value and re-encodes', async (t) => {
74
+ if (!fs.existsSync(FIXTURE)) { t.skip('fixture missing'); return; }
75
+ const hex = (await grabAll('Time'))[0];
76
+ const edited = setKeyedValue(hex, 'NumFrames', 1234);
77
+ assert.strictEqual(getKeyedValue(edited, 'NumFrames'), 1234);
78
+ assert.strictEqual(getKeyedValue(edited, 'StartFrame'), 0, 'other keys untouched');
79
+ });
80
+
81
+ test('readKeyedDict back-compat returns [{key,value}]', async (t) => {
82
+ if (!fs.existsSync(FIXTURE)) { t.skip('fixture missing'); return; }
83
+ const keys = readKeyedDict((await grabAll('Time'))[0]).map((e) => e.key);
84
+ assert.ok(keys.includes('NumFrames'));
85
+ });
@@ -0,0 +1,119 @@
1
+ /**
2
+ * P5.2 — extractProjectLUTRefs unit tests.
3
+ *
4
+ * Strategy: build synthetic DRPs via buildDRP with optional LUT slots
5
+ * populated. Verify the extractor surfaces each slot when set, and
6
+ * returns empty array when none are.
7
+ *
8
+ * Real Resolve-fixture verification is deferred to the Resolve-batch
9
+ * session (queued in knowledge/resolve-verifications.md).
10
+ */
11
+
12
+ const test = require('node:test');
13
+ const assert = require('node:assert/strict');
14
+ const fs = require('node:fs/promises');
15
+ const path = require('node:path');
16
+ const os = require('node:os');
17
+
18
+ const drpFormat = require('..');
19
+
20
+ const BASE_SPEC = {
21
+ projectName: 'lut-test',
22
+ timelines: [{
23
+ name: 'T1', frameRate: 24, startTimecode: '01:00:00:00', resolution: '1920x1080',
24
+ videoTracks: [{ clips: [{ start: 0, duration: 24, in: 0, mediaFilePath: '/m/c.mov' }] }],
25
+ audioTracks: [],
26
+ }],
27
+ };
28
+
29
+ async function buildDrpWithLuts(luts) {
30
+ return drpFormat.buildDRP({
31
+ ...BASE_SPEC,
32
+ projectSettings: { ...luts },
33
+ });
34
+ }
35
+
36
+ test('extractProjectLUTRefs: returns empty when no LUTs in project.xml', async () => {
37
+ const buf = await drpFormat.buildDRP(BASE_SPEC);
38
+ const refs = await drpFormat.extractProjectLUTRefs(buf);
39
+ assert.deepEqual(refs, []);
40
+ });
41
+
42
+ test('extractProjectLUTRefs: surfaces a TimelineLUT path', async () => {
43
+ const buf = await buildDrpWithLuts({ timelineLUT: '/lut/look.cube' });
44
+ const refs = await drpFormat.extractProjectLUTRefs(buf);
45
+ assert.equal(refs.length, 1);
46
+ assert.equal(refs[0].slot, 'TimelineLUT');
47
+ assert.equal(refs[0].path, '/lut/look.cube');
48
+ });
49
+
50
+ test('extractProjectLUTRefs: surfaces all four standard slots', async () => {
51
+ const buf = await buildDrpWithLuts({
52
+ inputLUT: '/lut/idt.clf',
53
+ outputLUT: '/lut/odt.clf',
54
+ timelineLUT: '/lut/look.cube',
55
+ monitorLUT: '/lut/monitor.cube',
56
+ });
57
+ const refs = await drpFormat.extractProjectLUTRefs(buf);
58
+ const slots = refs.map((r) => r.slot).sort();
59
+ assert.deepEqual(slots, ['InputLUT', 'MonitorLUT', 'OutputLUT', 'TimelineLUT']);
60
+ const byTag = Object.fromEntries(refs.map((r) => [r.slot, r.path]));
61
+ assert.equal(byTag.InputLUT, '/lut/idt.clf');
62
+ assert.equal(byTag.OutputLUT, '/lut/odt.clf');
63
+ assert.equal(byTag.TimelineLUT, '/lut/look.cube');
64
+ assert.equal(byTag.MonitorLUT, '/lut/monitor.cube');
65
+ });
66
+
67
+ test('extractProjectLUTRefs: surfaces claimed color spaces alongside each LUT', async () => {
68
+ const buf = await buildDrpWithLuts({ timelineLUT: '/lut/look.cube' });
69
+ const refs = await drpFormat.extractProjectLUTRefs(buf);
70
+ const spaces = refs[0].claimedSpaces;
71
+ // buildDRP defaults populate the color-space fields.
72
+ assert.ok(spaces.InputColorSpace);
73
+ assert.ok(spaces.TimelineColorSpace);
74
+ assert.ok(spaces.OutputColorSpace);
75
+ });
76
+
77
+ test('extractProjectLUTRefs: accepts a filesystem path', async () => {
78
+ const tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'lut-refs-'));
79
+ try {
80
+ const drpPath = path.join(tmp, 'with-lut.drp');
81
+ await fs.writeFile(drpPath, await buildDrpWithLuts({ timelineLUT: '/lut/x.cube' }));
82
+ const refs = await drpFormat.extractProjectLUTRefs(drpPath);
83
+ assert.equal(refs.length, 1);
84
+ assert.equal(refs[0].path, '/lut/x.cube');
85
+ } finally {
86
+ await fs.rm(tmp, { recursive: true, force: true });
87
+ }
88
+ });
89
+
90
+ test('extractProjectLUTRefs: rejects bad input types', async () => {
91
+ await assert.rejects(() => drpFormat.extractProjectLUTRefs(null), /string path or a Buffer/);
92
+ await assert.rejects(() => drpFormat.extractProjectLUTRefs(42), /string path or a Buffer/);
93
+ });
94
+
95
+ test('extractProjectLUTRefs: returns [] when archive has no project.xml (DRT-like)', async () => {
96
+ // Build a minimal zip with no project.xml — mimics DRT layout.
97
+ const JSZip = require('jszip');
98
+ const z = new JSZip();
99
+ z.folder('Primary1').file('SeqContainer1.xml', '<Sm2SequenceContainer><Name>x</Name><FrameRate>0</FrameRate></Sm2SequenceContainer>');
100
+ const buf = await z.generateAsync({ type: 'nodebuffer' });
101
+ const refs = await drpFormat.extractProjectLUTRefs(buf);
102
+ assert.deepEqual(refs, []);
103
+ });
104
+
105
+ test('extractProjectLUTRefs: ignores empty-string LUT slots', async () => {
106
+ // If someone manually constructed a DRP with empty LUT tags, those
107
+ // shouldn't count as references.
108
+ const buf = await buildDrpWithLuts({ timelineLUT: '' });
109
+ const refs = await drpFormat.extractProjectLUTRefs(buf);
110
+ assert.deepEqual(refs, []);
111
+ });
112
+
113
+ test('LUT_RECOGNIZED_SLOTS: exposes the slot whitelist', () => {
114
+ assert.ok(Array.isArray(drpFormat.LUT_RECOGNIZED_SLOTS));
115
+ assert.ok(drpFormat.LUT_RECOGNIZED_SLOTS.includes('TimelineLUT'));
116
+ assert.ok(drpFormat.LUT_RECOGNIZED_SLOTS.includes('InputLUT'));
117
+ assert.ok(drpFormat.LUT_RECOGNIZED_SLOTS.includes('OutputLUT'));
118
+ assert.ok(drpFormat.LUT_RECOGNIZED_SLOTS.includes('MonitorLUT'));
119
+ });
@@ -0,0 +1,25 @@
1
+ const test = require('node:test');
2
+ const assert = require('node:assert');
3
+ const mb = require('../media-blobs');
4
+
5
+ // Samples captured from real Resolve 21 exports (canary).
6
+ test('decodeResolutionBlob matches a real sample (1920x1080)', () => {
7
+ assert.deepStrictEqual(mb.decodeResolutionBlob('00000000000007800000000000000438'), { width: 1920, height: 1080 });
8
+ });
9
+ test('decodeRateBlob: FrameRate=24, MediaFrameRate=29.97', () => {
10
+ assert.strictEqual(mb.decodeRateBlob('00000000000038400000000000000000'), 24);
11
+ assert.ok(Math.abs(mb.decodeRateBlob('286b55e253f83d400000000000000000') - 30000 / 1001) < 1e-9);
12
+ });
13
+ test('decodeMediaExtentsBlob: start 3600s, ~152.67s', () => {
14
+ const e = mb.decodeMediaExtentsBlob('000000000020ac405055555555156340');
15
+ assert.strictEqual(e.startSeconds, 3600);
16
+ assert.ok(Math.abs(e.durationSeconds - 152.666) < 0.01);
17
+ });
18
+ test('round-trips', () => {
19
+ assert.deepStrictEqual(mb.decodeResolutionBlob(mb.encodeResolutionBlob({ width: 3840, height: 2160 })), { width: 3840, height: 2160 });
20
+ assert.strictEqual(mb.decodeRateBlob(mb.encodeRateBlob(23.976)), 23.976);
21
+ const ex = { startSeconds: 3600, durationSeconds: 10.5 };
22
+ assert.deepStrictEqual(mb.decodeMediaExtentsBlob(mb.encodeMediaExtentsBlob(ex)), ex);
23
+ // encodeResolutionBlob reproduces the real 1080p sample exactly
24
+ assert.strictEqual(mb.encodeResolutionBlob({ width: 1920, height: 1080 }), '00000000000007800000000000000438');
25
+ });
@@ -0,0 +1,119 @@
1
+ const test = require('node:test');
2
+ const assert = require('node:assert');
3
+ const fs = require('fs');
4
+ const JSZip = require('jszip');
5
+ const {
6
+ decodeTimemap, encodeTimemap, encodeRetimedTimemap,
7
+ identityTimemap, buildConstantSpeedTimemap, buildTimemap,
8
+ } = require('../media-timemap');
9
+
10
+ const FIXTURE = 'docs/design/drp-drx-drt-closeout-harness/fixtures/canary-resolve21.drp';
11
+ // Live-captured retimes (Sm2TimeMap keyed-dicts) from real Resolve 21 exports.
12
+ const RETIMED_50 = fs.readFileSync(
13
+ require('path').join(__dirname, 'fixtures', 'retimed-timemap-50pct.hex'), 'utf8').trim();
14
+ // Dynamic ramp: 50% for the first segment, then a speed point, then 200%.
15
+ const RETIMED_DYNAMIC = fs.readFileSync(
16
+ require('path').join(__dirname, 'fixtures', 'retimed-timemap-dynamic.hex'), 'utf8').trim();
17
+
18
+ async function grab(tag) {
19
+ const zip = await JSZip.loadAsync(fs.readFileSync(FIXTURE));
20
+ for (const name of Object.keys(zip.files)) {
21
+ if (!name.endsWith('.xml')) continue;
22
+ const x = await zip.files[name].async('string');
23
+ const m = x.match(new RegExp(`<${tag}>([0-9a-f]+)</${tag}>`));
24
+ if (m) return m[1];
25
+ }
26
+ return null;
27
+ }
28
+
29
+ test('decodeTimemap reads the identity (1x) compact form', async (t) => {
30
+ if (!fs.existsSync(FIXTURE)) { t.skip('fixture missing'); return; }
31
+ const { form, type, seconds } = decodeTimemap(await grab('MediaTimemapBA'));
32
+ assert.strictEqual(form, 'identity');
33
+ assert.strictEqual(type, 2);
34
+ assert.strictEqual(seconds.length, 5);
35
+ // 152.6525 s * 29.97 fps == 4575 (canary clip last-frame index).
36
+ assert.strictEqual(Math.round(seconds[0] * (30000 / 1001)), 4575);
37
+ assert.strictEqual(seconds[1], 0);
38
+ });
39
+
40
+ test('encodeTimemap round-trips the identity blob byte-for-byte', async (t) => {
41
+ if (!fs.existsSync(FIXTURE)) { t.skip('fixture missing'); return; }
42
+ const hex = await grab('MediaTimemapBA');
43
+ assert.strictEqual(encodeTimemap(decodeTimemap(hex)).toString('hex'), hex);
44
+ });
45
+
46
+ test('decodeTimemap handles the degenerate (title) 9-byte form', () => {
47
+ const { form, type, seconds } = decodeTimemap('024013d55555555555');
48
+ assert.strictEqual(form, 'identity');
49
+ assert.strictEqual(type, 2);
50
+ assert.strictEqual(seconds.length, 1);
51
+ });
52
+
53
+ test('decodeTimemap reads the retimed Sm2TimeMap form + exact keyframe speed', () => {
54
+ const d = decodeTimemap(RETIMED_50);
55
+ assert.strictEqual(d.form, 'retimed');
56
+ // Exact speed comes from the keyframe ratio: 0.5 (the dialog input), not the
57
+ // frame-quantized LastValidYOffset/XMax (≈0.49996).
58
+ assert.strictEqual(d.speed, 0.5);
59
+ assert.ok(Math.abs(d.sourceDurationSec - 152.6525) < 1e-3);
60
+ assert.ok(Math.abs(d.recordDurationSec - 305.33) < 1e-2);
61
+ });
62
+
63
+ test('encodeRetimedTimemap round-trips the live 50% blob byte-for-byte', () => {
64
+ const d = decodeTimemap(RETIMED_50);
65
+ assert.strictEqual(encodeRetimedTimemap(d).toString('hex'), RETIMED_50);
66
+ });
67
+
68
+ test('buildConstantSpeedTimemap rebuilds the exact captured 50% blob', () => {
69
+ // Given Resolve's frame-quantized XMax, everything else (keyframe endpoint) is derived.
70
+ const built = buildConstantSpeedTimemap({
71
+ speed: 0.5,
72
+ sourceDurationSec: 152.6525,
73
+ recordDurationSec: 305.33006666666665,
74
+ uniqueId: '346625ac-b0e3-4768-8418-276483860709',
75
+ });
76
+ assert.strictEqual(built.toString('hex'), RETIMED_50);
77
+ });
78
+
79
+ test('buildConstantSpeedTimemap default (nominal XMax) decodes to the exact speed', () => {
80
+ const built = buildConstantSpeedTimemap({
81
+ speed: 0.5, sourceDurationSec: 152.6525, uniqueId: '346625ac-b0e3-4768-8418-276483860709',
82
+ });
83
+ const d = decodeTimemap(built);
84
+ assert.strictEqual(d.form, 'retimed');
85
+ assert.strictEqual(d.speed, 0.5);
86
+ });
87
+
88
+ test('decodeTimemap reads a DYNAMIC ramp: per-segment speeds 50% then 200%', () => {
89
+ const d = decodeTimemap(RETIMED_DYNAMIC);
90
+ assert.strictEqual(d.form, 'retimed');
91
+ assert.strictEqual(d.variable, true);
92
+ assert.strictEqual(d.keyframes.length, 2);
93
+ assert.strictEqual(d.segments.length, 2);
94
+ assert.ok(Math.abs(d.segments[0].speed - 0.5) < 1e-3, `seg0 ${d.segments[0].speed}`);
95
+ assert.ok(Math.abs(d.segments[1].speed - 2.0) < 1e-3, `seg1 ${d.segments[1].speed}`);
96
+ });
97
+
98
+ test('encodeRetimedTimemap round-trips the dynamic ramp byte-for-byte', () => {
99
+ const d = decodeTimemap(RETIMED_DYNAMIC);
100
+ assert.strictEqual(encodeRetimedTimemap(d).toString('hex'), RETIMED_DYNAMIC);
101
+ });
102
+
103
+ test('buildTimemap rebuilds the captured dynamic ramp byte-for-byte', () => {
104
+ const d = decodeTimemap(RETIMED_DYNAMIC);
105
+ const built = buildTimemap({
106
+ keyframes: d.keyframes,
107
+ sourceDurationSec: d.sourceDurationSec,
108
+ recordDurationSec: d.recordDurationSec,
109
+ uniqueId: d.entries.find((e) => e.key === 'UniqueId').value,
110
+ });
111
+ assert.strictEqual(built.toString('hex'), RETIMED_DYNAMIC);
112
+ });
113
+
114
+ test('identityTimemap builds a [02][end,0,end,0,end] map', () => {
115
+ const b = decodeTimemap(identityTimemap(4576, 30000 / 1001));
116
+ assert.strictEqual(b.form, 'identity');
117
+ assert.strictEqual(b.seconds.length, 5);
118
+ assert.strictEqual(Math.round(b.seconds[0] * (30000 / 1001)), 4575);
119
+ });
@@ -0,0 +1,52 @@
1
+ // Unit: placeFusionTitle drops a Fusion Title onto a chosen video track of a real .drp.
2
+ // Verified structurally with the (reconciled) DRT parser. Live Resolve round-trip is the
3
+ // acceptance gate, proven manually — see resolve21-schema-reconciliation.md.
4
+
5
+ const test = require('node:test');
6
+ const assert = require('node:assert');
7
+ const path = require('node:path');
8
+ const fs = require('node:fs');
9
+ const { placeFusionTitle } = require('../place-fusion-title');
10
+ const { parseDRT } = require('../../drt-format');
11
+
12
+ const FIXTURE = path.resolve(
13
+ __dirname,
14
+ '../../../docs/design/drp-drx-drt-closeout-harness/fixtures/canary-resolve21.drp',
15
+ );
16
+
17
+ test('placeFusionTitle adds an empty V2 and places the title there', async (t) => {
18
+ if (!fs.existsSync(FIXTURE)) { t.skip(`fixture missing: ${FIXTURE}`); return; }
19
+
20
+ const START = 90000;
21
+ const res = await placeFusionTitle(FIXTURE, { trackIndex: 2, startFrame: START, durationFrames: 96 });
22
+
23
+ assert.ok(Buffer.isBuffer(res.buffer), 'returns a buffer');
24
+ assert.strictEqual(res.trackIndex, 2);
25
+ assert.strictEqual(res.createdTracks, 1, 'should have created one new (V2) track');
26
+ assert.ok(res.videoTrackCount >= 2, 'target timeline now has >=2 video tracks');
27
+
28
+ // Parse the result and find the modified timeline by its DbId.
29
+ const parsed = await parseDRT(res.buffer);
30
+ const tl = parsed.timelines.find((t2) => t2.sequence.includes(res.timelineUuid) || true)
31
+ && parsed.timelines.find((t2) => (t2.videoTracks || []).length >= 2);
32
+ assert.ok(tl, 'a timeline with >=2 video tracks exists after placement');
33
+
34
+ const v2 = tl.videoTracks[1];
35
+ assert.strictEqual((v2.clips || []).length, 1, 'V2 holds exactly the placed title');
36
+ assert.strictEqual(v2.clips[0].start, START, 'placed clip start matches startFrame');
37
+ // A Fusion Title carries no MediaFilePath (empty) — distinguishes it from a media clip.
38
+ assert.ok(!v2.clips[0].mediaFilePath, 'title clip has no MediaFilePath');
39
+ });
40
+
41
+ test('placeFusionTitle requires startFrame', async () => {
42
+ await assert.rejects(() => placeFusionTitle(FIXTURE, { trackIndex: 2 }), /startFrame/);
43
+ });
44
+
45
+ test('placeFusionTitle places onto an existing track without creating one', async (t) => {
46
+ if (!fs.existsSync(FIXTURE)) { t.skip(`fixture missing: ${FIXTURE}`); return; }
47
+ const res = await placeFusionTitle(FIXTURE, { trackIndex: 1, startFrame: 200000 });
48
+ assert.strictEqual(res.createdTracks, 0, 'V1 already exists — no track created');
49
+ const parsed = await parseDRT(res.buffer);
50
+ const tl = parsed.timelines.find((t2) => (t2.videoTracks || [])[0]?.clips?.some((c) => c.start === 200000));
51
+ assert.ok(tl, 'placed title found on V1 at the requested start');
52
+ });
@@ -0,0 +1,40 @@
1
+ // Unit: place a built-in generator (Sm2TiGenerator) on a chosen track. Live Resolve
2
+ // round-trip is the acceptance gate (see resolve21-schema-reconciliation.md).
3
+
4
+ const test = require('node:test');
5
+ const assert = require('node:assert');
6
+ const { placeGenerator } = require('../place-generator');
7
+ const { createEmptyProject } = require('../author-project');
8
+
9
+ async function genOnTrack(buffer, trackIdx) {
10
+ const JSZip = require('jszip');
11
+ const zip = await JSZip.loadAsync(buffer);
12
+ for (const n of Object.keys(zip.files)) {
13
+ if (!/SeqContainer\/[^/]+\.xml$|SeqContainer\d*\.xml$/.test(n)) continue;
14
+ const x = await zip.files[n].async('string');
15
+ const vtv = (x.match(/<VideoTrackVec>([\s\S]*?)<\/VideoTrackVec>/) || [null, ''])[1];
16
+ const tracks = vtv.match(/<Element>\s*<Sm2TiTrack\b[\s\S]*?<\/Sm2TiTrack>\s*<\/Element>/g) || [];
17
+ return tracks[trackIdx - 1] || '';
18
+ }
19
+ return '';
20
+ }
21
+
22
+ test('placeGenerator drops a Solid Color generator on V2 from scratch', async () => {
23
+ const { buffer, startFrame } = await createEmptyProject({ timelineName: 'Gen' });
24
+ const res = await placeGenerator(buffer, { trackIndex: 2, startFrame, durationFrames: 90 });
25
+ assert.strictEqual(res.generatorName, 'Solid Color');
26
+ assert.strictEqual(res.createdTracks, 1);
27
+ const v2 = await genOnTrack(res.buffer, 2);
28
+ assert.ok(/<Sm2TiGenerator\b/.test(v2), 'V2 holds an Sm2TiGenerator');
29
+ assert.ok(/<PrettyType>Solid Color<\/PrettyType>/.test(v2), 'PrettyType set');
30
+ assert.ok(new RegExp(`<Start>${startFrame}</Start>`).test(v2), 'start set');
31
+ assert.ok(/<Duration>90<\/Duration>/.test(v2), 'duration set');
32
+ });
33
+
34
+ test('placeGenerator honors generatorName + requires startFrame', async () => {
35
+ const { buffer, startFrame } = await createEmptyProject();
36
+ const res = await placeGenerator(buffer, { generatorName: 'Gray Scale', trackIndex: 1, startFrame });
37
+ const v1 = await genOnTrack(res.buffer, 1);
38
+ assert.ok(/<PrettyType>Gray Scale<\/PrettyType>/.test(v1));
39
+ await assert.rejects(() => placeGenerator(buffer, { trackIndex: 1 }), /startFrame/);
40
+ });
@@ -0,0 +1,48 @@
1
+ // Unit: insert a cross-dissolve between two abutting clips. Live Resolve round-trip is the
2
+ // acceptance gate. Template captured from a GUI-authored Cross Dissolve in Resolve 21.
3
+
4
+ const test = require('node:test');
5
+ const assert = require('node:assert');
6
+ const { placeTransition } = require('../place-transition');
7
+
8
+ // Two abutting Sm2TiVideoClips: c0 [0,100), c1 [100,200) — cut at 100.
9
+ async function synth2() {
10
+ const JSZip = require('jszip');
11
+ const clip = (i) => `<Element><Sm2TiVideoClip DbId="c${i}"><FieldsBlob/><Name>c${i}</Name><Start>${i * 100}</Start><Duration>100</Duration><In/></Sm2TiVideoClip></Element>`;
12
+ const track = `<Element><Sm2TiTrack DbId="t"><FieldsBlob/><Type>0</Type><SubType>0</SubType><Flags>0</Flags><Sequence>s</Sequence><Items>${clip(0)}${clip(1)}</Items><FusionCompHolderItems/><UserDefinedName/><LayersVec/></Sm2TiTrack></Element>`;
13
+ const seq = `<?xml version="1.0"?>\n<Sm2SequenceContainer DbId="s1"><FieldsBlob/><VideoTrackVec>${track}</VideoTrackVec><AudioTrackVec/></Sm2SequenceContainer>`;
14
+ const z = new JSZip(); z.file('SeqContainer/s1.xml', seq); return z.generateAsync({ type: 'nodebuffer' });
15
+ }
16
+
17
+ async function vtv(buf) {
18
+ const JSZip = require('jszip');
19
+ const zip = await JSZip.loadAsync(buf);
20
+ const x = await zip.file('SeqContainer/s1.xml').async('string');
21
+ return x.match(/<VideoTrackVec>([\s\S]*?)<\/VideoTrackVec>/)[1];
22
+ }
23
+
24
+ test('placeTransition inserts an Sm2TiTransition between the two clips, centered on the cut', async () => {
25
+ const res = await placeTransition(await synth2(), { track: 1, atFrame: 100, durationFrames: 24 });
26
+ assert.strictEqual(res.start, 88, 'centered: 100 - 24/2');
27
+ assert.strictEqual(res.durationFrames, 24);
28
+ assert.ok(res.transitionDbId, 'fresh DbId assigned');
29
+
30
+ const body = await vtv(res.buffer);
31
+ assert.ok(/<Sm2TiTransition\b/.test(body), 'transition present');
32
+ assert.ok(/<PrettyType>Cross Dissolve<\/PrettyType>/.test(body), 'is a Cross Dissolve');
33
+ assert.ok(/<Start>88<\/Start>/.test(body) && /<Duration>24<\/Duration>/.test(body), 'start/duration set');
34
+ // ordering: clip c0 ... transition ... clip c1
35
+ const order = [...body.matchAll(/<Sm2Ti(VideoClip|Transition)\b[^>]*DbId="([^"]+)"/g)].map((m) => m[1]);
36
+ assert.deepStrictEqual(order, ['VideoClip', 'Transition', 'VideoClip'], 'transition sits between the clips');
37
+ });
38
+
39
+ test('placeTransition errors when no abutting boundary at atFrame', async () => {
40
+ const buf = await synth2();
41
+ await assert.rejects(() => placeTransition(buf, { track: 1, atFrame: 9999 }), /no abutting clip boundary/);
42
+ });
43
+
44
+ test('placeTransition validates args', async () => {
45
+ const buf = await synth2();
46
+ await assert.rejects(() => placeTransition(buf, { track: 1, atFrame: 100, durationFrames: 1 }), /durationFrames/);
47
+ await assert.rejects(() => placeTransition(buf, { track: 1, atFrame: 100, trackType: 'audio' }), /only video/);
48
+ });
@@ -0,0 +1,64 @@
1
+ const test = require('node:test');
2
+ const assert = require('node:assert');
3
+ const fs = require('fs');
4
+ const JSZip = require('jszip');
5
+ const {
6
+ decodeProtobuf, encodeProtobuf, decodeEffectFilters, encodeEffectFilters,
7
+ } = require('../protobuf-wire');
8
+ const { decodeKeyedDict, encodeKeyedDict, classifyBlob } = require('../keyed-dict');
9
+
10
+ const FIXTURE = 'docs/design/drp-drx-drt-closeout-harness/fixtures/canary-resolve21.drp';
11
+ const TRANS = require('path').join(__dirname, '..', 'templates', 'transition-cross-dissolve.xml');
12
+
13
+ async function grabAll(path, tag) {
14
+ let xmls = [];
15
+ if (path.endsWith('.drp')) {
16
+ const zip = await JSZip.loadAsync(fs.readFileSync(path));
17
+ for (const n of Object.keys(zip.files)) {
18
+ if (n.endsWith('.xml')) xmls.push(await zip.files[n].async('string'));
19
+ }
20
+ } else {
21
+ xmls = [fs.readFileSync(path, 'utf8')];
22
+ }
23
+ const out = [];
24
+ for (const x of xmls) {
25
+ const re = new RegExp(`<${tag}>([0-9a-f]+)</${tag}>`, 'g');
26
+ let m;
27
+ while ((m = re.exec(x))) out.push(m[1]);
28
+ }
29
+ return out;
30
+ }
31
+
32
+ test('decodeProtobuf reads Radiometry color-science fields + round-trips', async (t) => {
33
+ if (!fs.existsSync(FIXTURE)) { t.skip('fixture missing'); return; }
34
+ const hex = (await grabAll(FIXTURE, 'Radiometry'))[0];
35
+ const fields = decodeProtobuf(hex);
36
+ assert.ok(fields.length >= 5, 'several varint fields');
37
+ assert.ok(fields.every((f) => f.wire === 0), 'Radiometry is all varints');
38
+ assert.strictEqual(encodeProtobuf(fields).toString('hex'), hex, 'round-trip exact');
39
+ });
40
+
41
+ test('decodeEffectFilters reads the transition effect envelope + round-trips', async () => {
42
+ const hex = (await grabAll(TRANS, 'EffectFiltersBA'))[0];
43
+ const dec = decodeEffectFilters(hex);
44
+ assert.strictEqual(dec.hdr, 2);
45
+ assert.ok(dec.fields.length > 0);
46
+ assert.strictEqual(encodeEffectFilters(dec).toString('hex'), hex, 'envelope round-trip exact');
47
+ });
48
+
49
+ test('classifyBlob splits FieldsBlob into keyed-dict / protobuf / zstd; keyed-dicts round-trip', async (t) => {
50
+ if (!fs.existsSync(FIXTURE)) { t.skip('fixture missing'); return; }
51
+ const all = await grabAll(FIXTURE, 'FieldsBlob');
52
+ const seen = new Set();
53
+ for (const hex of all) {
54
+ const cls = classifyBlob(hex);
55
+ seen.add(cls);
56
+ if (cls === 'keyed-dict') {
57
+ // Every keyed-dict FieldsBlob (element state + UI view-options) round-trips exactly.
58
+ assert.strictEqual(encodeKeyedDict(decodeKeyedDict(hex)).toString('hex'), hex);
59
+ }
60
+ // 'zstd' = compressed internal state, carried verbatim (no built-in zstd in Node).
61
+ }
62
+ assert.ok(seen.has('keyed-dict'), 'has keyed-dict FieldsBlobs');
63
+ assert.ok(seen.has('zstd'), 'has zstd internal-state FieldsBlobs');
64
+ });
@@ -0,0 +1,106 @@
1
+ // Unit: offline media relink — rewrite the media path in the Media Pool blobs + plain text.
2
+ // Live Resolve acceptance proven manually (lint: 0 offline; re-export blob shows the new path)
3
+ // — see resolve21-schema-reconciliation.md.
4
+
5
+ const test = require('node:test');
6
+ const assert = require('node:assert');
7
+ const path = require('node:path');
8
+ const fs = require('node:fs');
9
+ const { relinkMedia } = require('../relink-media');
10
+
11
+ const FIXTURE = path.resolve(
12
+ __dirname,
13
+ '../../../docs/design/drp-drx-drt-closeout-harness/fixtures/canary-resolve21.drp',
14
+ );
15
+ const FROM = '/media/Sample Clip.mp4';
16
+
17
+ async function videoClipBlob(buffer) {
18
+ const JSZip = require('jszip');
19
+ const zip = await JSZip.loadAsync(buffer);
20
+ const mp = await zip.file('MediaPool/Master/MpFolder.xml').async('string');
21
+ const m = mp.match(/<Video>\s*<BtVideoInfo\b[\s\S]*?<Clip>([0-9a-fA-F]*)<\/Clip>/);
22
+ return Buffer.from(m[1], 'hex');
23
+ }
24
+ async function timelineMediaPaths(buffer) {
25
+ const JSZip = require('jszip');
26
+ const zip = await JSZip.loadAsync(buffer);
27
+ const out = [];
28
+ for (const n of Object.keys(zip.files)) {
29
+ if (!/SeqContainer\/[^/]+\.xml$/.test(n)) continue;
30
+ const x = await zip.files[n].async('string');
31
+ for (const p of x.match(/<MediaFilePath>([^<]+)<\/MediaFilePath>/g) || []) out.push(p);
32
+ }
33
+ return out;
34
+ }
35
+
36
+ test('relinkMedia repoints the blob path AND the plain-text MediaFilePath', async (t) => {
37
+ if (!fs.existsSync(FIXTURE)) { t.skip(`fixture missing: ${FIXTURE}`); return; }
38
+ const TO = '/Volumes/ARCHIVE/relinked/My Cut Final.mov'; // different dir + name + length
39
+
40
+ const res = await relinkMedia(FIXTURE, { from: FROM, to: TO });
41
+ assert.strictEqual(res.relinked.length, 1);
42
+ assert.ok(res.relinked[0].blobsEdited >= 1, 'at least the video blob edited');
43
+ assert.ok(res.relinked[0].textRefs >= 1, 'plain-text refs edited');
44
+
45
+ const blob = await videoClipBlob(res.buffer);
46
+ assert.ok(blob.includes(Buffer.from('/Volumes/ARCHIVE/relinked', 'utf8')), 'new dir in blob');
47
+ assert.ok(blob.includes(Buffer.from('My Cut Final.mov', 'utf8')), 'new filename in blob');
48
+ assert.ok(!blob.includes(Buffer.from('Sample Clip.mp4', 'utf8')), 'old filename gone from blob');
49
+
50
+ // The 4-byte BE payload-length header must stay consistent (len = blob.length - 8).
51
+ assert.strictEqual(blob.readUInt32BE(4), blob.length - 8, 'blob payload-length header updated');
52
+
53
+ const paths = await timelineMediaPaths(res.buffer);
54
+ assert.ok(paths.every((p) => p.includes(TO)), 'timeline MediaFilePath repointed');
55
+ });
56
+
57
+ test('relinkMedia supports multiple mappings', async (t) => {
58
+ if (!fs.existsSync(FIXTURE)) { t.skip(`fixture missing: ${FIXTURE}`); return; }
59
+ const res = await relinkMedia(FIXTURE, {
60
+ mappings: [{ from: FROM, to: '/new/loc/clip-a.mov' }, { from: '/never/here.mov', to: '/x/y.mov' }],
61
+ });
62
+ assert.strictEqual(res.relinked.length, 2);
63
+ assert.ok(res.relinked[0].blobsEdited >= 1);
64
+ assert.strictEqual(res.relinked[1].blobsEdited, 0, 'absent source maps to nothing');
65
+ });
66
+
67
+ test('relinkMedia requires absolute paths', async () => {
68
+ await assert.rejects(() => relinkMedia(FIXTURE, { from: 'rel/a.mov', to: '/abs/b.mov' }), /absolute/);
69
+ });
70
+
71
+ test('relinkMedia requires a mapping', async () => {
72
+ await assert.rejects(() => relinkMedia(FIXTURE, {}), /from.*to|mappings/);
73
+ });
74
+
75
+ const { repointMedia } = require('../relink-media');
76
+
77
+ test('repointMedia relinks the path AND patches resolution/frames/fps in the matching entry', async (t) => {
78
+ if (!fs.existsSync(FIXTURE)) { t.skip(`fixture missing: ${FIXTURE}`); return; }
79
+ const TO = '/Volumes/ARCHIVE/new clip.mov';
80
+ const res = await repointMedia(FIXTURE, {
81
+ from: FROM, to: TO,
82
+ fromSpec: { width: 352, height: 262, frameCount: 4576, fps: 30000 / 1001 },
83
+ toSpec: { width: 1920, height: 1080, frameCount: 240, fps: 24 },
84
+ });
85
+ assert.strictEqual(res.specPatched[0].patched, true);
86
+
87
+ const JSZip = require('jszip');
88
+ const zip = await JSZip.loadAsync(res.buffer);
89
+ const mp = await zip.file('MediaPool/Master/MpFolder.xml').async('string');
90
+ // entry renamed + geometry/time patched to target values
91
+ const entry = mp.match(/<Sm2MpVideoClip\b[\s\S]*?<\/Sm2MpVideoClip>/)[0];
92
+ assert.ok(entry.includes('<Name>new clip.mov</Name>'), 'entry renamed');
93
+ const geo = Buffer.from(entry.match(/<Geometry>([0-9a-fA-F]+)<\/Geometry>/)[1], 'hex');
94
+ assert.ok(geo.includes(Buffer.from([0, 0, 7, 128])), 'width 1920 (0x780) patched');
95
+ assert.ok(geo.includes(Buffer.from([0, 0, 4, 56])), 'height 1080 (0x438) patched');
96
+ assert.ok(!geo.includes(Buffer.from([0, 0, 1, 96])), 'old width 352 gone');
97
+ const time = Buffer.from(entry.match(/<Time>([0-9a-fA-F]+)<\/Time>/)[1], 'hex');
98
+ assert.ok(time.includes(Buffer.from([0, 0, 0, 240])), 'frame count 240 patched');
99
+ });
100
+
101
+ test('repointMedia without specs just relinks (no patch)', async (t) => {
102
+ if (!fs.existsSync(FIXTURE)) { t.skip(`fixture missing: ${FIXTURE}`); return; }
103
+ const res = await repointMedia(FIXTURE, { from: FROM, to: '/x/y.mov' });
104
+ assert.strictEqual(res.specPatched[0].patched, false);
105
+ assert.ok(res.relinked[0].blobsEdited >= 1);
106
+ });