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.
- package/AGENTS.md +20 -0
- package/CHANGELOG.md +197 -2
- package/LICENSE +1 -1
- package/README.md +73 -1
- package/bin/davinci-resolve-advanced-mcp.mjs +20 -0
- package/docs/README.md +19 -0
- package/docs/SKILL.md +62 -1
- package/docs/contributing.md +1 -1
- package/docs/guides/control-panel.md +9 -0
- package/docs/kernels/README.md +21 -0
- package/docs/kernels/audio-fairlight-kernel.md +22 -0
- package/docs/kernels/color-grade-kernel.md +51 -0
- package/docs/kernels/fusion-composition-kernel.md +17 -0
- package/docs/kernels/media-pool-ingest-kernel.md +20 -0
- package/docs/kernels/render-deliver-kernel.md +32 -0
- package/docs/kernels/timeline-conform-interchange-kernel.md +39 -0
- package/docs/kernels/timeline-edit-kernel.md +17 -0
- package/docs/process/release-process.md +29 -7
- package/docs/reference/api-coverage.md +1 -1
- package/docs/reference/api-limitations.md +38 -1
- package/install.py +27 -4
- package/package.json +23 -2
- package/resolve-advanced/README.md +194 -0
- package/resolve-advanced/package.json +50 -0
- package/resolve-advanced/server/black-balance.mjs +92 -0
- package/resolve-advanced/server/capabilities.mjs +86 -0
- package/resolve-advanced/server/cdl-io.mjs +76 -0
- package/resolve-advanced/server/contrast-normalize.mjs +180 -0
- package/resolve-advanced/server/db-patch.mjs +91 -0
- package/resolve-advanced/server/deliverable-entities.mjs +86 -0
- package/resolve-advanced/server/deliverable-qc.mjs +270 -0
- package/resolve-advanced/server/editorial.mjs +370 -0
- package/resolve-advanced/server/exposure-level.mjs +129 -0
- package/resolve-advanced/server/extract-frames.mjs +196 -0
- package/resolve-advanced/server/ffprobe-media.mjs +88 -0
- package/resolve-advanced/server/gamut-legal.mjs +112 -0
- package/resolve-advanced/server/grade-body-patch.mjs +246 -0
- package/resolve-advanced/server/grade-transfer.mjs +66 -0
- package/resolve-advanced/server/group-grade-read.mjs +137 -0
- package/resolve-advanced/server/index.mjs +105 -0
- package/resolve-advanced/server/lib.mjs +84 -0
- package/resolve-advanced/server/libs.mjs +65 -0
- package/resolve-advanced/server/lineage-db.mjs +479 -0
- package/resolve-advanced/server/lut-apply.mjs +61 -0
- package/resolve-advanced/server/match-to-reference.mjs +173 -0
- package/resolve-advanced/server/media-inventory.mjs +169 -0
- package/resolve-advanced/server/media-ops.mjs +243 -0
- package/resolve-advanced/server/node-meta-db.mjs +62 -0
- package/resolve-advanced/server/node-provenance.mjs +102 -0
- package/resolve-advanced/server/offline-ref-db.mjs +420 -0
- package/resolve-advanced/server/offline-ref.mjs +179 -0
- package/resolve-advanced/server/project-db.mjs +233 -0
- package/resolve-advanced/server/provenance-audit.mjs +210 -0
- package/resolve-advanced/server/qc-frame.mjs +224 -0
- package/resolve-advanced/server/qc-sampler.mjs +67 -0
- package/resolve-advanced/server/readback.mjs +77 -0
- package/resolve-advanced/server/render-manifest.mjs +93 -0
- package/resolve-advanced/server/reverse-clip-db.mjs +270 -0
- package/resolve-advanced/server/runner-apply-contract.mjs +64 -0
- package/resolve-advanced/server/runner.mjs +244 -0
- package/resolve-advanced/server/saturation-match.mjs +108 -0
- package/resolve-advanced/server/scope-read.mjs +257 -0
- package/resolve-advanced/server/season-look.mjs +96 -0
- package/resolve-advanced/server/shot-intent.mjs +82 -0
- package/resolve-advanced/server/shot-match.mjs +147 -0
- package/resolve-advanced/server/skin-match.mjs +205 -0
- package/resolve-advanced/server/spec-compile.mjs +217 -0
- package/resolve-advanced/server/tone-curve-transfer.mjs +184 -0
- package/resolve-advanced/server/tool-catalog.mjs +265 -0
- package/resolve-advanced/server/tools/audio.mjs +54 -0
- package/resolve-advanced/server/tools/audio_plan.mjs +58 -0
- package/resolve-advanced/server/tools/capabilities.mjs +16 -0
- package/resolve-advanced/server/tools/color_trace.mjs +165 -0
- package/resolve-advanced/server/tools/conform.mjs +291 -0
- package/resolve-advanced/server/tools/deliverable.mjs +105 -0
- package/resolve-advanced/server/tools/drp.mjs +232 -0
- package/resolve-advanced/server/tools/drt.mjs +200 -0
- package/resolve-advanced/server/tools/drx.mjs +943 -0
- package/resolve-advanced/server/tools/editorial.mjs +69 -0
- package/resolve-advanced/server/tools/fairlight.mjs +108 -0
- package/resolve-advanced/server/tools/fusion.mjs +64 -0
- package/resolve-advanced/server/tools/media.mjs +102 -0
- package/resolve-advanced/server/tools/offline_ref.mjs +123 -0
- package/resolve-advanced/server/tools/pipeline.mjs +137 -0
- package/resolve-advanced/server/tools/project_db.mjs +200 -0
- package/resolve-advanced/server/tools/project_read.mjs +212 -0
- package/resolve-advanced/server/tools/provenance.mjs +73 -0
- package/resolve-advanced/server/verify-grade.mjs +113 -0
- package/resolve-advanced/server/white-balance-match.mjs +122 -0
- package/resolve-advanced/vendor/audio/format-converter.js +330 -0
- package/resolve-advanced/vendor/audio/split.js +173 -0
- package/resolve-advanced/vendor/audio/trim.js +188 -0
- package/resolve-advanced/vendor/audio-fairlight/index.js +391 -0
- package/resolve-advanced/vendor/conform-qc/adapters/frame-sampler.js +56 -0
- package/resolve-advanced/vendor/conform-qc/adapters/local-ffmpeg-sampler.js +65 -0
- package/resolve-advanced/vendor/conform-qc/adapters/rendernode-ffmpeg-sampler.js +50 -0
- package/resolve-advanced/vendor/conform-qc/adapters/resolve/driver.py +176 -0
- package/resolve-advanced/vendor/conform-qc/adapters/resolve-driver.js +61 -0
- package/resolve-advanced/vendor/conform-qc/adapters/resolve-headless-driver.js +86 -0
- package/resolve-advanced/vendor/conform-qc/adapters/vision-validator.js +75 -0
- package/resolve-advanced/vendor/conform-qc/cli.js +63 -0
- package/resolve-advanced/vendor/conform-qc/compare/advisory.js +106 -0
- package/resolve-advanced/vendor/conform-qc/compare/decode.js +36 -0
- package/resolve-advanced/vendor/conform-qc/compare/index.js +61 -0
- package/resolve-advanced/vendor/conform-qc/compare/locate.js +70 -0
- package/resolve-advanced/vendor/conform-qc/compare/metrics.js +409 -0
- package/resolve-advanced/vendor/conform-qc/docs/runbook.md +72 -0
- package/resolve-advanced/vendor/conform-qc/index.js +49 -0
- package/resolve-advanced/vendor/conform-qc/knowledge/index.js +110 -0
- package/resolve-advanced/vendor/conform-qc/ops/conform-core.js +137 -0
- package/resolve-advanced/vendor/conform-qc/ops/conform-diff.js +109 -0
- package/resolve-advanced/vendor/conform-qc/ops/insert.js +108 -0
- package/resolve-advanced/vendor/conform-qc/ops/local-conform.js +61 -0
- package/resolve-advanced/vendor/conform-qc/ops/patch.js +80 -0
- package/resolve-advanced/vendor/conform-qc/ops/trigger.js +32 -0
- package/resolve-advanced/vendor/conform-qc/ops/verify.js +105 -0
- package/resolve-advanced/vendor/conform-qc/ops/workflow.js +30 -0
- package/resolve-advanced/vendor/conform-qc/oracle/emulate.js +155 -0
- package/resolve-advanced/vendor/conform-qc/oracle/index.js +38 -0
- package/resolve-advanced/vendor/conform-qc/oracle/resolve.js +196 -0
- package/resolve-advanced/vendor/conform-qc/package.json +16 -0
- package/resolve-advanced/vendor/conform-qc/packaging/emit-aaf.js +30 -0
- package/resolve-advanced/vendor/conform-qc/packaging/emit-fcp7.js +64 -0
- package/resolve-advanced/vendor/conform-qc/packaging/index.js +169 -0
- package/resolve-advanced/vendor/conform-qc/packaging/media-ops.js +56 -0
- package/resolve-advanced/vendor/conform-qc/packaging/otio.js +50 -0
- package/resolve-advanced/vendor/conform-qc/packaging/relink.js +50 -0
- package/resolve-advanced/vendor/conform-qc/packaging/surgical-relink.js +195 -0
- package/resolve-advanced/vendor/conform-qc/parse/index.js +54 -0
- package/resolve-advanced/vendor/conform-qc/parse/merge-dto.js +27 -0
- package/resolve-advanced/vendor/conform-qc/parse/xmeml-geometry.js +256 -0
- package/resolve-advanced/vendor/conform-qc/reference/burnin-ocr.js +100 -0
- package/resolve-advanced/vendor/conform-qc/reference/sidecar.js +37 -0
- package/resolve-advanced/vendor/conform-qc/repair/analysis.js +62 -0
- package/resolve-advanced/vendor/conform-qc/repair/index.js +78 -0
- package/resolve-advanced/vendor/conform-qc/repair/media-analysis.js +139 -0
- package/resolve-advanced/vendor/conform-qc/repair/media-index.js +135 -0
- package/resolve-advanced/vendor/conform-qc/repair/strategies.js +250 -0
- package/resolve-advanced/vendor/conform-qc/report/index.js +133 -0
- package/resolve-advanced/vendor/conform-qc/roles/index.js +96 -0
- package/resolve-advanced/vendor/conform-qc/synthetic/generate.js +233 -0
- package/resolve-advanced/vendor/conform-qc/test/advisory.test.js +85 -0
- package/resolve-advanced/vendor/conform-qc/test/cli.test.js +39 -0
- package/resolve-advanced/vendor/conform-qc/test/compare.test.js +152 -0
- package/resolve-advanced/vendor/conform-qc/test/conform-diff.test.js +70 -0
- package/resolve-advanced/vendor/conform-qc/test/emulate.test.js +51 -0
- package/resolve-advanced/vendor/conform-qc/test/frame-sampler.test.js +30 -0
- package/resolve-advanced/vendor/conform-qc/test/geometry.test.js +112 -0
- package/resolve-advanced/vendor/conform-qc/test/knowledge.test.js +56 -0
- package/resolve-advanced/vendor/conform-qc/test/lifecycle.test.js +110 -0
- package/resolve-advanced/vendor/conform-qc/test/local-ffmpeg-sampler.test.js +66 -0
- package/resolve-advanced/vendor/conform-qc/test/locate.test.js +63 -0
- package/resolve-advanced/vendor/conform-qc/test/media-analysis.test.js +63 -0
- package/resolve-advanced/vendor/conform-qc/test/media-index-normalized.test.js +65 -0
- package/resolve-advanced/vendor/conform-qc/test/media-index-origin.test.js +53 -0
- package/resolve-advanced/vendor/conform-qc/test/metrics-fast.test.js +91 -0
- package/resolve-advanced/vendor/conform-qc/test/oracle-reverse.test.js +86 -0
- package/resolve-advanced/vendor/conform-qc/test/oracle.test.js +136 -0
- package/resolve-advanced/vendor/conform-qc/test/p0-acceptance.test.js +60 -0
- package/resolve-advanced/vendor/conform-qc/test/p1-acceptance.test.js +75 -0
- package/resolve-advanced/vendor/conform-qc/test/p45-patch-acceptance.test.js +102 -0
- package/resolve-advanced/vendor/conform-qc/test/packaging.test.js +133 -0
- package/resolve-advanced/vendor/conform-qc/test/parse-stubs.test.js +28 -0
- package/resolve-advanced/vendor/conform-qc/test/repair-strategies.test.js +128 -0
- package/resolve-advanced/vendor/conform-qc/test/repair.test.js +84 -0
- package/resolve-advanced/vendor/conform-qc/test/report.test.js +52 -0
- package/resolve-advanced/vendor/conform-qc/test/resolve-readback.test.js +71 -0
- package/resolve-advanced/vendor/conform-qc/test/roles.test.js +107 -0
- package/resolve-advanced/vendor/conform-qc/test/runbook.test.js +30 -0
- package/resolve-advanced/vendor/conform-qc/test/scale-residual.test.js +40 -0
- package/resolve-advanced/vendor/conform-qc/test/sidecar.test.js +51 -0
- package/resolve-advanced/vendor/conform-qc/test/smoke.test.js +87 -0
- package/resolve-advanced/vendor/conform-qc/test/surgical-relink.test.js +73 -0
- package/resolve-advanced/vendor/conform-qc/test/synthetic.test.js +98 -0
- package/resolve-advanced/vendor/conform-qc/test/verify.test.js +93 -0
- package/resolve-advanced/vendor/conform-qc/test/vision-validator.test.js +43 -0
- package/resolve-advanced/vendor/conform-qc/test/workflow-driver.test.js +46 -0
- package/resolve-advanced/vendor/conform-qc/toolset/index.js +53 -0
- package/resolve-advanced/vendor/drp-format/README.md +116 -0
- package/resolve-advanced/vendor/drp-format/__tests__/_resolve-verify.js +39 -0
- package/resolve-advanced/vendor/drp-format/__tests__/add-media-clip.test.js +30 -0
- package/resolve-advanced/vendor/drp-format/__tests__/assemble-timeline.test.js +68 -0
- package/resolve-advanced/vendor/drp-format/__tests__/author-project.test.js +57 -0
- package/resolve-advanced/vendor/drp-format/__tests__/composition-text.test.js +98 -0
- package/resolve-advanced/vendor/drp-format/__tests__/diff.test.js +272 -0
- package/resolve-advanced/vendor/drp-format/__tests__/effect-filters-compressed.test.js +143 -0
- package/resolve-advanced/vendor/drp-format/__tests__/fixtures/p0-3-session30-empty-effectfilters.drp +0 -0
- package/resolve-advanced/vendor/drp-format/__tests__/fixtures/retimed-timemap-50pct.hex +1 -0
- package/resolve-advanced/vendor/drp-format/__tests__/fixtures/retimed-timemap-dynamic.hex +1 -0
- package/resolve-advanced/vendor/drp-format/__tests__/inject-grades.test.js +238 -0
- package/resolve-advanced/vendor/drp-format/__tests__/keyed-dict.test.js +85 -0
- package/resolve-advanced/vendor/drp-format/__tests__/lut-refs.test.js +119 -0
- package/resolve-advanced/vendor/drp-format/__tests__/media-blobs.test.js +25 -0
- package/resolve-advanced/vendor/drp-format/__tests__/media-timemap.test.js +119 -0
- package/resolve-advanced/vendor/drp-format/__tests__/place-fusion-title.test.js +52 -0
- package/resolve-advanced/vendor/drp-format/__tests__/place-generator.test.js +40 -0
- package/resolve-advanced/vendor/drp-format/__tests__/place-transition.test.js +48 -0
- package/resolve-advanced/vendor/drp-format/__tests__/protobuf-wire.test.js +64 -0
- package/resolve-advanced/vendor/drp-format/__tests__/relink-media.test.js +106 -0
- package/resolve-advanced/vendor/drp-format/__tests__/splice-clips.test.js +256 -0
- package/resolve-advanced/vendor/drp-format/assemble-timeline.js +65 -0
- package/resolve-advanced/vendor/drp-format/author-project.js +118 -0
- package/resolve-advanced/vendor/drp-format/composition-text.js +213 -0
- package/resolve-advanced/vendor/drp-format/curves-encoder.js +763 -0
- package/resolve-advanced/vendor/drp-format/diff.js +310 -0
- package/resolve-advanced/vendor/drp-format/drp-packager.js +201 -0
- package/resolve-advanced/vendor/drp-format/drp-validator.js +809 -0
- package/resolve-advanced/vendor/drp-format/effect-encoder.js +640 -0
- package/resolve-advanced/vendor/drp-format/extract-lut-refs.js +119 -0
- package/resolve-advanced/vendor/drp-format/grade-encoder.example.js +133 -0
- package/resolve-advanced/vendor/drp-format/grade-encoder.js +401 -0
- package/resolve-advanced/vendor/drp-format/grade-encoder.test.js +171 -0
- package/resolve-advanced/vendor/drp-format/grade-encoder.verify.js +249 -0
- package/resolve-advanced/vendor/drp-format/grade-node-extractor.js +495 -0
- package/resolve-advanced/vendor/drp-format/grade-parameter-decoder.js +774 -0
- package/resolve-advanced/vendor/drp-format/index.js +174 -0
- package/resolve-advanced/vendor/drp-format/inject-grades.js +219 -0
- package/resolve-advanced/vendor/drp-format/keyed-dict.js +227 -0
- package/resolve-advanced/vendor/drp-format/marker-encoder.js +532 -0
- package/resolve-advanced/vendor/drp-format/media-blobs.js +53 -0
- package/resolve-advanced/vendor/drp-format/media-timemap.js +164 -0
- package/resolve-advanced/vendor/drp-format/mp-folder-builder.js +293 -0
- package/resolve-advanced/vendor/drp-format/node-tree-encoder.js +1246 -0
- package/resolve-advanced/vendor/drp-format/package.json +18 -0
- package/resolve-advanced/vendor/drp-format/place-fusion-title.js +131 -0
- package/resolve-advanced/vendor/drp-format/place-generator.js +77 -0
- package/resolve-advanced/vendor/drp-format/place-transition.js +91 -0
- package/resolve-advanced/vendor/drp-format/power-window-encoder.js +776 -0
- package/resolve-advanced/vendor/drp-format/protobuf-wire.js +111 -0
- package/resolve-advanced/vendor/drp-format/qualifier-encoder.js +706 -0
- package/resolve-advanced/vendor/drp-format/relink-media.js +218 -0
- package/resolve-advanced/vendor/drp-format/rich-title-encoder.js +447 -0
- package/resolve-advanced/vendor/drp-format/seq-container-builder.js +572 -0
- package/resolve-advanced/vendor/drp-format/seq-surgery.js +155 -0
- package/resolve-advanced/vendor/drp-format/splice-clips.js +445 -0
- package/resolve-advanced/vendor/drp-format/templates/empty-project.drp +0 -0
- package/resolve-advanced/vendor/drp-format/templates/fusion-title.xml +56 -0
- package/resolve-advanced/vendor/drp-format/templates/generator-solid-color.xml +21 -0
- package/resolve-advanced/vendor/drp-format/templates/media-clip-h264.drp +0 -0
- package/resolve-advanced/vendor/drp-format/templates/transition-cross-dissolve.xml +22 -0
- package/resolve-advanced/vendor/drp-format/tool-registry.descriptor.js +80 -0
- package/resolve-advanced/vendor/drp-format/utils/safe-archive.js +54 -0
- package/resolve-advanced/vendor/drp-format/xml-builder.js +759 -0
- package/resolve-advanced/vendor/drt-format/__tests__/_resolve-verify.js +21 -0
- package/resolve-advanced/vendor/drt-format/__tests__/build.test.js +113 -0
- package/resolve-advanced/vendor/drt-format/__tests__/multi-format.test.js +93 -0
- package/resolve-advanced/vendor/drt-format/__tests__/parse-real-resolve21.test.js +51 -0
- package/resolve-advanced/vendor/drt-format/__tests__/parse.test.js +134 -0
- package/resolve-advanced/vendor/drt-format/__tests__/resolve-versions.test.js +120 -0
- package/resolve-advanced/vendor/drt-format/__tests__/schema-fingerprint.test.js +62 -0
- package/resolve-advanced/vendor/drt-format/__tests__/smoke.test.js +27 -0
- package/resolve-advanced/vendor/drt-format/__tests__/validate.test.js +109 -0
- package/resolve-advanced/vendor/drt-format/capabilities.js +69 -0
- package/resolve-advanced/vendor/drt-format/capability-domains.js +56 -0
- package/resolve-advanced/vendor/drt-format/drt-builder.js +42 -0
- package/resolve-advanced/vendor/drt-format/drt-parser.js +164 -0
- package/resolve-advanced/vendor/drt-format/drt-validator.js +98 -0
- package/resolve-advanced/vendor/drt-format/index.js +45 -0
- package/resolve-advanced/vendor/drt-format/package.json +17 -0
- package/resolve-advanced/vendor/drt-format/resolve-versions.js +153 -0
- package/resolve-advanced/vendor/drt-format/schema-fingerprint.js +74 -0
- package/resolve-advanced/vendor/drt-format/templates/drt-project-shell.xml +124 -0
- package/resolve-advanced/vendor/drx-codec/__tests__/curves-roundtrip.test.js +148 -0
- package/resolve-advanced/vendor/drx-codec/__tests__/fixtures/p2-1-face-refinement.drx +58 -0
- package/resolve-advanced/vendor/drx-codec/__tests__/fixtures/p2-1-filmgrain.drx +58 -0
- package/resolve-advanced/vendor/drx-codec/__tests__/fixtures/p2-1-glow.drx +58 -0
- package/resolve-advanced/vendor/drx-codec/__tests__/fixtures/p2-1-lens-flare.drx +58 -0
- package/resolve-advanced/vendor/drx-codec/__tests__/fixtures/p2-3-beauty-25.drx +58 -0
- package/resolve-advanced/vendor/drx-codec/__tests__/fixtures/p2-3-beauty-75.drx +58 -0
- package/resolve-advanced/vendor/drx-codec/__tests__/fixtures/p5-1-node-no-lut.drx +58 -0
- package/resolve-advanced/vendor/drx-codec/__tests__/fixtures/p5-1-node-with-lut.drx +58 -0
- package/resolve-advanced/vendor/drx-codec/__tests__/hsl-curves-roundtrip.test.js +117 -0
- package/resolve-advanced/vendor/drx-codec/__tests__/lut-refs-roundtrip.test.js +112 -0
- package/resolve-advanced/vendor/drx-codec/__tests__/matte-roundtrip.test.js +167 -0
- package/resolve-advanced/vendor/drx-codec/__tests__/ofx-roundtrip.test.js +215 -0
- package/resolve-advanced/vendor/drx-codec/__tests__/ofx-slugs-discovery.test.js +149 -0
- package/resolve-advanced/vendor/drx-codec/__tests__/power-window-roundtrip.test.js +230 -0
- package/resolve-advanced/vendor/drx-codec/__tests__/qualifier-roundtrip.test.js +226 -0
- package/resolve-advanced/vendor/drx-codec/cdl-exporter.js +470 -0
- package/resolve-advanced/vendor/drx-codec/color-nlp-parser.js +833 -0
- package/resolve-advanced/vendor/drx-codec/data/color-nlp-action-mappings.json +2232 -0
- package/resolve-advanced/vendor/drx-codec/drx-generator.js +4123 -0
- package/resolve-advanced/vendor/drx-codec/drx-merger.js +320 -0
- package/resolve-advanced/vendor/drx-codec/drx-parser.js +1808 -0
- package/resolve-advanced/vendor/drx-codec/extract-custom-curves.js +215 -0
- package/resolve-advanced/vendor/drx-codec/extract-hsl-curves.js +180 -0
- package/resolve-advanced/vendor/drx-codec/extract-lut-refs.js +158 -0
- package/resolve-advanced/vendor/drx-codec/extract-matte-finesse.js +83 -0
- package/resolve-advanced/vendor/drx-codec/extract-ofx-params.js +226 -0
- package/resolve-advanced/vendor/drx-codec/extract-power-window.js +184 -0
- package/resolve-advanced/vendor/drx-codec/extract-qualifier.js +99 -0
- package/resolve-advanced/vendor/drx-codec/index.js +109 -0
- package/resolve-advanced/vendor/drx-codec/node-layout.js +249 -0
- package/resolve-advanced/vendor/drx-codec/resolved-drx-generator.js +393 -0
- package/resolve-advanced/vendor/drx-codec/tool-resolver.js +974 -0
- package/resolve-advanced/vendor/drx-codec/training-examples.js +548 -0
- package/resolve-advanced/vendor/drx-codec/vocabulary-intents.js +539 -0
- package/resolve-advanced/vendor/drx-parameters/CALIBRATION-STATUS.md +319 -0
- package/resolve-advanced/vendor/drx-parameters/DRX-VALUE-SCALING.md +251 -0
- package/resolve-advanced/vendor/drx-parameters/calibrated-ranges.json +17737 -0
- package/resolve-advanced/vendor/drx-parameters/corrector-types.js +120 -0
- package/resolve-advanced/vendor/drx-parameters/index.js +193 -0
- package/resolve-advanced/vendor/drx-parameters/parameter-codec.js +451 -0
- package/resolve-advanced/vendor/drx-parameters/parameter-ids.js +1292 -0
- package/resolve-advanced/vendor/drx-parameters/parameter-ranges.js +570 -0
- package/resolve-advanced/vendor/drx-parameters/parameter-validator.js +392 -0
- package/resolve-advanced/vendor/drx-parameters/resolvefx-registry.json +14973 -0
- package/resolve-advanced/vendor/drx-parameters/tool-registry.descriptor.js +38 -0
- package/resolve-advanced/vendor/drx-parameters/tools/drx-analyzer.js +371 -0
- package/resolve-advanced/vendor/fairlight/index.js +833 -0
- package/resolve-advanced/vendor/fusion-codec/composition-generator.js +401 -0
- package/resolve-advanced/vendor/fusion-codec/templates/blur-region.js +103 -0
- package/resolve-advanced/vendor/fusion-codec/templates/color-correct.js +87 -0
- package/resolve-advanced/vendor/fusion-codec/templates/film-grain.js +106 -0
- package/resolve-advanced/vendor/fusion-codec/templates/lower-third.js +241 -0
- package/resolve-advanced/vendor/fusion-codec/templates/picture-in-picture.js +188 -0
- package/resolve-advanced/vendor/fusion-codec/templates/text-overlay.js +161 -0
- package/resolve-advanced/vendor/fusion-codec/templates/title-card.js +184 -0
- package/resolve-advanced/vendor/fusion-codec/templates/vignette.js +107 -0
- package/resolve-advanced/vendor/fusion-codec/templates/watermark.js +101 -0
- package/scripts/agent-rules/README.md +68 -0
- package/src/analysis_dashboard.py +233 -1
- package/src/granular/common.py +1 -1
- package/src/server.py +479 -29
- package/src/utils/api_truth.py +117 -0
- package/src/utils/cut_ir.py +1 -1
- package/src/utils/failure_tracker.py +0 -2
- 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
|
+
};
|