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,102 @@
1
+ /**
2
+ * node-labeling / provenance (Layer-3 capability) — what makes AUTO-APPLY safe.
3
+ *
4
+ * The apply model (cross-craft review, Sam's call) is: the tool auto-applies conservative
5
+ * defaults, LABELS every node it creates, then the human reviews the whole PASS and gives
6
+ * feedback that reshapes the next run. The label is the safety mechanism — it makes every
7
+ * auto node VISIBLE, reversible, tweakable, deletable, and answers "why is this clip graded
8
+ * this way." So every emitted matcher node carries: tool + version + source + a params gist.
9
+ *
10
+ * Convention (stamped into the DRX node label F6, which round-trips through generate→parse):
11
+ * AUTO:<tool> v<ver> → <source> | <gist>
12
+ * e.g. AUTO:skin_match v1 → hero:CU_02 | gain(1.08,1.02,0.97)
13
+ *
14
+ * This module is PURE (no Resolve, no DB): it builds the label + a structured provenance
15
+ * record. The runner/pipeline persists records; grade_provenance (Cluster P) parses the
16
+ * label back out of an applied grade. Deterministic — no timestamps baked into the label
17
+ * (callers add time to the DB record, not the label, so DRX output stays reproducible).
18
+ */
19
+
20
+ // Per-tool version — bump when a tool's math/output changes so a stale auto node is visible.
21
+ export const TOOL_VERSIONS = {
22
+ exposure_level: 1,
23
+ skin_match: 2, // v2 = skin-line angle/distance metric
24
+ shot_match: 1,
25
+ white_balance_match: 1,
26
+ contrast_normalize: 1,
27
+ match_to_reference: 1,
28
+ saturation_match: 1,
29
+ black_balance: 1,
30
+ grade_transfer: 1,
31
+ cdl_io: 1,
32
+ };
33
+
34
+ export const AUTO_PREFIX = 'AUTO:';
35
+
36
+ /** Compact numeric gist, e.g. gain(1.08,1.02,0.97). Deterministic, fixed precision. */
37
+ export function gist(kind, obj) {
38
+ if (obj == null) return '';
39
+ if (Array.isArray(obj)) return `${kind}(${obj.map((v) => Number(v).toFixed(2)).join(',')})`;
40
+ const order = ['r', 'g', 'b', 'master'];
41
+ const vals = order.filter((k) => obj[k] != null).map((k) => Number(obj[k]).toFixed(2));
42
+ return vals.length ? `${kind}(${vals.join(',')})` : '';
43
+ }
44
+
45
+ /**
46
+ * Build the AUTO provenance label a tool stamps onto every node it creates.
47
+ * @param {string} tool tool id (keyed in TOOL_VERSIONS)
48
+ * @param {{source?:string, gist?:string, version?:number}} info
49
+ */
50
+ export function provenanceLabel(tool, info = {}) {
51
+ const ver = info.version ?? TOOL_VERSIONS[tool] ?? 1;
52
+ const parts = [`${AUTO_PREFIX}${tool} v${ver}`];
53
+ if (info.source) parts.push(`→ ${info.source}`);
54
+ const g = info.gist || '';
55
+ return g ? `${parts.join(' ')} | ${g}` : parts.join(' ');
56
+ }
57
+
58
+ /**
59
+ * Structured provenance record for the DB (tenant-ready: `actor` provenance, no global slug).
60
+ * Keeps the params + source so "why graded this way" is answerable without re-deriving.
61
+ * @param {object} p
62
+ * @returns {object}
63
+ */
64
+ export function provenanceRecord(p) {
65
+ const { tool, source = null, params = null, clipId = null, group = null, drxPath = null, pass = null, actor = 'system', gate = 'review' } = p;
66
+ return {
67
+ tool,
68
+ version: TOOL_VERSIONS[tool] ?? 1,
69
+ source,
70
+ params,
71
+ clipId,
72
+ group,
73
+ drxPath,
74
+ pass,
75
+ actor,
76
+ gate,
77
+ label: provenanceLabel(tool, { source, gist: p.gist }),
78
+ };
79
+ }
80
+
81
+ const LABEL_RE = new RegExp(`^${AUTO_PREFIX}(\\S+)\\s+v(\\d+)(?:\\s+→\\s+([^|]+?))?(?:\\s*\\|\\s*(.*))?$`);
82
+
83
+ /**
84
+ * Parse an AUTO provenance label back into its parts (for grade_provenance / verify_grade).
85
+ * Returns { auto:false } for a human/unknown label — never guesses.
86
+ */
87
+ export function parseProvenanceLabel(label) {
88
+ if (typeof label !== 'string' || !label.startsWith(AUTO_PREFIX)) return { auto: false, label: label ?? null };
89
+ const m = LABEL_RE.exec(label.trim());
90
+ if (!m) return { auto: true, tool: null, version: null, source: null, gist: null, label };
91
+ return {
92
+ auto: true,
93
+ tool: m[1],
94
+ version: Number(m[2]),
95
+ source: m[3] ? m[3].trim() : null,
96
+ gist: m[4] ? m[4].trim() : null,
97
+ label,
98
+ };
99
+ }
100
+
101
+ /** True if a node label is an AUTO provenance label this engine emitted. */
102
+ export const isAutoLabel = (label) => typeof label === 'string' && label.startsWith(AUTO_PREFIX);
@@ -0,0 +1,420 @@
1
+ /**
2
+ * Offline Reference Clip — LIVE PROJECT DB patch path.
3
+ *
4
+ * Unlike the .drp/.drt file patch (offline-ref.mjs), this writes the offline-ref
5
+ * link directly into the project's SQLite `Project.db` — `Sm2Timeline.OfflineClip`
6
+ * is a plain `uuid` column. Because it's the REAL project, DbIds are stable, so the
7
+ * link actually connects on reopen (the file/.drt-import path remaps DbIds and the
8
+ * link dangles). Same DB the Fairlight bus patcher targets.
9
+ *
10
+ * ⚠️ SAFETY (unsanctioned territory — Blackmagic doesn't support DB edits):
11
+ * - Patch ONLY with the project CLOSED (Resolve holds it in RAM + oversaves on save).
12
+ * - Always back up Project.db first (done automatically).
13
+ * - Schema is version-specific — we check the `Sm2Timeline.OfflineClip` column exists
14
+ * and refuse if the schema doesn't match, rather than blindly writing.
15
+ * - Caller must pass iConfirmProjectClosed: true for any write.
16
+ *
17
+ * TWO BACKENDS:
18
+ * - SQLite disk database (default): pass projectDb/projectName. Needs the
19
+ * optional native dep `better-sqlite3` (lazy-loaded).
20
+ * - PostgreSQL (studio databases): pass a `postgres` connection object. The
21
+ * schema mirrors SQLite but identifiers are CamelCase and must be quoted.
22
+ * Needs the optional dep `pg` (lazy-loaded). Connection params come from the
23
+ * caller (host/IpAddress, database/DbName, user, password, port); the password
24
+ * is NEVER hardcoded — pass it (Resolve's default is "DaVinci") or set
25
+ * PGPASSWORD. The project must still be CLOSED, and we pg_dump a backup first.
26
+ */
27
+
28
+ import fs from 'node:fs';
29
+ import path from 'node:path';
30
+ import os from 'node:os';
31
+ import { createRequire } from 'node:module';
32
+
33
+ const require = createRequire(import.meta.url);
34
+
35
+ const DISK_DB_ROOT = path.join(os.homedir(), 'Library/Application Support/Blackmagic Design/DaVinci Resolve/Resolve Disk Database/Resolve Projects');
36
+
37
+ function loadSqlite() {
38
+ try {
39
+ return require('better-sqlite3');
40
+ } catch {
41
+ throw new Error("offline_ref DB path needs the optional native dep 'better-sqlite3'. Install: npm i better-sqlite3");
42
+ }
43
+ }
44
+
45
+ /** Recursively locate <projectName>/Project.db under the Resolve Disk Database. */
46
+ export function findProjectDb(projectName, root = DISK_DB_ROOT) {
47
+ const hits = [];
48
+ const walk = (dir, depth) => {
49
+ if (depth > 8) return;
50
+ let entries;
51
+ try {
52
+ entries = fs.readdirSync(dir, { withFileTypes: true });
53
+ } catch {
54
+ return;
55
+ }
56
+ for (const e of entries) {
57
+ const full = path.join(dir, e.name);
58
+ if (e.isDirectory()) {
59
+ if (e.name === projectName && fs.existsSync(path.join(full, 'Project.db'))) {
60
+ hits.push(path.join(full, 'Project.db'));
61
+ }
62
+ walk(full, depth + 1);
63
+ }
64
+ }
65
+ };
66
+ if (fs.existsSync(root)) walk(root, 0);
67
+ return hits;
68
+ }
69
+
70
+ function openDb(dbPath, writable) {
71
+ const Database = loadSqlite();
72
+ if (!fs.existsSync(dbPath)) throw new Error(`Project.db not found: ${dbPath}`);
73
+ const db = new Database(dbPath, { readonly: !writable });
74
+ // schema guard — refuse unless Sm2Timeline.OfflineClip exists (version safety)
75
+ const cols = db
76
+ .prepare('PRAGMA table_info(Sm2Timeline)')
77
+ .all()
78
+ .map((c) => c.name);
79
+ if (!cols.includes('OfflineClip')) {
80
+ db.close();
81
+ throw new Error('Sm2Timeline.OfflineClip column not found — unsupported Project.db schema/version; refusing to patch.');
82
+ }
83
+ return db;
84
+ }
85
+
86
+ function resolveDbPath({ projectDb, projectName }) {
87
+ if (projectDb) return projectDb;
88
+ if (!projectName) throw new Error('provide projectDb (path) or projectName');
89
+ const hits = findProjectDb(projectName);
90
+ if (!hits.length) throw new Error(`no Project.db found for project "${projectName}" under the Resolve Disk Database`);
91
+ if (hits.length > 1) throw new Error(`multiple Project.db match "${projectName}": ${hits.join(', ')} — pass projectDb explicitly`);
92
+ return hits[0];
93
+ }
94
+
95
+ /** List timelines + their current offline-ref state (read-only) — SQLite disk DB. */
96
+ function listInProjectSqlite(opts = {}) {
97
+ const dbPath = resolveDbPath(opts);
98
+ const db = openDb(dbPath, false);
99
+ try {
100
+ const rows = db.prepare('SELECT Sm2Timeline_id AS id, Name, OfflineClip, OfflineFrameOffset FROM Sm2Timeline ORDER BY Name').all();
101
+ return { backend: 'sqlite', projectDb: dbPath, count: rows.length, linked: rows.filter((r) => r.OfflineClip).length, timelines: rows };
102
+ } finally {
103
+ db.close();
104
+ }
105
+ }
106
+
107
+ // Resolve a reference clip to its Sm2MpMedia uuid (explicit id, or by name/basename).
108
+ function resolveRefId(db, { referenceDbId, referenceName }) {
109
+ if (referenceDbId) return referenceDbId;
110
+ if (!referenceName) throw new Error('provide referenceDbId or referenceName');
111
+ const base = referenceName.split('/').pop();
112
+ const row = db.prepare('SELECT Sm2MpMedia_id AS id, Name FROM Sm2MpMedia WHERE Name = ? OR Name = ?').get(referenceName, base);
113
+ if (!row) throw new Error(`reference clip not found in media pool by name: ${referenceName}`);
114
+ return row.id;
115
+ }
116
+
117
+ function backup(dbPath) {
118
+ const bak = `${dbPath}.bak`;
119
+ fs.copyFileSync(dbPath, bak);
120
+ return bak;
121
+ }
122
+
123
+ function selectTimeline(db, { timelineId, timelineName }) {
124
+ if (timelineId) {
125
+ const r = db.prepare('SELECT Sm2Timeline_id AS id, Name, OfflineClip FROM Sm2Timeline WHERE Sm2Timeline_id = ?').get(timelineId);
126
+ if (!r) throw new Error(`no timeline with id ${timelineId}`);
127
+ return r;
128
+ }
129
+ if (timelineName) {
130
+ const rs = db.prepare('SELECT Sm2Timeline_id AS id, Name, OfflineClip FROM Sm2Timeline WHERE Name = ?').all(timelineName);
131
+ if (!rs.length) throw new Error(`no timeline named "${timelineName}"`);
132
+ if (rs.length > 1) throw new Error(`multiple timelines named "${timelineName}" — pass timelineId`);
133
+ return rs[0];
134
+ }
135
+ throw new Error('provide timelineId or timelineName');
136
+ }
137
+
138
+ /** Link an offline reference clip to a timeline — SQLite disk DB (project CLOSED). */
139
+ function linkInProjectSqlite(opts = {}) {
140
+ if (!opts.iConfirmProjectClosed) {
141
+ throw new Error('Refusing to write: close the project in Resolve first (it oversaves on save), then pass iConfirmProjectClosed: true.');
142
+ }
143
+ const dbPath = resolveDbPath(opts);
144
+ const bak = backup(dbPath);
145
+ const db = openDb(dbPath, true);
146
+ try {
147
+ const tl = selectTimeline(db, opts);
148
+ const refId = resolveRefId(db, opts);
149
+ const frameOffset = Number.isInteger(opts.frameOffset) ? opts.frameOffset : 0;
150
+ db.prepare('UPDATE Sm2Timeline SET OfflineClip = ?, OfflineFrameOffset = ? WHERE Sm2Timeline_id = ?').run(refId, frameOffset, tl.id);
151
+ const after = db.prepare('SELECT OfflineClip, OfflineFrameOffset FROM Sm2Timeline WHERE Sm2Timeline_id = ?').get(tl.id);
152
+ return {
153
+ backend: 'sqlite',
154
+ projectDb: dbPath,
155
+ backup: bak,
156
+ timeline: { id: tl.id, name: tl.Name },
157
+ linkedTo: refId,
158
+ frameOffset,
159
+ verified: after.OfflineClip === refId,
160
+ };
161
+ } finally {
162
+ db.close();
163
+ }
164
+ }
165
+
166
+ /** Remove the offline-reference link from a timeline — SQLite disk DB (project CLOSED). */
167
+ function unlinkInProjectSqlite(opts = {}) {
168
+ if (!opts.iConfirmProjectClosed) {
169
+ throw new Error('Refusing to write: close the project in Resolve first, then pass iConfirmProjectClosed: true.');
170
+ }
171
+ const dbPath = resolveDbPath(opts);
172
+ const bak = backup(dbPath);
173
+ const db = openDb(dbPath, true);
174
+ try {
175
+ const tl = selectTimeline(db, opts);
176
+ db.prepare('UPDATE Sm2Timeline SET OfflineClip = NULL WHERE Sm2Timeline_id = ?').run(tl.id);
177
+ const after = db.prepare('SELECT OfflineClip FROM Sm2Timeline WHERE Sm2Timeline_id = ?').get(tl.id);
178
+ return { backend: 'sqlite', projectDb: dbPath, backup: bak, timeline: { id: tl.id, name: tl.Name }, verified: after.OfflineClip === null };
179
+ } finally {
180
+ db.close();
181
+ }
182
+ }
183
+
184
+ // ───────────────────────────── PostgreSQL backend ─────────────────────────────
185
+ // Studio Resolve databases are Postgres, not a disk SQLite file. The logical
186
+ // schema is the same, but identifiers are CamelCase → every one must be quoted.
187
+
188
+ function loadPg() {
189
+ try {
190
+ return require('pg');
191
+ } catch {
192
+ throw new Error("offline_ref Postgres path needs the optional dep 'pg'. Install: npm i pg");
193
+ }
194
+ }
195
+
196
+ /** Normalize caller connection params (accepts Resolve's IpAddress/DbName aliases). */
197
+ function pgConfig(pg = {}) {
198
+ const cfg = {
199
+ host: pg.host || pg.IpAddress,
200
+ port: pg.port || 5432,
201
+ user: pg.user || 'postgres',
202
+ // NEVER hardcode the password. Caller passes it (Resolve default "DaVinci")
203
+ // or it comes from the standard PGPASSWORD env. Undefined → pg uses.pgpass.
204
+ password: pg.password != null ? pg.password : process.env.PGPASSWORD,
205
+ database: pg.database || pg.DbName,
206
+ };
207
+ if (!cfg.host) throw new Error('postgres: provide host (or IpAddress)');
208
+ if (!cfg.database) throw new Error('postgres: provide database (or DbName)');
209
+ return cfg;
210
+ }
211
+
212
+ /** Connect (or use an injected client for tests via pg.__client). */
213
+ async function pgConnect(pg) {
214
+ if (pg && pg.__client) return pg.__client;
215
+ const { Client } = loadPg();
216
+ const client = new Client(pgConfig(pg));
217
+ await client.connect();
218
+ return client;
219
+ }
220
+
221
+ async function pgEnd(client, pg) {
222
+ if (pg && pg.__client) return; // injected client — caller owns its lifecycle
223
+ try {
224
+ await client.end();
225
+ } catch {
226
+ /* ignore */
227
+ }
228
+ }
229
+
230
+ async function pgSchemaGuard(client) {
231
+ const { rows } = await client.query("SELECT 1 FROM information_schema.columns WHERE table_name = 'Sm2Timeline' AND column_name = 'OfflineClip' LIMIT 1");
232
+ if (!rows.length) {
233
+ throw new Error('Sm2Timeline.OfflineClip column not found — unsupported Postgres schema/version; refusing to patch.');
234
+ }
235
+ }
236
+
237
+ /** Walk Sm2MpFolder_Owner_id UP from any folder to the project's no-owner Master root. */
238
+ async function pgProjectMasterRoot(client, anyFolderId) {
239
+ const { rows } = await client.query(
240
+ `WITH RECURSIVE up AS (SELECT "Sm2MpFolder_id", "Sm2MpFolder_Owner_id" FROM "Sm2MpFolder" WHERE "Sm2MpFolder_id" = $1
241
+ UNION ALL
242
+ SELECT f."Sm2MpFolder_id", f."Sm2MpFolder_Owner_id" FROM "Sm2MpFolder" f JOIN up ON f."Sm2MpFolder_id" = up."Sm2MpFolder_Owner_id")
243
+ SELECT "Sm2MpFolder_id" AS id FROM up WHERE "Sm2MpFolder_Owner_id" IS NULL LIMIT 1`,
244
+ [anyFolderId],
245
+ );
246
+ if (!rows.length) throw new Error(`could not resolve a project Master root from folder ${anyFolderId}`);
247
+ return rows[0].id;
248
+ }
249
+
250
+ /** All folder ids in the project (recursive subtree under the Master root). */
251
+ async function pgProjectFolderIds(client, masterRootId) {
252
+ const { rows } = await client.query(
253
+ `WITH RECURSIVE dn AS (SELECT "Sm2MpFolder_id" FROM "Sm2MpFolder" WHERE "Sm2MpFolder_id" = $1
254
+ UNION ALL
255
+ SELECT f."Sm2MpFolder_id" FROM "Sm2MpFolder" f JOIN dn ON f."Sm2MpFolder_Owner_id" = dn."Sm2MpFolder_id")
256
+ SELECT "Sm2MpFolder_id" AS id FROM dn`,
257
+ [masterRootId],
258
+ );
259
+ return rows.map((r) => r.id);
260
+ }
261
+
262
+ /**
263
+ * Resolve a reference clip to its Sm2MpMedia uuid — PROJECT-SCOPED.
264
+ *
265
+ * A studio Postgres holds MANY projects; a bare `WHERE Name=…` returns clips from
266
+ * OTHER projects, and linking OfflineClip to a cross-project clip silently fails to
267
+ * bind (the live API reports it "Clip not found"). So:
268
+ * - PREFER `referenceDbId` resolved via the project-scoped live MCP
269
+ * (media_pool_item get_unique_id / get_clip_property verifies it resolves in the
270
+ * current project). Passed through (verified to exist; and to be in-project when
271
+ * a folder scope is given).
272
+ * - `referenceName` is resolved ONLY within the target project: pass
273
+ * `referenceFolderRoot` = ANY folder uuid in that project (e.g. the Sm2MpFolder_id
274
+ * of a known current-project clip from the MCP). We walk up to the project Master
275
+ * and match the name within its recursive subtree, refusing on multiple matches.
276
+ * - `referenceName` with NO scope is refused — it cannot be made safe on a shared DB.
277
+ */
278
+ async function pgResolveRefId(client, { referenceDbId, referenceName, referenceFolderRoot }) {
279
+ // Project subtree (if a scope folder is given).
280
+ let projectFolderIds = null;
281
+ if (referenceFolderRoot) {
282
+ const master = await pgProjectMasterRoot(client, referenceFolderRoot);
283
+ projectFolderIds = await pgProjectFolderIds(client, master);
284
+ }
285
+
286
+ if (referenceDbId) {
287
+ const { rows } = await client.query('SELECT "Sm2MpFolder_id" AS folder FROM "Sm2MpMedia" WHERE "Sm2MpMedia_id" = $1', [referenceDbId]);
288
+ if (!rows.length) throw new Error(`referenceDbId ${referenceDbId} is not a media pool clip in this database`);
289
+ if (projectFolderIds && !projectFolderIds.includes(rows[0].folder)) {
290
+ throw new Error(`referenceDbId ${referenceDbId} is NOT in the target project (cross-project clip won't bind) — resolve it via the project-scoped MCP`);
291
+ }
292
+ return referenceDbId;
293
+ }
294
+
295
+ if (!referenceName) throw new Error('provide referenceDbId (MCP-verified) or referenceName + referenceFolderRoot');
296
+ if (!projectFolderIds) {
297
+ throw new Error(
298
+ 'referenceName on a shared Postgres DB is unreliable (cross-project name collisions) — pass referenceFolderRoot (any folder uuid in the target project) or an MCP-verified referenceDbId',
299
+ );
300
+ }
301
+ const base = referenceName.split('/').pop();
302
+ const { rows } = await client.query(
303
+ 'SELECT "Sm2MpMedia_id" AS id, "Name" FROM "Sm2MpMedia" WHERE ("Name" = $1 OR "Name" = $2) AND "Sm2MpFolder_id" = ANY($3::uuid[])',
304
+ [referenceName, base, projectFolderIds],
305
+ );
306
+ if (!rows.length)
307
+ throw new Error(`reference clip "${referenceName}" not found IN THIS PROJECT — import it into the project's media pool first, or pass referenceDbId`);
308
+ if (rows.length > 1) {
309
+ throw new Error(`multiple in-project clips named "${referenceName}" (${rows.map((r) => r.id).join(', ')}) — pass referenceDbId`);
310
+ }
311
+ return rows[0].id;
312
+ }
313
+
314
+ async function pgSelectTimeline(client, { timelineId, timelineName }) {
315
+ if (timelineId) {
316
+ const { rows } = await client.query('SELECT "Sm2Timeline_id" AS id, "Name", "OfflineClip" FROM "Sm2Timeline" WHERE "Sm2Timeline_id" = $1', [timelineId]);
317
+ if (!rows.length) throw new Error(`no timeline with id ${timelineId}`);
318
+ return rows[0];
319
+ }
320
+ if (timelineName) {
321
+ const { rows } = await client.query('SELECT "Sm2Timeline_id" AS id, "Name", "OfflineClip" FROM "Sm2Timeline" WHERE "Name" = $1', [timelineName]);
322
+ if (!rows.length) throw new Error(`no timeline named "${timelineName}"`);
323
+ if (rows.length > 1) throw new Error(`multiple timelines named "${timelineName}" — pass timelineId`);
324
+ return rows[0];
325
+ }
326
+ throw new Error('provide timelineId or timelineName');
327
+ }
328
+
329
+ /** Back up the Sm2Timeline table via pg_dump before a write (test hook: pg.__backupFn). */
330
+ function pgBackup(pg) {
331
+ if (pg && typeof pg.__backupFn === 'function') return pg.__backupFn();
332
+ const cfg = pgConfig(pg);
333
+ const out = path.join(os.tmpdir(), `offline-ref-Sm2Timeline-backup-${cfg.database}.sql`);
334
+ const dump = pg.pgDumpPath || 'pg_dump';
335
+ const env = { ...process.env };
336
+ if (cfg.password != null) env.PGPASSWORD = String(cfg.password);
337
+ const r = require('node:child_process').spawnSync(
338
+ dump,
339
+ ['-h', String(cfg.host), '-p', String(cfg.port), '-U', String(cfg.user), '-d', String(cfg.database), '--data-only', '-t', '"Sm2Timeline"', '-w', '-f', out],
340
+ { env, encoding: 'utf8' },
341
+ );
342
+ if (r.status !== 0) {
343
+ throw new Error(
344
+ `pg_dump backup failed (refusing to write without a backup): ${(r.stderr || r.error?.message || 'unknown').toString().slice(-300)}. Pass postgres.pgDumpPath if pg_dump isn't on PATH.`,
345
+ );
346
+ }
347
+ return out;
348
+ }
349
+
350
+ // NOTE: a Postgres database holds EVERY project, so this lists all of their
351
+ // timelines (unlike a per-project SQLite Project.db). link/unlink stay correct
352
+ // regardless — they target a specific Sm2Timeline_id (or refuse an ambiguous name).
353
+ async function listInProjectPg(opts) {
354
+ const pg = opts.postgres;
355
+ const client = await pgConnect(pg);
356
+ try {
357
+ await pgSchemaGuard(client);
358
+ const { rows } = await client.query('SELECT "Sm2Timeline_id" AS id, "Name", "OfflineClip", "OfflineFrameOffset" FROM "Sm2Timeline" ORDER BY "Name"');
359
+ return { backend: 'postgres', database: pgConfig(pg).database, count: rows.length, linked: rows.filter((r) => r.OfflineClip).length, timelines: rows };
360
+ } finally {
361
+ await pgEnd(client, pg);
362
+ }
363
+ }
364
+
365
+ async function linkInProjectPg(opts) {
366
+ if (!opts.iConfirmProjectClosed) {
367
+ throw new Error('Refusing to write: close the project in Resolve first (it oversaves on save), then pass iConfirmProjectClosed: true.');
368
+ }
369
+ const pg = opts.postgres;
370
+ const client = await pgConnect(pg);
371
+ try {
372
+ await pgSchemaGuard(client);
373
+ const tl = await pgSelectTimeline(client, opts);
374
+ const refId = await pgResolveRefId(client, opts);
375
+ const frameOffset = Number.isInteger(opts.frameOffset) ? opts.frameOffset : 0;
376
+ const bak = pgBackup(pg); // back up BEFORE the write
377
+ await client.query('UPDATE "Sm2Timeline" SET "OfflineClip" = $1, "OfflineFrameOffset" = $2 WHERE "Sm2Timeline_id" = $3', [refId, frameOffset, tl.id]);
378
+ const { rows } = await client.query('SELECT "OfflineClip", "OfflineFrameOffset" FROM "Sm2Timeline" WHERE "Sm2Timeline_id" = $1', [tl.id]);
379
+ return { backend: 'postgres', backup: bak, timeline: { id: tl.id, name: tl.Name }, linkedTo: refId, frameOffset, verified: rows[0].OfflineClip === refId };
380
+ } finally {
381
+ await pgEnd(client, pg);
382
+ }
383
+ }
384
+
385
+ async function unlinkInProjectPg(opts) {
386
+ if (!opts.iConfirmProjectClosed) {
387
+ throw new Error('Refusing to write: close the project in Resolve first, then pass iConfirmProjectClosed: true.');
388
+ }
389
+ const pg = opts.postgres;
390
+ const client = await pgConnect(pg);
391
+ try {
392
+ await pgSchemaGuard(client);
393
+ const tl = await pgSelectTimeline(client, opts);
394
+ const bak = pgBackup(pg);
395
+ await client.query('UPDATE "Sm2Timeline" SET "OfflineClip" = NULL WHERE "Sm2Timeline_id" = $1', [tl.id]);
396
+ const { rows } = await client.query('SELECT "OfflineClip" FROM "Sm2Timeline" WHERE "Sm2Timeline_id" = $1', [tl.id]);
397
+ return { backend: 'postgres', backup: bak, timeline: { id: tl.id, name: tl.Name }, verified: rows[0].OfflineClip === null };
398
+ } finally {
399
+ await pgEnd(client, pg);
400
+ }
401
+ }
402
+
403
+ // ───────────────────────── public async dispatchers ─────────────────────────
404
+ // Branch on whether a `postgres` connection object is supplied; otherwise the
405
+ // SQLite disk-DB path (default). Both are awaited so callers use one async API.
406
+
407
+ /** List timelines + their current offline-ref state (read-only). */
408
+ export async function listInProject(opts = {}) {
409
+ return opts.postgres ? listInProjectPg(opts) : listInProjectSqlite(opts);
410
+ }
411
+
412
+ /** Link an offline reference clip to a timeline (project must be CLOSED). */
413
+ export async function linkInProject(opts = {}) {
414
+ return opts.postgres ? linkInProjectPg(opts) : linkInProjectSqlite(opts);
415
+ }
416
+
417
+ /** Remove the offline-reference link from a timeline (project must be CLOSED). */
418
+ export async function unlinkInProject(opts = {}) {
419
+ return opts.postgres ? unlinkInProjectPg(opts) : unlinkInProjectSqlite(opts);
420
+ }