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,135 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* repair/media-index.js — a media index for re-resolving sources (spec §3.2 /
|
|
5
|
+
* §9 true-source search). Pure: built from a list of media descriptors; no IO.
|
|
6
|
+
*
|
|
7
|
+
* Resolves by exact basename, by reel+TC, and by naming-variant (proxy↔original,
|
|
8
|
+
* e.g. a "…4K-2K…" proxy → its "…4K…" scan) — the proxy/scan naming relationship.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
function normTokens(name) {
|
|
12
|
+
return String(name || '')
|
|
13
|
+
.toLowerCase()
|
|
14
|
+
.replace(/\.[a-z0-9]+$/, '')
|
|
15
|
+
.split(/[^a-z0-9]+/)
|
|
16
|
+
.filter((t) => t.length >= 2);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A normalized basename key for proxy↔original EXACT matching, before the fuzzy
|
|
21
|
+
* IDF tier. Unlike normTokens this PRESERVES single-char discriminators (a take
|
|
22
|
+
* marker like "#1" vs "#2" — different rescans of the same shot — must not
|
|
23
|
+
* collapse), and only erases the deterministic proxy/duplicate decorations:
|
|
24
|
+
* - the extension (proxy .mp4 ↔ original .mov),
|
|
25
|
+
* - the proxy resolution marker "4k-2k" → "4k",
|
|
26
|
+
* - a trailing "_<n>" Premiere duplicate-import suffix ("…0703a_1" → "…0703a").
|
|
27
|
+
* Whitespace is collapsed; the take marker (#2) and roll/clip ids survive.
|
|
28
|
+
*/
|
|
29
|
+
function normalizedKey(name) {
|
|
30
|
+
return String(name || '')
|
|
31
|
+
.toLowerCase()
|
|
32
|
+
.replace(/\.[a-z0-9]+$/, '')
|
|
33
|
+
.replace(/4k-2k/g, '4k')
|
|
34
|
+
.replace(/_\d+$/, '')
|
|
35
|
+
.replace(/\s+/g, ' ')
|
|
36
|
+
.trim();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Proxy-ish tokens that shouldn't block a proxy↔original match. */
|
|
40
|
+
const VARIANT_NOISE = new Set(['2k', 'proxy', 'prores', 'h264', 'dnx']);
|
|
41
|
+
|
|
42
|
+
class MediaIndex {
|
|
43
|
+
/** @param {object[]} media [{ path, basename, reel?, startTc?, role? }] */
|
|
44
|
+
constructor(media = []) {
|
|
45
|
+
this.media = media.map((m) => ({ ...m, _tokens: new Set(normTokens(m.basename)) }));
|
|
46
|
+
this.byBasenameMap = new Map(this.media.map((m) => [m.basename, m.path]));
|
|
47
|
+
// Normalized-key map for the proxy↔original exact tier. A key with >1
|
|
48
|
+
// candidate is AMBIGUOUS — we store the list and refuse to guess (byNormalized
|
|
49
|
+
// returns null), letting the caller fall to IDF or flag it.
|
|
50
|
+
this.byNormalizedMap = new Map();
|
|
51
|
+
for (const m of this.media) {
|
|
52
|
+
const k = normalizedKey(m.basename);
|
|
53
|
+
if (!this.byNormalizedMap.has(k)) this.byNormalizedMap.set(k, []);
|
|
54
|
+
this.byNormalizedMap.get(k).push(m.path);
|
|
55
|
+
}
|
|
56
|
+
// Document frequency per token, for IDF weighting in findTrueSource: generic
|
|
57
|
+
// tokens (camera/format/resolution words common to nearly every name) weigh
|
|
58
|
+
// ~nothing; distinctive tokens (the roll/date) carry the match. This is what
|
|
59
|
+
// stops a generic-only overlap (e.g. a TEST roll vs a production scan) from
|
|
60
|
+
// scoring high on shared boilerplate.
|
|
61
|
+
this.N = this.media.length;
|
|
62
|
+
this.df = new Map();
|
|
63
|
+
for (const m of this.media) for (const t of m._tokens) this.df.set(t, (this.df.get(t) || 0) + 1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Inverse document frequency (smoothed): rare/distinctive tokens weigh more. */
|
|
67
|
+
idf(token) {
|
|
68
|
+
return Math.log((this.N + 1) / ((this.df.get(token) || 0) + 1)) + 1;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
byBasename(basename) {
|
|
72
|
+
return this.byBasenameMap.get(basename) || null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Exact match on the normalized key (proxy↔original, take-marker preserving).
|
|
77
|
+
* Returns the path only when EXACTLY one candidate normalizes to the same key;
|
|
78
|
+
* null when none or when ambiguous (caller falls back to IDF / flags it).
|
|
79
|
+
*/
|
|
80
|
+
byNormalized(basename) {
|
|
81
|
+
const hits = this.byNormalizedMap.get(normalizedKey(basename));
|
|
82
|
+
return hits && hits.length === 1 ? hits[0] : null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
byReelTc(reel, startTc) {
|
|
86
|
+
const m = this.media.find((x) => x.reel === reel && (startTc == null || x.startTc === startTc));
|
|
87
|
+
return m ? m.path : null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Find the best true-source match for a name (OCR'd or proxy basename), using
|
|
92
|
+
* token overlap with proxy-noise tolerance. Returns { path, score } or null.
|
|
93
|
+
*/
|
|
94
|
+
findTrueSource(name, opts = {}) {
|
|
95
|
+
const want = [...new Set(normTokens(name))].filter((t) => !VARIANT_NOISE.has(t));
|
|
96
|
+
if (!want.length) return null;
|
|
97
|
+
// IDF-weighted token overlap (§10.5): distinctive roll/date tokens carry the
|
|
98
|
+
// score; boilerplate (camera/format/resolution words in nearly every name)
|
|
99
|
+
// weighs ~nothing. This is what stops a TEST roll from false-matching a
|
|
100
|
+
// production scan on shared boilerplate alone: they share only generic tokens,
|
|
101
|
+
// so the score stays well under threshold.
|
|
102
|
+
//
|
|
103
|
+
// Origin separation (test vs production, exclude the Tests tree) is the
|
|
104
|
+
// CALLER's job: it's done by index composition — a finishing relink builds the
|
|
105
|
+
// index from the finishing trees only — not by a name heuristic here, because
|
|
106
|
+
// not every test source carries a "test" token (some test/dev rolls are named
|
|
107
|
+
// only by look). opts.excludePathContains lets a caller drop trees inline if it
|
|
108
|
+
// prefers.
|
|
109
|
+
const excludePathContains = opts.excludePathContains || [];
|
|
110
|
+
const totalIdf = want.reduce((s, t) => s + this.idf(t), 0);
|
|
111
|
+
let best = null;
|
|
112
|
+
for (const m of this.media) {
|
|
113
|
+
if (excludePathContains.length && excludePathContains.some((p) => (m.path || '').includes(p))) continue;
|
|
114
|
+
// A match must share at least one DISCRIMINATING token — one not present in
|
|
115
|
+
// every candidate (df < N). Boilerplate alone (camera/format/resolution words
|
|
116
|
+
// shared by the whole index) can never carry a match, so a TEST roll cannot
|
|
117
|
+
// resolve to a production scan on boilerplate even in a small index. (Skipped
|
|
118
|
+
// for a single-candidate index, where df==N for everything by definition.)
|
|
119
|
+
let matchedIdf = 0;
|
|
120
|
+
let discriminating = this.N <= 1;
|
|
121
|
+
for (const t of want) {
|
|
122
|
+
if (!m._tokens.has(t)) continue;
|
|
123
|
+
matchedIdf += this.idf(t);
|
|
124
|
+
if ((this.df.get(t) || 0) < this.N) discriminating = true;
|
|
125
|
+
}
|
|
126
|
+
if (!discriminating) continue;
|
|
127
|
+
const score = totalIdf ? matchedIdf / totalIdf : 0;
|
|
128
|
+
if (!best || score > best.score) best = { path: m.path, basename: m.basename, score };
|
|
129
|
+
}
|
|
130
|
+
const min = opts.minScore != null ? opts.minScore : 0.6;
|
|
131
|
+
return best && best.score >= min ? best : null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
module.exports = { MediaIndex, normTokens, normalizedKey };
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* repair/strategies.js — the §9 failure-mode catalog as individual strategies.
|
|
5
|
+
*
|
|
6
|
+
* Each strategy is pure: { id, klass, deterministic, detect(cut,ctx)->bool,
|
|
7
|
+
* propose(cut,ctx)->{ fix, diagnosis, scope?, confidence? } }. `deterministic`
|
|
8
|
+
* strategies are auto-apply-eligible (subject to re-verify + the §15 gate);
|
|
9
|
+
* the rest are propose-only (human approves).
|
|
10
|
+
*
|
|
11
|
+
* `cut` carries the captured geometry + Oracle outputs; `ctx` carries
|
|
12
|
+
* { ticksPerFrame, sequenceWidth, mediaIndex? }.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const TPF_24 = 10584000000;
|
|
16
|
+
|
|
17
|
+
/** Scale double-count (§9): Premiere scale bakes in seqW/srcW; normalize per source width. */
|
|
18
|
+
const scaleDoubleCount = {
|
|
19
|
+
id: 'normalize-scale-per-source-width',
|
|
20
|
+
klass: 'scale-double-count',
|
|
21
|
+
deterministic: true,
|
|
22
|
+
detect(cut, ctx) {
|
|
23
|
+
if (cut.scale_premiere == null || !cut.srcW || !ctx.sequenceWidth) return false;
|
|
24
|
+
// Signature: the raw scale is ~ the seqW/srcW fit factor (i.e. not yet normalized to ~100).
|
|
25
|
+
const fit = (ctx.sequenceWidth / cut.srcW) * 100;
|
|
26
|
+
return Math.abs(cut.scale_premiere - fit) > 0.5 || cut.scale_premiere > 110 || cut.scale_premiere < 90;
|
|
27
|
+
},
|
|
28
|
+
propose(cut, ctx) {
|
|
29
|
+
const corrected = (cut.scale_premiere * cut.srcW) / ctx.sequenceWidth;
|
|
30
|
+
return { fix: { scale: corrected }, diagnosis: `scale ${cut.scale_premiere} normalized per source width -> ${corrected.toFixed(3)}`, confidence: 1 };
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** Subclip offset (§9): source = startoffset + in; never rewrite <in>. */
|
|
35
|
+
const subclipOffset = {
|
|
36
|
+
id: 'add-subclip-startoffset',
|
|
37
|
+
klass: 'subclip-offset',
|
|
38
|
+
deterministic: true,
|
|
39
|
+
detect(cut) {
|
|
40
|
+
return !!cut.is_subclip;
|
|
41
|
+
},
|
|
42
|
+
propose(cut) {
|
|
43
|
+
const sourceFrame = (cut.subclip_startoffset || 0) + cut.xml_in;
|
|
44
|
+
return { fix: { sourceFrame }, diagnosis: `subclip: source = startoffset ${cut.subclip_startoffset} + in ${cut.xml_in} = ${sourceFrame} (<in> unchanged)`, confidence: 1 };
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/** Slow-mo / ticks != in (§9): derive the sample frame from ticks. */
|
|
49
|
+
const ticksRetime = {
|
|
50
|
+
id: 'derive-sample-from-ticks',
|
|
51
|
+
klass: 'ticks-retime',
|
|
52
|
+
deterministic: true,
|
|
53
|
+
detect(cut, ctx) {
|
|
54
|
+
if (cut.pproTicksIn == null || cut.xml_in == null) return false;
|
|
55
|
+
const tpf = ctx.ticksPerFrame || TPF_24;
|
|
56
|
+
return Math.round(cut.pproTicksIn / tpf) !== cut.xml_in;
|
|
57
|
+
},
|
|
58
|
+
propose(cut, ctx) {
|
|
59
|
+
const tpf = ctx.ticksPerFrame || TPF_24;
|
|
60
|
+
const offset = cut.is_subclip ? cut.subclip_startoffset || 0 : 0;
|
|
61
|
+
const sampleFrame = offset + Math.round(cut.pproTicksIn / tpf);
|
|
62
|
+
return { fix: { sampleFrame }, diagnosis: `retime: sample from ticks = ${sampleFrame} (readback stays <in> ${cut.xml_in})`, confidence: 1 };
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/** Stale relink path (§9): path not found; re-resolve by exact basename via the media index. */
|
|
67
|
+
const staleRelink = {
|
|
68
|
+
id: 'relink-by-exact-basename',
|
|
69
|
+
klass: 'stale-relink',
|
|
70
|
+
deterministic: true,
|
|
71
|
+
detect(cut, ctx) {
|
|
72
|
+
return !!cut.pathMissing && !!ctx.mediaIndex && !!cut.source_basename;
|
|
73
|
+
},
|
|
74
|
+
propose(cut, ctx) {
|
|
75
|
+
const path = ctx.mediaIndex.byBasename ? ctx.mediaIndex.byBasename(cut.source_basename) : null;
|
|
76
|
+
if (!path) return { fix: null, diagnosis: `stale relink: no exact-basename match for ${cut.source_basename}`, confidence: 0 };
|
|
77
|
+
return { fix: { path }, diagnosis: `relinked ${cut.source_basename} -> ${path}`, confidence: 1 };
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Per-file frame offset (§9): the matched frame differs from the derived frame
|
|
83
|
+
* by a CONSTANT across a file's clips. Detect the constant, apply to that file.
|
|
84
|
+
* Propose-only (a measured correction, not deterministic from the XML).
|
|
85
|
+
*/
|
|
86
|
+
const perFileOffset = {
|
|
87
|
+
id: 'apply-per-file-frame-offset',
|
|
88
|
+
klass: 'per-file-offset',
|
|
89
|
+
deterministic: false,
|
|
90
|
+
detect(cut, ctx) {
|
|
91
|
+
return ctx.fileOffsets != null && cut.fileId != null && ctx.fileOffsets[cut.fileId] != null && ctx.fileOffsets[cut.fileId] !== 0;
|
|
92
|
+
},
|
|
93
|
+
propose(cut, ctx) {
|
|
94
|
+
const k = ctx.fileOffsets[cut.fileId];
|
|
95
|
+
return { fix: { sourceFrameDelta: k }, scope: cut.fileId, diagnosis: `per-file offset ${k > 0 ? '+' : ''}${k} applied to file ${cut.fileId}`, confidence: 0.8 };
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/** Estimate a constant per-file offset from verified (derived, matched) samples. */
|
|
100
|
+
function estimatePerFileOffset(samples) {
|
|
101
|
+
if (!samples || !samples.length) return null;
|
|
102
|
+
const deltas = samples.map((s) => s.matched - s.derived);
|
|
103
|
+
const counts = {};
|
|
104
|
+
for (const d of deltas) counts[d] = (counts[d] || 0) + 1;
|
|
105
|
+
const best = Object.keys(counts).sort((a, b) => counts[b] - counts[a])[0];
|
|
106
|
+
const k = Number(best);
|
|
107
|
+
// Only a confident constant offset (majority agree) counts.
|
|
108
|
+
return counts[best] / deltas.length >= 0.6 ? k : null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Aspect variant (§9): source aspect != reference/sequence aspect → reframe. Propose-only. */
|
|
112
|
+
const aspectVariant = {
|
|
113
|
+
id: 'reframe-aspect-variant',
|
|
114
|
+
klass: 'aspect-variant',
|
|
115
|
+
deterministic: false,
|
|
116
|
+
detect(cut, ctx) {
|
|
117
|
+
if (!cut.srcW || !cut.srcH || !ctx.sequenceWidth || !ctx.sequenceHeight) return false;
|
|
118
|
+
const srcAspect = cut.srcW / cut.srcH;
|
|
119
|
+
const seqAspect = ctx.sequenceWidth / ctx.sequenceHeight;
|
|
120
|
+
// Only a LARGE aspect mismatch (e.g. 4:3 vs 16:9) needs a per-clip reframe;
|
|
121
|
+
// a minor fit difference (e.g. 1.568 source in a 5:3 sequence) is handled by scale.
|
|
122
|
+
return Math.abs(srcAspect - seqAspect) / seqAspect > 0.1;
|
|
123
|
+
},
|
|
124
|
+
propose(cut, ctx) {
|
|
125
|
+
// Fit-to-fill the differing dimension; carry a reframe scale/center.
|
|
126
|
+
const fillScale = Math.max(ctx.sequenceWidth / cut.srcW, ctx.sequenceHeight / cut.srcH) / (ctx.sequenceWidth / cut.srcW);
|
|
127
|
+
return { fix: { reframe: { scale: fillScale, center: { h: 0, v: 0 } } }, diagnosis: `aspect ${cut.srcW}x${cut.srcH} != sequence; proposed reframe scale ${fillScale.toFixed(3)}`, confidence: 0.6 };
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Online/offline framing residual (§9): a measured X/Y(/scale) offset on SOME
|
|
133
|
+
* clips. Apply a per-clip position patch ONLY where measured (non-negotiable #4).
|
|
134
|
+
* Propose-only.
|
|
135
|
+
*/
|
|
136
|
+
const measuredResidual = {
|
|
137
|
+
id: 'per-clip-measured-position-patch',
|
|
138
|
+
klass: 'online-offline-residual',
|
|
139
|
+
deterministic: false,
|
|
140
|
+
detect(cut) {
|
|
141
|
+
return !!cut.measuredOffset && (cut.measuredOffset.dx !== 0 || cut.measuredOffset.dy !== 0 || (cut.measuredOffset.ds || 0) !== 0);
|
|
142
|
+
},
|
|
143
|
+
propose(cut) {
|
|
144
|
+
const o = cut.measuredOffset;
|
|
145
|
+
return { fix: { positionPatch: { dx: -o.dx, dy: -o.dy, ds: o.ds ? -o.ds : 0 } }, scope: cut.cutId, diagnosis: `measured residual (${o.dx},${o.dy}) -> per-clip patch (${-o.dx},${-o.dy})`, confidence: 0.7 };
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/** Apply measured patches ONLY to the cuts that have a measurement (measured-only). */
|
|
150
|
+
function applyMeasuredPatches(cuts, measurements) {
|
|
151
|
+
return cuts.map((c) => {
|
|
152
|
+
const m = measurements[c.cutId];
|
|
153
|
+
if (!m) return { cutId: c.cutId, patched: false };
|
|
154
|
+
return { cutId: c.cutId, patched: true, positionPatch: { dx: -m.dx, dy: -m.dy, ds: m.ds ? -m.ds : 0 } };
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Wrong source entirely (§9): content mismatch → use burn-in identity + media index to find the true source. Propose-only. */
|
|
159
|
+
const wrongSource = {
|
|
160
|
+
id: 'find-true-source-by-identity',
|
|
161
|
+
klass: 'wrong-source',
|
|
162
|
+
deterministic: false,
|
|
163
|
+
detect(cut, ctx) {
|
|
164
|
+
return cut.verdict === 'WRONG' && !!ctx.mediaIndex && (!!cut.burnInName || !!cut.source_basename);
|
|
165
|
+
},
|
|
166
|
+
propose(cut, ctx) {
|
|
167
|
+
const name = cut.burnInName || cut.source_basename;
|
|
168
|
+
const found = ctx.mediaIndex.findTrueSource(name, { minScore: 0.5 });
|
|
169
|
+
if (!found) return { fix: null, diagnosis: `wrong source: no true-source match for "${name}"`, confidence: 0 };
|
|
170
|
+
return { fix: { path: found.path, source_basename: found.basename }, diagnosis: `true source for "${name}" -> ${found.basename} (score ${found.score.toFixed(2)})`, confidence: Math.min(0.9, found.score) };
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
/** Reel-name conform (§9): EDL/AAF reel only → reel+TC match via the media index. */
|
|
175
|
+
const reelNameConform = {
|
|
176
|
+
id: 'reel-tc-match',
|
|
177
|
+
klass: 'reel-name-conform',
|
|
178
|
+
deterministic: true,
|
|
179
|
+
detect(cut, ctx) {
|
|
180
|
+
return !!cut.reel && !!ctx.mediaIndex;
|
|
181
|
+
},
|
|
182
|
+
propose(cut, ctx) {
|
|
183
|
+
const path = ctx.mediaIndex.byReelTc(cut.reel, cut.sourceTc);
|
|
184
|
+
if (!path) return { fix: null, diagnosis: `reel ${cut.reel} not found`, confidence: 0 };
|
|
185
|
+
return { fix: { path }, diagnosis: `reel ${cut.reel} @ ${cut.sourceTc} -> ${path}`, confidence: 1 };
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
/** Mixed frame rates (§9): clip rate != sequence rate → rate-aware source-frame mapping. Propose-only. */
|
|
190
|
+
const mixedFrameRate = {
|
|
191
|
+
id: 'rate-aware-frame-map',
|
|
192
|
+
klass: 'mixed-framerate',
|
|
193
|
+
deterministic: false,
|
|
194
|
+
detect(cut, ctx) {
|
|
195
|
+
return cut.clipRate != null && ctx.sequenceRate != null && cut.clipRate !== ctx.sequenceRate;
|
|
196
|
+
},
|
|
197
|
+
propose(cut, ctx) {
|
|
198
|
+
const mapped = Math.round((cut.sourceFrame || cut.xml_in) * (cut.clipRate / ctx.sequenceRate));
|
|
199
|
+
return { fix: { sourceFrame: mapped }, diagnosis: `rate ${cut.clipRate} != seq ${ctx.sequenceRate}; mapped source frame -> ${mapped}`, confidence: 0.7 };
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
/** Handle shortfall (§9): used range + handles near media bounds → warn + clamp. */
|
|
204
|
+
const handleShortfall = {
|
|
205
|
+
id: 'clamp-handles',
|
|
206
|
+
klass: 'handle-shortfall',
|
|
207
|
+
deterministic: true,
|
|
208
|
+
detect(cut) {
|
|
209
|
+
if (cut.mediaLength == null || cut.usedOut == null) return false;
|
|
210
|
+
const want = cut.handles || 0;
|
|
211
|
+
return cut.usedOut + want > cut.mediaLength || (cut.usedIn || 0) - want < 0;
|
|
212
|
+
},
|
|
213
|
+
propose(cut) {
|
|
214
|
+
const want = cut.handles || 0;
|
|
215
|
+
const head = Math.min(want, cut.usedIn || 0);
|
|
216
|
+
const tail = Math.min(want, cut.mediaLength - cut.usedOut);
|
|
217
|
+
return { fix: { handlesHead: head, handlesTail: tail }, diagnosis: `handle shortfall: clamped to head ${head}/tail ${tail} (wanted ${want})`, confidence: 1, warn: true };
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
/** Missing/offline media (§9): no match anywhere → flag to V2 with diagnosis. */
|
|
222
|
+
const missingMedia = {
|
|
223
|
+
id: 'flag-missing-media',
|
|
224
|
+
klass: 'missing-media',
|
|
225
|
+
deterministic: false,
|
|
226
|
+
detect(cut, ctx) {
|
|
227
|
+
return !!cut.pathMissing && (!ctx.mediaIndex || !ctx.mediaIndex.byBasename(cut.source_basename)) && !cut.burnInName;
|
|
228
|
+
},
|
|
229
|
+
propose(cut) {
|
|
230
|
+
return { fix: null, diagnosis: `missing/offline media: ${cut.source_basename || cut.cutId} — flag to V2`, confidence: 0 };
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
module.exports = {
|
|
235
|
+
scaleDoubleCount,
|
|
236
|
+
subclipOffset,
|
|
237
|
+
ticksRetime,
|
|
238
|
+
staleRelink,
|
|
239
|
+
perFileOffset,
|
|
240
|
+
aspectVariant,
|
|
241
|
+
measuredResidual,
|
|
242
|
+
wrongSource,
|
|
243
|
+
reelNameConform,
|
|
244
|
+
mixedFrameRate,
|
|
245
|
+
handleShortfall,
|
|
246
|
+
missingMedia,
|
|
247
|
+
estimatePerFileOffset,
|
|
248
|
+
applyMeasuredPatches,
|
|
249
|
+
TPF_24,
|
|
250
|
+
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* report/ — the UI-agnostic QC report model (spec §11).
|
|
5
|
+
*
|
|
6
|
+
* One report per verify/conform run; one entry per cut. Serializes to the
|
|
7
|
+
* `metadata.editorial.qc` shape (§11) so the API/UI can read it without knowing
|
|
8
|
+
* the engine internals.
|
|
9
|
+
*
|
|
10
|
+
* Status vocabulary (per cut):
|
|
11
|
+
* MATCH | OFFSET | WRONG | UNREADABLE — content-identity verdicts (had a reference)
|
|
12
|
+
* REVIEW — escalated to a human (e.g. vision dispute)
|
|
13
|
+
* MATH-VERIFIED — Tier C: Oracle-consistent, no picture proof
|
|
14
|
+
*
|
|
15
|
+
* Tier (§7): A (burned ref) | B (clean ref + sidecar) | C (no reference).
|
|
16
|
+
*
|
|
17
|
+
* BADGE RULE (non-negotiable, spec §7): "no picture, no content-verified." A
|
|
18
|
+
* `content-verified` badge requires an actual frame comparison (Tier A/B with a
|
|
19
|
+
* MATCH/OFFSET). Tier C can only ever be `math-verified`.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const REPORT_VERDICTS = Object.freeze(['MATCH', 'OFFSET', 'WRONG', 'UNREADABLE', 'REVIEW', 'MATH-VERIFIED']);
|
|
23
|
+
|
|
24
|
+
function makeReport(meta = {}) {
|
|
25
|
+
return {
|
|
26
|
+
runAt: meta.runAt != null ? meta.runAt : null,
|
|
27
|
+
target: meta.target || 'resolve',
|
|
28
|
+
oracleVersion: meta.oracleVersion || '1',
|
|
29
|
+
tier: meta.tier || null,
|
|
30
|
+
summary: { matched: 0, offset: 0, wrong: 0, flagged: 0, review: 0, mathVerified: 0, unreadable: 0 },
|
|
31
|
+
perCut: [],
|
|
32
|
+
packageKeys: [],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The verified badge for a cut. Enforces "no picture, no content-verified":
|
|
38
|
+
* - content-verified ONLY when a reference frame was compared (MATCH/OFFSET).
|
|
39
|
+
* - math-verified when there was no reference (Tier C) and the Oracle ran.
|
|
40
|
+
* - null for WRONG / UNREADABLE / REVIEW (no positive claim).
|
|
41
|
+
*/
|
|
42
|
+
function badgeFor(cut) {
|
|
43
|
+
if (cut.hasReference) {
|
|
44
|
+
if (cut.status === 'MATCH' || cut.status === 'OFFSET') return 'content-verified';
|
|
45
|
+
return null; // a reference existed but it did NOT verify
|
|
46
|
+
}
|
|
47
|
+
// No reference frame — the strongest claim possible is math-verified.
|
|
48
|
+
if (cut.status === 'MATH-VERIFIED' || cut.status === 'MATCH' || cut.status === 'OFFSET') {
|
|
49
|
+
return 'math-verified';
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function addCut(report, cut) {
|
|
55
|
+
if (!REPORT_VERDICTS.includes(cut.status)) {
|
|
56
|
+
throw new Error(`report: unknown status "${cut.status}" (one of ${REPORT_VERDICTS.join('|')})`);
|
|
57
|
+
}
|
|
58
|
+
const badge = badgeFor(cut);
|
|
59
|
+
// Guard the non-negotiable: a no-reference cut can NEVER carry content-verified.
|
|
60
|
+
if (!cut.hasReference && badge === 'content-verified') {
|
|
61
|
+
throw new Error('report: content-verified badge requires a reference frame (no picture, no content-verified)');
|
|
62
|
+
}
|
|
63
|
+
const entry = {
|
|
64
|
+
cutId: cut.cutId,
|
|
65
|
+
seqstart: cut.seqstart != null ? cut.seqstart : null,
|
|
66
|
+
status: cut.status,
|
|
67
|
+
tier: cut.tier || (cut.hasReference ? 'B' : 'C'),
|
|
68
|
+
hasReference: !!cut.hasReference,
|
|
69
|
+
derivedSourceFrame: cut.derivedSourceFrame != null ? cut.derivedSourceFrame : null,
|
|
70
|
+
derivedSampleFrame: cut.derivedSampleFrame != null ? cut.derivedSampleFrame : null,
|
|
71
|
+
scale: cut.scale != null ? cut.scale : null,
|
|
72
|
+
refScore: cut.refScore != null ? cut.refScore : null,
|
|
73
|
+
offset: cut.offset || null,
|
|
74
|
+
diagnosis: cut.diagnosis || null,
|
|
75
|
+
repair: cut.repair || null,
|
|
76
|
+
advisory: cut.advisory || null,
|
|
77
|
+
identity: cut.identity || null,
|
|
78
|
+
escalated: !!cut.escalated,
|
|
79
|
+
visionConsulted: !!cut.visionConsulted,
|
|
80
|
+
finalAction: cut.finalAction || null,
|
|
81
|
+
badge,
|
|
82
|
+
};
|
|
83
|
+
report.perCut.push(entry);
|
|
84
|
+
switch (cut.status) {
|
|
85
|
+
case 'MATCH':
|
|
86
|
+
report.summary.matched += 1;
|
|
87
|
+
break;
|
|
88
|
+
case 'OFFSET':
|
|
89
|
+
report.summary.offset += 1;
|
|
90
|
+
break;
|
|
91
|
+
case 'WRONG':
|
|
92
|
+
report.summary.wrong += 1;
|
|
93
|
+
report.summary.flagged += 1;
|
|
94
|
+
break;
|
|
95
|
+
case 'REVIEW':
|
|
96
|
+
report.summary.review += 1;
|
|
97
|
+
report.summary.flagged += 1;
|
|
98
|
+
break;
|
|
99
|
+
case 'UNREADABLE':
|
|
100
|
+
report.summary.unreadable += 1;
|
|
101
|
+
report.summary.flagged += 1;
|
|
102
|
+
break;
|
|
103
|
+
case 'MATH-VERIFIED':
|
|
104
|
+
report.summary.mathVerified += 1;
|
|
105
|
+
break;
|
|
106
|
+
default:
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
return entry;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Serialize to the documented `metadata.editorial.qc` shape (§11). */
|
|
113
|
+
function toMetadataQc(report) {
|
|
114
|
+
return {
|
|
115
|
+
runAt: report.runAt,
|
|
116
|
+
target: report.target,
|
|
117
|
+
oracleVersion: report.oracleVersion,
|
|
118
|
+
tier: report.tier,
|
|
119
|
+
summary: { ...report.summary },
|
|
120
|
+
perCut: report.perCut.map((c) => ({
|
|
121
|
+
cutId: c.cutId,
|
|
122
|
+
status: c.status,
|
|
123
|
+
derivedFrame: c.derivedSourceFrame,
|
|
124
|
+
refScore: c.refScore,
|
|
125
|
+
diagnosis: c.diagnosis,
|
|
126
|
+
repair: c.repair,
|
|
127
|
+
badge: c.badge,
|
|
128
|
+
})),
|
|
129
|
+
packageKeys: [...report.packageKeys],
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
module.exports = { REPORT_VERDICTS, makeReport, badgeFor, addCut, toMetadataQc };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* roles/ — finishing-media resolution (spec §10). Pure contracts + algorithms.
|
|
5
|
+
*
|
|
6
|
+
* Two mechanisms (decided): a per-version `editorialRole`, and a cross-asset
|
|
7
|
+
* `MediaRelationship` (the load-bearing proxy↔scan link with a frameOffset).
|
|
8
|
+
* The Prisma fields are thin bindings to these contracts; the resolution ORDER
|
|
9
|
+
* and ingest heuristic live here and are fully verifiable without a DB.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const EDITORIAL_ROLES = Object.freeze(['offline', 'proxy', 'online', 'finishing', 'reference', 'other']);
|
|
13
|
+
const MEDIA_RELATIONSHIP_KINDS = Object.freeze(['proxy_of', 'scan_of', 'vfx_of', 'upscale_of', 'regrade_of']);
|
|
14
|
+
// Relationship kinds that point from a working asset TO its finishing media.
|
|
15
|
+
const FINISHING_KINDS = Object.freeze(['scan_of', 'upscale_of', 'vfx_of', 'regrade_of']);
|
|
16
|
+
|
|
17
|
+
function isValidRole(r) {
|
|
18
|
+
return EDITORIAL_ROLES.includes(r);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function makeMediaRelationship({ fromAssetId, toAssetId, kind, frameOffset = 0, notes = '' }) {
|
|
22
|
+
if (!fromAssetId || !toAssetId) throw new Error('MediaRelationship: fromAssetId + toAssetId required');
|
|
23
|
+
if (!MEDIA_RELATIONSHIP_KINDS.includes(kind)) throw new Error(`MediaRelationship: kind must be one of ${MEDIA_RELATIONSHIP_KINDS.join('|')}`);
|
|
24
|
+
return { type: 'MediaRelationship', fromAssetId, toAssetId, kind, frameOffset, notes };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** In-memory store (the Prisma table is a thin binding to this). */
|
|
28
|
+
class MediaRelationshipStore {
|
|
29
|
+
constructor(seed = []) {
|
|
30
|
+
this.rels = seed.map((r) => (r.type === 'MediaRelationship' ? r : makeMediaRelationship(r)));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
add(rel) {
|
|
34
|
+
const r = rel.type === 'MediaRelationship' ? rel : makeMediaRelationship(rel);
|
|
35
|
+
this.rels.push(r);
|
|
36
|
+
return r;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
forAsset(assetId) {
|
|
40
|
+
return this.rels.filter((r) => r.fromAssetId === assetId || r.toAssetId === assetId);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Resolution order (§10): which media does this reference conform to?
|
|
46
|
+
* explicit MediaRelationship → project override → version editorialRole
|
|
47
|
+
* (finishing→online) → naming convention (4K-2K→4K) → highest-resolution version.
|
|
48
|
+
* @returns { assetId?, path?, via, frameOffset? }
|
|
49
|
+
*/
|
|
50
|
+
function resolveMedia(ref, ctx = {}) {
|
|
51
|
+
const versions = ref.versions || [];
|
|
52
|
+
|
|
53
|
+
// 1. explicit MediaRelationship to a finishing asset.
|
|
54
|
+
if (ctx.relationships) {
|
|
55
|
+
const rel = ctx.relationships.forAsset(ref.assetId).find((r) => r.fromAssetId === ref.assetId && FINISHING_KINDS.includes(r.kind));
|
|
56
|
+
if (rel) {
|
|
57
|
+
const p = ctx.resolveAssetPath ? ctx.resolveAssetPath(rel.toAssetId) : null;
|
|
58
|
+
return { assetId: rel.toAssetId, path: p, via: 'relationship', frameOffset: rel.frameOffset };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// 2. project override map.
|
|
62
|
+
if (ctx.projectOverride && ctx.projectOverride[ref.assetId] != null) {
|
|
63
|
+
return { path: ctx.projectOverride[ref.assetId], via: 'project-override' };
|
|
64
|
+
}
|
|
65
|
+
// 3. version editorialRole (finishing preferred, then online).
|
|
66
|
+
const byRole = versions.find((v) => v.role === 'finishing') || versions.find((v) => v.role === 'online');
|
|
67
|
+
if (byRole) return { path: byRole.path, via: 'role', role: byRole.role };
|
|
68
|
+
// 4. naming convention: a "4K" version (not the "4K-2K" proxy).
|
|
69
|
+
const scan = versions.find((v) => /\b4k\b/i.test(v.name || '') && !/4k-2k/i.test(v.name || ''));
|
|
70
|
+
if (scan) return { path: scan.path, via: 'naming' };
|
|
71
|
+
// 5. highest-resolution version.
|
|
72
|
+
const top = versions.reduce((best, v) => (best == null || (v.width || 0) > (best.width || 0) ? v : best), null);
|
|
73
|
+
return top ? { path: top.path, via: 'highest-res' } : { path: null, via: 'none' };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Ingest heuristic (§10): seed a default role/relationship from res/codec/name.
|
|
78
|
+
* Returns { role, relationship? } suggestions (user corrects via the UI picker).
|
|
79
|
+
*/
|
|
80
|
+
function ingestHeuristic(asset, opts = {}) {
|
|
81
|
+
const name = (asset.name || '').toLowerCase();
|
|
82
|
+
const w = asset.width || 0;
|
|
83
|
+
let role = 'other';
|
|
84
|
+
if (/proxy|4k-2k/.test(name) || (w > 0 && w <= 1024)) role = 'proxy';
|
|
85
|
+
else if (/\b4k\b/.test(name) || w >= 3600) role = 'finishing';
|
|
86
|
+
else if (/online|finish/.test(name)) role = 'online';
|
|
87
|
+
else if (/offline/.test(name)) role = 'offline';
|
|
88
|
+
const out = { role };
|
|
89
|
+
// If a sibling finishing asset is offered, seed the proxy↔scan relationship.
|
|
90
|
+
if (role === 'proxy' && opts.finishingAssetId) {
|
|
91
|
+
out.relationship = makeMediaRelationship({ fromAssetId: asset.id, toAssetId: opts.finishingAssetId, kind: 'scan_of', frameOffset: opts.frameOffset || 0, notes: 'ingest heuristic' });
|
|
92
|
+
}
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
module.exports = { EDITORIAL_ROLES, MEDIA_RELATIONSHIP_KINDS, isValidRole, makeMediaRelationship, MediaRelationshipStore, resolveMedia, ingestHeuristic };
|