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,256 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* parse/xmeml-geometry.js — the conform-field capture pass (spec §4).
|
|
5
|
+
*
|
|
6
|
+
* editorial-core's existing DTO drops every conform-critical field; this is the
|
|
7
|
+
* sibling extractor (cf. parsers/xmeml-tc.js) that captures the geometry the
|
|
8
|
+
* Oracle needs, keyed by clip, with FILE-IDS RESOLVED FIRST (non-negotiable #6:
|
|
9
|
+
* 269 of this reel's 334 <file> references are self-closing `<file id=".."/>`
|
|
10
|
+
* that only make sense once mapped back to their full definition).
|
|
11
|
+
*
|
|
12
|
+
* Output per clip:
|
|
13
|
+
* { seqstart, seqend, xml_in, xml_out, pproTicksIn, pproTicksOut,
|
|
14
|
+
* is_subclip, subclip_startoffset, scale_premiere, fileId,
|
|
15
|
+
* source_basename, srcW, srcH }
|
|
16
|
+
*
|
|
17
|
+
* Pure: takes an XML string, returns plain data. No IO.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const { XMLParser } = require('fast-xml-parser');
|
|
21
|
+
|
|
22
|
+
const PARSER = new XMLParser({
|
|
23
|
+
ignoreAttributes: false,
|
|
24
|
+
attributeNamePrefix: '@_',
|
|
25
|
+
parseTagValue: true,
|
|
26
|
+
trimValues: true,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
function asArray(x) {
|
|
30
|
+
if (x === undefined || x === null) return [];
|
|
31
|
+
return Array.isArray(x) ? x : [x];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function num(v) {
|
|
35
|
+
if (v === undefined || v === null || v === '') return null;
|
|
36
|
+
const n = Number(v);
|
|
37
|
+
return Number.isNaN(n) ? null : n;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Decode a file://… pathurl (or fall back to a name) to a basename. */
|
|
41
|
+
function basenameFromFile(fileDef) {
|
|
42
|
+
const raw = fileDef.pathurl != null ? String(fileDef.pathurl) : null;
|
|
43
|
+
if (raw) {
|
|
44
|
+
let p = raw;
|
|
45
|
+
try {
|
|
46
|
+
p = decodeURIComponent(p);
|
|
47
|
+
} catch (e) {
|
|
48
|
+
/* leave encoded if malformed */
|
|
49
|
+
}
|
|
50
|
+
const idx = p.lastIndexOf('/');
|
|
51
|
+
return idx >= 0 ? p.slice(idx + 1) : p;
|
|
52
|
+
}
|
|
53
|
+
return fileDef.name != null ? String(fileDef.name) : null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Extract the source W/H from a full <file> definition's video samplechars. */
|
|
57
|
+
function sourceDims(fileDef) {
|
|
58
|
+
const sc =
|
|
59
|
+
fileDef &&
|
|
60
|
+
fileDef.media &&
|
|
61
|
+
fileDef.media.video &&
|
|
62
|
+
fileDef.media.video.samplecharacteristics;
|
|
63
|
+
if (!sc) return { srcW: null, srcH: null };
|
|
64
|
+
return { srcW: num(sc.width), srcH: num(sc.height) };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Walk the whole tree collecting FULL <file> definitions (those carrying media
|
|
69
|
+
* or a pathurl) into an id -> def map, so self-closing references resolve.
|
|
70
|
+
*/
|
|
71
|
+
function collectFileDefs(node, map) {
|
|
72
|
+
if (node === null || typeof node !== 'object') return;
|
|
73
|
+
if (Array.isArray(node)) {
|
|
74
|
+
for (const n of node) collectFileDefs(n, map);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
for (const key of Object.keys(node)) {
|
|
78
|
+
if (key === 'file') {
|
|
79
|
+
for (const f of asArray(node[key])) {
|
|
80
|
+
if (f && typeof f === 'object' && f['@_id'] != null) {
|
|
81
|
+
const id = String(f['@_id']);
|
|
82
|
+
const isFullDef = f.media !== undefined || f.pathurl !== undefined;
|
|
83
|
+
if (isFullDef && !map[id]) {
|
|
84
|
+
const { srcW, srcH } = sourceDims(f);
|
|
85
|
+
map[id] = { basename: basenameFromFile(f), srcW, srcH };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
collectFileDefs(node[key], map);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Find a filter parameter object by parameterid (case-insensitive), or null. */
|
|
95
|
+
function findParam(clipitem, parameterid) {
|
|
96
|
+
const want = String(parameterid).toLowerCase();
|
|
97
|
+
for (const filter of asArray(clipitem.filter)) {
|
|
98
|
+
const effect = filter && filter.effect;
|
|
99
|
+
if (!effect) continue;
|
|
100
|
+
for (const p of asArray(effect.parameter)) {
|
|
101
|
+
if (p && String(p.parameterid).toLowerCase() === want) return p;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Scalar value of a parameter, or null. */
|
|
108
|
+
function scalarParam(clipitem, parameterid) {
|
|
109
|
+
const p = findParam(clipitem, parameterid);
|
|
110
|
+
return p ? num(p.value) : null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Capture the Time-Remap speed. A clip with no Time Remap is un-retimed; we
|
|
115
|
+
* report 100 (%) so downstream code has a number, and null only when truly
|
|
116
|
+
* absent and we cannot assume. Premiere encodes 50% slow-mo as a non-100 speed
|
|
117
|
+
* here AND as ticks/tpf != <in> in the Oracle — the two must agree.
|
|
118
|
+
*/
|
|
119
|
+
function captureSpeed(clipitem) {
|
|
120
|
+
const p = findParam(clipitem, 'speed');
|
|
121
|
+
if (!p) return 100; // no Time Remap => normal speed
|
|
122
|
+
const v = num(p.value);
|
|
123
|
+
return v == null ? 100 : v;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** A reversed clip carries a Time Remap with parameter `reverse` = TRUE. */
|
|
127
|
+
function captureReverse(clipitem) {
|
|
128
|
+
const p = findParam(clipitem, 'reverse');
|
|
129
|
+
if (!p) return false;
|
|
130
|
+
return String(p.value).toUpperCase() === 'TRUE';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Capture Basic-Motion center/rotation/crop (reframes + online/offline Y residual). */
|
|
134
|
+
function captureTransform(clipitem) {
|
|
135
|
+
const center = findParam(clipitem, 'center');
|
|
136
|
+
const c = center && center.value ? center.value : null;
|
|
137
|
+
return {
|
|
138
|
+
center: c ? { h: num(c.horiz), v: num(c.vert) } : null,
|
|
139
|
+
rotation: scalarParam(clipitem, 'rotation'),
|
|
140
|
+
crop: {
|
|
141
|
+
left: scalarParam(clipitem, 'leftcrop'),
|
|
142
|
+
top: scalarParam(clipitem, 'topcrop'),
|
|
143
|
+
right: scalarParam(clipitem, 'rightcrop'),
|
|
144
|
+
bottom: scalarParam(clipitem, 'bottomcrop'),
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** The video-track clipitems, in order. */
|
|
150
|
+
function videoClipitems(sequence) {
|
|
151
|
+
const tracks = asArray(
|
|
152
|
+
sequence && sequence.media && sequence.media.video && sequence.media.video.track,
|
|
153
|
+
);
|
|
154
|
+
const out = [];
|
|
155
|
+
for (const track of tracks) {
|
|
156
|
+
for (const ci of asArray(track.clipitem)) {
|
|
157
|
+
// A real clip has a record position + a source in-point.
|
|
158
|
+
if (ci && ci.start !== undefined && ci.in !== undefined) out.push(ci);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return out;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Capture video transitions (dissolves/wipes) across the sequence's video tracks. */
|
|
165
|
+
function videoTransitions(sequence) {
|
|
166
|
+
const tracks = asArray(
|
|
167
|
+
sequence && sequence.media && sequence.media.video && sequence.media.video.track,
|
|
168
|
+
);
|
|
169
|
+
const out = [];
|
|
170
|
+
tracks.forEach((track, ti) => {
|
|
171
|
+
for (const tr of asArray(track.transitionitem)) {
|
|
172
|
+
if (!tr) continue;
|
|
173
|
+
const effect = tr.effect || {};
|
|
174
|
+
out.push({
|
|
175
|
+
track: ti + 1,
|
|
176
|
+
start: num(tr.start),
|
|
177
|
+
end: num(tr.end),
|
|
178
|
+
duration: num(tr.end) != null && num(tr.start) != null ? num(tr.end) - num(tr.start) : null,
|
|
179
|
+
alignment: tr.alignment != null ? String(tr.alignment) : null,
|
|
180
|
+
effectid: effect.effectid != null ? String(effect.effectid) : null,
|
|
181
|
+
category: effect.effectcategory != null ? String(effect.effectcategory) : null,
|
|
182
|
+
mediatype: effect.mediatype != null ? String(effect.mediatype) : null,
|
|
183
|
+
reverse: effect.reverse != null ? String(effect.reverse).toUpperCase() === 'TRUE' : false,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
return out;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function sequenceInfo(sequence) {
|
|
191
|
+
const sc =
|
|
192
|
+
sequence &&
|
|
193
|
+
sequence.media &&
|
|
194
|
+
sequence.media.video &&
|
|
195
|
+
sequence.media.video.format &&
|
|
196
|
+
sequence.media.video.format.samplecharacteristics;
|
|
197
|
+
const rate = sc && sc.rate;
|
|
198
|
+
return {
|
|
199
|
+
name: sequence && sequence.name != null ? String(sequence.name) : null,
|
|
200
|
+
width: sc ? num(sc.width) : null,
|
|
201
|
+
height: sc ? num(sc.height) : null,
|
|
202
|
+
fps: rate ? num(rate.timebase) : null,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Parse an XMEML string into captured conform geometry.
|
|
208
|
+
* @param {string} xml
|
|
209
|
+
* @returns {{ sequence: object, fileDefs: object, clips: object[] }}
|
|
210
|
+
*/
|
|
211
|
+
function parseGeometry(xml) {
|
|
212
|
+
const doc = PARSER.parse(xml);
|
|
213
|
+
const sequence = doc && doc.xmeml && doc.xmeml.sequence;
|
|
214
|
+
if (!sequence) throw new Error('xmeml-geometry: no <xmeml><sequence> found');
|
|
215
|
+
|
|
216
|
+
// Pass 1 — resolve file-ids (non-negotiable #6) BEFORE reading clip fields.
|
|
217
|
+
const fileDefs = {};
|
|
218
|
+
collectFileDefs(doc, fileDefs);
|
|
219
|
+
|
|
220
|
+
// Pass 2 — capture per-clip geometry.
|
|
221
|
+
const clips = [];
|
|
222
|
+
for (const ci of videoClipitems(sequence)) {
|
|
223
|
+
const fileNode = ci.file;
|
|
224
|
+
const fileId = fileNode && fileNode['@_id'] != null ? String(fileNode['@_id']) : null;
|
|
225
|
+
const def = fileId ? fileDefs[fileId] : null;
|
|
226
|
+
const sub = ci.subclipinfo;
|
|
227
|
+
const startoffset = sub ? num(sub.startoffset) : null;
|
|
228
|
+
const endoffset = sub ? num(sub.endoffset) : null;
|
|
229
|
+
const transform = captureTransform(ci);
|
|
230
|
+
clips.push({
|
|
231
|
+
seqstart: num(ci.start),
|
|
232
|
+
seqend: num(ci.end),
|
|
233
|
+
xml_in: num(ci.in),
|
|
234
|
+
xml_out: num(ci.out),
|
|
235
|
+
pproTicksIn: num(ci.pproTicksIn),
|
|
236
|
+
pproTicksOut: num(ci.pproTicksOut),
|
|
237
|
+
is_subclip: startoffset != null,
|
|
238
|
+
subclip_startoffset: startoffset != null ? startoffset : 0,
|
|
239
|
+
subclip_endoffset: endoffset != null ? endoffset : 0,
|
|
240
|
+
scale_premiere: scalarParam(ci, 'scale'),
|
|
241
|
+
speed: captureSpeed(ci),
|
|
242
|
+
reverse: captureReverse(ci),
|
|
243
|
+
center: transform.center,
|
|
244
|
+
rotation: transform.rotation,
|
|
245
|
+
crop: transform.crop,
|
|
246
|
+
fileId,
|
|
247
|
+
source_basename: def ? def.basename : null,
|
|
248
|
+
srcW: def ? def.srcW : null,
|
|
249
|
+
srcH: def ? def.srcH : null,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return { sequence: sequenceInfo(sequence), fileDefs, clips, transitions: videoTransitions(sequence) };
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
module.exports = { parseGeometry, basenameFromFile };
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* reference/burnin-ocr.js — Tier A ground truth (spec §7): read the source
|
|
5
|
+
* timecode + filename BURNED INTO a review render, so a cut is verified as
|
|
6
|
+
* "content matches AND came from the named source." This is the mechanism that
|
|
7
|
+
* recovers the true source of a mismatched cut (e.g. a reversed/subclip whose
|
|
8
|
+
* frame can't be trusted from the editorial offsets alone).
|
|
9
|
+
*
|
|
10
|
+
* Uses tesseract (system dep) with a dark-lift pre-pass (review renders are
|
|
11
|
+
* often temped dark) and optional MULTI-FRAME CONSENSUS (vote across frames) per
|
|
12
|
+
* the spec. OCR is noisy, so identity matching is FUZZY (token overlap), not
|
|
13
|
+
* exact — a misread camera/roll word still resolves because the distinctive
|
|
14
|
+
* tokens (roll/date) pin the source.
|
|
15
|
+
*
|
|
16
|
+
* tesseract is invoked as a subprocess; callers should skip-if-absent.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const sharp = require('sharp');
|
|
20
|
+
const { execFileSync } = require('child_process');
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
const os = require('os');
|
|
23
|
+
const path = require('path');
|
|
24
|
+
|
|
25
|
+
// Default burn-in regions (fractions of W/H) for a common review-render template.
|
|
26
|
+
const DEFAULT_REGIONS = Object.freeze({
|
|
27
|
+
bottom: { x0: 0.0, y0: 0.93, x1: 1.0, y1: 1.0 }, // filename (left) + record TC (right)
|
|
28
|
+
topTc: { x0: 0.34, y0: 0.06, x1: 0.66, y1: 0.18 }, // centered source TC
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const TC_RE = /\b(\d{2}:\d{2}:\d{2}[:;]\d{2})\b/;
|
|
32
|
+
|
|
33
|
+
function tokenize(text) {
|
|
34
|
+
return String(text || '')
|
|
35
|
+
.toLowerCase()
|
|
36
|
+
.split(/[^a-z0-9]+/)
|
|
37
|
+
.filter((t) => t.length >= 2);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Fraction of the expected source-name tokens that appear in the OCR tokens. */
|
|
41
|
+
function identityOverlap(ocrText, expectedName) {
|
|
42
|
+
const want = new Set(tokenize(expectedName));
|
|
43
|
+
if (want.size === 0) return 0;
|
|
44
|
+
const got = new Set(tokenize(ocrText));
|
|
45
|
+
let hit = 0;
|
|
46
|
+
for (const t of want) if (got.has(t)) hit += 1;
|
|
47
|
+
return hit / want.size;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function ocrRegion(framePath, region, lift) {
|
|
51
|
+
const meta = await sharp(framePath).metadata();
|
|
52
|
+
const W = meta.width;
|
|
53
|
+
const H = meta.height;
|
|
54
|
+
const left = Math.max(0, Math.round(region.x0 * W));
|
|
55
|
+
const top = Math.max(0, Math.round(region.y0 * H));
|
|
56
|
+
const width = Math.min(W - left, Math.round((region.x1 - region.x0) * W));
|
|
57
|
+
const height = Math.min(H - top, Math.round((region.y1 - region.y0) * H));
|
|
58
|
+
let img = sharp(framePath).extract({ left, top, width, height }).greyscale().normalise();
|
|
59
|
+
if (lift && lift !== 1) img = img.linear(lift, 0).normalise();
|
|
60
|
+
const buf = await img.resize({ width: width * 2 }).png().toBuffer();
|
|
61
|
+
const tmp = path.join(os.tmpdir(), `cqc-ocr-${process.pid}-${left}-${top}.png`);
|
|
62
|
+
fs.writeFileSync(tmp, buf);
|
|
63
|
+
try {
|
|
64
|
+
return execFileSync('tesseract', [tmp, 'stdout', '--psm', '7'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
65
|
+
} finally {
|
|
66
|
+
fs.rmSync(tmp, { force: true });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Read the burn-in from one frame (or several, for consensus).
|
|
72
|
+
* @param {string|string[]} frames frame path(s)
|
|
73
|
+
* @param {object} opts { regions?, lift? }
|
|
74
|
+
* @returns {Promise<{ sourceTc: string|null, ocrText: string, perFrame: object[] }>}
|
|
75
|
+
*/
|
|
76
|
+
async function readBurnIn(frames, opts = {}) {
|
|
77
|
+
const list = Array.isArray(frames) ? frames : [frames];
|
|
78
|
+
const regions = { ...DEFAULT_REGIONS, ...(opts.regions || {}) };
|
|
79
|
+
const lift = opts.lift || 1;
|
|
80
|
+
const perFrame = [];
|
|
81
|
+
for (const f of list) {
|
|
82
|
+
const bottom = await ocrRegion(f, regions.bottom, lift);
|
|
83
|
+
let topTc = '';
|
|
84
|
+
try {
|
|
85
|
+
topTc = await ocrRegion(f, regions.topTc, lift);
|
|
86
|
+
} catch (e) {
|
|
87
|
+
/* top TC optional */
|
|
88
|
+
}
|
|
89
|
+
const tcMatch = (topTc.match(TC_RE) || bottom.match(TC_RE) || [])[1] || null;
|
|
90
|
+
perFrame.push({ frame: f, bottom, topTc, tc: tcMatch });
|
|
91
|
+
}
|
|
92
|
+
// Consensus: the most common non-null TC across frames; concatenated bottom text.
|
|
93
|
+
const tcVotes = {};
|
|
94
|
+
for (const r of perFrame) if (r.tc) tcVotes[r.tc] = (tcVotes[r.tc] || 0) + 1;
|
|
95
|
+
const sourceTc = Object.keys(tcVotes).sort((a, b) => tcVotes[b] - tcVotes[a])[0] || null;
|
|
96
|
+
const ocrText = perFrame.map((r) => r.bottom).join(' ');
|
|
97
|
+
return { sourceTc, ocrText, perFrame };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
module.exports = { readBurnIn, identityOverlap, tokenize, DEFAULT_REGIONS, TC_RE };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* reference/sidecar.js — Tier B ground truth (spec §7): a clean review render
|
|
5
|
+
* keyed by a TC sidecar (EDL / ALE / source-TC). Content is verified by frame;
|
|
6
|
+
* IDENTITY comes from the sidecar (no OCR). Lower-friction than Tier A when a
|
|
7
|
+
* burn-in isn't available but a sidecar is.
|
|
8
|
+
*
|
|
9
|
+
* Produces a referenceProvider(clip) for ops/verify — pure (takes the sidecar +
|
|
10
|
+
* a frame-path resolver; no IO of its own).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {object[]|object} sidecar entries { seqstart, sourceTc, sourceName, referenceFrame, derivedFrame?, label? }
|
|
15
|
+
* (array, or an object keyed by seqstart)
|
|
16
|
+
* @param {(p:string)=>string} [resolveFramePath] map a sidecar frame ref to a real path
|
|
17
|
+
* @returns {(clip:object)=>object|null} a verify referenceProvider
|
|
18
|
+
*/
|
|
19
|
+
function tcSidecarProvider(sidecar, resolveFramePath) {
|
|
20
|
+
const map = Array.isArray(sidecar)
|
|
21
|
+
? Object.fromEntries(sidecar.map((e) => [e.seqstart, e]))
|
|
22
|
+
: sidecar || {};
|
|
23
|
+
const resolve = resolveFramePath || ((p) => p);
|
|
24
|
+
return function referenceProvider(clip) {
|
|
25
|
+
const e = map[clip.seqstart];
|
|
26
|
+
if (!e) return null; // no sidecar entry => Tier C for this cut
|
|
27
|
+
return {
|
|
28
|
+
referencePath: resolve(e.referenceFrame),
|
|
29
|
+
derivedPath: e.derivedFrame ? resolve(e.derivedFrame) : null,
|
|
30
|
+
tier: 'B',
|
|
31
|
+
label: e.label,
|
|
32
|
+
identity: { sourceTc: e.sourceTc || null, sourceName: e.sourceName || null },
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
module.exports = { tcSidecarProvider };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* repair/analysis.js — correctness checks + the proposed-timeline / V2-flag-track
|
|
5
|
+
* assembler (spec §9). Pure functions.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Dupes / repeated source (§9): a correctness check, not a repair. Group clips
|
|
10
|
+
* by resolved source; report clusters where one source feeds many cuts.
|
|
11
|
+
* Returns [{ source, count, cutIds }] for sources used more than once.
|
|
12
|
+
*/
|
|
13
|
+
function detectDupes(clips, keyOf = (c) => c.source_basename) {
|
|
14
|
+
const groups = new Map();
|
|
15
|
+
for (const c of clips) {
|
|
16
|
+
const k = keyOf(c);
|
|
17
|
+
if (k == null) continue;
|
|
18
|
+
if (!groups.has(k)) groups.set(k, []);
|
|
19
|
+
groups.get(k).push(c.cutId != null ? c.cutId : c.seqstart);
|
|
20
|
+
}
|
|
21
|
+
return [...groups.entries()]
|
|
22
|
+
.filter(([, ids]) => ids.length > 1)
|
|
23
|
+
.map(([source, cutIds]) => ({ source, count: cutIds.length, cutIds }));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Build a PROPOSED corrected timeline + a V2 flag track from repair results
|
|
28
|
+
* (§9). Auto-applied deterministic fixes land in the corrected timeline;
|
|
29
|
+
* everything else (propose-only, auto-rejected, flag-v2, unrepairable) becomes
|
|
30
|
+
* ONE annotated V2 clip with a V1 gap — nothing is auto-applied silently.
|
|
31
|
+
*
|
|
32
|
+
* @param {object[]} results [{ cutId, seqstart, repair: <ladder result> }]
|
|
33
|
+
* @returns {{ correctedTimeline: object[], v2Flags: object[], summary: object }}
|
|
34
|
+
*/
|
|
35
|
+
function proposeTimeline(results) {
|
|
36
|
+
const correctedTimeline = [];
|
|
37
|
+
const v2Flags = [];
|
|
38
|
+
for (const r of results) {
|
|
39
|
+
const rep = r.repair || {};
|
|
40
|
+
if (rep.mode === 'auto-apply' && rep.proposal && rep.proposal.fix) {
|
|
41
|
+
correctedTimeline.push({ cutId: r.cutId, seqstart: r.seqstart, applied: rep.proposal.fix, strategy: rep.strategy, v1Gap: false });
|
|
42
|
+
} else {
|
|
43
|
+
// Irreducible / needs review: keep a V1 gap and annotate one V2 clip.
|
|
44
|
+
correctedTimeline.push({ cutId: r.cutId, seqstart: r.seqstart, applied: null, v1Gap: true });
|
|
45
|
+
v2Flags.push({
|
|
46
|
+
cutId: r.cutId,
|
|
47
|
+
seqstart: r.seqstart,
|
|
48
|
+
mode: rep.mode || 'flag-v2',
|
|
49
|
+
klass: rep.klass || 'unrepairable',
|
|
50
|
+
note: rep.diagnosis || 'flagged for review',
|
|
51
|
+
proposedFix: rep.proposal ? rep.proposal.fix : null,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
correctedTimeline,
|
|
57
|
+
v2Flags,
|
|
58
|
+
summary: { cuts: results.length, autoApplied: correctedTimeline.filter((c) => !c.v1Gap).length, flagged: v2Flags.length },
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = { detectDupes, proposeTimeline };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* repair/ — the repair strategy LADDER (spec §9).
|
|
5
|
+
*
|
|
6
|
+
* For a cut that failed verification, try strategies in order; the first that
|
|
7
|
+
* detects proposes a fix. The fix is RE-VERIFIED (re-sample + compare) before
|
|
8
|
+
* acceptance; deterministic strategies that re-verify clean ≥ gate auto-apply
|
|
9
|
+
* (§15), everything else is propose-only (human approves). The diagnosis class +
|
|
10
|
+
* outcome are recorded to ConformKnowledge so confidence improves with use.
|
|
11
|
+
*
|
|
12
|
+
* Unrepairable / low-confidence → flag to the V2 track (one annotated clip).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const { ConformKnowledge } = require('../knowledge');
|
|
16
|
+
const strategies = require('./strategies');
|
|
17
|
+
|
|
18
|
+
const DEFAULT_ORDER = [
|
|
19
|
+
strategies.scaleDoubleCount,
|
|
20
|
+
strategies.subclipOffset,
|
|
21
|
+
strategies.ticksRetime,
|
|
22
|
+
strategies.staleRelink,
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {object} opts {
|
|
27
|
+
* order? strategy list (default DEFAULT_ORDER)
|
|
28
|
+
* knowledge? a ConformKnowledge store (created if absent)
|
|
29
|
+
* confidenceGate? default 0.90 (§15 SSIM threshold)
|
|
30
|
+
* reverify?(cut, proposal, ctx) -> Promise<{ ok:boolean, confidence:number }> optional re-sample+compare
|
|
31
|
+
* }
|
|
32
|
+
*/
|
|
33
|
+
function makeRepairLadder(opts = {}) {
|
|
34
|
+
const order = opts.order || DEFAULT_ORDER;
|
|
35
|
+
const knowledge = opts.knowledge || new ConformKnowledge();
|
|
36
|
+
const gate = opts.confidenceGate != null ? opts.confidenceGate : 0.9;
|
|
37
|
+
|
|
38
|
+
async function repair(cut, ctx = {}) {
|
|
39
|
+
for (const s of order) {
|
|
40
|
+
if (!s.detect(cut, ctx)) continue;
|
|
41
|
+
const proposal = s.propose(cut, ctx);
|
|
42
|
+
const hasFix = proposal && proposal.fix != null;
|
|
43
|
+
|
|
44
|
+
// Re-verify the proposed fix if a verifier is injected; else trust the
|
|
45
|
+
// strategy's own confidence (deterministic strategies derive exact values).
|
|
46
|
+
let reverified = null;
|
|
47
|
+
let confidence = proposal && proposal.confidence != null ? proposal.confidence : null;
|
|
48
|
+
if (opts.reverify && hasFix) {
|
|
49
|
+
reverified = await opts.reverify(cut, proposal, ctx);
|
|
50
|
+
confidence = reverified.confidence;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const reverifyOk = reverified ? reverified.ok : true;
|
|
54
|
+
const accepted = hasFix && s.deterministic && reverifyOk && (confidence == null || confidence >= gate);
|
|
55
|
+
const mode = accepted ? 'auto-apply' : (hasFix ? (s.deterministic ? 'auto-rejected' : 'propose-only') : 'flag-v2');
|
|
56
|
+
const outcome = hasFix && reverifyOk ? 'success' : 'failure';
|
|
57
|
+
knowledge.record({ class: s.klass, scope: proposal.scope }, { strategy: s.id, outcome });
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
strategy: s.id,
|
|
61
|
+
klass: s.klass,
|
|
62
|
+
deterministic: !!s.deterministic,
|
|
63
|
+
proposal,
|
|
64
|
+
accepted,
|
|
65
|
+
mode,
|
|
66
|
+
confidence,
|
|
67
|
+
diagnosis: proposal.diagnosis,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
// Nothing matched — irreducible, goes to V2.
|
|
71
|
+
knowledge.record({ class: 'unrepairable', scope: cut.cutId }, { strategy: 'flag-v2', outcome: 'failure' });
|
|
72
|
+
return { strategy: null, klass: 'unrepairable', accepted: false, mode: 'flag-v2', diagnosis: 'no repair strategy matched — flag to V2' };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return { knowledge, repair, order };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = { makeRepairLadder, DEFAULT_ORDER, strategies };
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* repair/media-analysis.js — analyze target media so the conform emulator knows
|
|
5
|
+
* the real qualities/options on the volume (stage 1).
|
|
6
|
+
*
|
|
7
|
+
* Resolve frames a clip from the RELINKED media's real geometry, not the proxy
|
|
8
|
+
* dims in the turnover (a scaleToFit bug arises when a highres relink has different
|
|
9
|
+
* dims/aspect than the proxy the editor framed against). So before emulating we
|
|
10
|
+
* probe each candidate: resolution,
|
|
11
|
+
* pixel aspect (SAR/DAR), codec, fps, bit-depth, colour — and rank quality tiers
|
|
12
|
+
* so the relink can choose the intended original over a proxy/test.
|
|
13
|
+
*
|
|
14
|
+
* Split for testability: `parseFfprobe` is PURE (ffprobe JSON -> normalized info);
|
|
15
|
+
* `probeMedia` shells out to ffprobe and calls it.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const { spawnSync } = require('child_process');
|
|
19
|
+
|
|
20
|
+
/** Parse an `r_frame_rate`/`avg_frame_rate` "24000/1001" string to a number. */
|
|
21
|
+
function parseRate(r) {
|
|
22
|
+
if (!r || typeof r !== 'string') return null;
|
|
23
|
+
const [n, d] = r.split('/').map(Number);
|
|
24
|
+
if (!n || !d) return n || null;
|
|
25
|
+
return +(n / d).toFixed(6);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Bit depth from bits_per_raw_sample, else inferred from pix_fmt (…p10/12/16…). */
|
|
29
|
+
function bitDepth(stream) {
|
|
30
|
+
const b = Number(stream.bits_per_raw_sample);
|
|
31
|
+
if (Number.isFinite(b) && b > 0) return b;
|
|
32
|
+
const pf = String(stream.pix_fmt || '');
|
|
33
|
+
const m = pf.match(/p(\d{1,2})(le|be)?$/);
|
|
34
|
+
if (m) return Number(m[1]);
|
|
35
|
+
return pf ? 8 : null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Effective display aspect = (W/H) * (SAR), so anamorphic media reports its true shape. */
|
|
39
|
+
function aspectOf(w, h, sar) {
|
|
40
|
+
if (!w || !h) return null;
|
|
41
|
+
let par = 1;
|
|
42
|
+
if (sar && typeof sar === 'string' && sar.includes(':')) {
|
|
43
|
+
const [sn, sd] = sar.split(':').map(Number);
|
|
44
|
+
if (sn > 0 && sd > 0) par = sn / sd;
|
|
45
|
+
}
|
|
46
|
+
return +((w / h) * par).toFixed(4);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Normalize an ffprobe `-of json` result (first video stream + format) into a
|
|
51
|
+
* compact media-info record. Pure.
|
|
52
|
+
*/
|
|
53
|
+
function parseFfprobe(jsonStr, path) {
|
|
54
|
+
let doc;
|
|
55
|
+
try {
|
|
56
|
+
doc = typeof jsonStr === 'string' ? JSON.parse(jsonStr) : jsonStr;
|
|
57
|
+
} catch (e) {
|
|
58
|
+
return { path: path || null, ok: false, error: 'ffprobe JSON parse failed' };
|
|
59
|
+
}
|
|
60
|
+
// We pass -select_streams v:0, so streams[0] is the video stream even when
|
|
61
|
+
// codec_type isn't among the requested entries; prefer an explicit match.
|
|
62
|
+
const streams = doc.streams || [];
|
|
63
|
+
const v = streams.find((s) => s.codec_type === 'video') || streams[0] || null;
|
|
64
|
+
if (!v || v.width == null) return { path: path || null, ok: false, error: 'no video stream' };
|
|
65
|
+
const w = Number(v.width) || null;
|
|
66
|
+
const h = Number(v.height) || null;
|
|
67
|
+
const fpsVal = parseRate(v.r_frame_rate) || parseRate(v.avg_frame_rate);
|
|
68
|
+
const durVal = doc.format && doc.format.duration ? Number(doc.format.duration) : null;
|
|
69
|
+
// Exact container frame count when present; else derive from duration*fps.
|
|
70
|
+
// Used to end-anchor reversed clips (no reference render required).
|
|
71
|
+
let frames = Number(v.nb_frames);
|
|
72
|
+
if (!Number.isFinite(frames) || frames <= 0) {
|
|
73
|
+
frames = fpsVal && durVal ? Math.round(durVal * fpsVal) : null;
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
path: path || null,
|
|
77
|
+
ok: true,
|
|
78
|
+
width: w,
|
|
79
|
+
height: h,
|
|
80
|
+
frames: frames || null,
|
|
81
|
+
sar: v.sample_aspect_ratio || null,
|
|
82
|
+
dar: v.display_aspect_ratio || null,
|
|
83
|
+
aspect: aspectOf(w, h, v.sample_aspect_ratio),
|
|
84
|
+
codec: v.codec_name || null,
|
|
85
|
+
profile: v.profile || null,
|
|
86
|
+
pixFmt: v.pix_fmt || null,
|
|
87
|
+
bitDepth: bitDepth(v),
|
|
88
|
+
fps: fpsVal,
|
|
89
|
+
colorPrimaries: v.color_primaries || null,
|
|
90
|
+
colorTransfer: v.color_transfer || null,
|
|
91
|
+
durationSec: durVal != null ? +durVal.toFixed(3) : null,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const FFPROBE_ARGS = [
|
|
96
|
+
'-v', 'error', '-select_streams', 'v:0',
|
|
97
|
+
'-show_entries',
|
|
98
|
+
'stream=width,height,codec_name,profile,pix_fmt,r_frame_rate,avg_frame_rate,sample_aspect_ratio,display_aspect_ratio,color_primaries,color_transfer,bits_per_raw_sample,nb_frames:format=duration',
|
|
99
|
+
'-of', 'json',
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
/** Probe one file via ffprobe. Returns the normalized record (ok:false on failure). */
|
|
103
|
+
function probeMedia(path, { ffprobePath = 'ffprobe' } = {}) {
|
|
104
|
+
const r = spawnSync(ffprobePath, [...FFPROBE_ARGS, path], { encoding: 'utf8', maxBuffer: 8 * 1024 * 1024 });
|
|
105
|
+
if (r.status !== 0) return { path, ok: false, error: (r.stderr || 'ffprobe failed').slice(-200) };
|
|
106
|
+
return parseFfprobe(r.stdout, path);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Quality rank for choosing among options for the same shot (higher = better
|
|
111
|
+
* original). ProRes 4444/XQ and higher resolution/bit-depth win; h264/proxy and a
|
|
112
|
+
* "proxy"/"test" path lose. Used by the relink to pick the intended source.
|
|
113
|
+
*/
|
|
114
|
+
const CODEC_RANK = { prores: 50, dnxhd: 45, dnxhr: 45, h264: 10, hevc: 12, mjpeg: 8 };
|
|
115
|
+
function qualityRank(info) {
|
|
116
|
+
if (!info || !info.ok) return -1;
|
|
117
|
+
let s = 0;
|
|
118
|
+
const codec = String(info.codec || '').toLowerCase();
|
|
119
|
+
for (const k of Object.keys(CODEC_RANK)) if (codec.includes(k)) s += CODEC_RANK[k];
|
|
120
|
+
if (/4444|xq|hq/.test(String(info.profile || '').toLowerCase())) s += 20;
|
|
121
|
+
s += (info.width || 0) / 100; // resolution
|
|
122
|
+
s += (info.bitDepth || 8) * 2;
|
|
123
|
+
const p = String(info.path || '').toLowerCase();
|
|
124
|
+
if (/\bproxy\b/.test(p) || /\.mp4$/.test(p)) s -= 40;
|
|
125
|
+
if (/\btest(s)?\b/.test(p)) s -= 25;
|
|
126
|
+
return +s.toFixed(2);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Probe a list of paths; returns records sorted by quality (best first). */
|
|
130
|
+
function analyzeMediaList(paths, opts = {}) {
|
|
131
|
+
const out = (paths || []).map((p) => {
|
|
132
|
+
const info = probeMedia(p, opts);
|
|
133
|
+
return { ...info, qualityRank: qualityRank(info) };
|
|
134
|
+
});
|
|
135
|
+
out.sort((a, b) => b.qualityRank - a.qualityRank);
|
|
136
|
+
return out;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
module.exports = { parseFfprobe, probeMedia, analyzeMediaList, qualityRank, aspectOf, bitDepth, parseRate };
|