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,833 @@
1
+ /**
2
+ * Color Grading NLP Parser
3
+ *
4
+ * Natural language parser for color grading instructions.
5
+ * Uses comprehensive glossary and action mappings to interpret
6
+ * cinematographer/colorist directions into DaVinci Resolve parameters.
7
+ *
8
+ * @module drx/color-nlp-parser
9
+ */
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+
14
+ // Load action mappings
15
+ let ACTION_MAPPINGS = null;
16
+ let _customDataPath = null;
17
+
18
+ /**
19
+ * Set a custom data path for loading action mappings.
20
+ * Call before first parse to point at your app's knowledge directory.
21
+ * @param {string} dataPath - Absolute path to the directory containing color-nlp-action-mappings.json
22
+ */
23
+ function setDataPath(dataPath) {
24
+ _customDataPath = dataPath;
25
+ ACTION_MAPPINGS = null; // force reload on next access
26
+ }
27
+
28
+ function loadActionMappings() {
29
+ if (ACTION_MAPPINGS) return ACTION_MAPPINGS;
30
+
31
+ // Method 1: require() — works in Vercel serverless bundles and Node.js
32
+ // require() is resolved by bundlers (ncc, webpack) at build time,
33
+ // unlike fs.readFileSync which needs runtime path resolution.
34
+ try {
35
+ ACTION_MAPPINGS = require('./data/color-nlp-action-mappings.json');
36
+ return ACTION_MAPPINGS;
37
+ } catch {
38
+ // Not available as a require-able module
39
+ }
40
+
41
+ // Method 2: fs.readFileSync with path search (works in dev/Electron)
42
+ const searchPaths = [
43
+ _customDataPath ? path.join(_customDataPath, 'color-nlp-action-mappings.json') : null,
44
+ path.join(__dirname, 'data', 'color-nlp-action-mappings.json'),
45
+ ].filter(Boolean);
46
+
47
+ for (const mappingsPath of searchPaths) {
48
+ try {
49
+ const content = fs.readFileSync(mappingsPath, 'utf8');
50
+ ACTION_MAPPINGS = JSON.parse(content);
51
+ return ACTION_MAPPINGS;
52
+ } catch {
53
+ // try next path
54
+ }
55
+ }
56
+
57
+ console.error('[NLP Parser] Failed to load action mappings from any known path');
58
+ return {
59
+ intent_classification: {},
60
+ phrase_to_action: {},
61
+ modifiers: { intensity: {}, scope: {} },
62
+ parameters: {},
63
+ synonyms: {},
64
+ problem_patterns: {},
65
+ };
66
+ }
67
+
68
+ /**
69
+ * Normalize input text for matching
70
+ */
71
+ function normalizeText(text) {
72
+ return text
73
+ .toLowerCase()
74
+ .trim()
75
+ .replace(/['']/g, "'")
76
+ .replace(/[""]/g, '"')
77
+ .replace(/\s+/g, ' ')
78
+ .replace(/[.,!?;:]+$/, ''); // Remove trailing punctuation
79
+ }
80
+
81
+ /**
82
+ * Expand synonyms in input text
83
+ */
84
+ function expandSynonyms(text, synonyms) {
85
+ let expanded = text;
86
+ for (const [canonical, alternatives] of Object.entries(synonyms)) {
87
+ for (const alt of alternatives) {
88
+ const regex = new RegExp(`\\b${alt}\\b`, 'gi');
89
+ if (regex.test(expanded)) {
90
+ // Don't replace, just note the canonical form
91
+ expanded = expanded.replace(regex, canonical);
92
+ }
93
+ }
94
+ }
95
+ return expanded;
96
+ }
97
+
98
+ /**
99
+ * Classify the intent of the input
100
+ */
101
+ function classifyIntent(text, mappings) {
102
+ const normalized = normalizeText(text);
103
+ const intents = mappings.intent_classification;
104
+
105
+ const scores = {};
106
+
107
+ for (const [intentName, intentData] of Object.entries(intents)) {
108
+ let score = 0;
109
+
110
+ // Check keywords
111
+ if (intentData.keywords) {
112
+ for (const keyword of intentData.keywords) {
113
+ if (normalized.includes(keyword.toLowerCase())) {
114
+ score += 2;
115
+ }
116
+ }
117
+ }
118
+
119
+ // Check regex patterns
120
+ if (intentData.regex_patterns) {
121
+ for (const pattern of intentData.regex_patterns) {
122
+ try {
123
+ const regex = new RegExp(pattern, 'i');
124
+ if (regex.test(normalized)) {
125
+ score += 5;
126
+ }
127
+ } catch (e) {
128
+ // Invalid regex, skip
129
+ }
130
+ }
131
+ }
132
+
133
+ if (score > 0) {
134
+ scores[intentName] = {
135
+ score,
136
+ requires_secondary: intentData.requires_secondary || false,
137
+ is_compound: intentData.is_compound || false,
138
+ primary_tools: intentData.primary_tools || [],
139
+ };
140
+ }
141
+ }
142
+
143
+ // Sort by score and return top intent(s)
144
+ const sorted = Object.entries(scores)
145
+ .sort((a, b) => b[1].score - a[1].score);
146
+
147
+ if (sorted.length === 0) {
148
+ return { primary: null, all: [] };
149
+ }
150
+
151
+ return {
152
+ primary: sorted[0][0],
153
+ primaryData: sorted[0][1],
154
+ all: sorted.map(([name, data]) => ({ name, ...data })),
155
+ };
156
+ }
157
+
158
+ /**
159
+ * Extract intensity modifier from input with emphatic language detection
160
+ */
161
+ function extractIntensityModifier(text, mappings) {
162
+ const normalized = normalizeText(text);
163
+ const original = text; // Keep original for caps detection
164
+ const intensityMods = mappings.modifiers?.intensity || {};
165
+
166
+ let baseMultiplier = 1.0; // Default to significant (1.0)
167
+ let emphasisBoost = 0;
168
+ let matchedLevel = 'significant';
169
+
170
+ // === EMPHATIC LANGUAGE DETECTION ===
171
+
172
+ // 1. Check for ALL CAPS words (emphasis indicator)
173
+ const capsWords = original.match(/\b[A-Z]{2,}\b/g) || [];
174
+ const significantCapsWords = capsWords.filter(w =>
175
+ !['A', 'I', 'TV', 'OK', 'HD', 'SDR', 'HDR', 'RGB', 'LOG', 'LUT', 'DCI', 'REC'].includes(w)
176
+ );
177
+ if (significantCapsWords.length > 0) {
178
+ emphasisBoost += 0.2 * significantCapsWords.length;
179
+ }
180
+
181
+ // 2. Check for exclamation marks (! = +0.15, !! = +0.3, !!! = +0.5)
182
+ const exclamationCount = (original.match(/!/g) || []).length;
183
+ if (exclamationCount === 1) emphasisBoost += 0.15;
184
+ else if (exclamationCount === 2) emphasisBoost += 0.3;
185
+ else if (exclamationCount >= 3) emphasisBoost += 0.5;
186
+
187
+ // 3. Check for repetition patterns ("really really", "way way", "much much", "very very")
188
+ const repetitionPatterns = [
189
+ /\b(really|very|much|way|super|so|extra)\s+\1\b/gi,
190
+ /\b(really|very|much|way|super|so|extra)\s+(really|very|much|way|super|so|extra)\b/gi,
191
+ ];
192
+ for (const pattern of repetitionPatterns) {
193
+ const matches = normalized.match(pattern) || [];
194
+ emphasisBoost += 0.25 * matches.length;
195
+ }
196
+
197
+ // 4. Check for stacked intensifiers ("really very", "super extremely", "way too much")
198
+ const stackedPatterns = [
199
+ /\b(really|very|super|extremely|incredibly|insanely)\s+(really|very|super|extremely|incredibly|insanely|much|more)\b/gi,
200
+ /\bway\s+too\s+(much|more|far)\b/gi,
201
+ /\bso\s+(much|very|incredibly)\b/gi,
202
+ ];
203
+ for (const pattern of stackedPatterns) {
204
+ if (pattern.test(normalized)) {
205
+ emphasisBoost += 0.3;
206
+ }
207
+ }
208
+
209
+ // 5. Check for intensity amplifier words (cumulative!)
210
+ const intensityAmplifiers = {
211
+ // Tier 1: Mild amplifiers (+0.1 each)
212
+ mild: {
213
+ boost: 0.1,
214
+ words: ['pretty', 'quite', 'fairly', 'rather', 'somewhat', 'kinda', 'sorta', 'more']
215
+ },
216
+ // Tier 2: Moderate amplifiers (+0.15 each)
217
+ moderate: {
218
+ boost: 0.15,
219
+ words: ['very', 'really', 'definitely', 'certainly', 'clearly', 'noticeably', 'visibly', 'obviously', 'actually', 'truly', 'genuinely']
220
+ },
221
+ // Tier 3: Strong amplifiers (+0.2 each)
222
+ strong: {
223
+ boost: 0.2,
224
+ words: ['super', 'extra', 'hella', 'mad', 'wicked', 'damn', 'dang', 'bloody', 'friggin', 'freaking', 'frickin', 'heckin', 'real', 'proper', 'straight up', 'legit', 'lowkey', 'highkey', 'deadass']
225
+ },
226
+ // Tier 4: Very strong amplifiers (+0.25 each)
227
+ very_strong: {
228
+ boost: 0.25,
229
+ words: ['extremely', 'incredibly', 'seriously', 'majorly', 'heavily', 'intensely', 'severely', 'deeply', 'profoundly', 'remarkably', 'exceptionally', 'particularly', 'especially', 'significantly', 'substantially', 'considerably', 'dramatically']
230
+ },
231
+ // Tier 5: Extreme amplifiers (+0.35 each)
232
+ extreme: {
233
+ boost: 0.35,
234
+ words: ['ultra', 'mega', 'hyper', 'uber', 'turbo', 'giga', 'tera', 'maximum', 'max', 'peak', 'ultimate', 'supreme', 'paramount', 'radical', 'drastic', 'severe', 'massive', 'enormous', 'tremendous', 'immense', 'colossal', 'monumental', 'epic']
235
+ },
236
+ // Tier 6: Insane/absurd amplifiers (+0.45 each)
237
+ insane: {
238
+ boost: 0.45,
239
+ words: ['insanely', 'ridiculously', 'absurdly', 'outrageously', 'obscenely', 'ludicrously', 'preposterously', 'stupidly', 'crazy', 'bonkers', 'mental', 'nuts', 'bananas', 'wild', 'savage', 'brutal', 'beastly', 'godly', 'ungodly', 'otherworldly', 'astronomical']
240
+ },
241
+ // Tier 7: Nuclear/max amplifiers (+0.55 each)
242
+ nuclear: {
243
+ boost: 0.55,
244
+ words: ['nuclear', 'atomic', 'thermonuclear', 'apocalyptic', 'cataclysmic', 'earth-shattering', 'mind-blowing', 'face-melting', 'brain-melting', 'soul-crushing', 'maxed', 'cranked', 'slammed', 'pegged', 'pinned', 'redlined', 'balls to the wall', 'full send', 'all out', 'gonzo', 'off the charts', 'through the roof', 'to eleven', 'to the max', 'to the moon']
245
+ }
246
+ };
247
+
248
+ // Count all amplifier words (they stack!)
249
+ for (const [tier, data] of Object.entries(intensityAmplifiers)) {
250
+ for (const word of data.words) {
251
+ // Use word boundary matching for accuracy
252
+ const regex = new RegExp(`\\b${word.replace(/\s+/g, '\\s+')}\\b`, 'gi');
253
+ const matches = normalized.match(regex) || [];
254
+ emphasisBoost += data.boost * matches.length;
255
+ }
256
+ }
257
+
258
+ // 5b. Check for action/verb intensifiers (+0.3 each)
259
+ const actionIntensifiers = [
260
+ 'crank', 'cranked', 'slam', 'slammed', 'blast', 'blasted', 'nuke', 'nuked',
261
+ 'crush', 'crushed', 'smash', 'smashed', 'kill', 'killed', 'destroy', 'destroyed',
262
+ 'hammer', 'hammered', 'pound', 'pounded', 'pump', 'pumped', 'jack', 'jacked',
263
+ 'boost', 'boosted', 'amp', 'amped', 'juice', 'juiced', 'goose', 'goosed',
264
+ 'ramp', 'ramped', 'dial', 'dialed', 'push', 'pushed', 'shove', 'shoved',
265
+ 'yank', 'yanked', 'rip', 'ripped', 'tear', 'send', 'sent', 'launch', 'launched'
266
+ ];
267
+ for (const word of actionIntensifiers) {
268
+ if (normalized.includes(word)) {
269
+ emphasisBoost += 0.3;
270
+ break; // Only count action verbs once
271
+ }
272
+ }
273
+
274
+ // 5c. Check for "all the way" / "as X as possible" patterns
275
+ const maxPatterns = [
276
+ /\ball\s+the\s+way\b/gi,
277
+ /\bas\s+\w+\s+as\s+(?:possible|it\s+(?:can|will)\s+go|you\s+can)\b/gi,
278
+ /\bto\s+the\s+(?:max|limit|extreme|moon|stars)\b/gi,
279
+ /\b(?:full|max|complete|total)\s+(?:blast|power|throttle|strength|force|effect)\b/gi,
280
+ /\b(?:100|hundred)\s*(?:percent|%)\b/gi,
281
+ /\bgive\s+(?:it|me)\s+(?:everything|all\s+(?:of\s+it|you(?:'ve)?\s+got))\b/gi,
282
+ /\bdon'?t\s+hold\s+back\b/gi,
283
+ /\bgo\s+(?:hard|ham|crazy|nuts|wild|all\s+in|all\s+out|big)\b/gi,
284
+ /\blet\s+(?:it|her|'?er)\s+rip\b/gi,
285
+ ];
286
+ for (const pattern of maxPatterns) {
287
+ if (pattern.test(normalized)) {
288
+ emphasisBoost += 0.4;
289
+ }
290
+ }
291
+
292
+ // 6. Check for diminutive patterns (reduce intensity)
293
+ const diminutivePatterns = [
294
+ /\bjust\s+a\s+(bit|touch|tad|hair|smidge|little)\b/gi,
295
+ /\bonly\s+(slightly|a\s+bit|a\s+touch)\b/gi,
296
+ /\bhardly\s+any\b/gi,
297
+ /\bbarely\s+(any|noticeable)\b/gi,
298
+ ];
299
+ for (const pattern of diminutivePatterns) {
300
+ if (pattern.test(normalized)) {
301
+ emphasisBoost -= 0.4; // Reduce intensity
302
+ }
303
+ }
304
+
305
+ // === FIND BASE INTENSITY FROM MAPPINGS ===
306
+
307
+ // Sort by multiplier descending so we match strongest first
308
+ const sortedIntensities = Object.entries(intensityMods)
309
+ .sort((a, b) => (b[1].multiplier || 0) - (a[1].multiplier || 0));
310
+
311
+ for (const [level, data] of sortedIntensities) {
312
+ // Check the level name itself
313
+ if (normalized.includes(level)) {
314
+ baseMultiplier = data.multiplier;
315
+ matchedLevel = level;
316
+ break;
317
+ }
318
+ // Check synonyms
319
+ if (data.synonyms) {
320
+ let found = false;
321
+ for (const syn of data.synonyms) {
322
+ if (normalized.includes(syn.toLowerCase())) {
323
+ baseMultiplier = data.multiplier;
324
+ matchedLevel = level;
325
+ found = true;
326
+ break;
327
+ }
328
+ }
329
+ if (found) break;
330
+ }
331
+ }
332
+
333
+ // === COMBINE BASE + EMPHASIS ===
334
+
335
+ let finalMultiplier = baseMultiplier + emphasisBoost;
336
+
337
+ // Clamp to reasonable range (0.1 to 2.5)
338
+ finalMultiplier = Math.max(0.1, Math.min(2.5, finalMultiplier));
339
+
340
+ // Determine effective level based on final multiplier
341
+ let effectiveLevel = matchedLevel;
342
+ if (emphasisBoost > 0) {
343
+ if (finalMultiplier >= 2.0) effectiveLevel = 'nuclear';
344
+ else if (finalMultiplier >= 1.75) effectiveLevel = 'massive';
345
+ else if (finalMultiplier >= 1.5) effectiveLevel = 'extreme';
346
+ else if (finalMultiplier >= 1.35) effectiveLevel = 'heavy';
347
+ else if (finalMultiplier >= 1.2) effectiveLevel = 'bold';
348
+ } else if (emphasisBoost < 0) {
349
+ if (finalMultiplier <= 0.3) effectiveLevel = 'very_subtle';
350
+ else if (finalMultiplier <= 0.5) effectiveLevel = 'subtle';
351
+ }
352
+
353
+ return {
354
+ level: effectiveLevel,
355
+ baseLevel: matchedLevel,
356
+ multiplier: finalMultiplier,
357
+ emphasisBoost,
358
+ emphasisDetected: emphasisBoost !== 0,
359
+ };
360
+ }
361
+
362
+ /**
363
+ * Extract scope modifier from input
364
+ */
365
+ function extractScopeModifier(text, mappings) {
366
+ const normalized = normalizeText(text);
367
+ const scopeMods = mappings.modifiers?.scope || {};
368
+
369
+ for (const [scopeName, data] of Object.entries(scopeMods)) {
370
+ // Check synonyms
371
+ if (data.synonyms) {
372
+ for (const syn of data.synonyms) {
373
+ if (normalized.includes(syn.toLowerCase())) {
374
+ return {
375
+ scope: scopeName,
376
+ target_range: data.target_range || null,
377
+ requires_secondary: data.requires_secondary || false,
378
+ };
379
+ }
380
+ }
381
+ }
382
+ }
383
+
384
+ // Default to global
385
+ return { scope: 'global', target_range: null, requires_secondary: false };
386
+ }
387
+
388
+ /**
389
+ * Find best matching phrase action
390
+ * Checks both phrase_to_action and compound_adjustments
391
+ */
392
+ function findPhraseMatch(text, mappings) {
393
+ const normalized = normalizeText(text);
394
+
395
+ // Combine phrase_to_action and compound_adjustments into one lookup
396
+ const phrases = mappings.phrase_to_action || {};
397
+ const compounds = mappings.compound_adjustments || {};
398
+
399
+ // Convert compound_adjustments to same format as phrase_to_action
400
+ const allPhrases = { ...phrases };
401
+ for (const [phrase, data] of Object.entries(compounds)) {
402
+ if (!allPhrases[phrase]) {
403
+ allPhrases[phrase] = {
404
+ intent: data.intent || 'style',
405
+ is_compound: true,
406
+ actions: data.actions || [],
407
+ };
408
+ }
409
+ }
410
+
411
+ let bestMatch = null;
412
+ let bestScore = 0;
413
+
414
+ for (const [phrase, actionData] of Object.entries(allPhrases)) {
415
+ const normalizedPhrase = normalizeText(phrase);
416
+
417
+ // Exact match
418
+ if (normalized === normalizedPhrase) {
419
+ return { phrase, score: 100, exact: true, ...actionData };
420
+ }
421
+
422
+ // Contains match (phrase is in input)
423
+ if (normalized.includes(normalizedPhrase)) {
424
+ const score = (normalizedPhrase.length / normalized.length) * 80;
425
+ if (score > bestScore) {
426
+ bestScore = score;
427
+ bestMatch = { phrase, score, exact: false, ...actionData };
428
+ }
429
+ }
430
+
431
+ // Input contains phrase (for "make it look like X" patterns)
432
+ if (normalizedPhrase.length >= 3 && normalized.includes(normalizedPhrase)) {
433
+ const score = Math.min(90, normalizedPhrase.length * 5);
434
+ if (score > bestScore) {
435
+ bestScore = score;
436
+ bestMatch = { phrase, score, exact: false, ...actionData };
437
+ }
438
+ }
439
+
440
+ // Word overlap match
441
+ const phraseWords = normalizedPhrase.split(' ');
442
+ const inputWords = normalized.split(' ');
443
+ const overlap = phraseWords.filter(w => inputWords.includes(w)).length;
444
+ const overlapScore = (overlap / phraseWords.length) * 60;
445
+
446
+ if (overlapScore > bestScore && overlap >= 2) {
447
+ bestScore = overlapScore;
448
+ bestMatch = { phrase, score: overlapScore, exact: false, ...actionData };
449
+ }
450
+ }
451
+
452
+ return bestMatch;
453
+ }
454
+
455
+ /**
456
+ * Detect problem patterns in input
457
+ */
458
+ function detectProblemPattern(text, mappings) {
459
+ const normalized = normalizeText(text);
460
+ const patterns = mappings.problem_patterns || {};
461
+
462
+ for (const [problemName, data] of Object.entries(patterns)) {
463
+ if (data.indicators) {
464
+ for (const indicator of data.indicators) {
465
+ if (normalized.includes(indicator.toLowerCase())) {
466
+ return {
467
+ problem: problemName,
468
+ fix: data.fix,
469
+ };
470
+ }
471
+ }
472
+ }
473
+ }
474
+
475
+ return null;
476
+ }
477
+
478
+ /**
479
+ * Parse color terms from input
480
+ */
481
+ function parseColorTerms(text, mappings) {
482
+ const normalized = normalizeText(text);
483
+ const colorTerms = mappings.color_terms || {};
484
+ const found = [];
485
+
486
+ for (const [category, terms] of Object.entries(colorTerms)) {
487
+ for (const term of terms) {
488
+ if (normalized.includes(term.toLowerCase())) {
489
+ found.push({ term, category });
490
+ }
491
+ }
492
+ }
493
+
494
+ return found;
495
+ }
496
+
497
+ /**
498
+ * Convert parsed result to interpreter-compatible format
499
+ */
500
+ function toInterpreterFormat(parseResult) {
501
+ const params = {
502
+ temperature: 0,
503
+ tint: 0,
504
+ contrast: 0,
505
+ saturation: 0,
506
+ exposure: 0,
507
+ shadowLift: 0,
508
+ highlightCompression: 0,
509
+ midtoneDetail: 0,
510
+ pivot: 0.435,
511
+ explanation: '',
512
+ };
513
+
514
+ if (!parseResult.actions || parseResult.actions.length === 0) {
515
+ params.explanation = 'No actions matched';
516
+ return params;
517
+ }
518
+
519
+ const multiplier = parseResult.intensity?.multiplier || 1.0;
520
+ const explanations = [];
521
+
522
+ for (const action of parseResult.actions) {
523
+ const param = action.parameter;
524
+ const amount = (action.default_amount || 0) * multiplier;
525
+
526
+ // Map Resolve parameters to our simplified format
527
+ if (param.includes('temp')) {
528
+ // Temperature is in Kelvin-like units, convert to our -1 to 1 range
529
+ params.temperature = (amount / 1000) * (action.operation === 'decrease' ? -1 : 1);
530
+ explanations.push(`temperature ${action.operation}`);
531
+ } else if (param.includes('tint')) {
532
+ params.tint = (amount / 50) * (action.operation === 'decrease' ? -1 : 1);
533
+ explanations.push(`tint ${action.operation}`);
534
+ } else if (param.includes('contrast')) {
535
+ // TUNED: Production contrast multipliers average 1.18 (delta 0.18)
536
+ // Changed from /50 to /60 for more subtle, production-like adjustments
537
+ params.contrast = (amount / 60) * (action.operation === 'decrease' ? -1 : 1);
538
+ explanations.push(`contrast ${action.operation}`);
539
+ } else if (param.includes('saturation')) {
540
+ if (action.operation === 'set') {
541
+ params.saturation = (action.value - 50) / 50; // Convert 0-100 to -1 to 1
542
+ } else {
543
+ // TUNED: Production analysis shows 95th percentile saturation boost is 0.48 (1.48x)
544
+ // Changed from /25 to /35 for more natural, production-like adjustments
545
+ params.saturation = (amount / 35) * (action.operation === 'decrease' ? -1 : 1);
546
+ }
547
+ explanations.push(`saturation ${action.operation}`);
548
+ } else if (param.includes('offset.y') || param.includes('gamma.y') ||
549
+ (param.includes('offset') && !param.includes('.r') && !param.includes('.g') && !param.includes('.b'))) {
550
+ params.exposure += amount * (action.operation === 'decrease' ? -1 : 1);
551
+ explanations.push(`exposure ${action.operation}`);
552
+ } else if (param.includes('lift.y')) {
553
+ params.shadowLift = amount * (action.operation === 'decrease' ? -1 : 1);
554
+ explanations.push(`shadows ${action.operation}`);
555
+ } else if (param.includes('gain.y')) {
556
+ // Highlight adjustment via gain
557
+ if (action.operation === 'decrease') {
558
+ params.highlightCompression = Math.abs(amount);
559
+ }
560
+ explanations.push(`highlights ${action.operation}`);
561
+ } else if (param.includes('md')) {
562
+ params.midtoneDetail = (amount / 50) * (action.operation === 'decrease' ? -1 : 1);
563
+ explanations.push(`detail ${action.operation}`);
564
+ } else if (param.includes('color_boost')) {
565
+ // Color boost acts like saturation but preserves skin tones
566
+ // TUNED: Reduced from /50 to /70 to stay within production 95th percentile
567
+ params.saturation = (amount / 70) * (action.operation === 'decrease' ? -1 : 1);
568
+ explanations.push(`color boost ${action.operation}`);
569
+ } else if (param.includes('lift.r') || param.includes('lift.b') ||
570
+ param.includes('gain.r') || param.includes('gain.b')) {
571
+ // Color balance via wheels - convert to temperature/tint approximation
572
+ if (param.includes('.r') && param.includes('gain')) {
573
+ params.temperature += amount * 0.5 * (action.operation === 'decrease' ? -1 : 1);
574
+ }
575
+ if (param.includes('.b') && param.includes('lift')) {
576
+ params.temperature -= amount * 0.5 * (action.operation === 'decrease' ? -1 : 1);
577
+ }
578
+ explanations.push(`color balance adjusted`);
579
+ }
580
+ }
581
+
582
+ params.explanation = `NLP: ${parseResult.phrase || parseResult.intent} (${explanations.join(', ')})`;
583
+
584
+ // Clamp all values to valid ranges
585
+ params.temperature = Math.max(-1, Math.min(1, params.temperature));
586
+ params.tint = Math.max(-1, Math.min(1, params.tint));
587
+ params.contrast = Math.max(-1, Math.min(1, params.contrast));
588
+ params.saturation = Math.max(-1, Math.min(1, params.saturation));
589
+ params.exposure = Math.max(-1, Math.min(1, params.exposure));
590
+ params.shadowLift = Math.max(-1, Math.min(1, params.shadowLift));
591
+ params.highlightCompression = Math.max(0, Math.min(1, params.highlightCompression));
592
+ params.midtoneDetail = Math.max(-1, Math.min(1, params.midtoneDetail));
593
+
594
+ return params;
595
+ }
596
+
597
+ /**
598
+ * Main parse function
599
+ *
600
+ * @param {string} input - Natural language color grading instruction
601
+ * @returns {Object} - Parsed result with intent, actions, modifiers
602
+ */
603
+ /**
604
+ * Build fallback actions from intent when no phrase matches.
605
+ * Uses the intent's primary_tools and keyword direction to generate
606
+ * reasonable default adjustments for single-word inputs.
607
+ */
608
+ function buildFallbackActions(normalized, intentName, intentData, mappings) {
609
+ const actions = [];
610
+
611
+ // Determine direction from common keywords
612
+ const decreaseWords = /\b(less|reduce|remove|lower|cooler|colder|desaturate|flatten|softer|darker|dimmer|muted|dull|subtract)\b/;
613
+ const increaseWords = /\b(more|add|boost|increase|warmer|hotter|saturate|punch|brighter|lighter|vivid|rich|crispy|sharper)\b/;
614
+ const isDecrease = decreaseWords.test(normalized);
615
+ const isIncrease = increaseWords.test(normalized) || !isDecrease; // default to increase
616
+
617
+ const operation = isDecrease ? 'decrease' : 'increase';
618
+
619
+ // Default amounts per tool type (moderate adjustments)
620
+ const defaultAmounts = {
621
+ temp: 600,
622
+ tint: 15,
623
+ contrast: 12,
624
+ saturation: 15,
625
+ color_boost: 15,
626
+ md: 20,
627
+ shadows: 10,
628
+ highlights: 10,
629
+ exposure: 0.15,
630
+ lift: 0.03,
631
+ gamma: 0.02,
632
+ gain: 0.05,
633
+ offset: 0.02,
634
+ };
635
+
636
+ // Map intent primary_tools to parameter names and generate actions
637
+ for (const tool of intentData.primary_tools) {
638
+ // Only use the most relevant tool for single-word inputs
639
+ // For "warmer" → temp, for "contrast" → contrast, etc.
640
+ const toolLower = tool.toLowerCase();
641
+
642
+ // Check if the normalized input relates to this specific tool
643
+ const toolRelevant =
644
+ (toolLower.includes('temp') && /\b(warm|cool|cold|hot|golden|amber|orange|blue|ice|icy|tungsten|daylight|kelvin|temperature)\b/.test(normalized)) ||
645
+ (toolLower.includes('tint') && /\b(tint|green|magenta|pink)\b/.test(normalized)) ||
646
+ (toolLower.includes('contrast') && /\b(contrast|punch|punchy|flat|flatten|crispy|pop)\b/.test(normalized)) ||
647
+ (toolLower.includes('saturation') && /\b(saturat|desat|vivid|muted|dull|rich|vibran|color)\b/.test(normalized)) ||
648
+ (toolLower.includes('color_boost') && /\b(vibran|boost|color)\b/.test(normalized)) ||
649
+ (toolLower.includes('md') && /\b(detail|sharp|soft|clarity|texture)\b/.test(normalized)) ||
650
+ (toolLower.includes('exposure') || toolLower.includes('offset') || toolLower.includes('gamma')) &&
651
+ /\b(bright|dark|light|dim|expose|level)\b/.test(normalized);
652
+
653
+ if (toolRelevant) {
654
+ // Find the best matching default amount
655
+ let amount = 10; // generic fallback
656
+ for (const [key, val] of Object.entries(defaultAmounts)) {
657
+ if (toolLower.includes(key)) {
658
+ amount = val;
659
+ break;
660
+ }
661
+ }
662
+
663
+ actions.push({
664
+ parameter: `primaries.${tool}`,
665
+ operation,
666
+ default_amount: amount,
667
+ });
668
+ break; // Only one primary action for single-word inputs
669
+ }
670
+ }
671
+
672
+ // If no specific tool matched but we have an intent, use the first primary tool
673
+ if (actions.length === 0 && intentData.primary_tools.length > 0) {
674
+ const firstTool = intentData.primary_tools[0];
675
+ let amount = 10;
676
+ for (const [key, val] of Object.entries(defaultAmounts)) {
677
+ if (firstTool.toLowerCase().includes(key)) {
678
+ amount = val;
679
+ break;
680
+ }
681
+ }
682
+ actions.push({
683
+ parameter: `primaries.${firstTool}`,
684
+ operation,
685
+ default_amount: amount,
686
+ });
687
+ }
688
+
689
+ return actions;
690
+ }
691
+
692
+ function parse(input) {
693
+ const mappings = loadActionMappings();
694
+ const normalized = normalizeText(input);
695
+
696
+ // 1. Classify intent
697
+ const intent = classifyIntent(normalized, mappings);
698
+
699
+ // 2. Find phrase match
700
+ const phraseMatch = findPhraseMatch(normalized, mappings);
701
+
702
+ // 3. Extract modifiers
703
+ const intensity = extractIntensityModifier(normalized, mappings);
704
+ const scope = extractScopeModifier(normalized, mappings);
705
+
706
+ // 4. Detect problem patterns
707
+ const problem = detectProblemPattern(normalized, mappings);
708
+
709
+ // 5. Parse color terms
710
+ const colorTerms = parseColorTerms(normalized, mappings);
711
+
712
+ // 6. Build result
713
+ const result = {
714
+ input,
715
+ normalized,
716
+ intent: intent.primary,
717
+ intentData: intent.primaryData,
718
+ allIntents: intent.all,
719
+ phrase: phraseMatch?.phrase || null,
720
+ phraseScore: phraseMatch?.score || 0,
721
+ actions: phraseMatch?.actions || [],
722
+ workflow: phraseMatch?.workflow || null,
723
+ intensity,
724
+ scope,
725
+ problem,
726
+ colorTerms,
727
+ requires_secondary: phraseMatch?.requires_secondary || scope.requires_secondary || false,
728
+ is_compound: phraseMatch?.is_compound || false,
729
+ side_effects: phraseMatch?.side_effects || [],
730
+ alternatives: phraseMatch?.alternatives || [],
731
+ };
732
+
733
+ // If we have a problem pattern but no phrase match, use the problem fix
734
+ if (problem && !phraseMatch) {
735
+ result.actions = [problem.fix];
736
+ result.phrase = `fix: ${problem.problem}`;
737
+ }
738
+
739
+ // Intent-based fallback: when intent is detected but no phrase matched,
740
+ // generate default actions from the intent's primary tools and keywords.
741
+ // This handles single-word inputs like "warmer", "contrast", "desaturate"
742
+ // that don't match any full phrase in the phrase_to_action dictionary.
743
+ if (result.intent && result.actions.length === 0 && !problem) {
744
+ const intentData = mappings.intent_classification?.[result.intent];
745
+ if (intentData?.primary_tools) {
746
+ const fallbackActions = buildFallbackActions(normalized, result.intent, intentData, mappings);
747
+ if (fallbackActions.length > 0) {
748
+ result.actions = fallbackActions;
749
+ result.phrase = `intent-fallback: ${result.intent}`;
750
+ }
751
+ }
752
+ }
753
+
754
+ return result;
755
+ }
756
+
757
+ /**
758
+ * Parse and convert to interpreter format
759
+ *
760
+ * @param {string} input - Natural language input
761
+ * @returns {Object} - Parameters compatible with cinematographer-interpreter
762
+ */
763
+ function parseToParams(input) {
764
+ const parseResult = parse(input);
765
+ return toInterpreterFormat(parseResult);
766
+ }
767
+
768
+ /**
769
+ * Get available actions for an intent
770
+ */
771
+ function getActionsForIntent(intentName) {
772
+ const mappings = loadActionMappings();
773
+ const phrases = mappings.phrase_to_action || {};
774
+
775
+ return Object.entries(phrases)
776
+ .filter(([_, data]) => data.intent === intentName)
777
+ .map(([phrase, data]) => ({ phrase, ...data }));
778
+ }
779
+
780
+ /**
781
+ * Get all available intents
782
+ */
783
+ function getAvailableIntents() {
784
+ const mappings = loadActionMappings();
785
+ return Object.keys(mappings.intent_classification || {});
786
+ }
787
+
788
+ /**
789
+ * Get parameter info
790
+ */
791
+ function getParameterInfo(paramPath) {
792
+ const mappings = loadActionMappings();
793
+ return mappings.parameters?.[paramPath] || null;
794
+ }
795
+
796
+ /**
797
+ * Suggest completions for partial input
798
+ */
799
+ function suggestCompletions(partialInput, limit = 5) {
800
+ const mappings = loadActionMappings();
801
+ const normalized = normalizeText(partialInput);
802
+ const phrases = Object.keys(mappings.phrase_to_action || {});
803
+
804
+ const suggestions = phrases
805
+ .filter(phrase => {
806
+ const normPhrase = normalizeText(phrase);
807
+ return normPhrase.includes(normalized) ||
808
+ normalized.split(' ').some(word => normPhrase.includes(word));
809
+ })
810
+ .slice(0, limit);
811
+
812
+ return suggestions;
813
+ }
814
+
815
+ module.exports = {
816
+ parse,
817
+ parseToParams,
818
+ classifyIntent,
819
+ findPhraseMatch,
820
+ extractIntensityModifier,
821
+ extractScopeModifier,
822
+ detectProblemPattern,
823
+ parseColorTerms,
824
+ toInterpreterFormat,
825
+ getActionsForIntent,
826
+ getAvailableIntents,
827
+ getParameterInfo,
828
+ suggestCompletions,
829
+ normalizeText,
830
+ expandSynonyms,
831
+ loadActionMappings,
832
+ setDataPath,
833
+ };