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,92 @@
1
+ /**
2
+ * black_balance (P1d) — neutralize a SHADOW cast specifically. Where white_balance_match
3
+ * balances a mid/highlight neutral patch, this operates on the p1 shadow point ONLY: it aligns
4
+ * each channel's black point to a common target (the lowest channel black), removing a colour
5
+ * cast in the shadows WITHOUT lifting or crushing them (gain=1, offset-only).
6
+ *
7
+ * Emits an offset-only affine (reuse generateAssertedAffineDRX). Skips a clip whose shadows are
8
+ * already neutral (nothing to do — skip-not-fake). LOCAL & deterministic; no Resolve.
9
+ */
10
+ import fs from 'node:fs';
11
+ import { createRequire } from 'node:module';
12
+ import { generateAssertedAffineDRX } from './contrast-normalize.mjs';
13
+ import { provenanceLabel, gist } from './node-provenance.mjs';
14
+
15
+ const require = createRequire(import.meta.url);
16
+ function loadSharp() {
17
+ try {
18
+ return require('sharp');
19
+ } catch {
20
+ throw new Error("black-balance needs the optional dep 'sharp'. Install: npm i sharp");
21
+ }
22
+ }
23
+ const clamp = (x, lo, hi) => Math.max(lo, Math.min(hi, x));
24
+ function percentile(sorted, p) {
25
+ if (!sorted.length) return 0;
26
+ const idx = clamp(Math.round((p / 100) * (sorted.length - 1)), 0, sorted.length - 1);
27
+ return sorted[idx];
28
+ }
29
+
30
+ /** Per-channel black point (p1, 0..1). Returns {r,g,b} or null. */
31
+ export async function measureBlackPoint(pngPath, opts = {}) {
32
+ const sharp = loadSharp();
33
+ const lowP = opts.lowPct ?? 1,
34
+ maxSide = opts.maxSide ?? 480;
35
+ let data, ch;
36
+ try {
37
+ const out = await sharp(pngPath).resize(maxSide, maxSide, { fit: 'inside', kernel: 'nearest' }).raw().toBuffer({ resolveWithObject: true });
38
+ data = out.data;
39
+ ch = out.info.channels;
40
+ } catch {
41
+ return null;
42
+ }
43
+ const R = [],
44
+ G = [],
45
+ B = [];
46
+ for (let i = 0; i < data.length; i += ch) {
47
+ R.push(data[i]);
48
+ G.push(data[i + 1]);
49
+ B.push(data[i + 2]);
50
+ }
51
+ if (!R.length) return null;
52
+ R.sort((a, b) => a - b);
53
+ G.sort((a, b) => a - b);
54
+ B.sort((a, b) => a - b);
55
+ return { r: percentile(R, lowP) / 255, g: percentile(G, lowP) / 255, b: percentile(B, lowP) / 255 };
56
+ }
57
+
58
+ /**
59
+ * @param {Array<{id, png, group?}>} clips
60
+ * @param {{outDir, castThreshold?:number, clampOffset?:[number,number]}} opts
61
+ */
62
+ export async function computeBlackBalance(clips, opts = {}) {
63
+ if (!opts.outDir) throw new Error('opts.outDir required');
64
+ fs.mkdirSync(opts.outDir, { recursive: true });
65
+ const castThreshold = opts.castThreshold ?? 0.004; // ~1/255 — below this the shadows are already neutral
66
+ const [olo, ohi] = opts.clampOffset || [-0.2, 0.2];
67
+
68
+ const grades = [];
69
+ const warnings = [];
70
+ const skipped = [];
71
+ const report = {};
72
+ for (const c of clips) {
73
+ const bp = await measureBlackPoint(c.png, opts);
74
+ if (!bp) {
75
+ skipped.push({ id: c.id, reason: 'unreadable frame' });
76
+ continue;
77
+ }
78
+ const target = Math.min(bp.r, bp.g, bp.b); // align to the lowest black — pull casts DOWN, don't lift
79
+ const spread = Math.max(bp.r, bp.g, bp.b) - target;
80
+ if (spread < castThreshold) {
81
+ skipped.push({ id: c.id, reason: 'shadows already neutral', castSpread: +spread.toFixed(4) });
82
+ continue;
83
+ }
84
+ const gain = { r: 1, g: 1, b: 1 };
85
+ const offset = { r: clamp(target - bp.r, olo, ohi), g: clamp(target - bp.g, olo, ohi), b: clamp(target - bp.b, olo, ohi) };
86
+ const drxPath = `${opts.outDir}/${c.id}_blackbal.drx`;
87
+ await generateAssertedAffineDRX(gain, offset, provenanceLabel('black_balance', { source: 'shadow-neutral', gist: gist('offset', offset) }), drxPath, warnings);
88
+ grades.push({ id: c.id, offset, castSpreadPct: +(100 * spread).toFixed(2), drxPath });
89
+ report[c.id] = { castSpreadPct: +(100 * spread).toFixed(2) };
90
+ }
91
+ return { grades, report, warnings, skipped };
92
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Optional-capability detection. The published package is pure-JS/MIT with ZERO
3
+ * required native modules or bundled binaries. A few tools need an external tool
4
+ * the user installs themselves:
5
+ *
6
+ * ffmpeg / ffprobe (on PATH) — audio ops (split/trim/convert) + conform frame ops
7
+ * sharp (npm i) — conform.verify brightness-robust frame compare
8
+ * better-sqlite3 (npm i) — fairlight live-project-DB path (the .drp-zip path needs none)
9
+ *
10
+ * We DON'T bundle ffmpeg-static (its FFmpeg binaries are GPL — incompatible with a
11
+ * clean MIT distribution). Resolve users already have ffmpeg, or: `brew install ffmpeg`.
12
+ */
13
+
14
+ import { spawnSync } from 'node:child_process';
15
+ import { createRequire } from 'node:module';
16
+
17
+ const require = createRequire(import.meta.url);
18
+ const cache = new Map();
19
+
20
+ function onPath(bin) {
21
+ if (cache.has(bin)) return cache.get(bin);
22
+ let ok = false;
23
+ try {
24
+ const r = spawnSync(bin, ['-version'], { stdio: 'ignore', timeout: 4000 });
25
+ ok = !r.error && (r.status === 0 || r.status === null);
26
+ } catch {
27
+ ok = false;
28
+ }
29
+ cache.set(bin, ok);
30
+ return ok;
31
+ }
32
+
33
+ function nodeModuleAvailable(name) {
34
+ const key = `mod:${name}`;
35
+ if (cache.has(key)) return cache.get(key);
36
+ let ok = false;
37
+ try {
38
+ require.resolve(name);
39
+ ok = true;
40
+ } catch {
41
+ ok = false;
42
+ }
43
+ cache.set(key, ok);
44
+ return ok;
45
+ }
46
+
47
+ export const hasFfmpeg = () => onPath('ffmpeg');
48
+ export const hasFfprobe = () => onPath('ffprobe');
49
+ export const hasSharp = () => nodeModuleAvailable('sharp');
50
+ export const hasBetterSqlite3 = () => nodeModuleAvailable('better-sqlite3');
51
+
52
+ /** Throw a clear, actionable error if ffmpeg/ffprobe aren't on PATH. */
53
+ export function requireFfmpeg() {
54
+ if (!hasFfmpeg() || !hasFfprobe()) {
55
+ throw new Error(
56
+ 'This feature needs ffmpeg + ffprobe on your PATH (not bundled — FFmpeg binaries are GPL). ' +
57
+ 'Install: macOS `brew install ffmpeg`, Debian/Ubuntu `apt install ffmpeg`, Windows `choco install ffmpeg`.',
58
+ );
59
+ }
60
+ }
61
+
62
+ /** Capability snapshot — what's available + how to enable what isn't. */
63
+ export function capabilities() {
64
+ const ff = hasFfmpeg() && hasFfprobe();
65
+ return {
66
+ core: 'always available (pure-JS): drp, drt, drx, offline_ref, editorial, fusion, audio_plan, conform(core)',
67
+ optional: {
68
+ ffmpeg: { available: ff, enables: 'audio (split/trim/convert)', install: 'brew install ffmpeg / apt install ffmpeg' },
69
+ sharp: { available: hasSharp(), enables: 'conform.verify (frame compare)', install: 'npm i sharp' },
70
+ 'better-sqlite3': { available: hasBetterSqlite3(), enables: 'fairlight live-project-DB path (zip path needs none)', install: 'npm i better-sqlite3' },
71
+ },
72
+ };
73
+ }
74
+
75
+ /** One-line startup summary to stderr (never stdout — that's the MCP channel). */
76
+ export function logCapabilities() {
77
+ const c = capabilities();
78
+ const miss = Object.entries(c.optional)
79
+ .filter(([, v]) => !v.available)
80
+ .map(([k]) => k);
81
+ if (miss.length) {
82
+ process.stderr.write(
83
+ `[davinci-resolve-advanced-mcp] optional features needing setup: ${miss.join(', ')} — call the 'capabilities' tool for install hints.\n`,
84
+ );
85
+ }
86
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * ASC CDL import (C3 `cdl_io`).
3
+ *
4
+ * DITs and on-set colour ship looks as ASC CDL (.cc single,.ccc collection,.cdl) —
5
+ * slope/offset/power + saturation. This brings those into the grade as per-clip.drx
6
+ * so an on-set CDL can seed the look. The reverse (DRX → CDL) already exists as the
7
+ * `export_cdl` action; this is the import direction.
8
+ *
9
+ * Pure composition of the validated codec: parseCDL → cdlToDRX → generateDRX, with a
10
+ * round-trip non-empty assert (a non-identity CDL that decodes to an EMPTY grade is the
11
+ * silent-drop bug — throw, don't ship a no-op). slope→gain, offset→offset, power→gamma
12
+ * (model conversion, approximate by design — flagged), sat (0–2)→Resolve sat (0–100).
13
+ * LOCAL & deterministic; no Resolve.
14
+ */
15
+ import fs from 'node:fs';
16
+ import { drxCdl, drxGenerator, drxParser } from './libs.mjs';
17
+
18
+ const isIdentityCDL = (c) => {
19
+ const s = c.slope || {},
20
+ o = c.offset || {},
21
+ p = c.power || {};
22
+ const near = (v, t) => Math.abs((v ?? t) - t) < 1e-4;
23
+ return (
24
+ near(s.r, 1) &&
25
+ near(s.g, 1) &&
26
+ near(s.b, 1) &&
27
+ near(o.r, 0) &&
28
+ near(o.g, 0) &&
29
+ near(o.b, 0) &&
30
+ near(p.r, 1) &&
31
+ near(p.g, 1) &&
32
+ near(p.b, 1) &&
33
+ near(c.saturation, 1)
34
+ );
35
+ };
36
+
37
+ async function paramCount(content) {
38
+ const parsed = await drxParser().parseDRXContent(content);
39
+ return (parsed.nodes || []).flatMap((n) => (n.correctors || []).flatMap((cor) => cor.parameters || [])).length;
40
+ }
41
+
42
+ /**
43
+ * @param {string} cdlContent — raw .cc/.ccc/.cdl XML
44
+ * @param {{outDir:string}} opts
45
+ * @returns {Promise<{grades:Array<{id,drxPath,identity:boolean}>, warnings:string[]}>}
46
+ */
47
+ export async function importCDL(cdlContent, opts = {}) {
48
+ if (!opts.outDir) throw new Error('opts.outDir required');
49
+ const cdl = drxCdl();
50
+ const corrections = cdl.parseCDL(cdlContent);
51
+ if (!corrections.length) throw new Error('no <ColorCorrection> found — not a CDL/CCC/CC document');
52
+ fs.mkdirSync(opts.outDir, { recursive: true });
53
+
54
+ const grades = [];
55
+ const warnings = [];
56
+ let i = 0;
57
+ for (const c of corrections) {
58
+ const id = (c.id || `cc${i}`).replace(/[^\w.-]/g, '_');
59
+ const values = c.cdlValues || c; // parseCDL nests under cdlValues
60
+ const params = cdl.cdlToDRX(values);
61
+ const out = await drxGenerator().generateDRX(params, { label: `CDL ${id}` });
62
+ const content = typeof out === 'string' ? out : out?.content || out?.drxContent;
63
+ const identity = isIdentityCDL(values);
64
+ if (!identity && (await paramCount(content)) === 0) {
65
+ throw new Error(`round-trip assert FAILED: CDL '${id}' is non-identity but generated an EMPTY grade (${JSON.stringify(values)})`);
66
+ }
67
+ const drxPath = `${opts.outDir}/${id}.drx`;
68
+ fs.writeFileSync(drxPath, content);
69
+ grades.push({ id, drxPath, identity });
70
+ i++;
71
+ }
72
+ warnings.push(
73
+ 'power→gamma is a model conversion (Resolve gamma ≠ ASC power); the look is approximate by design, not a 1:1 transfer. Validate against the on-set reference.',
74
+ );
75
+ return { grades, warnings };
76
+ }
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Contrast normalize — black/white-point match (C3 `contrast_normalize`).
3
+ *
4
+ * Where exposure_level matches a MEAN (a single offset), contrast_normalize matches the
5
+ * tonal RANGE: each clip's robust black point and white point are mapped onto a group
6
+ * hero's, per channel, by an affine fit out = scale·in + shift (scale→gain, shift→
7
+ * offset). This lines clips up in the same tonal range BEFORE a shared look, so the look
8
+ * lands consistently. Robust points = 1st/99th percentile (ignores a few hot/black outliers).
9
+ *
10
+ * OFFSET SEMANTICS (live-validated 2026-07-03, DaVinci YRGB/Rec709): Resolve's wire
11
+ * offset shifts output by wire×0.2 normalized, wire range ±1 (→ ±0.2 norm max). The
12
+ * emitter converts normalized intent → wire (×5), clamps, and warns on clamp. The RGB
13
+ * gain wheels are luma-renormalized by Resolve (balance only) — the achromatic level
14
+ * rides gain.master.
15
+ *
16
+ * ACEScct (measured live 2026-07-03, synthetic project, Rec.709 IDT/ODT): the
17
+ * STRUCTURAL semantics hold (luma-renorm of RGB wheels; master carries level), but
18
+ * wheels operate in cct WORKING space before the ACES output tonescale, so the ×0.2
19
+ * display factor does NOT transfer — display response is level-dependent. Under
20
+ * ACEScct, treat emitted trims as directionally-correct starting nudges and CONVERGE
21
+ * by iteration (apply → re-extract → re-measure), per the pass-based review design.
22
+ * LOCAL & deterministic; no Resolve.
23
+ */
24
+ import fs from 'node:fs';
25
+ import { createRequire } from 'node:module';
26
+ import { drxTool } from './tools/drx.mjs';
27
+ import { provenanceLabel, gist } from './node-provenance.mjs';
28
+
29
+ const require = createRequire(import.meta.url);
30
+ function loadSharp() {
31
+ try {
32
+ return require('sharp');
33
+ } catch {
34
+ throw new Error("contrast-normalize needs the optional dep 'sharp'. Install: npm i sharp");
35
+ }
36
+ }
37
+ const clamp = (x, lo, hi) => Math.max(lo, Math.min(hi, x));
38
+ function percentile(sorted, p) {
39
+ if (!sorted.length) return 0;
40
+ const idx = clamp(Math.round((p / 100) * (sorted.length - 1)), 0, sorted.length - 1);
41
+ return sorted[idx];
42
+ }
43
+
44
+ /** Per-channel black/white points (normalized 0..1) at the given percentiles. */
45
+ export async function measureBWPoints(pngPath, opts = {}) {
46
+ const sharp = loadSharp();
47
+ const lowP = opts.lowPct ?? 1,
48
+ highP = opts.highPct ?? 99,
49
+ maxSide = opts.maxSide ?? 480;
50
+ let data, ch;
51
+ try {
52
+ const out = await sharp(pngPath).resize(maxSide, maxSide, { fit: 'inside', kernel: 'nearest' }).raw().toBuffer({ resolveWithObject: true });
53
+ data = out.data;
54
+ ch = out.info.channels;
55
+ } catch {
56
+ return null;
57
+ }
58
+ const R = [],
59
+ G = [],
60
+ B = [];
61
+ for (let i = 0; i < data.length; i += ch) {
62
+ R.push(data[i]);
63
+ G.push(data[i + 1]);
64
+ B.push(data[i + 2]);
65
+ }
66
+ if (!R.length) return null;
67
+ R.sort((a, b) => a - b);
68
+ G.sort((a, b) => a - b);
69
+ B.sort((a, b) => a - b);
70
+ const mk = (arr) => ({ black: percentile(arr, lowP) / 255, white: percentile(arr, highP) / 255 });
71
+ return { r: mk(R), g: mk(G), b: mk(B) };
72
+ }
73
+
74
+ /** Wire-offset units per NORMALIZED (0..1) shift. Live-validated 2026-07-03 (DaVinci
75
+ * YRGB / Rec709 render): wire offset −0.1 shifts output by exactly −0.02 normalized
76
+ * (−5/255), i.e. norm = wire × 0.2. Recalibrate for other color sciences (ACEScct) on
77
+ * real-footage validation before trusting large offsets there. */
78
+ export const OFFSET_WIRE_PER_NORM = 5;
79
+
80
+ /** Generate a gain+offset DRX and THROW if a non-identity affine decodes empty.
81
+ * `gain`/`offset` are INTENT in display-normalized units: out = gain·in + offset (0..1).
82
+ * The emitter converts to wire units (offset ×5; gain.master carries the achromatic
83
+ * level — Resolve luma-renormalizes the RGB gain wheels, see generateAssertedGainDRX). */
84
+ export async function generateAssertedAffineDRX(gain, offset, label, outPath, warnings) {
85
+ const master = 0.2126 * gain.r + 0.7152 * gain.g + 0.0722 * gain.b;
86
+ // Wire offset range is ±1 (decode clamps beyond it — live-validated: wrote −1.22,
87
+ // read back −1.0). Clamp HERE and warn so intent and wire never silently diverge.
88
+ const wire = {};
89
+ for (const k of ['r', 'g', 'b']) {
90
+ const w = offset[k] * OFFSET_WIRE_PER_NORM;
91
+ wire[k] = Math.max(-1, Math.min(1, w));
92
+ if (w !== wire[k] && Array.isArray(warnings))
93
+ warnings.push(`${label}: offset.${k} ${offset[k].toFixed(4)} (norm) exceeds the wire range ±1 (=±0.2 norm) — clamped; the black/white point will land short. Split the move across gain/lift or grade manually.`);
94
+ }
95
+ await drxTool.handler({
96
+ action: 'generate',
97
+ // gain/offset here are computed in raw DRX-internal units → pin to space:'drx'
98
+ // (the tool default is 'ui', which would rescale offset by ÷25).
99
+ args: {
100
+ gradeParams: {
101
+ space: 'drx',
102
+ gain: [gain.r, gain.g, gain.b, master],
103
+ offset: [wire.r, wire.g, wire.b, 0.0],
104
+ },
105
+ metadata: { label },
106
+ outputPath: outPath,
107
+ },
108
+ });
109
+ const back = await drxTool.handler({ action: 'parse', args: { content: fs.readFileSync(outPath, 'utf8') } });
110
+ const got = (back.nodes || [])
111
+ .flatMap((n) => (n.correctors || []).flatMap((c) => c.parameters || []))
112
+ .filter((p) => /(gain|offset)\.(r|g|b)/i.test(p.name)).length;
113
+ const nonIdentity = ['r', 'g', 'b'].some((k) => Math.abs(gain[k] - 1) > 0.002 || Math.abs(offset[k]) > 0.002);
114
+ if (nonIdentity && got === 0)
115
+ throw new Error(`round-trip assert FAILED: ${label} affine decoded EMPTY (gain ${JSON.stringify(gain)} offset ${JSON.stringify(offset)})`);
116
+ return outPath;
117
+ }
118
+
119
+ /**
120
+ * @param {Array<{id:string|number, png:string, group:string}>} clips
121
+ * @param {{outDir:string, heroId?:string|number, clampGain?:[number,number],
122
+ * clampOffset?:[number,number], maxCorrWarn?:number}} opts
123
+ */
124
+ export async function computeContrastNormalize(clips, opts = {}) {
125
+ if (!opts.outDir) throw new Error('opts.outDir required');
126
+ const [glo, ghi] = opts.clampGain || [0.5, 2];
127
+ const [olo, ohi] = opts.clampOffset || [-0.5, 0.5];
128
+ const maxCorrWarn = opts.maxCorrWarn ?? 0.3;
129
+ fs.mkdirSync(opts.outDir, { recursive: true });
130
+
131
+ const byGroup = new Map();
132
+ for (const c of clips) {
133
+ if (!byGroup.has(c.group)) byGroup.set(c.group, []);
134
+ byGroup.get(c.group).push(c);
135
+ }
136
+
137
+ const grades = [];
138
+ const report = {};
139
+ const warnings = [];
140
+ const skipped = [];
141
+
142
+ for (const [group, gclips] of byGroup) {
143
+ for (const c of gclips) {
144
+ c.bw = await measureBWPoints(c.png, opts);
145
+ }
146
+ const usable = gclips.filter((c) => c.bw);
147
+ for (const c of gclips.filter((c) => !c.bw)) skipped.push({ id: c.id, group, reason: 'unreadable frame' });
148
+ if (!usable.length) {
149
+ report[group] = { error: 'no readable frames' };
150
+ continue;
151
+ }
152
+ const hero = (opts.heroId != null && usable.find((c) => c.id === opts.heroId)) || usable[0];
153
+
154
+ let maxCorr = 0;
155
+ for (const c of usable) {
156
+ const gain = {},
157
+ offset = {};
158
+ for (const k of ['r', 'g', 'b']) {
159
+ const H = hero.bw[k],
160
+ C = c.bw[k];
161
+ const span = C.white - C.black || 1e-6;
162
+ const scale = clamp((H.white - H.black) / span, glo, ghi);
163
+ const shift = clamp(H.black - scale * C.black, olo, ohi);
164
+ gain[k] = scale;
165
+ offset[k] = shift;
166
+ }
167
+ const corr = Math.max(...['r', 'g', 'b'].map((k) => Math.max(Math.abs(gain[k] - 1), Math.abs(offset[k]))));
168
+ maxCorr = Math.max(maxCorr, corr);
169
+ const drxPath = `${opts.outDir}/${group}_${c.id}_contrast.drx`;
170
+ await generateAssertedAffineDRX(gain, offset, provenanceLabel('contrast_normalize', { source: `hero:${hero.id}`, gist: gist('gain', gain) }), drxPath, warnings);
171
+ grades.push({ id: c.id, group, gain, offset, correctionPct: +(100 * corr).toFixed(2), drxPath, isHero: c === hero });
172
+ }
173
+ report[group] = { clips: usable.length, hero_id: hero.id, max_correction_pct: +(100 * maxCorr).toFixed(2) };
174
+ if (maxCorr > maxCorrWarn)
175
+ warnings.push(
176
+ `${group}: max contrast correction ${(100 * maxCorr).toFixed(1)}% — large; check the hero is representative and frames are display-referred.`,
177
+ );
178
+ }
179
+ return { grades, report, warnings, skipped };
180
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Shared safety framework for LIVE Project.db patches — the "beyond-the-API" tier.
3
+ *
4
+ * ⚠️ Unsanctioned territory (Blackmagic doesn't support direct DB edits). Every write
5
+ * goes through: project-CLOSED gate → auto-backup → schema-version guard → read-back
6
+ * verify. The schema map is Resolve 21 / ProjectVersion 17 (the design notes design notes);
7
+ * the column guard refuses rather than corrupting if the schema differs.
8
+ *
9
+ * Needs the optional native dep `better-sqlite3` (lazy).
10
+ */
11
+
12
+ import fs from 'node:fs';
13
+ import path from 'node:path';
14
+ import os from 'node:os';
15
+ import { createRequire } from 'node:module';
16
+
17
+ const require = createRequire(import.meta.url);
18
+
19
+ export const DISK_DB_ROOT = path.join(os.homedir(), 'Library/Application Support/Blackmagic Design/DaVinci Resolve/Resolve Disk Database/Resolve Projects');
20
+
21
+ export function loadSqlite() {
22
+ try {
23
+ return require('better-sqlite3');
24
+ } catch {
25
+ throw new Error("Project.db patching needs the optional native dep 'better-sqlite3'. Install: npm i better-sqlite3");
26
+ }
27
+ }
28
+
29
+ /** Recursively locate <projectName>/Project.db under the Resolve Disk Database. */
30
+ export function findProjectDb(projectName, root = DISK_DB_ROOT) {
31
+ const hits = [];
32
+ const walk = (dir, depth) => {
33
+ if (depth > 8) return;
34
+ let entries;
35
+ try {
36
+ entries = fs.readdirSync(dir, { withFileTypes: true });
37
+ } catch {
38
+ return;
39
+ }
40
+ for (const e of entries) {
41
+ if (!e.isDirectory()) continue;
42
+ const full = path.join(dir, e.name);
43
+ if (e.name === projectName && fs.existsSync(path.join(full, 'Project.db'))) hits.push(path.join(full, 'Project.db'));
44
+ walk(full, depth + 1);
45
+ }
46
+ };
47
+ if (fs.existsSync(root)) walk(root, 0);
48
+ return hits;
49
+ }
50
+
51
+ export function resolveDbPath({ projectDb, projectName }) {
52
+ if (projectDb) return projectDb;
53
+ if (!projectName) throw new Error('provide projectDb (path) or projectName');
54
+ const hits = findProjectDb(projectName);
55
+ if (!hits.length) throw new Error(`no Project.db found for project "${projectName}" under the Resolve Disk Database`);
56
+ if (hits.length > 1) throw new Error(`multiple Project.db match "${projectName}": pass projectDb explicitly`);
57
+ return hits[0];
58
+ }
59
+
60
+ /** Open a Project.db, refusing if a required (table, column) guard is absent (version safety). */
61
+ export function openGuarded(dbPath, { writable = false, table, column } = {}) {
62
+ const Database = loadSqlite();
63
+ if (!fs.existsSync(dbPath)) throw new Error(`Project.db not found: ${dbPath}`);
64
+ const db = new Database(dbPath, { readonly: !writable });
65
+ if (table && column) {
66
+ // Quote the table identifier — Resolve tables like "ListMgt::LmVersion" contain "::" which is an
67
+ // illegal token unquoted (the PRAGMA would fail with "unrecognized token: :").
68
+ const cols = db
69
+ .prepare(`PRAGMA table_info("${String(table).replace(/"/g, '""')}")`)
70
+ .all()
71
+ .map((c) => c.name);
72
+ if (!cols.includes(column)) {
73
+ db.close();
74
+ throw new Error(`${table}.${column} not found — unsupported Project.db schema/version; refusing to patch.`);
75
+ }
76
+ }
77
+ return db;
78
+ }
79
+
80
+ export function backup(dbPath) {
81
+ const bak = `${dbPath}.bak`;
82
+ fs.copyFileSync(dbPath, bak);
83
+ return bak;
84
+ }
85
+
86
+ /** Gate every write: the project must be closed (Resolve oversaves an open project). */
87
+ export function requireClosed(opts) {
88
+ if (!opts.iConfirmProjectClosed) {
89
+ throw new Error('Refusing to write: close the project in Resolve first (it oversaves on save), then pass iConfirmProjectClosed: true.');
90
+ }
91
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Cluster D — deliverable ENTITY modeling `[cap]`. A "deliverable" is not one file: it's a
3
+ * family of entities each with its own compliance rules — texted (final) + textless (clean,
4
+ * for localization) + stems/M&E (music & effects, audio-only) + slate + leader. Modeling them
5
+ * as first-class entities lets deliverable_qc run the RIGHT rules per entity (a textless master
6
+ * must carry NO burned-in titles; an M&E stem is audio-only with a fixed layout; a slate has a
7
+ * duration rule) instead of one flat spec.
8
+ *
9
+ * PURE: expands a base deliverable spec into concrete entities, each with a merged QC spec +
10
+ * named rules deliverable_qc / loudness_qc consume. No Resolve, no LLM. Tenant-ready: no globals.
11
+ */
12
+
13
+ // Per-entity defaults layered onto the base master spec. `notes` are human rules a tool can't
14
+ // fully verify offline (e.g. "no burned-in text") — surfaced so online/producer check them.
15
+ export const ENTITY_DEFAULTS = {
16
+ texted: {
17
+ kind: 'texted',
18
+ describe: 'Final program master with titles/lower-thirds/graphics.',
19
+ specOverride: {},
20
+ rules: ['video_compliance', 'audio_compliance', 'duration'],
21
+ notes: [],
22
+ },
23
+ textless: {
24
+ kind: 'textless',
25
+ describe: 'Clean master for localization — identical picture spec, NO burned-in text.',
26
+ specOverride: {},
27
+ rules: ['video_compliance', 'audio_compliance', 'duration', 'no_burned_in_text'],
28
+ notes: ['no_burned_in_text is not auto-verifiable offline — online must eyeball for residual titles/lower-thirds'],
29
+ },
30
+ stems_ME: {
31
+ kind: 'stems_ME',
32
+ describe: 'Music & Effects stem — audio-only deliverable, dialogue removed.',
33
+ // Audio-only: drop the video spec; keep/override the audio layout.
34
+ dropVideo: true,
35
+ specOverride: {},
36
+ rules: ['audio_compliance', 'loudness', 'no_dialogue'],
37
+ notes: ['no_dialogue is not auto-verifiable offline — mix/online confirms dialogue is absent'],
38
+ },
39
+ slate: {
40
+ kind: 'slate',
41
+ describe: 'Slate/ident — fixed short duration with programme metadata.',
42
+ specOverride: {},
43
+ rules: ['duration', 'video_compliance'],
44
+ notes: ['slate content (title/ep/date) is a human check'],
45
+ },
46
+ leader: {
47
+ kind: 'leader',
48
+ describe: 'Countdown/black leader ahead of programme start.',
49
+ specOverride: {},
50
+ rules: ['duration'],
51
+ notes: [],
52
+ },
53
+ };
54
+
55
+ /** Shallow-merge a spec override onto a base spec (per top-level block). */
56
+ function mergeSpec(base = {}, override = {}, dropVideo = false) {
57
+ const out = { ...base, ...override };
58
+ if (base.video || override.video) out.video = { ...(base.video || {}), ...(override.video || {}) };
59
+ if (base.audio || override.audio) out.audio = { ...(base.audio || {}), ...(override.audio || {}) };
60
+ if (dropVideo) delete out.video;
61
+ return out;
62
+ }
63
+
64
+ /**
65
+ * Expand a base deliverable into its entities.
66
+ * @param {{name?:string, spec:object, entities?:string[]}} deliverable
67
+ * spec = the master compliance spec (video/audio/container/duration/...); entities = kinds to emit.
68
+ * @returns {{name:string, entities:Array<{kind, name, describe, spec, rules, notes}>}}
69
+ */
70
+ export function expandDeliverable(deliverable) {
71
+ const name = deliverable.name || 'deliverable';
72
+ const kinds = deliverable.entities && deliverable.entities.length ? deliverable.entities : ['texted'];
73
+ const entities = kinds.map((k) => {
74
+ const def = ENTITY_DEFAULTS[k];
75
+ if (!def) throw new Error(`expandDeliverable: unknown entity kind '${k}' (known: ${Object.keys(ENTITY_DEFAULTS).join(', ')})`);
76
+ return {
77
+ kind: def.kind,
78
+ name: `${name}.${def.kind}`,
79
+ describe: def.describe,
80
+ spec: mergeSpec(deliverable.spec || {}, def.specOverride, def.dropVideo),
81
+ rules: def.rules,
82
+ notes: def.notes,
83
+ };
84
+ });
85
+ return { name, entities };
86
+ }