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,65 @@
1
+ /**
2
+ * Bridge: load the vendored CommonJS format libraries from ESM server code.
3
+ *
4
+ * The vendored libs under../vendor/** are CommonJS (require()), scoped to
5
+ * CJS by resolve-advanced/package.json having no "type":"module". The server
6
+ * files are.mjs (ESM) so they can import the ESM MCP SDK. createRequire
7
+ * bridges the two — lazy, so a tool that's never called never loads its lib
8
+ * (and a missing optional dep never crashes startup).
9
+ */
10
+
11
+ import { createRequire } from 'node:module';
12
+
13
+ const require = createRequire(import.meta.url);
14
+
15
+ let _drp = null;
16
+ let _drt = null;
17
+ let _drxParser = null;
18
+ let _drxGen = null;
19
+ let _drxCdl = null;
20
+ let _drxMerger = null;
21
+ let _drxCodec = null;
22
+ let _nodeLayout = null;
23
+ let _audioFairlight = null;
24
+ let _fusion = null;
25
+
26
+ export function drp() {
27
+ if (!_drp) _drp = require('../vendor/drp-format/index.js');
28
+ return _drp;
29
+ }
30
+ export function drt() {
31
+ if (!_drt) _drt = require('../vendor/drt-format/index.js');
32
+ return _drt;
33
+ }
34
+ export function drxParser() {
35
+ if (!_drxParser) _drxParser = require('../vendor/drx-codec/drx-parser.js');
36
+ return _drxParser;
37
+ }
38
+ export function drxGenerator() {
39
+ if (!_drxGen) _drxGen = require('../vendor/drx-codec/drx-generator.js');
40
+ return _drxGen;
41
+ }
42
+ export function drxCdl() {
43
+ if (!_drxCdl) _drxCdl = require('../vendor/drx-codec/cdl-exporter.js');
44
+ return _drxCdl;
45
+ }
46
+ export function drxMerger() {
47
+ if (!_drxMerger) _drxMerger = require('../vendor/drx-codec/drx-merger.js');
48
+ return _drxMerger;
49
+ }
50
+ export function drxCodec() {
51
+ if (!_drxCodec) _drxCodec = require('../vendor/drx-codec/index.js');
52
+ return _drxCodec;
53
+ }
54
+ export function nodeLayout() {
55
+ if (!_nodeLayout) _nodeLayout = require('../vendor/drx-codec/node-layout.js');
56
+ return _nodeLayout;
57
+ }
58
+ export function audioFairlight() {
59
+ if (!_audioFairlight) _audioFairlight = require('../vendor/audio-fairlight/index.js');
60
+ return _audioFairlight;
61
+ }
62
+ export function fusion() {
63
+ if (!_fusion) _fusion = require('../vendor/fusion-codec/composition-generator.js');
64
+ return _fusion;
65
+ }
@@ -0,0 +1,479 @@
1
+ /**
2
+ * Sequence lineage store (Phase 1) — content-hashed parsed-geometry snapshots of a
3
+ * sequence as it evolves (OG editorial XML → conform XML → live versions), so any two
4
+ * versions can be diffed and a prior one rolled back to. SQLite sidecar (mirrors the
5
+ * offline-ref / reverse-clip DB pattern). Frame-QC verdicts (later phases) attach to a
6
+ * snapshot's cuts in `qc_verdicts`.
7
+ *
8
+ * Phase 1: schema + ingest of an XMEML (editorial or conform) → snapshot + per-cut
9
+ * geometry (oracle source frame + corrected scale when derivable), with per-cut and
10
+ * per-snapshot content hashes for dedup + change detection.
11
+ */
12
+
13
+ import fs from 'node:fs';
14
+ import crypto from 'node:crypto';
15
+ import { createRequire } from 'node:module';
16
+
17
+ const require = createRequire(import.meta.url);
18
+ const V = '../vendor/conform-qc';
19
+ const parse = require(`${V}/parse/index.js`);
20
+ const resolveTarget = require(`${V}/oracle/resolve.js`);
21
+
22
+ const TICKS_PER_SEC = 254016000000;
23
+
24
+ function loadSqlite() {
25
+ try {
26
+ return require('better-sqlite3');
27
+ } catch {
28
+ throw new Error("lineage store needs the optional native dep 'better-sqlite3'. Install: npm i better-sqlite3");
29
+ }
30
+ }
31
+
32
+ const SCHEMA = `
33
+ CREATE TABLE IF NOT EXISTS snapshots (snapshot_id TEXT PRIMARY KEY, reel TEXT, kind TEXT, source_ref TEXT, label TEXT,
34
+ parent_id TEXT, content_hash TEXT, seq_w INTEGER, seq_h INTEGER, fps REAL,
35
+ cut_count INTEGER, created_at TEXT, provenance TEXT);
36
+ CREATE TABLE IF NOT EXISTS cuts (snapshot_id TEXT, cut_index INTEGER, record_start INTEGER, record_end INTEGER,
37
+ source_basename TEXT, source_path TEXT, xml_in INTEGER, xml_out INTEGER,
38
+ ppro_ticks_in INTEGER, oracle_source_frame INTEGER, is_subclip INTEGER,
39
+ subclip_startoffset INTEGER, subclip_endoffset INTEGER, reverse INTEGER,
40
+ scale_corrected REAL, pan_h REAL, pan_v REAL, rotation REAL, transition TEXT,
41
+ cut_hash TEXT, PRIMARY KEY (snapshot_id, cut_index));
42
+ CREATE TABLE IF NOT EXISTS qc_verdicts (snapshot_id TEXT, cut_index INTEGER, reference_ref TEXT, reference_frame INTEGER,
43
+ verdict TEXT, category TEXT, structure REAL, psnr REAL, dx INTEGER, dy INTEGER,
44
+ scale_residual REAL, ran_at TEXT, PRIMARY KEY (snapshot_id, cut_index, reference_ref));
45
+ CREATE INDEX IF NOT EXISTS idx_snapshots_reel ON snapshots(reel);
46
+ CREATE INDEX IF NOT EXISTS idx_snapshots_hash ON snapshots(content_hash);
47
+ `;
48
+
49
+ export function openStore(dbPath) {
50
+ const Database = loadSqlite();
51
+ const db = new Database(dbPath);
52
+ db.pragma('journal_mode = WAL');
53
+ db.exec(SCHEMA);
54
+ return db;
55
+ }
56
+
57
+ const sha1 = (s) => crypto.createHash('sha1').update(s).digest('hex');
58
+
59
+ // Geometry fields that define a cut's identity (drive cut_hash + diff).
60
+ const CUT_GEOMETRY = [
61
+ 'record_start',
62
+ 'record_end',
63
+ 'source_basename',
64
+ 'xml_in',
65
+ 'xml_out',
66
+ 'ppro_ticks_in',
67
+ 'oracle_source_frame',
68
+ 'is_subclip',
69
+ 'subclip_startoffset',
70
+ 'subclip_endoffset',
71
+ 'reverse',
72
+ 'scale_corrected',
73
+ 'pan_h',
74
+ 'pan_v',
75
+ 'rotation',
76
+ 'transition',
77
+ ];
78
+
79
+ function cutHash(cut) {
80
+ return sha1(JSON.stringify(CUT_GEOMETRY.map((k) => cut[k] ?? null)));
81
+ }
82
+
83
+ function buildCuts(xml, mediaFrames) {
84
+ const g = parse.parseGeometry(xml);
85
+ const seqW = g.sequence.width || null;
86
+ const seqH = g.sequence.height || null;
87
+ const fps = g.sequence.fps || 24;
88
+ const ticksPerFrame = TICKS_PER_SEC / fps;
89
+ const cuts = g.clips.map((c, i) => {
90
+ const ctx = { ticksPerFrame, sequenceWidth: seqW, sequenceHeight: seqH, masterFrames: mediaFrames[c.source_basename] };
91
+ let oracleFrame = null;
92
+ let scaleCorrected = null;
93
+ try {
94
+ oracleFrame = resolveTarget.deriveSourceFrame(c, ctx);
95
+ } catch {
96
+ /* reverse w/o frame count, etc. */
97
+ }
98
+ try {
99
+ scaleCorrected = resolveTarget.deriveScaleCorrected(c, ctx);
100
+ } catch {
101
+ /* missing dims */
102
+ }
103
+ const cut = {
104
+ cut_index: i,
105
+ record_start: c.seqstart ?? null,
106
+ record_end: c.seqend ?? null,
107
+ source_basename: c.source_basename ?? null,
108
+ source_path: c.source_path ?? null,
109
+ xml_in: c.xml_in ?? null,
110
+ xml_out: c.xml_out ?? null,
111
+ ppro_ticks_in: c.pproTicksIn ?? null,
112
+ oracle_source_frame: oracleFrame,
113
+ is_subclip: c.is_subclip ? 1 : 0,
114
+ subclip_startoffset: c.subclip_startoffset ?? null,
115
+ subclip_endoffset: c.subclip_endoffset ?? null,
116
+ reverse: c.reverse ? 1 : 0,
117
+ scale_corrected: scaleCorrected != null ? +scaleCorrected.toFixed(5) : null,
118
+ pan_h: c.center ? c.center.h : null,
119
+ pan_v: c.center ? c.center.v : null,
120
+ rotation: c.rotation ?? null,
121
+ transition: null,
122
+ };
123
+ cut.cut_hash = cutHash(cut);
124
+ return cut;
125
+ });
126
+ return { cuts, seqW, seqH, fps, transitions: g.transitions };
127
+ }
128
+
129
+ // Write a built cut list as a snapshot (dedup on (reel, content_hash)). Shared by
130
+ // XML and live ingest so OG and live versions land in ONE schema.
131
+ function writeSnapshot(dbPath, cuts, meta) {
132
+ const contentHash = sha1(cuts.map((c) => c.cut_hash).join(''));
133
+ const db = openStore(dbPath);
134
+ try {
135
+ const existing = db.prepare('SELECT snapshot_id FROM snapshots WHERE reel IS ? AND content_hash = ?').get(meta.reel ?? null, contentHash);
136
+ if (existing) return { snapshotId: existing.snapshot_id, contentHash, cutCount: cuts.length, deduped: true };
137
+ const snapshotId = sha1(`${meta.reel ?? ''}:${contentHash}:${meta.label ?? ''}:${meta.now ?? ''}`).slice(0, 24);
138
+ const insertSnap = db.prepare(`INSERT INTO snapshots
139
+ (snapshot_id, reel, kind, source_ref, label, parent_id, content_hash, seq_w, seq_h, fps, cut_count, created_at, provenance)
140
+ VALUES (@snapshot_id,@reel,@kind,@source_ref,@label,@parent_id,@content_hash,@seq_w,@seq_h,@fps,@cut_count,@created_at,@provenance)`);
141
+ const insertCut = db.prepare(`INSERT INTO cuts
142
+ (snapshot_id, cut_index, record_start, record_end, source_basename, source_path, xml_in, xml_out, ppro_ticks_in, oracle_source_frame, is_subclip, subclip_startoffset, subclip_endoffset, reverse, scale_corrected, pan_h, pan_v, rotation, transition, cut_hash)
143
+ VALUES (@snapshot_id,@cut_index,@record_start,@record_end,@source_basename,@source_path,@xml_in,@xml_out,@ppro_ticks_in,@oracle_source_frame,@is_subclip,@subclip_startoffset,@subclip_endoffset,@reverse,@scale_corrected,@pan_h,@pan_v,@rotation,@transition,@cut_hash)`);
144
+ db.transaction(() => {
145
+ insertSnap.run({
146
+ snapshot_id: snapshotId,
147
+ reel: meta.reel ?? null,
148
+ kind: meta.kind,
149
+ source_ref: meta.source_ref ?? null,
150
+ label: meta.label ?? null,
151
+ parent_id: meta.parentId ?? null,
152
+ content_hash: contentHash,
153
+ seq_w: meta.seqW ?? null,
154
+ seq_h: meta.seqH ?? null,
155
+ fps: meta.fps ?? null,
156
+ cut_count: cuts.length,
157
+ created_at: meta.now ?? null,
158
+ provenance: meta.provenance ?? null,
159
+ });
160
+ for (const c of cuts) insertCut.run({ snapshot_id: snapshotId, ...c });
161
+ })();
162
+ return { snapshotId, contentHash, cutCount: cuts.length, deduped: false };
163
+ } finally {
164
+ db.close();
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Ingest an XMEML (editorial or conform) into the lineage store.
170
+ * opts: { kind:'editorial_xml'|'conform_xml', label, reel, parentId, provenance,
171
+ * mediaFrames:{basename:frameCount} (optional; enables reverse oracle frames),
172
+ * now (ISO string — no Date.now in this runtime) }
173
+ */
174
+ export function ingestXml(dbPath, xmlPath, opts = {}) {
175
+ const xml = fs.readFileSync(xmlPath, 'utf8');
176
+ const { cuts, seqW, seqH, fps } = buildCuts(xml, opts.mediaFrames || {});
177
+ return writeSnapshot(dbPath, cuts, {
178
+ ...opts,
179
+ kind: opts.kind ?? 'conform_xml',
180
+ source_ref: xmlPath,
181
+ seqW,
182
+ seqH,
183
+ fps,
184
+ provenance: opts.provenance ?? 'ingestXml',
185
+ });
186
+ }
187
+
188
+ function loadPg() {
189
+ try {
190
+ return require('pg');
191
+ } catch {
192
+ throw new Error("live ingest Postgres path needs the optional dep 'pg'. Install: npm i pg");
193
+ }
194
+ }
195
+ const numOrNull = (v) => {
196
+ const n = Number(v);
197
+ return Number.isFinite(n) ? Math.round(n) : null;
198
+ };
199
+
200
+ // Read a live timeline's cuts from the project DB (Sm2Timeline→Sm2TiTrack→Sm2TiItem).
201
+ async function readLiveCuts(opts) {
202
+ const SQL = (ph) => `SELECT CAST(i."Start" AS INTEGER) AS rec, i."In" AS inv, LENGTH(i."MediaTimemapBA") AS tmlen, i."MediaFilePath" AS path
203
+ FROM "Sm2TiItem" i JOIN "Sm2TiTrack" tr ON i."Sm2TiTrack_id" = tr."Sm2TiTrack_id"
204
+ JOIN "Sm2Timeline" tl ON tr."Sequence" = tl."Sequence"
205
+ WHERE tl."Sm2Timeline_id" = ${ph} ORDER BY rec`;
206
+ if (opts.postgres) {
207
+ const pg = opts.postgres;
208
+ const client =
209
+ pg.__client ||
210
+ (await (async () => {
211
+ const { Client } = loadPg();
212
+ const c = new Client({
213
+ host: pg.host || pg.IpAddress,
214
+ port: pg.port || 5432,
215
+ user: pg.user || 'postgres',
216
+ password: pg.password != null ? pg.password : process.env.PGPASSWORD,
217
+ database: pg.database || pg.DbName,
218
+ });
219
+ await c.connect();
220
+ return c;
221
+ })());
222
+ try {
223
+ return (await client.query(SQL('$1'), [opts.timelineId])).rows;
224
+ } finally {
225
+ if (!pg.__client) await client.end();
226
+ }
227
+ }
228
+ if (!opts.projectDb) throw new Error('live ingest: provide projectDb (SQLite) or postgres');
229
+ const db = new (loadSqlite())(opts.projectDb, { readonly: true });
230
+ try {
231
+ return db.prepare(SQL('?')).all(opts.timelineId);
232
+ } finally {
233
+ db.close();
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Snapshot a LIVE Resolve timeline into the lineage store (same schema as XML).
239
+ * Reads structural geometry (record, source, stored In, reverse via timemap length) from
240
+ * the project DB. For accurate diffs vs an XML baseline — especially REVERSE clips whose
241
+ * stored In is mirrored — pass API readbacks:
242
+ * opts.sourceFrames:{ recordStart: get_source_start_frame } (preferred for oracle_source_frame)
243
+ * opts.transforms:{ recordStart: {scale_corrected, pan_h, pan_v, rotation} }
244
+ * opts: { lineage scope: projectDb|postgres, timelineId, reel, label, parentId, now, seqW, seqH, fps }
245
+ */
246
+ export async function ingestLiveTimeline(dbPath, opts = {}) {
247
+ const raw = await readLiveCuts(opts);
248
+ const sourceFrames = opts.sourceFrames || {};
249
+ const transforms = opts.transforms || {};
250
+ const revThresh = opts.reverseTimemapLen || 100;
251
+ const cuts = raw.map((r, i) => {
252
+ const rec = r.rec;
253
+ const t = transforms[rec] || {};
254
+ const cut = {
255
+ cut_index: i,
256
+ record_start: rec,
257
+ record_end: raw[i + 1] ? raw[i + 1].rec : null,
258
+ source_basename: r.path ? r.path.split('/').pop() : null,
259
+ source_path: r.path || null,
260
+ xml_in: numOrNull(r.inv),
261
+ xml_out: null,
262
+ ppro_ticks_in: null,
263
+ oracle_source_frame: rec in sourceFrames ? sourceFrames[rec] : numOrNull(r.inv),
264
+ is_subclip: null,
265
+ subclip_startoffset: null,
266
+ subclip_endoffset: null,
267
+ reverse: (r.tmlen || 0) > revThresh ? 1 : 0,
268
+ scale_corrected: t.scale_corrected ?? null,
269
+ pan_h: t.pan_h ?? null,
270
+ pan_v: t.pan_v ?? null,
271
+ rotation: t.rotation ?? null,
272
+ transition: null,
273
+ };
274
+ cut.cut_hash = cutHash(cut);
275
+ return cut;
276
+ });
277
+ return writeSnapshot(dbPath, cuts, { ...opts, kind: 'live_timeline', source_ref: opts.timelineId, provenance: opts.provenance ?? 'ingestLiveTimeline' });
278
+ }
279
+
280
+ /** Store a frame-QC verdict for a snapshot's cut (one per reference). */
281
+ export function writeVerdict(dbPath, v) {
282
+ const db = openStore(dbPath);
283
+ try {
284
+ db.prepare(
285
+ `INSERT OR REPLACE INTO qc_verdicts
286
+ (snapshot_id, cut_index, reference_ref, reference_frame, verdict, category, structure, psnr, dx, dy, scale_residual, ran_at)
287
+ VALUES (@snapshot_id,@cut_index,@reference_ref,@reference_frame,@verdict,@category,@structure,@psnr,@dx,@dy,@scale_residual,@ran_at)`,
288
+ ).run({ reference_frame: null, structure: null, psnr: null, dx: null, dy: null, scale_residual: null, ran_at: null, ...v });
289
+ } finally {
290
+ db.close();
291
+ }
292
+ }
293
+
294
+ export function getVerdict(dbPath, snapshotId, cutIndex, referenceRef) {
295
+ const db = openStore(dbPath);
296
+ try {
297
+ return db.prepare('SELECT * FROM qc_verdicts WHERE snapshot_id=? AND cut_index=? AND reference_ref=?').get(snapshotId, cutIndex, referenceRef) || null;
298
+ } finally {
299
+ db.close();
300
+ }
301
+ }
302
+
303
+ export function listVerdicts(dbPath, snapshotId, referenceRef) {
304
+ const db = openStore(dbPath);
305
+ try {
306
+ return referenceRef
307
+ ? db.prepare('SELECT * FROM qc_verdicts WHERE snapshot_id=? AND reference_ref=? ORDER BY cut_index').all(snapshotId, referenceRef)
308
+ : db.prepare('SELECT * FROM qc_verdicts WHERE snapshot_id=? ORDER BY cut_index').all(snapshotId);
309
+ } finally {
310
+ db.close();
311
+ }
312
+ }
313
+
314
+ export function listSnapshots(dbPath, { reel } = {}) {
315
+ const db = openStore(dbPath);
316
+ try {
317
+ const rows = reel
318
+ ? db.prepare('SELECT * FROM snapshots WHERE reel = ? ORDER BY created_at, label').all(reel)
319
+ : db.prepare('SELECT * FROM snapshots ORDER BY reel, created_at, label').all();
320
+ return rows;
321
+ } finally {
322
+ db.close();
323
+ }
324
+ }
325
+
326
+ export function getSnapshot(dbPath, snapshotId) {
327
+ const db = openStore(dbPath);
328
+ try {
329
+ const snap = db.prepare('SELECT * FROM snapshots WHERE snapshot_id = ?').get(snapshotId);
330
+ if (!snap) return null;
331
+ const cuts = db.prepare('SELECT * FROM cuts WHERE snapshot_id = ? ORDER BY cut_index').all(snapshotId);
332
+ return { ...snap, cuts };
333
+ } finally {
334
+ db.close();
335
+ }
336
+ }
337
+
338
+ // Canonical fields for DIFF — the derived "what plays + how it's framed", comparable
339
+ // ACROSS snapshot kinds (XML vs live). Raw XML-parse inputs (xml_in/out, ppro ticks,
340
+ // subclip offsets) are excluded: they're oracle inputs, present only on XML snapshots,
341
+ // and would make every XML-vs-live cut read as changed.
342
+ const CANONICAL_FIELDS = [
343
+ 'record_start',
344
+ 'record_end',
345
+ 'source_basename',
346
+ 'oracle_source_frame',
347
+ 'reverse',
348
+ 'scale_corrected',
349
+ 'pan_h',
350
+ 'pan_v',
351
+ 'rotation',
352
+ 'transition',
353
+ ];
354
+
355
+ function fieldDeltas(a, b) {
356
+ const deltas = {};
357
+ for (const k of CANONICAL_FIELDS) {
358
+ if ((a[k] ?? null) !== (b[k] ?? null)) deltas[k] = { from: a[k] ?? null, to: b[k] ?? null };
359
+ }
360
+ return deltas;
361
+ }
362
+ // Bucket a change for QC routing (which cuts to re-pixel-verify, and why).
363
+ function changeKind(deltas) {
364
+ const kinds = [];
365
+ if ('oracle_source_frame' in deltas) kinds.push('source_frame');
366
+ if ('scale_corrected' in deltas || 'pan_h' in deltas || 'pan_v' in deltas || 'rotation' in deltas) kinds.push('transform');
367
+ if ('reverse' in deltas) kinds.push('reverse');
368
+ if ('transition' in deltas) kinds.push('transition');
369
+ if ('source_basename' in deltas) kinds.push('source');
370
+ if ('record_start' in deltas || 'record_end' in deltas) kinds.push('timing');
371
+ return kinds;
372
+ }
373
+
374
+ /**
375
+ * Diff two snapshots → per-cut deltas. Cuts align by record_start; an A-only vs B-only
376
+ * pair sharing (source_basename, xml_in) is reported as MOVED. The `changed` list (with
377
+ * its `kinds`) is the incremental frame-QC worklist — only these cuts need re-comparing.
378
+ */
379
+ export function diffSnapshots(dbPath, aId, bId) {
380
+ const A = getSnapshot(dbPath, aId);
381
+ const B = getSnapshot(dbPath, bId);
382
+ if (!A) throw new Error(`no snapshot ${aId}`);
383
+ if (!B) throw new Error(`no snapshot ${bId}`);
384
+ const byRec = (cuts) => {
385
+ const m = new Map();
386
+ for (const c of cuts) {
387
+ const k = c.record_start;
388
+ (m.get(k) || m.set(k, []).get(k)).push(c);
389
+ }
390
+ return m;
391
+ };
392
+ const aMap = byRec(A.cuts);
393
+ const bMap = byRec(B.cuts);
394
+ const changed = [];
395
+ const unchanged = [];
396
+ const removed = [];
397
+ const added = [];
398
+ const seenB = new Set();
399
+ for (const [rec, aCuts] of aMap) {
400
+ const bCuts = bMap.get(rec) || [];
401
+ aCuts.forEach((a, i) => {
402
+ const b = bCuts[i];
403
+ if (!b) {
404
+ removed.push(a);
405
+ return;
406
+ }
407
+ seenB.add(`${rec}#${i}`);
408
+ const deltas = fieldDeltas(a, b); // canonical — robust across XML/live kinds
409
+ if (Object.keys(deltas).length === 0) {
410
+ unchanged.push(rec);
411
+ return;
412
+ }
413
+ changed.push({ record_start: rec, cut_index: b.cut_index, kinds: changeKind(deltas), deltas });
414
+ });
415
+ }
416
+ for (const [rec, bCuts] of bMap)
417
+ bCuts.forEach((b, i) => {
418
+ if (!seenB.has(`${rec}#${i}`)) added.push(b);
419
+ });
420
+ // pair removed↔added on (source_basename, xml_in) → moved
421
+ const moved = [];
422
+ for (let r = removed.length - 1; r >= 0; r--) {
423
+ const a = removed[r];
424
+ const ai = added.findIndex((b) => b.source_basename === a.source_basename && b.xml_in === a.xml_in);
425
+ if (ai >= 0) {
426
+ moved.push({ source_basename: a.source_basename, from: a.record_start, to: added[ai].record_start });
427
+ removed.splice(r, 1);
428
+ added.splice(ai, 1);
429
+ }
430
+ }
431
+ return {
432
+ a: { id: aId, label: A.label, cutCount: A.cut_count },
433
+ b: { id: bId, label: B.label, cutCount: B.cut_count },
434
+ identical: A.content_hash === B.content_hash,
435
+ summary: { unchanged: unchanged.length, changed: changed.length, added: added.length, removed: removed.length, moved: moved.length },
436
+ changed,
437
+ added,
438
+ removed,
439
+ moved,
440
+ };
441
+ }
442
+
443
+ /**
444
+ * Rollback plan: how to get the CURRENT state (currentId) back to a TARGET snapshot
445
+ * (targetId — a known-good prior version or the OG editorial intent). It's the diff plus
446
+ * the target's per-cut values + the suggested tool per changed cut. The agent executes
447
+ * (DB-patch / API) — Node can't drive Resolve. scale_corrected is a PERCENT (ZoomX = /100).
448
+ */
449
+ export function rollbackPlan(dbPath, currentId, targetId) {
450
+ const d = diffSnapshots(dbPath, currentId, targetId);
451
+ const target = getSnapshot(dbPath, targetId);
452
+ const byRec = new Map(target.cuts.map((c) => [c.record_start, c]));
453
+ const changes = d.changed.map((ch) => {
454
+ const t = byRec.get(ch.record_start) || {};
455
+ const actions = [];
456
+ if (ch.kinds.includes('source_frame') || ch.kinds.includes('reverse')) {
457
+ actions.push({ tool: 'conform.fix_reverse_clip', for: 'source frame / reverse', targetFrame: t.oracle_source_frame, reverse: !!t.reverse });
458
+ }
459
+ if (ch.kinds.includes('transform')) {
460
+ const zoom = t.scale_corrected != null ? +(t.scale_corrected / 100).toFixed(5) : null;
461
+ actions.push({ tool: 'timeline_item.set_transform', set: { ZoomX: zoom, ZoomY: zoom, Pan: t.pan_h, Tilt: t.pan_v, RotationAngle: t.rotation } });
462
+ }
463
+ return {
464
+ record_start: ch.record_start,
465
+ source_basename: t.source_basename,
466
+ kinds: ch.kinds,
467
+ target: {
468
+ oracle_source_frame: t.oracle_source_frame,
469
+ reverse: t.reverse,
470
+ scale_corrected: t.scale_corrected,
471
+ pan_h: t.pan_h,
472
+ pan_v: t.pan_v,
473
+ rotation: t.rotation,
474
+ },
475
+ suggestedActions: actions,
476
+ };
477
+ });
478
+ return { fromSnapshot: currentId, toSnapshot: targetId, changeCount: changes.length, changes, added: d.added, removed: d.removed, moved: d.moved };
479
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * lut_apply (Phase-3) — the Body-LUT WRITE path. RE'd 2026-07-01 from the paired p5-1 fixtures
3
+ * (no-lut vs with-lut): a node's F9 gains an F1 corrector carrying SLOT_META (varint) + LUT_PATH
4
+ * (F5 string). The encoder in grade-body-patch.mjs (injectNodeLut) reproduces the captured
5
+ * with-LUT corrector BYTE-EXACT, so a named `.cube` (e.g. a Kodak 5219 film look) can be attached
6
+ * to a node offline — no Resolve, no generator (which drops LUT refs).
7
+ *
8
+ * Every write is ROUND-TRIP ASSERTED: decode the patched Body and confirm lutPath + slotMeta come
9
+ * back (refuse-not-fake). Operates on a .drx file (Body hex lives in the GyStill XML). The LUT
10
+ * actually taking effect in Resolve requires a live-confirm pass, like every Resolve-behavior tool.
11
+ *
12
+ * Fallback ladder (if a target needs a NEW .cube the encoder can't express): grade_transfer of a
13
+ * look that already carries the LUT + live-API assignment. This offline path covers named-LUT attach.
14
+ */
15
+ import fs from 'node:fs/promises';
16
+ import { createRequire } from 'node:module';
17
+ import { injectNodeLut } from './grade-body-patch.mjs';
18
+ import { drxParser } from './libs.mjs';
19
+
20
+ const require = createRequire(import.meta.url);
21
+ const { extractDrxLutRefs } = require('../vendor/drx-codec/extract-lut-refs.js');
22
+
23
+ const BODY_RE = /<Body>([0-9a-fA-F\s]*)<\/Body>/;
24
+
25
+ /** Pull the Body hex (with the 0x81 prefix) out of a GyStill .drx XML. */
26
+ export function extractBodyHex(xml) {
27
+ const m = BODY_RE.exec(xml);
28
+ if (!m) throw new Error('lut_apply: no <Body> found in the .drx (not a GyStill grade?)');
29
+ return m[1].replace(/\s+/g, '');
30
+ }
31
+
32
+ /**
33
+ * Attach a named LUT to a node of a .drx, with a round-trip assert.
34
+ * @param {{drxPath?:string, content?:string}} ref
35
+ * @param {{lutPath:string, nodeIndex?:number, slotMeta?:number, outPath?:string}} opts
36
+ * @returns {Promise<{content:string, outPath?:string, lutPath:string, slotMeta:number, nodeIndex:number, verified:true}>}
37
+ */
38
+ export async function applyLut(ref, opts = {}) {
39
+ if (!opts.lutPath) throw new Error('lut_apply: opts.lutPath (the .cube name/path) required');
40
+ const nodeIndex = opts.nodeIndex ?? 0;
41
+ const slotMeta = opts.slotMeta ?? 6;
42
+ const xml = ref.content != null ? ref.content : await fs.readFile(ref.drxPath, 'utf8');
43
+ const bodyHex = extractBodyHex(xml);
44
+ const newBodyHex = await injectNodeLut(bodyHex, nodeIndex, { lutPath: opts.lutPath, slotMeta });
45
+ const newXml = xml.replace(BODY_RE, `<Body>${newBodyHex}</Body>`);
46
+
47
+ // ROUND-TRIP ASSERT: decode the patched grade and confirm the LUT ref survives.
48
+ const parsed = await drxParser().parseDRXContent(newXml);
49
+ const refs = extractDrxLutRefs(parsed);
50
+ const got = refs.find((r) => r.nodeIndex === nodeIndex) || refs[0];
51
+ if (!got || got.lutPath !== opts.lutPath)
52
+ throw new Error(`lut_apply round-trip assert FAILED: wrote '${opts.lutPath}' but decoded ${got ? `'${got.lutPath}'` : 'nothing'}`);
53
+ if (got.slotMeta !== slotMeta) throw new Error(`lut_apply round-trip assert FAILED: slotMeta wrote ${slotMeta} but decoded ${got.slotMeta}`);
54
+
55
+ const out = { content: newXml, lutPath: got.lutPath, slotMeta: got.slotMeta, nodeIndex, verified: true, liveConfirm: 'pending (live validation)' };
56
+ if (opts.outPath) {
57
+ await fs.writeFile(opts.outPath, newXml);
58
+ out.outPath = opts.outPath;
59
+ }
60
+ return out;
61
+ }