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,809 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DaVinci Resolve Project (.drp) Validator
|
|
3
|
+
*
|
|
4
|
+
* Validates DRP files to ensure they will load correctly in DaVinci Resolve.
|
|
5
|
+
* Checks archive structure, XML validity, binary blob integrity, and cross-references.
|
|
6
|
+
*
|
|
7
|
+
* @module drp-validator
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const AdmZip = require('adm-zip');
|
|
13
|
+
const { isSafeArchiveEntryName } = require('./utils/safe-archive');
|
|
14
|
+
const { XMLParser } = require('fast-xml-parser');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Validation result levels
|
|
18
|
+
*/
|
|
19
|
+
const SEVERITY = {
|
|
20
|
+
ERROR: 'error', // Will prevent loading
|
|
21
|
+
WARNING: 'warning', // May cause issues
|
|
22
|
+
INFO: 'info', // Informational only
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Known valid frame rates
|
|
27
|
+
*/
|
|
28
|
+
const VALID_FRAME_RATES = [
|
|
29
|
+
23.976, 23.98, 24, 25, 29.97, 30, 48, 50, 59.94, 60, 120
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Required files in a DRP archive
|
|
34
|
+
*/
|
|
35
|
+
const REQUIRED_FILES = ['project.xml'];
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Valid FieldsBlob type markers
|
|
39
|
+
*/
|
|
40
|
+
const VALID_FIELDSBLOB_TYPES = [0x00000001, 0x00000002];
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Main validation result class
|
|
44
|
+
*/
|
|
45
|
+
class ValidationResult {
|
|
46
|
+
constructor() {
|
|
47
|
+
this.valid = true;
|
|
48
|
+
this.errors = [];
|
|
49
|
+
this.warnings = [];
|
|
50
|
+
this.info = [];
|
|
51
|
+
this.stats = {
|
|
52
|
+
filesChecked: 0,
|
|
53
|
+
blobsValidated: 0,
|
|
54
|
+
clipsFound: 0,
|
|
55
|
+
groupsFound: 0,
|
|
56
|
+
markersFound: 0,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
addIssue(severity, category, message, details = null) {
|
|
61
|
+
const issue = { category, message, details };
|
|
62
|
+
|
|
63
|
+
switch (severity) {
|
|
64
|
+
case SEVERITY.ERROR:
|
|
65
|
+
this.valid = false;
|
|
66
|
+
this.errors.push(issue);
|
|
67
|
+
break;
|
|
68
|
+
case SEVERITY.WARNING:
|
|
69
|
+
this.warnings.push(issue);
|
|
70
|
+
break;
|
|
71
|
+
case SEVERITY.INFO:
|
|
72
|
+
this.info.push(issue);
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
isValid() {
|
|
78
|
+
return this.valid;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
getSummary() {
|
|
82
|
+
return {
|
|
83
|
+
valid: this.valid,
|
|
84
|
+
errorCount: this.errors.length,
|
|
85
|
+
warningCount: this.warnings.length,
|
|
86
|
+
infoCount: this.info.length,
|
|
87
|
+
stats: this.stats,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
toJSON() {
|
|
92
|
+
return {
|
|
93
|
+
valid: this.valid,
|
|
94
|
+
summary: this.getSummary(),
|
|
95
|
+
errors: this.errors,
|
|
96
|
+
warnings: this.warnings,
|
|
97
|
+
info: this.info,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Validate a DRP file
|
|
104
|
+
* @param {string|Buffer} input - File path or buffer
|
|
105
|
+
* @param {Object} options - Validation options
|
|
106
|
+
* @returns {ValidationResult} Validation result
|
|
107
|
+
*/
|
|
108
|
+
function validateDRP(input, options = {}) {
|
|
109
|
+
const result = new ValidationResult();
|
|
110
|
+
const opts = {
|
|
111
|
+
checkBlobs: true,
|
|
112
|
+
checkReferences: true,
|
|
113
|
+
checkTiming: true,
|
|
114
|
+
verbose: false,
|
|
115
|
+
...options,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
let zip;
|
|
119
|
+
let files = {};
|
|
120
|
+
|
|
121
|
+
// Step 1: Archive Integrity
|
|
122
|
+
try {
|
|
123
|
+
if (typeof input === 'string') {
|
|
124
|
+
if (!fs.existsSync(input)) {
|
|
125
|
+
result.addIssue(SEVERITY.ERROR, 'archive', `File not found: ${input}`);
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
zip = new AdmZip(input);
|
|
129
|
+
} else if (Buffer.isBuffer(input)) {
|
|
130
|
+
zip = new AdmZip(input);
|
|
131
|
+
} else {
|
|
132
|
+
result.addIssue(SEVERITY.ERROR, 'archive', 'Invalid input: expected file path or buffer');
|
|
133
|
+
return result;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Check ZIP magic bytes
|
|
137
|
+
const zipBuffer = typeof input === 'string' ? fs.readFileSync(input) : input;
|
|
138
|
+
if (zipBuffer[0] !== 0x50 || zipBuffer[1] !== 0x4b ||
|
|
139
|
+
zipBuffer[2] !== 0x03 || zipBuffer[3] !== 0x04) {
|
|
140
|
+
result.addIssue(SEVERITY.ERROR, 'archive', 'Invalid ZIP magic bytes (expected PK..)');
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
result.addIssue(SEVERITY.INFO, 'archive', 'ZIP archive structure valid');
|
|
145
|
+
|
|
146
|
+
} catch (err) {
|
|
147
|
+
result.addIssue(SEVERITY.ERROR, 'archive', `Failed to open archive: ${err.message}`);
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Step 2: Extract and check required files
|
|
152
|
+
try {
|
|
153
|
+
const entries = zip.getEntries();
|
|
154
|
+
result.stats.filesChecked = entries.length;
|
|
155
|
+
|
|
156
|
+
for (const entry of entries) {
|
|
157
|
+
if (!entry.isDirectory) {
|
|
158
|
+
// SECURITY: reject path-traversal entries and entries that would land on
|
|
159
|
+
// JS prototype keys (__proto__, constructor). Even though files[] is
|
|
160
|
+
// memory-only here, these names propagate into downstream Map/Object lookups.
|
|
161
|
+
const name = entry.entryName;
|
|
162
|
+
if (!isSafeArchiveEntryName(name)) {
|
|
163
|
+
result.addIssue(SEVERITY.ERROR, 'archive', `Unsafe archive entry rejected: ${name}`);
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
files[name] = zip.readFile(entry);
|
|
168
|
+
} catch (err) {
|
|
169
|
+
result.addIssue(SEVERITY.ERROR, 'archive', `Failed to extract: ${name}`, err.message);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Check required files
|
|
175
|
+
for (const required of REQUIRED_FILES) {
|
|
176
|
+
if (!files[required]) {
|
|
177
|
+
result.addIssue(SEVERITY.ERROR, 'archive', `Missing required file: ${required}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
result.addIssue(SEVERITY.INFO, 'archive', `Extracted ${Object.keys(files).length} files`);
|
|
182
|
+
|
|
183
|
+
} catch (err) {
|
|
184
|
+
result.addIssue(SEVERITY.ERROR, 'archive', `Archive extraction failed: ${err.message}`);
|
|
185
|
+
return result;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Step 3: Parse and validate XML files
|
|
189
|
+
const xmlParser = new XMLParser({
|
|
190
|
+
ignoreAttributes: false,
|
|
191
|
+
attributeNamePrefix: '@_',
|
|
192
|
+
allowBooleanAttributes: true,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const parsedXml = {};
|
|
196
|
+
const dbIds = new Set();
|
|
197
|
+
const linkedGroups = new Set();
|
|
198
|
+
const groupDbIds = new Set();
|
|
199
|
+
const mediaPoolIds = new Set();
|
|
200
|
+
const clipMediaRefs = new Set();
|
|
201
|
+
|
|
202
|
+
for (const [filename, content] of Object.entries(files)) {
|
|
203
|
+
if (filename.endsWith('.xml')) {
|
|
204
|
+
try {
|
|
205
|
+
const xmlString = content.toString('utf-8');
|
|
206
|
+
const parsed = xmlParser.parse(xmlString);
|
|
207
|
+
parsedXml[filename] = parsed;
|
|
208
|
+
|
|
209
|
+
// Extract DbIds, groups, and references
|
|
210
|
+
extractReferences(parsed, dbIds, linkedGroups, groupDbIds, mediaPoolIds, clipMediaRefs, result);
|
|
211
|
+
|
|
212
|
+
result.addIssue(SEVERITY.INFO, 'xml', `Parsed: ${filename}`);
|
|
213
|
+
|
|
214
|
+
} catch (err) {
|
|
215
|
+
result.addIssue(SEVERITY.ERROR, 'xml', `Failed to parse ${filename}`, err.message);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Step 4: Validate binary blobs
|
|
221
|
+
if (opts.checkBlobs) {
|
|
222
|
+
validateBlobs(parsedXml, result, opts.verbose);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Step 5: Check reference integrity
|
|
226
|
+
if (opts.checkReferences) {
|
|
227
|
+
validateReferences(linkedGroups, groupDbIds, clipMediaRefs, mediaPoolIds, result);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Step 6: Check timing values
|
|
231
|
+
if (opts.checkTiming) {
|
|
232
|
+
validateTiming(parsedXml, result);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return result;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Extract references from parsed XML
|
|
240
|
+
*/
|
|
241
|
+
function extractReferences(obj, dbIds, linkedGroups, groupDbIds, mediaPoolIds, clipMediaRefs, result) {
|
|
242
|
+
if (!obj || typeof obj !== 'object') return;
|
|
243
|
+
|
|
244
|
+
// Handle DbId attributes
|
|
245
|
+
if (obj['@_DbId']) {
|
|
246
|
+
if (dbIds.has(obj['@_DbId'])) {
|
|
247
|
+
result.addIssue(SEVERITY.WARNING, 'reference', `Duplicate DbId found`, obj['@_DbId']);
|
|
248
|
+
}
|
|
249
|
+
dbIds.add(obj['@_DbId']);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Track Sm2Group DbIds
|
|
253
|
+
if (obj['Sm2Group']) {
|
|
254
|
+
const groups = Array.isArray(obj['Sm2Group']) ? obj['Sm2Group'] : [obj['Sm2Group']];
|
|
255
|
+
for (const group of groups) {
|
|
256
|
+
if (group['@_DbId']) {
|
|
257
|
+
groupDbIds.add(group['@_DbId']);
|
|
258
|
+
result.stats.groupsFound++;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Track LinkedGroup references
|
|
264
|
+
if (obj['LinkedGroup']) {
|
|
265
|
+
const refs = Array.isArray(obj['LinkedGroup']) ? obj['LinkedGroup'] : [obj['LinkedGroup']];
|
|
266
|
+
for (const ref of refs) {
|
|
267
|
+
if (typeof ref === 'string') {
|
|
268
|
+
linkedGroups.add(ref);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Track media pool IDs
|
|
274
|
+
if (obj['Sm2MpVideoClip'] || obj['Sm2SourceClip']) {
|
|
275
|
+
const clips = obj['Sm2MpVideoClip'] || obj['Sm2SourceClip'];
|
|
276
|
+
const clipArray = Array.isArray(clips) ? clips : [clips];
|
|
277
|
+
for (const clip of clipArray) {
|
|
278
|
+
if (clip['@_DbId']) {
|
|
279
|
+
mediaPoolIds.add(clip['@_DbId']);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Track clip references
|
|
285
|
+
if (obj['Sm2VideoClip'] || obj['Sm2TiVideoClip']) {
|
|
286
|
+
const clips = obj['Sm2VideoClip'] || obj['Sm2TiVideoClip'];
|
|
287
|
+
const clipArray = Array.isArray(clips) ? clips : [clips];
|
|
288
|
+
for (const clip of clipArray) {
|
|
289
|
+
result.stats.clipsFound++;
|
|
290
|
+
if (clip['MediaRef']) {
|
|
291
|
+
clipMediaRefs.add(clip['MediaRef']);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Recurse
|
|
297
|
+
for (const key of Object.keys(obj)) {
|
|
298
|
+
if (key.startsWith('@_')) continue;
|
|
299
|
+
extractReferences(obj[key], dbIds, linkedGroups, groupDbIds, mediaPoolIds, clipMediaRefs, result);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Validate binary blob fields
|
|
305
|
+
*/
|
|
306
|
+
function validateBlobs(parsedXml, result, verbose) {
|
|
307
|
+
const blobFields = [
|
|
308
|
+
'FieldsBlob', 'EffectFiltersBA', 'MediaTimemapBA', 'Body',
|
|
309
|
+
'MediaFrameRate', 'ImportExportMetadataBA', 'TracksBA',
|
|
310
|
+
'VirtualAudioTrackBA', 'Clip', 'Radiometry', 'PreConformMediaExtents'
|
|
311
|
+
];
|
|
312
|
+
|
|
313
|
+
function findBlobs(obj, path = '') {
|
|
314
|
+
if (!obj || typeof obj !== 'object') return;
|
|
315
|
+
|
|
316
|
+
for (const key of Object.keys(obj)) {
|
|
317
|
+
if (key.startsWith('@_')) continue;
|
|
318
|
+
|
|
319
|
+
const currentPath = path ? `${path}.${key}` : key;
|
|
320
|
+
|
|
321
|
+
if (blobFields.includes(key)) {
|
|
322
|
+
const value = obj[key];
|
|
323
|
+
if (typeof value === 'string' && value.length > 0) {
|
|
324
|
+
validateBlobHex(key, value, currentPath, result, verbose);
|
|
325
|
+
result.stats.blobsValidated++;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (typeof obj[key] === 'object') {
|
|
330
|
+
findBlobs(obj[key], currentPath);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
for (const [filename, parsed] of Object.entries(parsedXml)) {
|
|
336
|
+
findBlobs(parsed, filename);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Validate a hex-encoded blob
|
|
342
|
+
*/
|
|
343
|
+
function validateBlobHex(blobType, hexString, path, result, verbose) {
|
|
344
|
+
// Check hex validity
|
|
345
|
+
if (!/^[0-9a-fA-F]*$/.test(hexString)) {
|
|
346
|
+
result.addIssue(SEVERITY.ERROR, 'blob', `Invalid hex characters in ${blobType}`, path);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (hexString.length % 2 !== 0) {
|
|
351
|
+
result.addIssue(SEVERITY.ERROR, 'blob', `Odd hex length in ${blobType}`, path);
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const buffer = Buffer.from(hexString, 'hex');
|
|
356
|
+
|
|
357
|
+
switch (blobType) {
|
|
358
|
+
case 'FieldsBlob':
|
|
359
|
+
validateFieldsBlob(buffer, path, result, verbose);
|
|
360
|
+
break;
|
|
361
|
+
|
|
362
|
+
case 'EffectFiltersBA':
|
|
363
|
+
validateEffectFiltersBA(buffer, path, result, verbose);
|
|
364
|
+
break;
|
|
365
|
+
|
|
366
|
+
case 'MediaFrameRate':
|
|
367
|
+
validateMediaFrameRate(buffer, path, result, verbose);
|
|
368
|
+
break;
|
|
369
|
+
|
|
370
|
+
case 'MediaTimemapBA':
|
|
371
|
+
validateMediaTimemapBA(buffer, path, result, verbose);
|
|
372
|
+
break;
|
|
373
|
+
|
|
374
|
+
case 'Body':
|
|
375
|
+
validateBody(buffer, path, result, verbose);
|
|
376
|
+
break;
|
|
377
|
+
|
|
378
|
+
case 'ImportExportMetadataBA':
|
|
379
|
+
validateFieldsBlob(buffer, path, result, verbose); // Same format
|
|
380
|
+
break;
|
|
381
|
+
|
|
382
|
+
default:
|
|
383
|
+
if (verbose) {
|
|
384
|
+
result.addIssue(SEVERITY.INFO, 'blob', `${blobType}: ${buffer.length} bytes`, path);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Validate FieldsBlob structure
|
|
391
|
+
*/
|
|
392
|
+
function validateFieldsBlob(buffer, path, result, verbose) {
|
|
393
|
+
if (buffer.length < 8) {
|
|
394
|
+
result.addIssue(SEVERITY.ERROR, 'blob', 'FieldsBlob too short (< 8 bytes)', path);
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const type = buffer.readUInt32BE(0);
|
|
399
|
+
|
|
400
|
+
if (!VALID_FIELDSBLOB_TYPES.includes(type)) {
|
|
401
|
+
// Check for zlib compression
|
|
402
|
+
if (buffer[0] === 0x78 && buffer[1] === 0x9c) {
|
|
403
|
+
if (verbose) {
|
|
404
|
+
result.addIssue(SEVERITY.INFO, 'blob', 'FieldsBlob: Zlib compressed', path);
|
|
405
|
+
}
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
result.addIssue(SEVERITY.WARNING, 'blob', `FieldsBlob unknown type: 0x${type.toString(16)}`, path);
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (type === 0x00000001) {
|
|
414
|
+
// Type 1: Key-value format
|
|
415
|
+
// Note: FieldsBlob format can have internal complexity after first field
|
|
416
|
+
// We validate the header and first field, but allow for format variations
|
|
417
|
+
const fieldCount = buffer.readUInt32BE(4);
|
|
418
|
+
let offset = 8;
|
|
419
|
+
let validatedFields = 0;
|
|
420
|
+
|
|
421
|
+
for (let i = 0; i < fieldCount && offset < buffer.length; i++) {
|
|
422
|
+
if (offset + 4 > buffer.length) {
|
|
423
|
+
// Ran out of data - if we validated at least one field, this is OK
|
|
424
|
+
if (validatedFields === 0) {
|
|
425
|
+
result.addIssue(SEVERITY.ERROR, 'blob', `FieldsBlob has no valid fields`, path);
|
|
426
|
+
}
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const nameLen = buffer.readUInt32BE(offset);
|
|
431
|
+
|
|
432
|
+
// Sanity check: name length should be reasonable (< 1000 bytes for a field name)
|
|
433
|
+
// If not, the format likely has internal structure we don't fully understand
|
|
434
|
+
if (nameLen > 1000 || nameLen === 0) {
|
|
435
|
+
// After first field, internal format may vary - this is OK
|
|
436
|
+
if (validatedFields > 0) {
|
|
437
|
+
if (verbose) {
|
|
438
|
+
result.addIssue(SEVERITY.INFO, 'blob',
|
|
439
|
+
`FieldsBlob Type 1: ${validatedFields}+ fields (internal structure)`, path);
|
|
440
|
+
}
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
// First field has unreasonable name length
|
|
444
|
+
result.addIssue(SEVERITY.WARNING, 'blob',
|
|
445
|
+
`FieldsBlob unusual name length: ${nameLen}`, path);
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
offset += 4;
|
|
449
|
+
|
|
450
|
+
if (offset + nameLen > buffer.length) {
|
|
451
|
+
if (validatedFields > 0) return; // OK if we got some fields
|
|
452
|
+
result.addIssue(SEVERITY.WARNING, 'blob', `FieldsBlob field ${i} name extends beyond buffer`, path);
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
offset += nameLen;
|
|
456
|
+
|
|
457
|
+
if (offset + 4 > buffer.length) {
|
|
458
|
+
if (validatedFields > 0) return;
|
|
459
|
+
result.addIssue(SEVERITY.WARNING, 'blob', `FieldsBlob field ${i} missing value length`, path);
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const valueLen = buffer.readUInt32BE(offset);
|
|
464
|
+
offset += 4;
|
|
465
|
+
|
|
466
|
+
if (offset + valueLen > buffer.length) {
|
|
467
|
+
if (validatedFields > 0) return;
|
|
468
|
+
result.addIssue(SEVERITY.WARNING, 'blob', `FieldsBlob field ${i} value extends beyond buffer`, path);
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
offset += valueLen;
|
|
472
|
+
validatedFields++;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (verbose) {
|
|
476
|
+
result.addIssue(SEVERITY.INFO, 'blob', `FieldsBlob Type 1: ${validatedFields} fields validated`, path);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
} else if (type === 0x00000002) {
|
|
480
|
+
// Type 2: Protobuf-like
|
|
481
|
+
const payloadSize = buffer.readUInt32BE(4);
|
|
482
|
+
const actualSize = buffer.length - 8;
|
|
483
|
+
|
|
484
|
+
// Allow some tolerance for size mismatch (internal compression/padding)
|
|
485
|
+
if (Math.abs(payloadSize - actualSize) > 100 && payloadSize !== actualSize) {
|
|
486
|
+
result.addIssue(SEVERITY.WARNING, 'blob',
|
|
487
|
+
`FieldsBlob Type 2 size mismatch: header=${payloadSize}, actual=${actualSize}`, path);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (verbose) {
|
|
491
|
+
result.addIssue(SEVERITY.INFO, 'blob', `FieldsBlob Type 2: ${payloadSize} bytes payload`, path);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Validate EffectFiltersBA structure
|
|
498
|
+
*/
|
|
499
|
+
function validateEffectFiltersBA(buffer, path, result, verbose) {
|
|
500
|
+
if (buffer.length < 9) {
|
|
501
|
+
result.addIssue(SEVERITY.ERROR, 'blob', 'EffectFiltersBA too short (< 9 bytes)', path);
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const version = buffer.readUInt32BE(0);
|
|
506
|
+
if (version !== 2) {
|
|
507
|
+
result.addIssue(SEVERITY.WARNING, 'blob', `EffectFiltersBA unexpected version: ${version}`, path);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
const payloadSize = buffer.readUInt32BE(4);
|
|
511
|
+
const compressionMarker = buffer[8];
|
|
512
|
+
|
|
513
|
+
if (compressionMarker !== 0x80 && compressionMarker !== 0x81) {
|
|
514
|
+
result.addIssue(SEVERITY.WARNING, 'blob',
|
|
515
|
+
`EffectFiltersBA unknown compression marker: 0x${compressionMarker.toString(16)}`, path);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Check for ZSTD if compressed
|
|
519
|
+
if (compressionMarker === 0x81) {
|
|
520
|
+
if (buffer.length < 13 ||
|
|
521
|
+
buffer[9] !== 0x28 || buffer[10] !== 0xb5 ||
|
|
522
|
+
buffer[11] !== 0x2f || buffer[12] !== 0xfd) {
|
|
523
|
+
result.addIssue(SEVERITY.WARNING, 'blob', 'EffectFiltersBA marked compressed but no ZSTD magic', path);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (verbose) {
|
|
528
|
+
result.addIssue(SEVERITY.INFO, 'blob',
|
|
529
|
+
`EffectFiltersBA: v${version}, ${compressionMarker === 0x81 ? 'compressed' : 'raw'}, ${payloadSize} bytes`, path);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Validate MediaFrameRate structure
|
|
535
|
+
*/
|
|
536
|
+
function validateMediaFrameRate(buffer, path, result, verbose) {
|
|
537
|
+
if (buffer.length !== 16) {
|
|
538
|
+
result.addIssue(SEVERITY.ERROR, 'blob', `MediaFrameRate wrong size: ${buffer.length} (expected 16)`, path);
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const fps = buffer.readDoubleLE(0);
|
|
543
|
+
const reserved = buffer.readDoubleLE(8);
|
|
544
|
+
|
|
545
|
+
if (reserved !== 0) {
|
|
546
|
+
result.addIssue(SEVERITY.WARNING, 'blob', `MediaFrameRate reserved bytes non-zero`, path);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Check for valid frame rate (within tolerance)
|
|
550
|
+
const isValidFps = VALID_FRAME_RATES.some(valid => Math.abs(fps - valid) < 0.01);
|
|
551
|
+
if (!isValidFps && fps > 0) {
|
|
552
|
+
result.addIssue(SEVERITY.WARNING, 'blob', `MediaFrameRate unusual value: ${fps.toFixed(6)} fps`, path);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
if (fps <= 0 || fps > 240) {
|
|
556
|
+
result.addIssue(SEVERITY.ERROR, 'blob', `MediaFrameRate invalid: ${fps}`, path);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
if (verbose) {
|
|
560
|
+
result.addIssue(SEVERITY.INFO, 'blob', `MediaFrameRate: ${fps.toFixed(6)} fps`, path);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Validate MediaTimemapBA structure
|
|
566
|
+
*/
|
|
567
|
+
function validateMediaTimemapBA(buffer, path, result, verbose) {
|
|
568
|
+
// Empty timemap (no retime effects) - valid
|
|
569
|
+
if (buffer.length === 0) {
|
|
570
|
+
if (verbose) {
|
|
571
|
+
result.addIssue(SEVERITY.INFO, 'blob', 'MediaTimemapBA: empty (no retime)', path);
|
|
572
|
+
}
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// Very short blobs with 0x00 marker indicate no retime - valid
|
|
577
|
+
if (buffer.length < 9) {
|
|
578
|
+
if (buffer[0] === 0x00) {
|
|
579
|
+
if (verbose) {
|
|
580
|
+
result.addIssue(SEVERITY.INFO, 'blob', 'MediaTimemapBA: none/default', path);
|
|
581
|
+
}
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
result.addIssue(SEVERITY.WARNING, 'blob', `MediaTimemapBA unusual size: ${buffer.length} bytes`, path);
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
const typeMarker = buffer[0];
|
|
589
|
+
|
|
590
|
+
if (typeMarker === 0x00) {
|
|
591
|
+
// No retime effect applied - this is valid
|
|
592
|
+
if (verbose) {
|
|
593
|
+
result.addIssue(SEVERITY.INFO, 'blob', 'MediaTimemapBA: no retime effect', path);
|
|
594
|
+
}
|
|
595
|
+
} else if (typeMarker === 0x02) {
|
|
596
|
+
// Standard timing format
|
|
597
|
+
if (buffer.length === 9) {
|
|
598
|
+
// Simple format
|
|
599
|
+
if (verbose) {
|
|
600
|
+
const duration = buffer.readDoubleBE(1);
|
|
601
|
+
result.addIssue(SEVERITY.INFO, 'blob', `MediaTimemapBA simple: ${duration.toFixed(4)}s`, path);
|
|
602
|
+
}
|
|
603
|
+
} else if (buffer.length === 41) {
|
|
604
|
+
// Standard format
|
|
605
|
+
if (verbose) {
|
|
606
|
+
result.addIssue(SEVERITY.INFO, 'blob', 'MediaTimemapBA standard (41 bytes)', path);
|
|
607
|
+
}
|
|
608
|
+
} else if (buffer.length > 100) {
|
|
609
|
+
// Complex format with keyframes
|
|
610
|
+
if (verbose) {
|
|
611
|
+
result.addIssue(SEVERITY.INFO, 'blob', `MediaTimemapBA complex (${buffer.length} bytes)`, path);
|
|
612
|
+
}
|
|
613
|
+
} else {
|
|
614
|
+
if (verbose) {
|
|
615
|
+
result.addIssue(SEVERITY.INFO, 'blob', `MediaTimemapBA: ${buffer.length} bytes`, path);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
} else {
|
|
619
|
+
// Other type markers may be valid variants
|
|
620
|
+
if (verbose) {
|
|
621
|
+
result.addIssue(SEVERITY.INFO, 'blob',
|
|
622
|
+
`MediaTimemapBA type 0x${typeMarker.toString(16)}: ${buffer.length} bytes`, path);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* Validate Body (grade) structure
|
|
629
|
+
*/
|
|
630
|
+
function validateBody(buffer, path, result, verbose) {
|
|
631
|
+
if (buffer.length < 2) {
|
|
632
|
+
result.addIssue(SEVERITY.ERROR, 'blob', 'Body too short', path);
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const header = buffer[0];
|
|
637
|
+
|
|
638
|
+
if (header === 0x80 && buffer[1] === 0x0a) {
|
|
639
|
+
// Uncompressed format
|
|
640
|
+
if (verbose) {
|
|
641
|
+
result.addIssue(SEVERITY.INFO, 'blob', `Body uncompressed (${buffer.length} bytes)`, path);
|
|
642
|
+
}
|
|
643
|
+
} else if (header === 0x81) {
|
|
644
|
+
// Check for ZSTD
|
|
645
|
+
if (buffer.length >= 5 &&
|
|
646
|
+
buffer[1] === 0x28 && buffer[2] === 0xb5 &&
|
|
647
|
+
buffer[3] === 0x2f && buffer[4] === 0xfd) {
|
|
648
|
+
if (verbose) {
|
|
649
|
+
result.addIssue(SEVERITY.INFO, 'blob', `Body ZSTD compressed (${buffer.length} bytes)`, path);
|
|
650
|
+
}
|
|
651
|
+
} else {
|
|
652
|
+
result.addIssue(SEVERITY.WARNING, 'blob', 'Body marked compressed but no ZSTD magic', path);
|
|
653
|
+
}
|
|
654
|
+
} else {
|
|
655
|
+
result.addIssue(SEVERITY.WARNING, 'blob',
|
|
656
|
+
`Body unknown header: 0x${header.toString(16)}`, path);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Validate cross-references
|
|
662
|
+
*/
|
|
663
|
+
function validateReferences(linkedGroups, groupDbIds, clipMediaRefs, mediaPoolIds, result) {
|
|
664
|
+
// Check LinkedGroup references
|
|
665
|
+
for (const linkedGroup of linkedGroups) {
|
|
666
|
+
if (!groupDbIds.has(linkedGroup)) {
|
|
667
|
+
result.addIssue(SEVERITY.ERROR, 'reference',
|
|
668
|
+
`LinkedGroup references non-existent group`, linkedGroup);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Check clip media references (if we have any)
|
|
673
|
+
// Note: This is a simplified check; full validation would need more context
|
|
674
|
+
if (clipMediaRefs.size > 0 && mediaPoolIds.size > 0) {
|
|
675
|
+
let orphanedRefs = 0;
|
|
676
|
+
for (const ref of clipMediaRefs) {
|
|
677
|
+
if (!mediaPoolIds.has(ref)) {
|
|
678
|
+
orphanedRefs++;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
if (orphanedRefs > 0) {
|
|
682
|
+
result.addIssue(SEVERITY.WARNING, 'reference',
|
|
683
|
+
`${orphanedRefs} clip(s) reference missing media pool entries`);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
result.addIssue(SEVERITY.INFO, 'reference',
|
|
688
|
+
`Checked: ${linkedGroups.size} LinkedGroup refs, ${groupDbIds.size} groups, ${mediaPoolIds.size} media pool items`);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* Validate timing values in XML
|
|
693
|
+
*/
|
|
694
|
+
function validateTiming(parsedXml, result) {
|
|
695
|
+
function checkTiming(obj, path = '') {
|
|
696
|
+
if (!obj || typeof obj !== 'object') return;
|
|
697
|
+
|
|
698
|
+
// Check StartFrame/EndFrame
|
|
699
|
+
if (obj['StartFrame'] !== undefined && obj['EndFrame'] !== undefined) {
|
|
700
|
+
const start = parseInt(obj['StartFrame'], 10);
|
|
701
|
+
const end = parseInt(obj['EndFrame'], 10);
|
|
702
|
+
|
|
703
|
+
if (isNaN(start) || isNaN(end)) {
|
|
704
|
+
result.addIssue(SEVERITY.ERROR, 'timing', `Invalid frame values at ${path}`);
|
|
705
|
+
} else if (start > end) {
|
|
706
|
+
result.addIssue(SEVERITY.ERROR, 'timing', `StartFrame > EndFrame at ${path}`, { start, end });
|
|
707
|
+
} else if (end - start > 1000000) {
|
|
708
|
+
result.addIssue(SEVERITY.WARNING, 'timing', `Unusually long clip (${end - start} frames) at ${path}`);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// Check Duration
|
|
713
|
+
if (obj['Duration'] !== undefined) {
|
|
714
|
+
const duration = parseInt(obj['Duration'], 10);
|
|
715
|
+
if (isNaN(duration) || duration < 0) {
|
|
716
|
+
result.addIssue(SEVERITY.ERROR, 'timing', `Invalid Duration at ${path}`);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// Recurse
|
|
721
|
+
for (const key of Object.keys(obj)) {
|
|
722
|
+
if (key.startsWith('@_')) continue;
|
|
723
|
+
if (typeof obj[key] === 'object') {
|
|
724
|
+
checkTiming(obj[key], path ? `${path}.${key}` : key);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
for (const [filename, parsed] of Object.entries(parsedXml)) {
|
|
730
|
+
checkTiming(parsed, filename);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
/**
|
|
735
|
+
* Validate a DRP file and print results
|
|
736
|
+
* @param {string} filePath - Path to DRP file
|
|
737
|
+
* @param {Object} options - Validation options
|
|
738
|
+
*/
|
|
739
|
+
function validateAndPrint(filePath, options = {}) {
|
|
740
|
+
console.log(`\nValidating: ${filePath}\n`);
|
|
741
|
+
console.log('='.repeat(60));
|
|
742
|
+
|
|
743
|
+
const result = validateDRP(filePath, options);
|
|
744
|
+
const summary = result.getSummary();
|
|
745
|
+
|
|
746
|
+
// Print summary
|
|
747
|
+
console.log(`\nSUMMARY:`);
|
|
748
|
+
console.log(` Status: ${result.valid ? '✓ VALID' : '✗ INVALID'}`);
|
|
749
|
+
console.log(` Files checked: ${summary.stats.filesChecked}`);
|
|
750
|
+
console.log(` Blobs validated: ${summary.stats.blobsValidated}`);
|
|
751
|
+
console.log(` Clips found: ${summary.stats.clipsFound}`);
|
|
752
|
+
console.log(` Groups found: ${summary.stats.groupsFound}`);
|
|
753
|
+
console.log(` Errors: ${summary.errorCount}`);
|
|
754
|
+
console.log(` Warnings: ${summary.warningCount}`);
|
|
755
|
+
|
|
756
|
+
// Print errors
|
|
757
|
+
if (result.errors.length > 0) {
|
|
758
|
+
console.log(`\nERRORS (${result.errors.length}):`);
|
|
759
|
+
for (const err of result.errors) {
|
|
760
|
+
console.log(` ✗ [${err.category}] ${err.message}`);
|
|
761
|
+
if (err.details) console.log(` → ${err.details}`);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// Print warnings
|
|
766
|
+
if (result.warnings.length > 0) {
|
|
767
|
+
console.log(`\nWARNINGS (${result.warnings.length}):`);
|
|
768
|
+
for (const warn of result.warnings) {
|
|
769
|
+
console.log(` ⚠ [${warn.category}] ${warn.message}`);
|
|
770
|
+
if (warn.details) console.log(` → ${warn.details}`);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// Print info if verbose
|
|
775
|
+
if (options.verbose && result.info.length > 0) {
|
|
776
|
+
console.log(`\nINFO (${result.info.length}):`);
|
|
777
|
+
for (const info of result.info) {
|
|
778
|
+
console.log(` ℹ [${info.category}] ${info.message}`);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
console.log('\n' + '='.repeat(60));
|
|
783
|
+
console.log(`Validation ${result.valid ? 'PASSED' : 'FAILED'}`);
|
|
784
|
+
|
|
785
|
+
return result;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// CLI support
|
|
789
|
+
if (require.main === module) {
|
|
790
|
+
const args = process.argv.slice(2);
|
|
791
|
+
|
|
792
|
+
if (args.length === 0) {
|
|
793
|
+
console.log('Usage: node drp-validator.js <file.drp> [--verbose]');
|
|
794
|
+
process.exit(1);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
const filePath = args[0];
|
|
798
|
+
const verbose = args.includes('--verbose') || args.includes('-v');
|
|
799
|
+
|
|
800
|
+
const result = validateAndPrint(filePath, { verbose });
|
|
801
|
+
process.exit(result.valid ? 0 : 1);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
module.exports = {
|
|
805
|
+
validateDRP,
|
|
806
|
+
validateAndPrint,
|
|
807
|
+
ValidationResult,
|
|
808
|
+
SEVERITY,
|
|
809
|
+
};
|