@camstack/addon-pipeline 1.1.0 → 1.1.2
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/dist/audio-analyzer/index.js +104 -29
- package/dist/audio-analyzer/index.mjs +100 -25
- package/dist/audio-codec-nodeav/index.js +1 -1
- package/dist/audio-codec-nodeav/index.mjs +1 -1
- package/dist/decoder-nodeav/index.js +1 -1
- package/dist/decoder-nodeav/index.mjs +1 -1
- package/dist/detection-pipeline/index.js +355 -116
- package/dist/detection-pipeline/index.mjs +343 -104
- package/dist/{dist-BA6DR_jV.mjs → dist-BWc-HYQz.mjs} +194 -1
- package/dist/{dist-BLcTVvol.js → dist-DnD2tm7T.js} +194 -1
- package/dist/{model-download-service-RxAOiYvX-CMAvhgO7.mjs → model-download-service-C-IHWnXx-3Mmeob3l.mjs} +1 -1
- package/dist/{model-download-service-RxAOiYvX-C8rTRJy_.js → model-download-service-C-IHWnXx-BnQ_awK4.js} +1 -1
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +14 -10
- package/dist/pipeline-runner/index.mjs +14 -10
- package/dist/recorder/index.js +4 -4
- package/dist/recorder/index.mjs +2 -2
- package/dist/stream-broker/_stub.js +1 -1
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DrohyZ5L.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DLgk22-S.mjs} +3 -3
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-FRD2eBuz.mjs +26 -0
- package/dist/stream-broker/{hostInit-zLZbYJcg.mjs → hostInit-pRSjUAJj.mjs} +3 -3
- package/dist/stream-broker/index.js +8 -8
- package/dist/stream-broker/index.mjs +2 -2
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-DJ7ztnFv.js → MaskShapeCanvas-DI4BY7W2-5UPreLSr.js} +1 -1
- package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-CQzEnQoq.js → MotionZonesSettings-NcxxQN8r-Bxqs-CpZ.js} +1 -1
- package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-Cl0eOy_U.js → PrivacyMaskSettings-APgPLF7p-BDMPeMJd.js} +1 -1
- package/embed-dist/assets/{index-CSuLwWK-.js → index-BgGwqHYl.js} +9 -9
- package/embed-dist/index.html +1 -1
- package/package.json +1 -1
- package/python/postprocessors/saliency.py +47 -1
- package/python/postprocessors/test_saliency.py +23 -0
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-COa17XL2.mjs +0 -26
|
@@ -2,12 +2,12 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const
|
|
6
|
-
const require_dist = require("../dist-
|
|
5
|
+
const require_model_download_service_C_IHWnXx = require("../model-download-service-C-IHWnXx-BnQ_awK4.js");
|
|
6
|
+
const require_dist = require("../dist-DnD2tm7T.js");
|
|
7
7
|
let node_fs = require("node:fs");
|
|
8
|
-
node_fs =
|
|
8
|
+
node_fs = require_model_download_service_C_IHWnXx.__toESM(node_fs);
|
|
9
9
|
let node_path = require("node:path");
|
|
10
|
-
node_path =
|
|
10
|
+
node_path = require_model_download_service_C_IHWnXx.__toESM(node_path);
|
|
11
11
|
//#region src/audio-analyzer/audio-pipeline.ts
|
|
12
12
|
/**
|
|
13
13
|
* Create the appropriate audio pipeline.
|
|
@@ -82,7 +82,7 @@ var YamnetPythonPipeline = class {
|
|
|
82
82
|
url: YAMNET_MODEL_URL,
|
|
83
83
|
dest: modelPath
|
|
84
84
|
} });
|
|
85
|
-
await
|
|
85
|
+
await require_model_download_service_C_IHWnXx.downloadFile(YAMNET_MODEL_URL, modelPath);
|
|
86
86
|
this.log.info("YAMNet ONNX model downloaded", { meta: { sizeBytes: node_fs.statSync(modelPath).size } });
|
|
87
87
|
}
|
|
88
88
|
if (!node_fs.existsSync(labelsPath)) {
|
|
@@ -90,7 +90,7 @@ var YamnetPythonPipeline = class {
|
|
|
90
90
|
url: YAMNET_LABELS_URL,
|
|
91
91
|
dest: labelsPath
|
|
92
92
|
} });
|
|
93
|
-
await
|
|
93
|
+
await require_model_download_service_C_IHWnXx.downloadFile(YAMNET_LABELS_URL, labelsPath);
|
|
94
94
|
}
|
|
95
95
|
const pythonDir = resolveAudioPythonDir();
|
|
96
96
|
if (this.installPythonRequirements) await this.installPythonRequirements(node_path.join(pythonDir, "requirements-audio.txt"));
|
|
@@ -623,6 +623,20 @@ var AudioAnalyzerProvider = class {
|
|
|
623
623
|
async dispose() {
|
|
624
624
|
await this.pipeline.dispose();
|
|
625
625
|
}
|
|
626
|
+
/**
|
|
627
|
+
* Swap the live pipeline in place (optimistic backend/model change) — disposes
|
|
628
|
+
* the old pipeline AFTER the new one is wired in, so an in-flight classify
|
|
629
|
+
* never sees a null pipeline. Lets the audio engine settings apply without an
|
|
630
|
+
* addon restart, mirroring the detection-pipeline's in-place engine swap.
|
|
631
|
+
*/
|
|
632
|
+
async swapPipeline(next, nextBackend) {
|
|
633
|
+
const prev = this.pipeline;
|
|
634
|
+
this.pipeline = next;
|
|
635
|
+
this.backendName = nextBackend;
|
|
636
|
+
if (prev && prev !== next) try {
|
|
637
|
+
await prev.dispose();
|
|
638
|
+
} catch {}
|
|
639
|
+
}
|
|
626
640
|
async reprobeAudioEngine() {
|
|
627
641
|
return this.reprobeImpl();
|
|
628
642
|
}
|
|
@@ -637,6 +651,9 @@ var AudioAnalyzerAddon = class extends require_dist.BaseAddon {
|
|
|
637
651
|
id = "audio-analyzer";
|
|
638
652
|
provider = null;
|
|
639
653
|
pipeline = null;
|
|
654
|
+
/** Backend the live `pipeline` actually runs — drives the optimistic in-place
|
|
655
|
+
* rebuild in `onConfigChanged` (rebuild only when it genuinely flips). */
|
|
656
|
+
activeBackend = null;
|
|
640
657
|
constructor() {
|
|
641
658
|
super(require_dist.DEFAULT_AUDIO_ANALYZER_CONFIG);
|
|
642
659
|
}
|
|
@@ -670,8 +687,7 @@ var AudioAnalyzerAddon = class extends require_dist.BaseAddon {
|
|
|
670
687
|
label: o.label
|
|
671
688
|
})),
|
|
672
689
|
default: require_dist.DEFAULT_AUDIO_ANALYZER_CONFIG.audioBackend,
|
|
673
|
-
immediate: true
|
|
674
|
-
requiresRestart: true
|
|
690
|
+
immediate: true
|
|
675
691
|
},
|
|
676
692
|
{
|
|
677
693
|
type: "select",
|
|
@@ -683,8 +699,7 @@ var AudioAnalyzerAddon = class extends require_dist.BaseAddon {
|
|
|
683
699
|
label: o.label
|
|
684
700
|
})),
|
|
685
701
|
default: require_dist.DEFAULT_AUDIO_ANALYZER_CONFIG.selectedAudioModel,
|
|
686
|
-
immediate: true
|
|
687
|
-
requiresRestart: true
|
|
702
|
+
immediate: true
|
|
688
703
|
}
|
|
689
704
|
]
|
|
690
705
|
}] };
|
|
@@ -704,20 +719,24 @@ var AudioAnalyzerAddon = class extends require_dist.BaseAddon {
|
|
|
704
719
|
* preview mode (operator typed but didn't save yet) — same
|
|
705
720
|
* semantics detection-pipeline relies on.
|
|
706
721
|
*/
|
|
707
|
-
async getGlobalSettings(overlay) {
|
|
722
|
+
async getGlobalSettings(overlay, nodeId) {
|
|
708
723
|
const ctx = this.ctxIfReady;
|
|
709
724
|
const stored = ctx?.settings ? await ctx.settings.readAddonStore() ?? {} : {};
|
|
710
725
|
const merged = overlay ? {
|
|
711
726
|
...stored,
|
|
712
727
|
...overlay
|
|
713
728
|
} : stored;
|
|
714
|
-
const
|
|
715
|
-
const
|
|
716
|
-
const
|
|
729
|
+
const isDarwin = await this.probeNodePlatform(nodeId) === "darwin";
|
|
730
|
+
const backendOptions = require_dist.AUDIO_BACKEND_CHOICES.filter((o) => o.value !== "apple-soundanalysis" || isDarwin);
|
|
731
|
+
const operatorChoiceRaw = typeof merged.audioBackend === "string" ? merged.audioBackend : require_dist.DEFAULT_AUDIO_ANALYZER_CONFIG.audioBackend;
|
|
732
|
+
const operatorChoice = backendOptions.some((o) => o.value === operatorChoiceRaw) ? operatorChoiceRaw : require_dist.DEFAULT_AUDIO_ANALYZER_CONFIG.audioBackend;
|
|
733
|
+
const effectiveBackend = operatorChoice === "apple-soundanalysis" ? "apple-soundanalysis" : operatorChoice === "yamnet-onnx" ? "yamnet-onnx" : isDarwin ? "apple-soundanalysis" : "yamnet-onnx";
|
|
734
|
+
const filteredModels = AUDIO_MODEL_OPTIONS.filter((o) => (o.value === "" || o.value === effectiveBackend) && (o.value !== "apple-soundanalysis" || isDarwin));
|
|
717
735
|
const storedModel = typeof merged.selectedAudioModel === "string" ? merged.selectedAudioModel : "";
|
|
718
736
|
const validModel = filteredModels.find((o) => o.value === storedModel)?.value ?? "";
|
|
719
737
|
const raw = {
|
|
720
738
|
...merged,
|
|
739
|
+
audioBackend: operatorChoice,
|
|
721
740
|
selectedAudioModel: validModel
|
|
722
741
|
};
|
|
723
742
|
const schema = this.globalSettingsSchema();
|
|
@@ -726,6 +745,13 @@ var AudioAnalyzerAddon = class extends require_dist.BaseAddon {
|
|
|
726
745
|
sections: schema.sections.map((section) => ({
|
|
727
746
|
...section,
|
|
728
747
|
fields: section.fields.map((field) => {
|
|
748
|
+
if (field.type === "select" && field.key === "audioBackend") return {
|
|
749
|
+
...field,
|
|
750
|
+
options: backendOptions.map((o) => ({
|
|
751
|
+
value: o.value,
|
|
752
|
+
label: o.label
|
|
753
|
+
}))
|
|
754
|
+
};
|
|
729
755
|
if (field.type === "select" && field.key === "selectedAudioModel") return {
|
|
730
756
|
...field,
|
|
731
757
|
options: filteredModels.map((o) => ({
|
|
@@ -739,6 +765,23 @@ var AudioAnalyzerAddon = class extends require_dist.BaseAddon {
|
|
|
739
765
|
}, raw);
|
|
740
766
|
}
|
|
741
767
|
/**
|
|
768
|
+
* Probe the OS platform of `nodeId` (default: this node) via the
|
|
769
|
+
* `platform-probe` cap — the same source the detection-pipeline uses to gate
|
|
770
|
+
* accelerator backends. Falls back to this host's `process.platform` when the
|
|
771
|
+
* probe is unreachable (cold-start / probe addon absent), so the form stays
|
|
772
|
+
* usable rather than collapsing.
|
|
773
|
+
*/
|
|
774
|
+
async probeNodePlatform(nodeId) {
|
|
775
|
+
const api = this.ctxIfReady?.api;
|
|
776
|
+
if (!api) return process.platform;
|
|
777
|
+
const localNode = this.ctxIfReady?.kernel?.localNodeId;
|
|
778
|
+
try {
|
|
779
|
+
return (nodeId && nodeId !== localNode ? await api.platformProbe.getCapabilities.query(void 0, require_dist.nodePin(nodeId)) : await api.platformProbe.getCapabilities.query())?.platform ?? process.platform;
|
|
780
|
+
} catch {
|
|
781
|
+
return process.platform;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
742
785
|
* Re-run the platform probe and persist the detected backend into
|
|
743
786
|
* `probedBestAudioBackend`. Operator `audioBackend` setting is not
|
|
744
787
|
* touched — only the hint.
|
|
@@ -758,23 +801,10 @@ var AudioAnalyzerAddon = class extends require_dist.BaseAddon {
|
|
|
758
801
|
}
|
|
759
802
|
async onInitialize() {
|
|
760
803
|
const logger = this.ctx.logger;
|
|
761
|
-
const modelsDir = await this.ctx.api.storage.resolve.query({
|
|
762
|
-
location: "models",
|
|
763
|
-
relativePath: ""
|
|
764
|
-
}).catch(() => "camstack-data/models");
|
|
765
804
|
const backend = this.resolveAudioBackend();
|
|
766
|
-
|
|
767
|
-
operatorChoice: this.config.audioBackend,
|
|
768
|
-
effectiveBackend: backend,
|
|
769
|
-
selectedModel: this.config.selectedAudioModel || null
|
|
770
|
-
} });
|
|
771
|
-
const p = await createAudioPipeline(modelsDir, logger, {
|
|
772
|
-
backend,
|
|
773
|
-
pythonPath: backend === "yamnet-onnx" ? await this.ctx.deps.ensurePython() ?? void 0 : void 0,
|
|
774
|
-
installPythonRequirements: (f) => this.ctx.deps.installPythonRequirements(f)
|
|
775
|
-
});
|
|
776
|
-
await p.initialize();
|
|
805
|
+
const p = await this.buildPipeline(backend);
|
|
777
806
|
this.pipeline = p;
|
|
807
|
+
this.activeBackend = backend;
|
|
778
808
|
if (!this.config.probedBestAudioBackend) this.reprobeAudioEngine().catch((err) => {
|
|
779
809
|
logger.warn("audio: auto-reprobe failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
780
810
|
});
|
|
@@ -812,6 +842,51 @@ var AudioAnalyzerAddon = class extends require_dist.BaseAddon {
|
|
|
812
842
|
this.provider = null;
|
|
813
843
|
}
|
|
814
844
|
this.pipeline = null;
|
|
845
|
+
this.activeBackend = null;
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* Build + initialize an audio pipeline for `backend`. YAMNet runs in the
|
|
849
|
+
* embedded portable Python (resolve interpreter + lazily install reqs); Apple
|
|
850
|
+
* SoundAnalysis needs neither. Shared by `onInitialize` (first boot) and
|
|
851
|
+
* `onConfigChanged` (in-place rebuild).
|
|
852
|
+
*/
|
|
853
|
+
async buildPipeline(backend) {
|
|
854
|
+
const logger = this.ctx.logger;
|
|
855
|
+
const modelsDir = await this.ctx.api.storage.resolve.query({
|
|
856
|
+
location: "models",
|
|
857
|
+
relativePath: ""
|
|
858
|
+
}).catch(() => "camstack-data/models");
|
|
859
|
+
logger.info("audio-analyzer: resolving pipeline", { meta: {
|
|
860
|
+
operatorChoice: this.config.audioBackend,
|
|
861
|
+
effectiveBackend: backend,
|
|
862
|
+
selectedModel: this.config.selectedAudioModel || null
|
|
863
|
+
} });
|
|
864
|
+
const p = await createAudioPipeline(modelsDir, logger, {
|
|
865
|
+
backend,
|
|
866
|
+
pythonPath: backend === "yamnet-onnx" ? await this.ctx.deps.ensurePython() ?? void 0 : void 0,
|
|
867
|
+
installPythonRequirements: (f) => this.ctx.deps.installPythonRequirements(f)
|
|
868
|
+
});
|
|
869
|
+
await p.initialize();
|
|
870
|
+
return p;
|
|
871
|
+
}
|
|
872
|
+
/**
|
|
873
|
+
* Optimistic settings apply — rebuild the pipeline IN PLACE when the effective
|
|
874
|
+
* audio backend changes, instead of bouncing the whole addon. The audio engine
|
|
875
|
+
* fields therefore drop `requiresRestart`: BaseAddon calls this after every
|
|
876
|
+
* global-settings write; we rebuild only when the backend genuinely flips.
|
|
877
|
+
*/
|
|
878
|
+
async onConfigChanged() {
|
|
879
|
+
if (!this.provider) return;
|
|
880
|
+
const backend = this.resolveAudioBackend();
|
|
881
|
+
if (backend === this.activeBackend) return;
|
|
882
|
+
this.ctx.logger.info("audio backend changed — rebuilding pipeline in place", { meta: {
|
|
883
|
+
from: this.activeBackend,
|
|
884
|
+
to: backend
|
|
885
|
+
} });
|
|
886
|
+
const next = await this.buildPipeline(backend);
|
|
887
|
+
await this.provider.swapPipeline(next, backend);
|
|
888
|
+
this.pipeline = next;
|
|
889
|
+
this.activeBackend = backend;
|
|
815
890
|
}
|
|
816
891
|
buildDeviceSchema() {
|
|
817
892
|
return { sections: [] };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as __require } from "../chunk-BdkLduGY.mjs";
|
|
2
|
-
import { B as BaseAddon, E as mapAudioLabelToMacro, J as hydrateSchema, g as audioAnalyzerCapability, h as audioAnalysisCapability, l as HF_BASE_URL, n as AUDIO_BACKEND_CHOICES, o as DEFAULT_AUDIO_ANALYZER_CONFIG, z as errMsg } from "../dist-
|
|
3
|
-
import { r as downloadFile } from "../model-download-service-
|
|
2
|
+
import { A as nodePin, B as BaseAddon, E as mapAudioLabelToMacro, J as hydrateSchema, g as audioAnalyzerCapability, h as audioAnalysisCapability, l as HF_BASE_URL, n as AUDIO_BACKEND_CHOICES, o as DEFAULT_AUDIO_ANALYZER_CONFIG, z as errMsg } from "../dist-BWc-HYQz.mjs";
|
|
3
|
+
import { r as downloadFile } from "../model-download-service-C-IHWnXx-3Mmeob3l.mjs";
|
|
4
4
|
import * as fs from "node:fs";
|
|
5
5
|
import * as path$1 from "node:path";
|
|
6
6
|
//#region src/audio-analyzer/audio-pipeline.ts
|
|
@@ -618,6 +618,20 @@ var AudioAnalyzerProvider = class {
|
|
|
618
618
|
async dispose() {
|
|
619
619
|
await this.pipeline.dispose();
|
|
620
620
|
}
|
|
621
|
+
/**
|
|
622
|
+
* Swap the live pipeline in place (optimistic backend/model change) — disposes
|
|
623
|
+
* the old pipeline AFTER the new one is wired in, so an in-flight classify
|
|
624
|
+
* never sees a null pipeline. Lets the audio engine settings apply without an
|
|
625
|
+
* addon restart, mirroring the detection-pipeline's in-place engine swap.
|
|
626
|
+
*/
|
|
627
|
+
async swapPipeline(next, nextBackend) {
|
|
628
|
+
const prev = this.pipeline;
|
|
629
|
+
this.pipeline = next;
|
|
630
|
+
this.backendName = nextBackend;
|
|
631
|
+
if (prev && prev !== next) try {
|
|
632
|
+
await prev.dispose();
|
|
633
|
+
} catch {}
|
|
634
|
+
}
|
|
621
635
|
async reprobeAudioEngine() {
|
|
622
636
|
return this.reprobeImpl();
|
|
623
637
|
}
|
|
@@ -632,6 +646,9 @@ var AudioAnalyzerAddon = class extends BaseAddon {
|
|
|
632
646
|
id = "audio-analyzer";
|
|
633
647
|
provider = null;
|
|
634
648
|
pipeline = null;
|
|
649
|
+
/** Backend the live `pipeline` actually runs — drives the optimistic in-place
|
|
650
|
+
* rebuild in `onConfigChanged` (rebuild only when it genuinely flips). */
|
|
651
|
+
activeBackend = null;
|
|
635
652
|
constructor() {
|
|
636
653
|
super(DEFAULT_AUDIO_ANALYZER_CONFIG);
|
|
637
654
|
}
|
|
@@ -665,8 +682,7 @@ var AudioAnalyzerAddon = class extends BaseAddon {
|
|
|
665
682
|
label: o.label
|
|
666
683
|
})),
|
|
667
684
|
default: DEFAULT_AUDIO_ANALYZER_CONFIG.audioBackend,
|
|
668
|
-
immediate: true
|
|
669
|
-
requiresRestart: true
|
|
685
|
+
immediate: true
|
|
670
686
|
},
|
|
671
687
|
{
|
|
672
688
|
type: "select",
|
|
@@ -678,8 +694,7 @@ var AudioAnalyzerAddon = class extends BaseAddon {
|
|
|
678
694
|
label: o.label
|
|
679
695
|
})),
|
|
680
696
|
default: DEFAULT_AUDIO_ANALYZER_CONFIG.selectedAudioModel,
|
|
681
|
-
immediate: true
|
|
682
|
-
requiresRestart: true
|
|
697
|
+
immediate: true
|
|
683
698
|
}
|
|
684
699
|
]
|
|
685
700
|
}] };
|
|
@@ -699,20 +714,24 @@ var AudioAnalyzerAddon = class extends BaseAddon {
|
|
|
699
714
|
* preview mode (operator typed but didn't save yet) — same
|
|
700
715
|
* semantics detection-pipeline relies on.
|
|
701
716
|
*/
|
|
702
|
-
async getGlobalSettings(overlay) {
|
|
717
|
+
async getGlobalSettings(overlay, nodeId) {
|
|
703
718
|
const ctx = this.ctxIfReady;
|
|
704
719
|
const stored = ctx?.settings ? await ctx.settings.readAddonStore() ?? {} : {};
|
|
705
720
|
const merged = overlay ? {
|
|
706
721
|
...stored,
|
|
707
722
|
...overlay
|
|
708
723
|
} : stored;
|
|
709
|
-
const
|
|
710
|
-
const
|
|
711
|
-
const
|
|
724
|
+
const isDarwin = await this.probeNodePlatform(nodeId) === "darwin";
|
|
725
|
+
const backendOptions = AUDIO_BACKEND_CHOICES.filter((o) => o.value !== "apple-soundanalysis" || isDarwin);
|
|
726
|
+
const operatorChoiceRaw = typeof merged.audioBackend === "string" ? merged.audioBackend : DEFAULT_AUDIO_ANALYZER_CONFIG.audioBackend;
|
|
727
|
+
const operatorChoice = backendOptions.some((o) => o.value === operatorChoiceRaw) ? operatorChoiceRaw : DEFAULT_AUDIO_ANALYZER_CONFIG.audioBackend;
|
|
728
|
+
const effectiveBackend = operatorChoice === "apple-soundanalysis" ? "apple-soundanalysis" : operatorChoice === "yamnet-onnx" ? "yamnet-onnx" : isDarwin ? "apple-soundanalysis" : "yamnet-onnx";
|
|
729
|
+
const filteredModels = AUDIO_MODEL_OPTIONS.filter((o) => (o.value === "" || o.value === effectiveBackend) && (o.value !== "apple-soundanalysis" || isDarwin));
|
|
712
730
|
const storedModel = typeof merged.selectedAudioModel === "string" ? merged.selectedAudioModel : "";
|
|
713
731
|
const validModel = filteredModels.find((o) => o.value === storedModel)?.value ?? "";
|
|
714
732
|
const raw = {
|
|
715
733
|
...merged,
|
|
734
|
+
audioBackend: operatorChoice,
|
|
716
735
|
selectedAudioModel: validModel
|
|
717
736
|
};
|
|
718
737
|
const schema = this.globalSettingsSchema();
|
|
@@ -721,6 +740,13 @@ var AudioAnalyzerAddon = class extends BaseAddon {
|
|
|
721
740
|
sections: schema.sections.map((section) => ({
|
|
722
741
|
...section,
|
|
723
742
|
fields: section.fields.map((field) => {
|
|
743
|
+
if (field.type === "select" && field.key === "audioBackend") return {
|
|
744
|
+
...field,
|
|
745
|
+
options: backendOptions.map((o) => ({
|
|
746
|
+
value: o.value,
|
|
747
|
+
label: o.label
|
|
748
|
+
}))
|
|
749
|
+
};
|
|
724
750
|
if (field.type === "select" && field.key === "selectedAudioModel") return {
|
|
725
751
|
...field,
|
|
726
752
|
options: filteredModels.map((o) => ({
|
|
@@ -734,6 +760,23 @@ var AudioAnalyzerAddon = class extends BaseAddon {
|
|
|
734
760
|
}, raw);
|
|
735
761
|
}
|
|
736
762
|
/**
|
|
763
|
+
* Probe the OS platform of `nodeId` (default: this node) via the
|
|
764
|
+
* `platform-probe` cap — the same source the detection-pipeline uses to gate
|
|
765
|
+
* accelerator backends. Falls back to this host's `process.platform` when the
|
|
766
|
+
* probe is unreachable (cold-start / probe addon absent), so the form stays
|
|
767
|
+
* usable rather than collapsing.
|
|
768
|
+
*/
|
|
769
|
+
async probeNodePlatform(nodeId) {
|
|
770
|
+
const api = this.ctxIfReady?.api;
|
|
771
|
+
if (!api) return process.platform;
|
|
772
|
+
const localNode = this.ctxIfReady?.kernel?.localNodeId;
|
|
773
|
+
try {
|
|
774
|
+
return (nodeId && nodeId !== localNode ? await api.platformProbe.getCapabilities.query(void 0, nodePin(nodeId)) : await api.platformProbe.getCapabilities.query())?.platform ?? process.platform;
|
|
775
|
+
} catch {
|
|
776
|
+
return process.platform;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
737
780
|
* Re-run the platform probe and persist the detected backend into
|
|
738
781
|
* `probedBestAudioBackend`. Operator `audioBackend` setting is not
|
|
739
782
|
* touched — only the hint.
|
|
@@ -753,23 +796,10 @@ var AudioAnalyzerAddon = class extends BaseAddon {
|
|
|
753
796
|
}
|
|
754
797
|
async onInitialize() {
|
|
755
798
|
const logger = this.ctx.logger;
|
|
756
|
-
const modelsDir = await this.ctx.api.storage.resolve.query({
|
|
757
|
-
location: "models",
|
|
758
|
-
relativePath: ""
|
|
759
|
-
}).catch(() => "camstack-data/models");
|
|
760
799
|
const backend = this.resolveAudioBackend();
|
|
761
|
-
|
|
762
|
-
operatorChoice: this.config.audioBackend,
|
|
763
|
-
effectiveBackend: backend,
|
|
764
|
-
selectedModel: this.config.selectedAudioModel || null
|
|
765
|
-
} });
|
|
766
|
-
const p = await createAudioPipeline(modelsDir, logger, {
|
|
767
|
-
backend,
|
|
768
|
-
pythonPath: backend === "yamnet-onnx" ? await this.ctx.deps.ensurePython() ?? void 0 : void 0,
|
|
769
|
-
installPythonRequirements: (f) => this.ctx.deps.installPythonRequirements(f)
|
|
770
|
-
});
|
|
771
|
-
await p.initialize();
|
|
800
|
+
const p = await this.buildPipeline(backend);
|
|
772
801
|
this.pipeline = p;
|
|
802
|
+
this.activeBackend = backend;
|
|
773
803
|
if (!this.config.probedBestAudioBackend) this.reprobeAudioEngine().catch((err) => {
|
|
774
804
|
logger.warn("audio: auto-reprobe failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
775
805
|
});
|
|
@@ -807,6 +837,51 @@ var AudioAnalyzerAddon = class extends BaseAddon {
|
|
|
807
837
|
this.provider = null;
|
|
808
838
|
}
|
|
809
839
|
this.pipeline = null;
|
|
840
|
+
this.activeBackend = null;
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* Build + initialize an audio pipeline for `backend`. YAMNet runs in the
|
|
844
|
+
* embedded portable Python (resolve interpreter + lazily install reqs); Apple
|
|
845
|
+
* SoundAnalysis needs neither. Shared by `onInitialize` (first boot) and
|
|
846
|
+
* `onConfigChanged` (in-place rebuild).
|
|
847
|
+
*/
|
|
848
|
+
async buildPipeline(backend) {
|
|
849
|
+
const logger = this.ctx.logger;
|
|
850
|
+
const modelsDir = await this.ctx.api.storage.resolve.query({
|
|
851
|
+
location: "models",
|
|
852
|
+
relativePath: ""
|
|
853
|
+
}).catch(() => "camstack-data/models");
|
|
854
|
+
logger.info("audio-analyzer: resolving pipeline", { meta: {
|
|
855
|
+
operatorChoice: this.config.audioBackend,
|
|
856
|
+
effectiveBackend: backend,
|
|
857
|
+
selectedModel: this.config.selectedAudioModel || null
|
|
858
|
+
} });
|
|
859
|
+
const p = await createAudioPipeline(modelsDir, logger, {
|
|
860
|
+
backend,
|
|
861
|
+
pythonPath: backend === "yamnet-onnx" ? await this.ctx.deps.ensurePython() ?? void 0 : void 0,
|
|
862
|
+
installPythonRequirements: (f) => this.ctx.deps.installPythonRequirements(f)
|
|
863
|
+
});
|
|
864
|
+
await p.initialize();
|
|
865
|
+
return p;
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* Optimistic settings apply — rebuild the pipeline IN PLACE when the effective
|
|
869
|
+
* audio backend changes, instead of bouncing the whole addon. The audio engine
|
|
870
|
+
* fields therefore drop `requiresRestart`: BaseAddon calls this after every
|
|
871
|
+
* global-settings write; we rebuild only when the backend genuinely flips.
|
|
872
|
+
*/
|
|
873
|
+
async onConfigChanged() {
|
|
874
|
+
if (!this.provider) return;
|
|
875
|
+
const backend = this.resolveAudioBackend();
|
|
876
|
+
if (backend === this.activeBackend) return;
|
|
877
|
+
this.ctx.logger.info("audio backend changed — rebuilding pipeline in place", { meta: {
|
|
878
|
+
from: this.activeBackend,
|
|
879
|
+
to: backend
|
|
880
|
+
} });
|
|
881
|
+
const next = await this.buildPipeline(backend);
|
|
882
|
+
await this.provider.swapPipeline(next, backend);
|
|
883
|
+
this.pipeline = next;
|
|
884
|
+
this.activeBackend = backend;
|
|
810
885
|
}
|
|
811
886
|
buildDeviceSchema() {
|
|
812
887
|
return { sections: [] };
|
|
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_dist = require("../dist-
|
|
5
|
+
const require_dist = require("../dist-DnD2tm7T.js");
|
|
6
6
|
const require_codec_runtime = require("../codec-runtime-BOk-13PN.js");
|
|
7
7
|
let node_crypto = require("node:crypto");
|
|
8
8
|
//#region src/audio-codec-nodeav/addon/index.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { B as BaseAddon, _ as audioCodecCapability, z as errMsg } from "../dist-
|
|
1
|
+
import { B as BaseAddon, _ as audioCodecCapability, z as errMsg } from "../dist-BWc-HYQz.mjs";
|
|
2
2
|
import { t as DecodeRuntime } from "../codec-runtime-BsqlEjPi.mjs";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
//#region src/audio-codec-nodeav/addon/index.ts
|
|
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_dist = require("../dist-
|
|
5
|
+
const require_dist = require("../dist-DnD2tm7T.js");
|
|
6
6
|
let _camstack_shm_ring = require("@camstack/shm-ring");
|
|
7
7
|
let node_crypto = require("node:crypto");
|
|
8
8
|
//#region src/decoder-nodeav/frame-ring-sink.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { B as BaseAddon, b as decoderCapability, f as RingBuffer, s as DEFAULT_DECODER_HWACCEL_CONFIG, u as HWACCEL_OPTIONS, z as errMsg } from "../dist-
|
|
1
|
+
import { B as BaseAddon, b as decoderCapability, f as RingBuffer, s as DEFAULT_DECODER_HWACCEL_CONFIG, u as HWACCEL_OPTIONS, z as errMsg } from "../dist-BWc-HYQz.mjs";
|
|
2
2
|
import { FrameRingReaderCache, FrameRingWriter, MIN_RING_SLOTS, computeSegmentSize, computeSlotByteLength, createSegment, deriveSlotCount } from "@camstack/shm-ring";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
//#region src/decoder-nodeav/frame-ring-sink.ts
|