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
+ * Fairlight Bus Routing Patcher
3
+ *
4
+ * Modifies DaVinci Resolve's FLStudioModelBA binary blob to configure
5
+ * Fairlight audio bus routing directly in the project database.
6
+ *
7
+ * This is the database-level companion to fairlight-automation.js (which uses
8
+ * macOS Accessibility API). This module works when Resolve is NOT running,
9
+ * by patching the SQLite database directly.
10
+ *
11
+ * Format Knowledge (reverse-engineered):
12
+ * - FLStudioModelBA lives in Sm2Sequence.FieldsBlob (KV type-12 blob)
13
+ * - Raw blob: 4-byte u32BE decompressed size + zlib body
14
+ * - Decompressed: ~400-550KB AdrDatabase format with 7 sections
15
+ * - Sections 0-5 are FIXED SIZE across all projects (281,839 bytes total)
16
+ * - Section 6 (offset 281,839+) contains all bus/track configuration
17
+ * - Bus definition table at offset 281,907: u32LE count + per-bus records
18
+ * - Bus names stored as id=0xBC records near end of Section 6
19
+ * - Per-bus property records follow bus names (0xBD, 0xBE, 0xDE, etc.)
20
+ *
21
+ * @module fairlight-bus-patcher
22
+ */
23
+
24
+ const zlib = require('zlib');
25
+
26
+ // Fixed offsets (verified across 16+ projects, all Resolve versions)
27
+ const SECTION_6_OFFSET = 281839;
28
+ const BUS_TABLE_OFFSET = 281907; // sec6 + 68
29
+ const BUS_TYPE_AUDIO = 7;
30
+
31
+ // Bus format → channel count mapping
32
+ const FORMAT_CHANNELS = {
33
+ mono: 1,
34
+ stereo: 2,
35
+ '5.1': 6,
36
+ '5.1film': 6,
37
+ '7.1': 8,
38
+ '7.1.4': 16,
39
+ };
40
+ const CHANNELS_FORMAT = { 1: 'mono', 2: 'stereo', 6: '5.1', 8: '7.1', 16: '7.1.4' };
41
+
42
+ // ============================================================
43
+ // FieldsBlob KV Codec
44
+ // ============================================================
45
+
46
+ /**
47
+ * Parse FieldsBlob KV binary format into entries array.
48
+ * Each entry: { key, typeCode, rawValue (Buffer) }
49
+ *
50
+ * Format: u32BE(version=1) + u32BE(count) + entries
51
+ * Entry: u32BE(keyLen) + utf16be(key) + u32BE(typeCode) + u8(pad=0) + value
52
+ * Types: 1=bool(1B), 2=u32(4B), 3=f64(8B), 4=i64(8B), 10=string(4+N), 12=blob(4+N)
53
+ */
54
+ function parseFieldsBlob(buf) {
55
+ const version = buf.readUInt32BE(0);
56
+ const count = buf.readUInt32BE(4);
57
+ const entries = [];
58
+ let offset = 8;
59
+
60
+ for (let i = 0; i < count; i++) {
61
+ const keyLen = buf.readUInt32BE(offset); offset += 4;
62
+ let key = '';
63
+ for (let j = 0; j < keyLen; j += 2) {
64
+ key += String.fromCharCode(buf.readUInt16BE(offset + j));
65
+ }
66
+ offset += keyLen;
67
+
68
+ const typeCode = buf.readUInt32BE(offset); offset += 4;
69
+ offset += 1; // pad byte
70
+
71
+ let valueSize;
72
+ switch (typeCode) {
73
+ case 1: valueSize = 1; break;
74
+ case 2: valueSize = 4; break;
75
+ case 3: case 4: valueSize = 8; break;
76
+ case 10: case 12:
77
+ valueSize = 4 + buf.readUInt32BE(offset);
78
+ break;
79
+ default:
80
+ throw new Error(`Unknown FieldsBlob type code: ${typeCode} for key "${key}"`);
81
+ }
82
+
83
+ entries.push({ key, typeCode, rawValue: buf.slice(offset, offset + valueSize) });
84
+ offset += valueSize;
85
+ }
86
+
87
+ return { version, entries };
88
+ }
89
+
90
+ /**
91
+ * Encode entries array back to FieldsBlob KV binary format.
92
+ */
93
+ function encodeFieldsBlob(entries) {
94
+ const parts = [];
95
+ const header = Buffer.alloc(8);
96
+ header.writeUInt32BE(1, 0); // version
97
+ header.writeUInt32BE(entries.length, 4);
98
+ parts.push(header);
99
+
100
+ for (const e of entries) {
101
+ const keyBuf = Buffer.alloc(e.key.length * 2);
102
+ for (let i = 0; i < e.key.length; i++) keyBuf.writeUInt16BE(e.key.charCodeAt(i), i * 2);
103
+ const keyLenBuf = Buffer.alloc(4);
104
+ keyLenBuf.writeUInt32BE(keyBuf.length, 0);
105
+ parts.push(keyLenBuf, keyBuf);
106
+
107
+ const typeBuf = Buffer.alloc(4);
108
+ typeBuf.writeUInt32BE(e.typeCode, 0);
109
+ parts.push(typeBuf, Buffer.from([0x00])); // type + pad
110
+
111
+ parts.push(e.rawValue);
112
+ }
113
+
114
+ return Buffer.concat(parts);
115
+ }
116
+
117
+ // ============================================================
118
+ // FLStudioModelBA Codec
119
+ // ============================================================
120
+
121
+ /**
122
+ * Decompress FLStudioModelBA raw blob.
123
+ * @param {Buffer} rawBlob - The raw blob (4-byte header + zlib)
124
+ * @returns {Buffer} Decompressed AdrDatabase data
125
+ */
126
+ function decompressFLModel(rawBlob) {
127
+ const expectedSize = rawBlob.readUInt32BE(0);
128
+ const data = zlib.inflateSync(rawBlob.slice(4));
129
+ if (data.length !== expectedSize) {
130
+ throw new Error(`FLStudioModelBA size mismatch: header says ${expectedSize}, got ${data.length}`);
131
+ }
132
+ return data;
133
+ }
134
+
135
+ /**
136
+ * Compress AdrDatabase data into FLStudioModelBA raw blob.
137
+ * @param {Buffer} data - Decompressed AdrDatabase data
138
+ * @returns {Buffer} Raw blob (4-byte header + zlib)
139
+ */
140
+ function compressFLModel(data) {
141
+ const header = Buffer.alloc(4);
142
+ header.writeUInt32BE(data.length, 0);
143
+ const compressed = zlib.deflateSync(data);
144
+ return Buffer.concat([header, compressed]);
145
+ }
146
+
147
+ // ============================================================
148
+ // Bus Configuration Reader
149
+ // ============================================================
150
+
151
+ /**
152
+ * Read bus configuration from decompressed FLStudioModelBA.
153
+ * @param {Buffer} data - Decompressed AdrDatabase data
154
+ * @returns {{ buses: Array<{id,type,channels,name}>, busNameOffset: number }}
155
+ */
156
+ function readBusConfig(data) {
157
+ // Verify Section 6 marker
158
+ const marker = data.readUInt32LE(SECTION_6_OFFSET);
159
+ if (marker !== 0x77668866) {
160
+ throw new Error('Section 6 marker not found at expected offset ' + SECTION_6_OFFSET);
161
+ }
162
+
163
+ // Read bus definition table
164
+ const busCount = data.readUInt32LE(BUS_TABLE_OFFSET);
165
+ if (busCount < 0 || busCount > 32) {
166
+ throw new Error('Invalid bus count: ' + busCount);
167
+ }
168
+
169
+ const buses = [];
170
+ let off = BUS_TABLE_OFFSET + 4;
171
+ for (let i = 0; i < busCount; i++) {
172
+ buses.push({
173
+ id: data.readUInt16LE(off),
174
+ type: data.readUInt16LE(off + 2),
175
+ channels: data.readUInt32LE(off + 4),
176
+ });
177
+ off += 8;
178
+ }
179
+
180
+ // Find bus name record (flag=1, id=0xBC with nameCount === busCount)
181
+ const bcPattern = Buffer.from([0x01, 0x00, 0x00, 0x00, 0xbc, 0x00, 0x00, 0x00]);
182
+ let busNameOffset = -1;
183
+ let searchFrom = SECTION_6_OFFSET + 10000;
184
+
185
+ while (searchFrom < data.length) {
186
+ const idx = data.indexOf(bcPattern, searchFrom);
187
+ if (idx === -1) break;
188
+ const nameCount = data.readUInt32LE(idx + 8);
189
+ if (nameCount === busCount) {
190
+ const strLen = data.readUInt32LE(idx + 12);
191
+ if (strLen > 0 && strLen < 200) {
192
+ busNameOffset = idx;
193
+ break;
194
+ }
195
+ }
196
+ searchFrom = idx + 8;
197
+ }
198
+
199
+ // Read bus names
200
+ if (busNameOffset >= 0) {
201
+ let strOff = busNameOffset + 12;
202
+ for (let i = 0; i < busCount; i++) {
203
+ const len = data.readUInt32LE(strOff);
204
+ if (len <= 0 || len > 200 || strOff + 4 + len > data.length) break;
205
+ buses[i].name = data.slice(strOff + 4, strOff + 4 + len).toString('ascii').replace(/\0/g, '');
206
+ strOff += 4 + len;
207
+ }
208
+ }
209
+
210
+ return { buses, busNameOffset, busCount };
211
+ }
212
+
213
+ /**
214
+ * Read complete Fairlight model info from a project database.
215
+ * @param {Database} db - better-sqlite3 database instance
216
+ * @returns {{ busFormat, buses, data, rawBlob, fieldsBlob, entries }}
217
+ */
218
+ function readFromDatabase(db) {
219
+ const row = db.prepare('SELECT FieldsBlob FROM Sm2Sequence WHERE FieldsBlob IS NOT NULL').get();
220
+ if (!row) throw new Error('No Sm2Sequence with FieldsBlob found');
221
+
222
+ const fieldsBlobBuf = Buffer.from(row.FieldsBlob);
223
+ const { entries } = parseFieldsBlob(fieldsBlobBuf);
224
+
225
+ let busFormat = null;
226
+ let rawBlob = null;
227
+ let data = null;
228
+
229
+ for (const e of entries) {
230
+ if (e.key === 'FirstMainBusFormat') {
231
+ busFormat = e.rawValue.readUInt32BE(0);
232
+ }
233
+ if (e.key === 'FLStudioModelBA') {
234
+ rawBlob = e.rawValue.slice(4); // skip size prefix
235
+ data = decompressFLModel(rawBlob);
236
+ }
237
+ }
238
+
239
+ if (!data) throw new Error('No FLStudioModelBA found in FieldsBlob');
240
+
241
+ const busConfig = readBusConfig(data);
242
+
243
+ return {
244
+ busFormat,
245
+ buses: busConfig.buses,
246
+ data,
247
+ rawBlob,
248
+ fieldsBlobBuf,
249
+ entries,
250
+ busNameOffset: busConfig.busNameOffset,
251
+ };
252
+ }
253
+
254
+ // ============================================================
255
+ // Bus Configuration Writer (Template Transplant)
256
+ // ============================================================
257
+
258
+ /**
259
+ * Apply a routing template to a project database.
260
+ *
261
+ * Strategy: transplant the entire decompressed FLStudioModelBA from the
262
+ * template, then patch bus names and track names to match the target config.
263
+ *
264
+ * @param {Database} db - better-sqlite3 database (writable)
265
+ * @param {Buffer} templateData - Decompressed FLStudioModelBA from template project
266
+ * @param {Object} [options]
267
+ * @param {string[]} [options.busNames] - Bus names to set (replaces template names)
268
+ * @param {number} [options.busFormat] - FirstMainBusFormat value to set
269
+ * @returns {{ success: boolean, details: string }}
270
+ */
271
+ function applyTemplate(db, templateData, options = {}) {
272
+ // Read existing FieldsBlob
273
+ const row = db.prepare('SELECT FieldsBlob FROM Sm2Sequence WHERE FieldsBlob IS NOT NULL').get();
274
+ if (!row) throw new Error('No Sm2Sequence with FieldsBlob found');
275
+
276
+ const fieldsBlobBuf = Buffer.from(row.FieldsBlob);
277
+ const { entries } = parseFieldsBlob(fieldsBlobBuf);
278
+
279
+ let patchedData = Buffer.from(templateData);
280
+
281
+ // Patch bus names if provided
282
+ if (options.busNames) {
283
+ const config = readBusConfig(patchedData);
284
+ if (config.busNameOffset >= 0) {
285
+ patchedData = patchBusNames(patchedData, config, options.busNames);
286
+ }
287
+ }
288
+
289
+ // Update AdrDatabase size field (offset 8, u32LE = total size - 12)
290
+ patchedData.writeUInt32LE(patchedData.length - 12, 8);
291
+
292
+ // Compress
293
+ const newRawBlob = compressFLModel(patchedData);
294
+
295
+ // Build new rawValue for the FLStudioModelBA entry (size prefix + blob)
296
+ const newRawValue = Buffer.alloc(4 + newRawBlob.length);
297
+ newRawValue.writeUInt32BE(newRawBlob.length, 0);
298
+ newRawBlob.copy(newRawValue, 4);
299
+
300
+ // Update entries
301
+ const newEntries = entries.map(e => {
302
+ if (e.key === 'FLStudioModelBA') {
303
+ return { ...e, rawValue: newRawValue };
304
+ }
305
+ if (e.key === 'FirstMainBusFormat' && options.busFormat !== undefined) {
306
+ const val = Buffer.alloc(4);
307
+ val.writeUInt32BE(options.busFormat, 0);
308
+ return { ...e, rawValue: val };
309
+ }
310
+ return e;
311
+ });
312
+
313
+ // Encode new FieldsBlob
314
+ const newFieldsBlob = encodeFieldsBlob(newEntries);
315
+
316
+ // Write to database
317
+ db.prepare('UPDATE Sm2Sequence SET FieldsBlob = ? WHERE FieldsBlob IS NOT NULL').run(newFieldsBlob);
318
+
319
+ return {
320
+ success: true,
321
+ details: `Applied template: ${patchedData.length}B decompressed, ${newRawBlob.length}B compressed`,
322
+ };
323
+ }
324
+
325
+ /**
326
+ * Patch bus names in a decompressed FLStudioModelBA blob.
327
+ * Because names are variable-length, this may change the blob size.
328
+ *
329
+ * @param {Buffer} data - Decompressed AdrDatabase data
330
+ * @param {{ busNameOffset: number, busCount: number }} config - Current bus config
331
+ * @param {string[]} newNames - New bus names
332
+ * @returns {Buffer} New decompressed data with patched names
333
+ */
334
+ function patchBusNames(data, config, newNames) {
335
+ if (config.busNameOffset < 0) return data;
336
+
337
+ // Read current names to find the exact byte range to replace
338
+ let off = config.busNameOffset + 12; // skip flag(4) + id(4) + count(4)
339
+ const oldNameStart = off;
340
+ for (let i = 0; i < config.busCount; i++) {
341
+ const len = data.readUInt32LE(off);
342
+ if (len <= 0 || len > 200) break;
343
+ off += 4 + len;
344
+ }
345
+ const oldNameEnd = off;
346
+ const oldNameBytes = data.slice(oldNameStart, oldNameEnd);
347
+
348
+ // Build new name bytes
349
+ const nameParts = [];
350
+ for (const name of newNames) {
351
+ const lenBuf = Buffer.alloc(4);
352
+ lenBuf.writeUInt32LE(name.length, 0);
353
+ nameParts.push(lenBuf, Buffer.from(name, 'ascii'));
354
+ }
355
+ const newNameBytes = Buffer.concat(nameParts);
356
+
357
+ // Splice: before + new names + after
358
+ const before = data.slice(0, oldNameStart);
359
+ const after = data.slice(oldNameEnd);
360
+ const result = Buffer.concat([before, newNameBytes, after]);
361
+
362
+ // Update the name count if it changed
363
+ if (newNames.length !== config.busCount) {
364
+ result.writeUInt32LE(newNames.length, config.busNameOffset + 8);
365
+ }
366
+
367
+ return result;
368
+ }
369
+
370
+ // ============================================================
371
+ // Template Management
372
+ // ============================================================
373
+
374
+ /**
375
+ * Export a routing template from a configured project.
376
+ * @param {Database} db - better-sqlite3 database (readonly)
377
+ * @returns {Object} Template object (JSON-serializable)
378
+ */
379
+ function exportTemplate(db) {
380
+ const model = readFromDatabase(db);
381
+ const config = readBusConfig(model.data);
382
+
383
+ return {
384
+ version: 1,
385
+ buses: config.buses.map(b => ({
386
+ id: b.id,
387
+ type: b.type,
388
+ channels: b.channels,
389
+ format: CHANNELS_FORMAT[b.channels] || b.channels + 'ch',
390
+ name: b.name || 'Bus ' + (b.id + 1),
391
+ })),
392
+ busFormat: model.busFormat,
393
+ blobSize: model.data.length,
394
+ blob: model.data.toString('base64'),
395
+ };
396
+ }
397
+
398
+ /**
399
+ * Import a routing template from JSON and apply it to a project.
400
+ * @param {Database} db - better-sqlite3 database (writable)
401
+ * @param {Object} template - Template object from exportTemplate()
402
+ * @param {Object} [options]
403
+ * @param {string[]} [options.busNames] - Override bus names
404
+ * @returns {{ success: boolean, details: string }}
405
+ */
406
+ function importTemplate(db, template, options = {}) {
407
+ const templateData = Buffer.from(template.blob, 'base64');
408
+ return applyTemplate(db, templateData, {
409
+ busNames: options.busNames || template.buses.map(b => b.name),
410
+ busFormat: template.busFormat,
411
+ });
412
+ }
413
+
414
+ // ============================================================
415
+ // Database Safety — Backup & Restore (Phase 3)
416
+ // ============================================================
417
+
418
+ const fsSafety = require('fs');
419
+ const pathSafety = require('path');
420
+
421
+ /**
422
+ * Backup the FieldsBlob for a timeline before any write operation.
423
+ *
424
+ * Writes the raw FieldsBlob bytes to a timestamped backup file in
425
+ * <dbDir>/.fairlight-backup/<timelineName>_<timestamp>.blob
426
+ *
427
+ * @param {Object} db - better-sqlite3 database instance
428
+ * @param {string} timelineName - Timeline to back up
429
+ * @returns {string} Path to the backup file
430
+ */
431
+ function backupFieldsBlob(db, timelineName) {
432
+ const row = db.prepare(`
433
+ SELECT s.FieldsBlob, t.Name as tlName
434
+ FROM Sm2Sequence s LEFT JOIN Sm2Timeline t ON s.Sm2Timeline_id = t.Sm2Timeline_id
435
+ WHERE t.Name = ? LIMIT 1
436
+ `).get(timelineName);
437
+
438
+ if (!row || !row.FieldsBlob) {
439
+ throw new Error(`Cannot backup: timeline not found or no FieldsBlob: ${timelineName}`);
440
+ }
441
+
442
+ // Determine backup directory from database path
443
+ const dbPath = db.name; // better-sqlite3 exposes .name as the file path
444
+ const dbDir = pathSafety.dirname(dbPath);
445
+ const backupDir = pathSafety.join(dbDir, '.fairlight-backup');
446
+ fsSafety.mkdirSync(backupDir, { recursive: true });
447
+
448
+ // Sanitize timeline name for filename
449
+ const safeName = timelineName.replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 80);
450
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
451
+ const backupPath = pathSafety.join(backupDir, `${safeName}_${timestamp}.blob`);
452
+
453
+ fsSafety.writeFileSync(backupPath, Buffer.from(row.FieldsBlob));
454
+ return backupPath;
455
+ }
456
+
457
+ /**
458
+ * Restore a FieldsBlob from a backup file.
459
+ *
460
+ * @param {Object} db - better-sqlite3 database instance (writable)
461
+ * @param {string} backupPath - Path to the .blob backup file
462
+ * @param {string} timelineName - Timeline to restore to
463
+ * @returns {{ success: boolean, restoredBytes: number }}
464
+ */
465
+ function restoreFieldsBlob(db, backupPath, timelineName) {
466
+ if (!fsSafety.existsSync(backupPath)) {
467
+ throw new Error(`Backup file not found: ${backupPath}`);
468
+ }
469
+
470
+ const backupData = fsSafety.readFileSync(backupPath);
471
+
472
+ const targetRow = db.prepare(`
473
+ SELECT s.Sm2Sequence_id
474
+ FROM Sm2Sequence s LEFT JOIN Sm2Timeline t ON s.Sm2Timeline_id = t.Sm2Timeline_id
475
+ WHERE t.Name = ? LIMIT 1
476
+ `).get(timelineName);
477
+
478
+ if (!targetRow) {
479
+ throw new Error(`Target timeline not found: ${timelineName}`);
480
+ }
481
+
482
+ const result = db.prepare('UPDATE Sm2Sequence SET FieldsBlob = ? WHERE Sm2Sequence_id = ?')
483
+ .run(backupData, targetRow.Sm2Sequence_id);
484
+
485
+ return {
486
+ success: result.changes > 0,
487
+ restoredBytes: backupData.length,
488
+ };
489
+ }
490
+
491
+ // ============================================================
492
+ // Dynamic Bus Expansion Engine
493
+ // ============================================================
494
+ // Expands bus configuration from any source blob to any target layout.
495
+ // Uses 0xBC-anchored zone detection (verified across 35 projects).
496
+ // The bus property zone contains exactly 303 records (302 property + 1 name).
497
+
498
+ /**
499
+ * Find the 0xBC bus name record for a given bus count.
500
+ * @param {Buffer} data - Decompressed AdrDatabase
501
+ * @param {number} busCount - Expected bus count
502
+ * @returns {number} Offset of the 0xBC record, or -1 if not found
503
+ */
504
+ function findBusNameRecord(data, busCount) {
505
+ const bcPattern = Buffer.from([0x01, 0x00, 0x00, 0x00, 0xBC, 0x00, 0x00, 0x00]);
506
+ let searchFrom = SECTION_6_OFFSET + 10000;
507
+ while (searchFrom < data.length) {
508
+ const idx = data.indexOf(bcPattern, searchFrom);
509
+ if (idx === -1) return -1;
510
+ const nc = data.readUInt32LE(idx + 8);
511
+ if (nc === busCount) {
512
+ const strLen = data.readUInt32LE(idx + 12);
513
+ if (strLen > 0 && strLen < 200) return idx;
514
+ }
515
+ searchFrom = idx + 8;
516
+ }
517
+ return -1;
518
+ }
519
+
520
+ /**
521
+ * Scan the bus property zone for all count=busCount records.
522
+ * Anchored to 0xBC: searches from bcOffset-75000 to bcOffset+10000.
523
+ * @param {Buffer} data - Decompressed AdrDatabase
524
+ * @param {number} busCount - Current bus count to match
525
+ * @param {number} bcOffset - Offset of the 0xBC bus name record
526
+ * @returns {Array<{offset,id,count,dataStart,values,isNames}>}
527
+ */
528
+ function findBusZoneRecords(data, busCount, bcOffset) {
529
+ const searchStart = Math.max(BUS_TABLE_OFFSET + 4 + busCount * 8, bcOffset - 75000);
530
+ const searchEnd = Math.min(data.length, bcOffset + 10000);
531
+ const records = [];
532
+ let search = searchStart;
533
+
534
+ while (search < searchEnd - 12) {
535
+ if (data.readUInt32LE(search) === 1) {
536
+ const id = data.readUInt32LE(search + 4);
537
+ const count = data.readUInt32LE(search + 8);
538
+ if (count === busCount && id > 0 && id < 0x2000) {
539
+ const dataStart = search + 12;
540
+ if (id === 0xBC) {
541
+ records.push({ offset: search, id, count, dataStart, values: [], isNames: true });
542
+ let off = dataStart;
543
+ for (let i = 0; i < count; i++) {
544
+ if (off + 4 > data.length) break;
545
+ const len = data.readUInt32LE(off);
546
+ if (len <= 0 || len > 200) break;
547
+ off += 4 + len;
548
+ }
549
+ search = off;
550
+ continue;
551
+ }
552
+ const values = [];
553
+ for (let i = 0; i < count; i++) {
554
+ if (dataStart + i * 4 + 4 <= data.length) {
555
+ values.push(data.readUInt32LE(dataStart + i * 4));
556
+ }
557
+ }
558
+ records.push({ offset: search, id, count, dataStart, values, isNames: false });
559
+ search = dataStart + count * 4;
560
+ continue;
561
+ }
562
+ }
563
+ search++;
564
+ }
565
+ return records;
566
+ }
567
+
568
+ /**
569
+ * Expand a single property record to a new bus count.
570
+ * @param {Buffer} data - Current blob
571
+ * @param {{offset,id,count,values}} record - Record to expand
572
+ * @param {number} newCount - Target bus count
573
+ * @param {number[]} allValues - Complete value array for all newCount entries
574
+ * @returns {Buffer} New blob with expanded record
575
+ */
576
+ function expandRecord(data, record, newCount, allValues) {
577
+ const oldSize = 12 + record.count * 4;
578
+ const newSize = 12 + newCount * 4;
579
+ const newRecord = Buffer.alloc(newSize);
580
+ newRecord.writeUInt32LE(1, 0);
581
+ newRecord.writeUInt32LE(record.id, 4);
582
+ newRecord.writeUInt32LE(newCount, 8);
583
+ for (let i = 0; i < newCount; i++) {
584
+ newRecord.writeUInt32LE(allValues[i], 12 + i * 4);
585
+ }
586
+ return Buffer.concat([data.slice(0, record.offset), newRecord, data.slice(record.offset + oldSize)]);
587
+ }
588
+
589
+ /**
590
+ * Expand bus names to a new set of names.
591
+ * @param {Buffer} data - Current blob
592
+ * @param {number} bcOffset - Offset of the 0xBC record
593
+ * @param {number} oldCount - Current bus count
594
+ * @param {string[]} newNames - New bus names
595
+ * @returns {Buffer} New blob with expanded names
596
+ */
597
+ function expandBusNameRecord(data, bcOffset, oldCount, newNames) {
598
+ let off = bcOffset + 12;
599
+ for (let i = 0; i < oldCount; i++) {
600
+ const len = data.readUInt32LE(off);
601
+ if (len <= 0 || len > 200 || off + 4 + len > data.length) break;
602
+ off += 4 + len;
603
+ }
604
+ const oldEnd = off;
605
+ const header = Buffer.alloc(12);
606
+ header.writeUInt32LE(1, 0);
607
+ header.writeUInt32LE(0xBC, 4);
608
+ header.writeUInt32LE(newNames.length, 8);
609
+ const nameParts = [header];
610
+ for (const name of newNames) {
611
+ const lenBuf = Buffer.alloc(4);
612
+ lenBuf.writeUInt32LE(name.length, 0);
613
+ nameParts.push(lenBuf, Buffer.from(name, 'ascii'));
614
+ }
615
+ return Buffer.concat([data.slice(0, bcOffset), Buffer.concat(nameParts), data.slice(oldEnd)]);
616
+ }
617
+
618
+ /**
619
+ * Dynamically expand bus configuration from a source blob to a target layout.
620
+ *
621
+ * This is the core dynamic expansion function. It takes any source blob (with ≥1 bus)
622
+ * and expands it to an arbitrary target layout with any mix of channel formats.
623
+ *
624
+ * @param {Buffer} sourceData - Decompressed AdrDatabase from source timeline
625
+ * @param {Array<{name: string, channels: number}>} targetBuses - Target bus configuration
626
+ * @returns {{ data: Buffer, stats: { oldBusCount, newBusCount, recordsExpanded, oldSize, newSize } }}
627
+ */
628
+ function expandBuses(sourceData, targetBuses) {
629
+ let modified = Buffer.from(sourceData);
630
+ const oldConfig = readBusConfig(modified);
631
+ const oldBusCount = oldConfig.busCount;
632
+ const newBusCount = targetBuses.length;
633
+
634
+ if (newBusCount === 0) throw new Error('targetBuses must not be empty');
635
+
636
+ // Step 1: Find 0xBC anchor
637
+ const bcOffset = findBusNameRecord(modified, oldBusCount);
638
+ if (bcOffset < 0) throw new Error('Bus name record (0xBC) not found in source blob');
639
+
640
+ // Step 2: Scan bus zone for all property records
641
+ const busRecords = findBusZoneRecords(modified, oldBusCount, bcOffset);
642
+ const normalRecords = busRecords.filter(r => !r.isNames);
643
+
644
+ // Step 3: Expand bus table
645
+ const oldTableSize = 4 + oldBusCount * 8;
646
+ const newTableSize = 4 + newBusCount * 8;
647
+ const newTable = Buffer.alloc(newTableSize);
648
+ newTable.writeUInt32LE(newBusCount, 0);
649
+ for (let i = 0; i < newBusCount; i++) {
650
+ newTable.writeUInt16LE(i, 4 + i * 8);
651
+ newTable.writeUInt16LE(BUS_TYPE_AUDIO, 4 + i * 8 + 2);
652
+ newTable.writeUInt32LE(targetBuses[i].channels, 4 + i * 8 + 4);
653
+ }
654
+ modified = Buffer.concat([
655
+ modified.slice(0, BUS_TABLE_OFFSET),
656
+ newTable,
657
+ modified.slice(BUS_TABLE_OFFSET + oldTableSize),
658
+ ]);
659
+ const tableShift = newTableSize - oldTableSize;
660
+
661
+ // Step 4: Expand all property records (bottom-up to preserve offsets)
662
+ const adjustedRecords = normalRecords.map(r => ({
663
+ ...r,
664
+ offset: r.offset + tableShift,
665
+ dataStart: r.dataStart + tableShift,
666
+ }));
667
+ const sortedRecords = [...adjustedRecords].sort((a, b) => b.offset - a.offset);
668
+
669
+ for (const rec of sortedRecords) {
670
+ const allValues = [];
671
+ for (let i = 0; i < newBusCount; i++) {
672
+ if (rec.id === 0x01B4) {
673
+ // Channel count per bus — MUST match target format for ALL buses
674
+ allValues.push(targetBuses[i].channels);
675
+ } else if (rec.id === 0x0458) {
676
+ // Strip IDs — continue sequential
677
+ if (i < rec.count) allValues.push(rec.values[i]);
678
+ else allValues.push(rec.values[rec.count - 1] + (i - rec.count + 1));
679
+ } else {
680
+ // Preserve existing values; copy first value for new entries
681
+ if (i < rec.count) allValues.push(rec.values[i]);
682
+ else allValues.push(rec.values[0]);
683
+ }
684
+ }
685
+ modified = expandRecord(modified, rec, newBusCount, allValues);
686
+ }
687
+
688
+ // Step 5: Expand bus names
689
+ const bcPatternBuf = Buffer.from([0x01, 0x00, 0x00, 0x00, 0xBC, 0x00, 0x00, 0x00]);
690
+ let newBcOffset = -1;
691
+ let bcSearchStart = Math.max(SECTION_6_OFFSET + 10000, bcOffset + tableShift - 5000);
692
+ while (bcSearchStart < modified.length) {
693
+ const idx = modified.indexOf(bcPatternBuf, bcSearchStart);
694
+ if (idx === -1) break;
695
+ if (modified.readUInt32LE(idx + 8) === oldBusCount) {
696
+ newBcOffset = idx;
697
+ break;
698
+ }
699
+ bcSearchStart = idx + 8;
700
+ }
701
+ if (newBcOffset >= 0) {
702
+ modified = expandBusNameRecord(modified, newBcOffset, oldBusCount, targetBuses.map(b => b.name));
703
+ }
704
+
705
+ // Step 6: Update AdrDatabase size field
706
+ modified.writeUInt32LE(modified.length - 12, 8);
707
+
708
+ return {
709
+ data: modified,
710
+ stats: {
711
+ oldBusCount,
712
+ newBusCount,
713
+ recordsExpanded: sortedRecords.length,
714
+ busZoneTotal: busRecords.length,
715
+ oldSize: sourceData.length,
716
+ newSize: modified.length,
717
+ },
718
+ };
719
+ }
720
+
721
+ /**
722
+ * Apply dynamic bus expansion to a specific timeline in a project database.
723
+ *
724
+ * @param {Object} db - better-sqlite3 database (writable)
725
+ * @param {string} sourceTimelineName - Timeline to read source blob from
726
+ * @param {string} targetTimelineName - Timeline to write expanded blob to
727
+ * @param {Array<{name: string, channels: number}>} targetBuses - Target bus layout
728
+ * @returns {{ success: boolean, stats: Object }}
729
+ */
730
+ function applyBusExpansion(db, sourceTimelineName, targetTimelineName, targetBuses) {
731
+ // Read source blob
732
+ const sourceRow = db.prepare(`
733
+ SELECT s.Sm2Sequence_id, s.FieldsBlob, t.Name as tlName
734
+ FROM Sm2Sequence s LEFT JOIN Sm2Timeline t ON s.Sm2Timeline_id = t.Sm2Timeline_id
735
+ WHERE t.Name = ? LIMIT 1
736
+ `).get(sourceTimelineName);
737
+ if (!sourceRow) throw new Error(`Source timeline not found: ${sourceTimelineName}`);
738
+
739
+ // Auto-backup target FieldsBlob before any write (Phase 3C)
740
+ let backupPath = null;
741
+ try {
742
+ backupPath = backupFieldsBlob(db, targetTimelineName);
743
+ } catch {
744
+ // Backup is best-effort — don't block the operation
745
+ }
746
+
747
+ // Read target row
748
+ const targetRow = db.prepare(`
749
+ SELECT s.Sm2Sequence_id, s.FieldsBlob, t.Name as tlName
750
+ FROM Sm2Sequence s LEFT JOIN Sm2Timeline t ON s.Sm2Timeline_id = t.Sm2Timeline_id
751
+ WHERE t.Name = ? LIMIT 1
752
+ `).get(targetTimelineName);
753
+ if (!targetRow) throw new Error(`Target timeline not found: ${targetTimelineName}`);
754
+
755
+ // Extract source FLStudioModelBA
756
+ const sourceParsed = parseFieldsBlob(Buffer.from(sourceRow.FieldsBlob));
757
+ let sourceData = null;
758
+ for (const e of sourceParsed.entries) {
759
+ if (e.key === 'FLStudioModelBA') {
760
+ const sz = e.rawValue.readUInt32BE(0);
761
+ sourceData = decompressFLModel(e.rawValue.slice(4, 4 + sz));
762
+ }
763
+ }
764
+ if (!sourceData) throw new Error('No FLStudioModelBA in source timeline');
765
+
766
+ // Expand
767
+ const { data: expanded, stats } = expandBuses(sourceData, targetBuses);
768
+
769
+ // Compress and write to target
770
+ const compressed = compressFLModel(expanded);
771
+ const newRawValue = Buffer.alloc(4 + compressed.length);
772
+ newRawValue.writeUInt32BE(compressed.length, 0);
773
+ compressed.copy(newRawValue, 4);
774
+
775
+ // Set FirstMainBusFormat to match first bus
776
+ const newBusFormatVal = Buffer.alloc(4);
777
+ newBusFormatVal.writeUInt32BE(targetBuses[0].channels, 0);
778
+
779
+ const targetParsed = parseFieldsBlob(Buffer.from(targetRow.FieldsBlob));
780
+ const newEntries = targetParsed.entries.map(e => {
781
+ if (e.key === 'FLStudioModelBA') return { ...e, rawValue: newRawValue };
782
+ if (e.key === 'FirstMainBusFormat') return { ...e, rawValue: newBusFormatVal };
783
+ return e;
784
+ });
785
+
786
+ const newFieldsBlob = encodeFieldsBlob(newEntries);
787
+ const result = db.prepare('UPDATE Sm2Sequence SET FieldsBlob = ? WHERE Sm2Sequence_id = ?')
788
+ .run(newFieldsBlob, targetRow.Sm2Sequence_id);
789
+
790
+ return {
791
+ success: result.changes > 0,
792
+ stats,
793
+ backupPath,
794
+ };
795
+ }
796
+
797
+ // ============================================================
798
+ // Exports
799
+ // ============================================================
800
+
801
+ module.exports = {
802
+ // Format constants
803
+ FORMAT_CHANNELS,
804
+ CHANNELS_FORMAT,
805
+ SECTION_6_OFFSET,
806
+ BUS_TABLE_OFFSET,
807
+
808
+ // Low-level codec
809
+ parseFieldsBlob,
810
+ encodeFieldsBlob,
811
+ decompressFLModel,
812
+ compressFLModel,
813
+
814
+ // Bus config operations
815
+ readBusConfig,
816
+ readFromDatabase,
817
+
818
+ // Database safety (Phase 3)
819
+ backupFieldsBlob,
820
+ restoreFieldsBlob,
821
+
822
+ // Dynamic bus expansion
823
+ findBusNameRecord,
824
+ findBusZoneRecords,
825
+ expandBuses,
826
+ applyBusExpansion,
827
+
828
+ // Template operations (legacy)
829
+ exportTemplate,
830
+ importTemplate,
831
+ applyTemplate,
832
+ patchBusNames,
833
+ };