omnius 1.0.641 → 1.0.643
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/index.js +1755 -884
- package/dist/postinstall-daemon.cjs +19 -0
- package/dist/scripts/audio-clap-semantic-worker.py +2 -0
- package/dist/scripts/ocr-advanced.py +81 -13
- package/dist/scripts/speaker-diarization-worker.py +85 -2
- package/dist/scripts/vision-open-clip-worker.py +103 -46
- package/dist/update-worker.js +22 -0
- package/docs/DISCOVERY.json +36 -29
- package/docs/DISCOVERY.md +2 -2
- package/docs/reference/configuration.md +16 -0
- package/docs/rest/endpoints/chat.md +2 -1
- package/docs/rest/endpoints/voice-vision.md +72 -28
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
|
@@ -618,10 +618,28 @@ function installSystemd(nodeBin, omniusScript, user) {
|
|
|
618
618
|
|
|
619
619
|
var unitDir = path.join(userHome, ".config", "systemd", "user");
|
|
620
620
|
var unitPath = path.join(unitDir, SERVICE_LABEL + ".service");
|
|
621
|
+
var envDir = path.join(userHome, ".config", "omnius");
|
|
622
|
+
var envPath = path.join(envDir, "daemon.env");
|
|
621
623
|
var logDir = path.join(userHome, ".omnius");
|
|
622
624
|
|
|
623
625
|
try { fs.mkdirSync(unitDir, { recursive: true }); } catch (e) {}
|
|
626
|
+
try { fs.mkdirSync(envDir, { recursive: true }); } catch (e) {}
|
|
624
627
|
try { fs.mkdirSync(logDir, { recursive: true }); } catch (e) {}
|
|
628
|
+
try {
|
|
629
|
+
if (!fs.existsSync(envPath)) {
|
|
630
|
+
fs.writeFileSync(envPath, [
|
|
631
|
+
"# Optional Omnius daemon runtime overrides (KEY=value).",
|
|
632
|
+
"# OMNIUS_AUDIO_PYTHON=/absolute/path/to/jetpack/python",
|
|
633
|
+
"# OMNIUS_HF_TOKEN=hf_setup_only_token",
|
|
634
|
+
"# OMNIUS_PYANNOTE_TERMS_ACCEPTED=1",
|
|
635
|
+
"",
|
|
636
|
+
].join("\n"), { encoding: "utf8", mode: 0o600 });
|
|
637
|
+
} else {
|
|
638
|
+
try { fs.chmodSync(envPath, 0o600); } catch (e) {}
|
|
639
|
+
}
|
|
640
|
+
} catch (e) {
|
|
641
|
+
warn("Could not create optional daemon environment file: " + (e && e.message));
|
|
642
|
+
}
|
|
625
643
|
|
|
626
644
|
// The unit runs: <node> <omniusScript> serve --daemon --quiet
|
|
627
645
|
// with OMNIUS_DAEMON=1 so the serve command picks the daemon path.
|
|
@@ -639,6 +657,7 @@ function installSystemd(nodeBin, omniusScript, user) {
|
|
|
639
657
|
"Environment=OMNIUS_DAEMON=1",
|
|
640
658
|
"Environment=OMNIUS_PORT=" + PORT,
|
|
641
659
|
"Environment=NODE_ENV=production",
|
|
660
|
+
"EnvironmentFile=-" + envPath,
|
|
642
661
|
"ExecStart=" + nodeBin + " " + omniusScript + " serve --daemon --quiet",
|
|
643
662
|
// Restart=always (was on-failure) — also relaunch on clean exit.
|
|
644
663
|
// Some upgrade flows trigger process.exit(0) (e.g. /update reload,
|
|
@@ -16,6 +16,8 @@ import wave
|
|
|
16
16
|
|
|
17
17
|
if os.environ.get("HF_HUB_OFFLINE") != "1" or os.environ.get("TRANSFORMERS_OFFLINE") != "1":
|
|
18
18
|
raise RuntimeError("CLAP worker must be started with offline model access enabled")
|
|
19
|
+
if os.environ.get("PYTHONNOUSERSITE") != "1":
|
|
20
|
+
raise RuntimeError("CLAP worker must run from the isolated managed venv without user-site packages")
|
|
19
21
|
|
|
20
22
|
import numpy as np
|
|
21
23
|
import torch
|
|
@@ -179,11 +179,17 @@ SMALL_CROP_AREA_PX = 512_000
|
|
|
179
179
|
MEDIUM_IMAGE_AREA_PX = 2_000_000
|
|
180
180
|
MAX_PIPELINE_DEADLINE_MS = 80_000
|
|
181
181
|
MAX_TESSERACT_ATTEMPT_SECONDS = 12.0
|
|
182
|
+
# Small conditioned crops normally complete in well under a second. A
|
|
183
|
+
# six-second cap still permits a busy Tesseract child, while keeping a failed
|
|
184
|
+
# recovery pair from consuming most of the REST deadline.
|
|
185
|
+
SMALL_CROP_TESSERACT_ATTEMPT_SECONDS = 6.0
|
|
182
186
|
MIN_TESSERACT_ATTEMPT_SECONDS = 0.25
|
|
183
187
|
MIN_ACCEPTED_CONFIDENCE = 50.0
|
|
184
188
|
MIN_SUBSTANTIVE_TEXT_CHARS = 12
|
|
185
189
|
HIGH_VOLUME_GARBAGE_CHARS = 64
|
|
186
190
|
HIGH_VOLUME_GARBAGE_CONFIDENCE = 35.0
|
|
191
|
+
TERMINAL_SYMBOL_GARBAGE_CHARS = 24
|
|
192
|
+
TERMINAL_SYMBOL_GARBAGE_ALNUM_RATIO = 0.20
|
|
187
193
|
ACTIVE_DEADLINE = None
|
|
188
194
|
|
|
189
195
|
|
|
@@ -211,11 +217,11 @@ class OcrDeadline:
|
|
|
211
217
|
if self.remaining_seconds() <= 0:
|
|
212
218
|
raise OcrPipelineTimeout(f"OCR deadline exceeded during {stage}")
|
|
213
219
|
|
|
214
|
-
def tesseract_timeout_seconds(self):
|
|
220
|
+
def tesseract_timeout_seconds(self, attempt_cap_seconds=MAX_TESSERACT_ATTEMPT_SECONDS):
|
|
215
221
|
self.check("Tesseract scheduling")
|
|
216
222
|
return max(
|
|
217
223
|
MIN_TESSERACT_ATTEMPT_SECONDS,
|
|
218
|
-
min(
|
|
224
|
+
min(attempt_cap_seconds, self.remaining_seconds()),
|
|
219
225
|
)
|
|
220
226
|
|
|
221
227
|
|
|
@@ -251,7 +257,8 @@ def text_from_tesseract_data(data):
|
|
|
251
257
|
return "\n".join(" ".join(words) for words in lines.values()).strip()
|
|
252
258
|
|
|
253
259
|
|
|
254
|
-
def run_tesseract(binary_img, deadline, language="eng", psm=6
|
|
260
|
+
def run_tesseract(binary_img, deadline, language="eng", psm=6,
|
|
261
|
+
attempt_cap_seconds=MAX_TESSERACT_ATTEMPT_SECONDS):
|
|
255
262
|
"""Run one bounded Tesseract TSV pass and derive text plus confidence."""
|
|
256
263
|
deadline.check("Tesseract")
|
|
257
264
|
pil_img = Image.fromarray(binary_img)
|
|
@@ -261,7 +268,7 @@ def run_tesseract(binary_img, deadline, language="eng", psm=6):
|
|
|
261
268
|
lang=language,
|
|
262
269
|
config=config,
|
|
263
270
|
output_type=pytesseract.Output.DICT,
|
|
264
|
-
timeout=deadline.tesseract_timeout_seconds(),
|
|
271
|
+
timeout=deadline.tesseract_timeout_seconds(attempt_cap_seconds),
|
|
265
272
|
)
|
|
266
273
|
text = text_from_tesseract_data(data)
|
|
267
274
|
confs = []
|
|
@@ -331,6 +338,24 @@ def assess_ocr_evidence(text, confidence, line_count):
|
|
|
331
338
|
}
|
|
332
339
|
|
|
333
340
|
|
|
341
|
+
def is_terminal_small_crop_rejection(evidence):
|
|
342
|
+
"""Whether a first small-crop pass proves another recovery pass is futile.
|
|
343
|
+
|
|
344
|
+
Blanks and plausible alphanumeric low-confidence text remain recoverable
|
|
345
|
+
through the second variant. A large very-low-confidence transcript or a
|
|
346
|
+
distinctly symbol-heavy stream cannot become safe evidence through another
|
|
347
|
+
PSM6 pass, and should not make callers wait for one.
|
|
348
|
+
"""
|
|
349
|
+
if evidence.get("state") != "rejected":
|
|
350
|
+
return False
|
|
351
|
+
if evidence.get("reason") == "high_volume_low_confidence_text":
|
|
352
|
+
return True
|
|
353
|
+
return (
|
|
354
|
+
evidence.get("chars", 0) >= TERMINAL_SYMBOL_GARBAGE_CHARS
|
|
355
|
+
and evidence.get("alnum_ratio", 1.0) < TERMINAL_SYMBOL_GARBAGE_ALNUM_RATIO
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
|
|
334
359
|
def compute_score(text, confidence, line_count, evidence=None):
|
|
335
360
|
"""Combined scoring heuristic:
|
|
336
361
|
- confidence * sqrt(char_count) — rewards quality and coverage
|
|
@@ -451,15 +476,18 @@ def write_all_outputs(text, base_name, output_dir):
|
|
|
451
476
|
# Main pipeline
|
|
452
477
|
# ---------------------------------------------------------------------------
|
|
453
478
|
|
|
454
|
-
def run_variant_plan(gray, plan, deadline, language, debug_dir=None, debug_prefix="full"
|
|
479
|
+
def run_variant_plan(gray, plan, deadline, language, debug_dir=None, debug_prefix="full",
|
|
480
|
+
attempt_cap_seconds=MAX_TESSERACT_ATTEMPT_SECONDS,
|
|
481
|
+
stop_on_terminal_rejection=False):
|
|
455
482
|
"""Run a bounded plan, stopping early once a legible result is proven."""
|
|
456
483
|
all_results = {}
|
|
457
484
|
ocr_errors = []
|
|
458
485
|
best_key = None
|
|
459
486
|
best_score = -1
|
|
460
487
|
early_exit = False
|
|
488
|
+
terminal_rejection = False
|
|
461
489
|
|
|
462
|
-
for vname, psm in plan:
|
|
490
|
+
for attempt_index, (vname, psm) in enumerate(plan):
|
|
463
491
|
deadline.check("preprocessing")
|
|
464
492
|
key = f"{vname}_psm{psm}"
|
|
465
493
|
try:
|
|
@@ -471,7 +499,9 @@ def run_variant_plan(gray, plan, deadline, language, debug_dir=None, debug_prefi
|
|
|
471
499
|
os.makedirs(debug_dir, exist_ok=True)
|
|
472
500
|
cv2.imwrite(os.path.join(debug_dir, f"{debug_prefix}_{vname}.png"), binary)
|
|
473
501
|
try:
|
|
474
|
-
text, confidence, line_count = run_tesseract(
|
|
502
|
+
text, confidence, line_count = run_tesseract(
|
|
503
|
+
binary, deadline, language, psm, attempt_cap_seconds
|
|
504
|
+
)
|
|
475
505
|
except OcrPipelineTimeout:
|
|
476
506
|
raise
|
|
477
507
|
except OcrPipelineCancelled:
|
|
@@ -496,8 +526,15 @@ def run_variant_plan(gray, plan, deadline, language, debug_dir=None, debug_prefi
|
|
|
496
526
|
if has_sufficient_evidence(text, confidence, line_count):
|
|
497
527
|
early_exit = True
|
|
498
528
|
break
|
|
529
|
+
if (
|
|
530
|
+
stop_on_terminal_rejection
|
|
531
|
+
and attempt_index == 0
|
|
532
|
+
and is_terminal_small_crop_rejection(evidence)
|
|
533
|
+
):
|
|
534
|
+
terminal_rejection = True
|
|
535
|
+
break
|
|
499
536
|
|
|
500
|
-
return all_results, ocr_errors, best_key, early_exit
|
|
537
|
+
return all_results, ocr_errors, best_key, early_exit, terminal_rejection
|
|
501
538
|
|
|
502
539
|
|
|
503
540
|
def run_pipeline(image_path, deadline, language="eng", do_regions=False, debug_dir=None,
|
|
@@ -522,10 +559,22 @@ def run_pipeline(image_path, deadline, language="eng", do_regions=False, debug_d
|
|
|
522
559
|
deadline.check("image conditioning")
|
|
523
560
|
gray_2x = upscale_2x(gray)
|
|
524
561
|
plan = build_ocr_plan(effective_area, single_psm)
|
|
562
|
+
is_small_crop = effective_area <= SMALL_CROP_AREA_PX
|
|
563
|
+
attempt_cap_seconds = (
|
|
564
|
+
SMALL_CROP_TESSERACT_ATTEMPT_SECONDS
|
|
565
|
+
if is_small_crop
|
|
566
|
+
else MAX_TESSERACT_ATTEMPT_SECONDS
|
|
567
|
+
)
|
|
525
568
|
attempts_completed = 0
|
|
526
569
|
try:
|
|
527
|
-
all_results, ocr_errors, best_key, early_exit = run_variant_plan(
|
|
528
|
-
gray_2x,
|
|
570
|
+
all_results, ocr_errors, best_key, early_exit, terminal_rejection = run_variant_plan(
|
|
571
|
+
gray_2x,
|
|
572
|
+
plan,
|
|
573
|
+
deadline,
|
|
574
|
+
language,
|
|
575
|
+
debug_dir,
|
|
576
|
+
attempt_cap_seconds=attempt_cap_seconds,
|
|
577
|
+
stop_on_terminal_rejection=is_small_crop,
|
|
529
578
|
)
|
|
530
579
|
attempts_completed = len(all_results)
|
|
531
580
|
if not best_key:
|
|
@@ -545,6 +594,8 @@ def run_pipeline(image_path, deadline, language="eng", do_regions=False, debug_d
|
|
|
545
594
|
"diagnostic": {
|
|
546
595
|
**diagnostic("ocr_evidence_rejected", message, deadline, "evidence_quality", attempts_completed, len(plan)),
|
|
547
596
|
"evidence": evidence,
|
|
597
|
+
"terminal_small_crop_rejection": terminal_rejection,
|
|
598
|
+
"attempt_cap_seconds": attempt_cap_seconds,
|
|
548
599
|
},
|
|
549
600
|
}
|
|
550
601
|
# Empty or tiny non-substantive detections are valid observations:
|
|
@@ -579,6 +630,8 @@ def run_pipeline(image_path, deadline, language="eng", do_regions=False, debug_d
|
|
|
579
630
|
"attempts_planned": len(plan),
|
|
580
631
|
"attempts_completed": attempts_completed,
|
|
581
632
|
"early_exit": False,
|
|
633
|
+
"terminal_small_crop_rejection": terminal_rejection,
|
|
634
|
+
"attempt_cap_seconds": attempt_cap_seconds,
|
|
582
635
|
"deadline_ms": deadline.deadline_ms,
|
|
583
636
|
},
|
|
584
637
|
}
|
|
@@ -615,6 +668,8 @@ def run_pipeline(image_path, deadline, language="eng", do_regions=False, debug_d
|
|
|
615
668
|
"attempts_planned": len(plan),
|
|
616
669
|
"attempts_completed": attempts_completed,
|
|
617
670
|
"early_exit": early_exit,
|
|
671
|
+
"terminal_small_crop_rejection": terminal_rejection,
|
|
672
|
+
"attempt_cap_seconds": attempt_cap_seconds,
|
|
618
673
|
"deadline_ms": deadline.deadline_ms,
|
|
619
674
|
},
|
|
620
675
|
}
|
|
@@ -627,9 +682,22 @@ def run_pipeline(image_path, deadline, language="eng", do_regions=False, debug_d
|
|
|
627
682
|
region_gray = extract_region(gray_2x, y_start, y_end)
|
|
628
683
|
# Region requests use at most two high-yield attempts. The
|
|
629
684
|
# main result already provides full-frame coverage.
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
685
|
+
region_area = region_gray.shape[0] * region_gray.shape[1]
|
|
686
|
+
region_plan = build_ocr_plan(region_area, single_psm)[:2]
|
|
687
|
+
region_is_small = region_area <= SMALL_CROP_AREA_PX
|
|
688
|
+
region_results, _errors, region_best_key, _early_exit, _terminal_rejection = run_variant_plan(
|
|
689
|
+
region_gray,
|
|
690
|
+
region_plan,
|
|
691
|
+
deadline,
|
|
692
|
+
language,
|
|
693
|
+
debug_dir,
|
|
694
|
+
f"region_{rname}",
|
|
695
|
+
attempt_cap_seconds=(
|
|
696
|
+
SMALL_CROP_TESSERACT_ATTEMPT_SECONDS
|
|
697
|
+
if region_is_small
|
|
698
|
+
else MAX_TESSERACT_ATTEMPT_SECONDS
|
|
699
|
+
),
|
|
700
|
+
stop_on_terminal_rejection=region_is_small,
|
|
633
701
|
)
|
|
634
702
|
regions[rname] = region_results[region_best_key]["text"] if region_best_key else ""
|
|
635
703
|
result["regions"] = regions
|
|
@@ -12,6 +12,7 @@ from __future__ import annotations
|
|
|
12
12
|
import argparse
|
|
13
13
|
import json
|
|
14
14
|
import os
|
|
15
|
+
import subprocess
|
|
15
16
|
import sys
|
|
16
17
|
import time
|
|
17
18
|
import wave
|
|
@@ -39,6 +40,33 @@ def validate_live_wav(path: str) -> None:
|
|
|
39
40
|
raise ValueError("live diarization accepts retained audio windows greater than 0 and at most 6 seconds")
|
|
40
41
|
|
|
41
42
|
|
|
43
|
+
def load_pcm16_waveform(path: str):
|
|
44
|
+
"""Decode locally without TorchCodec, which has no CPython 3.10/aarch64 wheel."""
|
|
45
|
+
import numpy as np
|
|
46
|
+
import torch
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
with wave.open(path, "rb") as wav:
|
|
50
|
+
if wav.getcomptype() != "NONE" or wav.getsampwidth() != 2:
|
|
51
|
+
raise ValueError("offline reconciliation requires uncompressed PCM16 WAV")
|
|
52
|
+
channels = wav.getnchannels()
|
|
53
|
+
sample_rate = wav.getframerate()
|
|
54
|
+
frames = wav.readframes(wav.getnframes())
|
|
55
|
+
except Exception as exc:
|
|
56
|
+
raise ValueError(f"offline reconciliation requires readable PCM16 WAV: {exc}") from exc
|
|
57
|
+
if channels < 1 or sample_rate < 1 or not frames:
|
|
58
|
+
raise ValueError("offline reconciliation WAV is empty or invalid")
|
|
59
|
+
samples = np.frombuffer(frames, dtype="<i2")
|
|
60
|
+
if samples.size % channels:
|
|
61
|
+
raise ValueError("offline reconciliation WAV has an incomplete PCM frame")
|
|
62
|
+
waveform = samples.reshape(-1, channels).T.astype(np.float32) / 32768.0
|
|
63
|
+
return {
|
|
64
|
+
"waveform": torch.from_numpy(waveform),
|
|
65
|
+
"sample_rate": sample_rate,
|
|
66
|
+
"uri": Path(path).stem,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
42
70
|
def number(value: Any) -> float | None:
|
|
43
71
|
try:
|
|
44
72
|
result = float(value)
|
|
@@ -64,6 +92,9 @@ def append_segment(result: list[dict[str, Any]], start: Any, end: Any, speaker:
|
|
|
64
92
|
|
|
65
93
|
def normalize_sortformer(value: Any) -> list[dict[str, Any]]:
|
|
66
94
|
"""Accept NeMo's current tuple/list result variants without inventing data."""
|
|
95
|
+
rttm_text = isinstance(value, str)
|
|
96
|
+
if isinstance(value, str):
|
|
97
|
+
value = [line for line in value.splitlines() if line.strip()]
|
|
67
98
|
while isinstance(value, list) and len(value) == 1 and isinstance(value[0], (list, tuple)):
|
|
68
99
|
value = value[0]
|
|
69
100
|
result: list[dict[str, Any]] = []
|
|
@@ -83,12 +114,60 @@ def normalize_sortformer(value: Any) -> list[dict[str, Any]]:
|
|
|
83
114
|
duration = number(fields[4])
|
|
84
115
|
if start is not None and duration is not None:
|
|
85
116
|
append_segment(result, start, start + duration, fields[7])
|
|
86
|
-
|
|
117
|
+
# A native RTTM command may emit diagnostics but no SPEAKER rows for a
|
|
118
|
+
# genuine silent window. Structured NeMo objects that are non-empty yet
|
|
119
|
+
# unparseable remain a hard contract failure.
|
|
120
|
+
if not result and value and not rttm_text:
|
|
87
121
|
raise RuntimeError("Sortformer result contained no parseable speaker turns")
|
|
88
122
|
return result
|
|
89
123
|
|
|
90
124
|
|
|
91
125
|
def load_live(args: argparse.Namespace):
|
|
126
|
+
if args.native_binary:
|
|
127
|
+
binary = Path(args.native_binary)
|
|
128
|
+
if not binary.is_file() or not os.access(binary, os.X_OK):
|
|
129
|
+
raise RuntimeError("managed nemo-speech binary is missing or not executable")
|
|
130
|
+
# Metadata inspection validates that this exact Q8 artifact is readable
|
|
131
|
+
# without keeping a second Python/Torch stack on JetPack.
|
|
132
|
+
inspected = subprocess.run(
|
|
133
|
+
[str(binary), "model", "info", args.artifact_path],
|
|
134
|
+
text=True,
|
|
135
|
+
capture_output=True,
|
|
136
|
+
timeout=60,
|
|
137
|
+
check=False,
|
|
138
|
+
)
|
|
139
|
+
if inspected.returncode != 0:
|
|
140
|
+
raise RuntimeError(
|
|
141
|
+
f"nemo-speech rejected the pinned Sortformer model: {(inspected.stderr or inspected.stdout)[-1000:]}"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
def diarize_native(path: str) -> list[dict[str, Any]]:
|
|
145
|
+
validate_live_wav(path)
|
|
146
|
+
completed = subprocess.run(
|
|
147
|
+
[
|
|
148
|
+
str(binary),
|
|
149
|
+
"diarize",
|
|
150
|
+
path,
|
|
151
|
+
"--model",
|
|
152
|
+
args.artifact_path,
|
|
153
|
+
"--device",
|
|
154
|
+
"cuda:0",
|
|
155
|
+
"--format",
|
|
156
|
+
"rttm",
|
|
157
|
+
],
|
|
158
|
+
text=True,
|
|
159
|
+
capture_output=True,
|
|
160
|
+
timeout=90,
|
|
161
|
+
check=False,
|
|
162
|
+
)
|
|
163
|
+
if completed.returncode != 0:
|
|
164
|
+
raise RuntimeError(
|
|
165
|
+
f"nemo-speech diarization failed: {(completed.stderr or completed.stdout)[-2000:]}"
|
|
166
|
+
)
|
|
167
|
+
return normalize_sortformer(completed.stdout)
|
|
168
|
+
|
|
169
|
+
return binary, diarize_native, "nemo-speech-cpp-q8-cuda"
|
|
170
|
+
|
|
92
171
|
import torch
|
|
93
172
|
from nemo.collections.asr.models import SortformerEncLabelModel
|
|
94
173
|
|
|
@@ -122,7 +201,10 @@ def load_reconcile(args: argparse.Namespace):
|
|
|
122
201
|
def diarize(path: str) -> list[dict[str, Any]]:
|
|
123
202
|
if not Path(path).is_file():
|
|
124
203
|
raise ValueError("offline reconciliation audio file does not exist")
|
|
125
|
-
|
|
204
|
+
# pyannote.audio officially supports in-memory waveform mappings. This
|
|
205
|
+
# avoids its optional TorchCodec decoder, which has no aarch64 wheel,
|
|
206
|
+
# without weakening the model or touching JetPack CUDA Torch.
|
|
207
|
+
output = pipeline(load_pcm16_waveform(path))
|
|
126
208
|
annotation = getattr(output, "speaker_diarization", output)
|
|
127
209
|
turns = getattr(annotation, "itertracks", None)
|
|
128
210
|
if not callable(turns):
|
|
@@ -142,6 +224,7 @@ def main() -> int:
|
|
|
142
224
|
parser.add_argument("--artifact-path", required=True)
|
|
143
225
|
parser.add_argument("--revision", required=True)
|
|
144
226
|
parser.add_argument("--model-digest", required=True)
|
|
227
|
+
parser.add_argument("--native-binary")
|
|
145
228
|
args = parser.parse_args()
|
|
146
229
|
|
|
147
230
|
# Do not allow an accidentally inherited setup token to become a request
|
|
@@ -17,6 +17,12 @@ import sys
|
|
|
17
17
|
|
|
18
18
|
MODEL = "ViT-B-32"
|
|
19
19
|
PRETRAINED = "laion2b_s34b_b79k"
|
|
20
|
+
MODEL_REPOSITORY = "laion/CLIP-ViT-B-32-laion2B-s34B-b79K"
|
|
21
|
+
MODEL_REVISION = "1a25a446712ba5ee05982a381eed697ef9b435cf"
|
|
22
|
+
MODEL_WEIGHTS_FILENAME = "open_clip_pytorch_model.bin"
|
|
23
|
+
MODEL_WEIGHTS_BYTES = 605219813
|
|
24
|
+
MODEL_WEIGHTS_SHA256 = "1bd3c7172de5b207ceac554f5ab5266166f3b9baccc9af5989bc801016d080ad"
|
|
25
|
+
MODEL_DIGEST = "sha256:" + MODEL_WEIGHTS_SHA256
|
|
20
26
|
EMBEDDING_DIMENSION = 512
|
|
21
27
|
|
|
22
28
|
|
|
@@ -32,36 +38,75 @@ def deny_network():
|
|
|
32
38
|
socket.socket.connect_ex = denied
|
|
33
39
|
|
|
34
40
|
|
|
35
|
-
def
|
|
36
|
-
entries = []
|
|
37
|
-
for root, _dirs, names in os.walk(cache_dir):
|
|
38
|
-
for name in sorted(names):
|
|
39
|
-
path = os.path.join(root, name)
|
|
40
|
-
if not os.path.isfile(path) or os.path.getsize(path) < 1024:
|
|
41
|
-
continue
|
|
42
|
-
relative = os.path.relpath(path, cache_dir).replace(os.sep, "/")
|
|
43
|
-
digest = hashlib.sha256()
|
|
44
|
-
with open(path, "rb") as source:
|
|
45
|
-
while True:
|
|
46
|
-
block = source.read(1024 * 1024)
|
|
47
|
-
if not block:
|
|
48
|
-
break
|
|
49
|
-
digest.update(block)
|
|
50
|
-
entries.append({"path": relative, "bytes": os.path.getsize(path), "sha256": digest.hexdigest()})
|
|
51
|
-
return sorted(entries, key=lambda item: item["path"])
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def artifact_digest(entries):
|
|
41
|
+
def sha256_file(path):
|
|
55
42
|
digest = hashlib.sha256()
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
43
|
+
with open(path, "rb") as source:
|
|
44
|
+
while True:
|
|
45
|
+
block = source.read(1024 * 1024)
|
|
46
|
+
if not block:
|
|
47
|
+
break
|
|
48
|
+
digest.update(block)
|
|
49
|
+
return digest.hexdigest()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def model_weights_path(cache_dir):
|
|
53
|
+
return os.path.join(cache_dir, MODEL_WEIGHTS_FILENAME)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def verify_model_weights(cache_dir):
|
|
57
|
+
path = model_weights_path(cache_dir)
|
|
58
|
+
if not os.path.isfile(path):
|
|
59
|
+
raise RuntimeError("Pinned OpenCLIP weight is missing; run explicit vision setup")
|
|
60
|
+
actual_bytes = os.path.getsize(path)
|
|
61
|
+
if actual_bytes != MODEL_WEIGHTS_BYTES:
|
|
62
|
+
raise RuntimeError(
|
|
63
|
+
"Pinned OpenCLIP weight has an unexpected size "
|
|
64
|
+
"(expected %d bytes, got %d); run explicit vision setup" % (MODEL_WEIGHTS_BYTES, actual_bytes)
|
|
65
|
+
)
|
|
66
|
+
actual_sha256 = sha256_file(path)
|
|
67
|
+
if actual_sha256 != MODEL_WEIGHTS_SHA256:
|
|
68
|
+
raise RuntimeError(
|
|
69
|
+
"Pinned OpenCLIP weight checksum mismatch "
|
|
70
|
+
"(expected %s, got %s); run explicit vision setup" % (MODEL_WEIGHTS_SHA256, actual_sha256)
|
|
71
|
+
)
|
|
72
|
+
return path
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def artifact_entries(cache_dir):
|
|
76
|
+
path = verify_model_weights(cache_dir)
|
|
77
|
+
return [{
|
|
78
|
+
"path": MODEL_WEIGHTS_FILENAME,
|
|
79
|
+
"bytes": os.path.getsize(path),
|
|
80
|
+
"sha256": MODEL_WEIGHTS_SHA256,
|
|
81
|
+
}]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def download_pinned_model_weights(cache_dir):
|
|
85
|
+
os.makedirs(cache_dir, exist_ok=True)
|
|
86
|
+
try:
|
|
87
|
+
verified = verify_model_weights(cache_dir)
|
|
88
|
+
except RuntimeError:
|
|
89
|
+
# An interrupted/corrupt setup may leave a file at the managed target.
|
|
90
|
+
# Only explicit setup is permitted to replace it.
|
|
91
|
+
force_download = True
|
|
92
|
+
else:
|
|
93
|
+
return verified
|
|
94
|
+
from huggingface_hub import hf_hub_download
|
|
95
|
+
downloaded = hf_hub_download(
|
|
96
|
+
repo_id=MODEL_REPOSITORY,
|
|
97
|
+
filename=MODEL_WEIGHTS_FILENAME,
|
|
98
|
+
revision=MODEL_REVISION,
|
|
99
|
+
local_dir=cache_dir,
|
|
100
|
+
local_files_only=False,
|
|
101
|
+
force_download=force_download,
|
|
102
|
+
)
|
|
103
|
+
expected_path = model_weights_path(cache_dir)
|
|
104
|
+
if os.path.abspath(downloaded) != os.path.abspath(expected_path):
|
|
105
|
+
raise RuntimeError("Pinned OpenCLIP download did not materialize in the managed cache")
|
|
106
|
+
return verify_model_weights(cache_dir)
|
|
62
107
|
|
|
63
108
|
|
|
64
|
-
def load_model(
|
|
109
|
+
def load_model(weights_path, offline):
|
|
65
110
|
if offline:
|
|
66
111
|
deny_network()
|
|
67
112
|
os.environ["HF_HUB_OFFLINE"] = "1"
|
|
@@ -70,11 +115,14 @@ def load_model(cache_dir, offline):
|
|
|
70
115
|
import open_clip
|
|
71
116
|
if not torch.cuda.is_available():
|
|
72
117
|
raise RuntimeError("JetPack CUDA Torch is unavailable for OpenCLIP")
|
|
118
|
+
if os.environ.get("CUDA_VISIBLE_DEVICES") != "0":
|
|
119
|
+
raise RuntimeError("OpenCLIP requires CUDA_VISIBLE_DEVICES=0")
|
|
120
|
+
if torch.cuda.current_device() != 0:
|
|
121
|
+
raise RuntimeError("OpenCLIP did not bind to logical CUDA device 0")
|
|
73
122
|
device = torch.device("cuda:0")
|
|
74
123
|
model, _, preprocess = open_clip.create_model_and_transforms(
|
|
75
124
|
MODEL,
|
|
76
|
-
pretrained=
|
|
77
|
-
cache_dir=cache_dir,
|
|
125
|
+
pretrained=weights_path,
|
|
78
126
|
device=device,
|
|
79
127
|
)
|
|
80
128
|
model.eval()
|
|
@@ -82,27 +130,33 @@ def load_model(cache_dir, offline):
|
|
|
82
130
|
|
|
83
131
|
|
|
84
132
|
def setup(cache_dir):
|
|
85
|
-
|
|
133
|
+
_weights_path = download_pinned_model_weights(cache_dir)
|
|
134
|
+
import torch
|
|
135
|
+
if not torch.cuda.is_available():
|
|
136
|
+
raise RuntimeError("JetPack CUDA Torch is unavailable for OpenCLIP")
|
|
137
|
+
if os.environ.get("CUDA_VISIBLE_DEVICES") != "0":
|
|
138
|
+
raise RuntimeError("OpenCLIP setup requires CUDA_VISIBLE_DEVICES=0")
|
|
139
|
+
if torch.cuda.current_device() != 0:
|
|
140
|
+
raise RuntimeError("OpenCLIP setup did not bind to logical CUDA device 0")
|
|
86
141
|
entries = artifact_entries(cache_dir)
|
|
87
|
-
if not entries:
|
|
88
|
-
raise RuntimeError("OpenCLIP setup did not create verifiable model artifacts")
|
|
89
|
-
with torch.inference_mode():
|
|
90
|
-
# Model loading is setup verification, not a user inference request.
|
|
91
|
-
torch.cuda.synchronize(device)
|
|
92
142
|
major, minor = torch.cuda.get_device_capability(0)
|
|
93
143
|
emit({
|
|
94
144
|
"type": "setup",
|
|
95
145
|
"success": True,
|
|
96
146
|
"model": MODEL,
|
|
97
147
|
"pretrained": PRETRAINED,
|
|
148
|
+
"model_repository": MODEL_REPOSITORY,
|
|
149
|
+
"model_revision": MODEL_REVISION,
|
|
150
|
+
"model_weights_sha256": MODEL_WEIGHTS_SHA256,
|
|
98
151
|
"backend": "open-clip",
|
|
99
152
|
"embedding_dimension": EMBEDDING_DIMENSION,
|
|
100
153
|
"normalization": "l2",
|
|
101
154
|
"device": torch.cuda.get_device_name(0),
|
|
102
155
|
"compute_capability": "%d.%d" % (major, minor),
|
|
103
156
|
"torch_cuda_version": str(torch.version.cuda),
|
|
157
|
+
"cuda_visible_devices": os.environ.get("CUDA_VISIBLE_DEVICES"),
|
|
104
158
|
"artifacts": entries,
|
|
105
|
-
"model_digest":
|
|
159
|
+
"model_digest": MODEL_DIGEST,
|
|
106
160
|
})
|
|
107
161
|
|
|
108
162
|
|
|
@@ -117,13 +171,12 @@ def image_from_input(input_):
|
|
|
117
171
|
raise ValueError("Provide path or bytesBase64")
|
|
118
172
|
|
|
119
173
|
|
|
120
|
-
def embed(cache_dir, expected_digest):
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
raise RuntimeError("OpenCLIP model artifacts are missing or changed; run explicit vision setup")
|
|
174
|
+
def embed(cache_dir, expected_digest, expected_revision):
|
|
175
|
+
if expected_digest != MODEL_DIGEST or expected_revision != MODEL_REVISION:
|
|
176
|
+
raise RuntimeError("OpenCLIP model identity does not match the pinned setup contract")
|
|
177
|
+
weights_path = verify_model_weights(cache_dir)
|
|
125
178
|
input_ = json.loads(sys.stdin.read() or "{}")
|
|
126
|
-
torch, model, preprocess, device = load_model(
|
|
179
|
+
torch, model, preprocess, device = load_model(weights_path, True)
|
|
127
180
|
image = image_from_input(input_)
|
|
128
181
|
with torch.inference_mode():
|
|
129
182
|
tensor = preprocess(image).unsqueeze(0).to(device)
|
|
@@ -137,8 +190,11 @@ def embed(cache_dir, expected_digest):
|
|
|
137
190
|
"success": True,
|
|
138
191
|
"model": MODEL,
|
|
139
192
|
"pretrained": PRETRAINED,
|
|
193
|
+
"model_repository": MODEL_REPOSITORY,
|
|
194
|
+
"model_revision": MODEL_REVISION,
|
|
195
|
+
"model_weights_sha256": MODEL_WEIGHTS_SHA256,
|
|
140
196
|
"backend": "open-clip",
|
|
141
|
-
"model_digest":
|
|
197
|
+
"model_digest": MODEL_DIGEST,
|
|
142
198
|
"embedding": values,
|
|
143
199
|
"dimension": EMBEDDING_DIMENSION,
|
|
144
200
|
"normalization": "l2",
|
|
@@ -151,15 +207,16 @@ def main():
|
|
|
151
207
|
parser.add_argument("--embed", action="store_true")
|
|
152
208
|
parser.add_argument("--cache-dir", required=True)
|
|
153
209
|
parser.add_argument("--model-digest")
|
|
210
|
+
parser.add_argument("--model-revision")
|
|
154
211
|
args = parser.parse_args()
|
|
155
212
|
if args.setup == args.embed:
|
|
156
213
|
raise ValueError("Choose exactly one of --setup or --embed")
|
|
157
214
|
if args.setup:
|
|
158
215
|
setup(args.cache_dir)
|
|
159
216
|
else:
|
|
160
|
-
if not args.model_digest:
|
|
161
|
-
raise ValueError("--model-digest
|
|
162
|
-
embed(args.cache_dir, args.model_digest)
|
|
217
|
+
if not args.model_digest or not args.model_revision:
|
|
218
|
+
raise ValueError("--model-digest and --model-revision are required for offline embedding")
|
|
219
|
+
embed(args.cache_dir, args.model_digest, args.model_revision)
|
|
163
220
|
|
|
164
221
|
|
|
165
222
|
if __name__ == "__main__":
|
package/dist/update-worker.js
CHANGED
|
@@ -245088,6 +245088,7 @@ var SETUP_TIMEOUT_MS = 20 * 6e4;
|
|
|
245088
245088
|
// packages/execution/dist/speaker-diarization-runtime.js
|
|
245089
245089
|
init_model_store();
|
|
245090
245090
|
init_process_async();
|
|
245091
|
+
init_venv_paths();
|
|
245091
245092
|
|
|
245092
245093
|
// packages/execution/dist/audio-semantic-embedding-runtime.js
|
|
245093
245094
|
init_process_async();
|
|
@@ -245095,6 +245096,27 @@ init_jetson_monitor();
|
|
|
245095
245096
|
init_model_store();
|
|
245096
245097
|
init_venv_paths();
|
|
245097
245098
|
var SETUP_TIMEOUT_MS2 = 30 * 6e4;
|
|
245099
|
+
var CLAP_RUNTIME_WHEELS = [
|
|
245100
|
+
{ distribution: "numpy", version: "1.26.4", importName: "numpy", filename: "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", source: "https://files.pythonhosted.org/packages/fc/a5/4beee6488160798683eed5bdb7eead455892c3b4e1f78d79d8d3f3b084ac/numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", sha256: "d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4" },
|
|
245101
|
+
{ distribution: "transformers", version: "4.57.3", importName: "transformers", filename: "transformers-4.57.3-py3-none-any.whl", source: "https://files.pythonhosted.org/packages/6a/6b/2f416568b3c4c91c96e5a365d164f8a4a4a88030aa8ab4644181fdadce97/transformers-4.57.3-py3-none-any.whl", sha256: "c77d353a4851b1880191603d36acb313411d3577f6e2897814f333841f7003f4" },
|
|
245102
|
+
{ distribution: "huggingface-hub", version: "0.36.0", importName: "huggingface_hub", filename: "huggingface_hub-0.36.0-py3-none-any.whl", source: "https://files.pythonhosted.org/packages/cb/bd/1a875e0d592d447cbc02805fd3fe0f497714d6a2583f59d14fa9ebad96eb/huggingface_hub-0.36.0-py3-none-any.whl", sha256: "7bcc9ad17d5b3f07b57c78e79d527102d08313caa278a641993acddcb894548d" },
|
|
245103
|
+
{ distribution: "hf-xet", version: "1.1.5", importName: "hf_xet", filename: "hf_xet-1.1.5-cp37-abi3-manylinux_2_28_aarch64.whl", source: "https://files.pythonhosted.org/packages/d0/54/0fcf2b619720a26fbb6cc941e89f2472a522cd963a776c089b189559447f/hf_xet-1.1.5-cp37-abi3-manylinux_2_28_aarch64.whl", sha256: "dbba1660e5d810bd0ea77c511a99e9242d920790d0e63c0e4673ed36c4022d18" },
|
|
245104
|
+
{ distribution: "tokenizers", version: "0.22.1", importName: "tokenizers", filename: "tokenizers-0.22.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", source: "https://files.pythonhosted.org/packages/1e/3b/55e64befa1e7bfea963cf4b787b2cea1011362c4193f5477047532ce127e/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", sha256: "19d2962dd28bc67c1f205ab180578a78eef89ac60ca7ef7cbe9635a46a56422a" },
|
|
245105
|
+
{ distribution: "safetensors", version: "0.5.3", importName: "safetensors", filename: "safetensors-0.5.3-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", source: "https://files.pythonhosted.org/packages/5d/9a/add3e6fef267658075c5a41573c26d42d80c935cdc992384dfae435feaef/safetensors-0.5.3-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", sha256: "11bce6164887cd491ca75c2326a113ba934be596e22b28b1742ce27b1d076467" },
|
|
245106
|
+
{ distribution: "filelock", version: "3.16.1", importName: "filelock", filename: "filelock-3.16.1-py3-none-any.whl", source: "https://files.pythonhosted.org/packages/b9/f8/feced7779d755758a52d1f6635d990b8d98dc0a29fa568bbe0625f18fdf3/filelock-3.16.1-py3-none-any.whl", sha256: "2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0" },
|
|
245107
|
+
{ distribution: "fsspec", version: "2024.10.0", importName: "fsspec", filename: "fsspec-2024.10.0-py3-none-any.whl", source: "https://files.pythonhosted.org/packages/c6/b2/454d6e7f0158951d8a78c2e1eb4f69ae81beb8dca5fee9809c6c99e9d0d0/fsspec-2024.10.0-py3-none-any.whl", sha256: "03b9a6785766a4de40368b88906366755e2819e758b83705c88cd7cb5fe81871" },
|
|
245108
|
+
{ distribution: "packaging", version: "24.2", importName: "packaging", filename: "packaging-24.2-py3-none-any.whl", source: "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", sha256: "09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759" },
|
|
245109
|
+
{ distribution: "PyYAML", version: "6.0.2", importName: "yaml", filename: "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", source: "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", sha256: "8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237" },
|
|
245110
|
+
{ distribution: "regex", version: "2024.11.6", importName: "regex", filename: "regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", source: "https://files.pythonhosted.org/packages/78/a2/6dd36e16341ab95e4c6073426561b9bfdeb1a9c9b63ab1b579c2e96cb105/regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", sha256: "d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde" },
|
|
245111
|
+
{ distribution: "requests", version: "2.32.3", importName: "requests", filename: "requests-2.32.3-py3-none-any.whl", source: "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", sha256: "70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6" },
|
|
245112
|
+
{ distribution: "tqdm", version: "4.67.1", importName: "tqdm", filename: "tqdm-4.67.1-py3-none-any.whl", source: "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", sha256: "26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2" },
|
|
245113
|
+
{ distribution: "typing-extensions", version: "4.12.2", importName: "typing_extensions", filename: "typing_extensions-4.12.2-py3-none-any.whl", source: "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", sha256: "04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d" },
|
|
245114
|
+
{ distribution: "certifi", version: "2024.8.30", importName: "certifi", filename: "certifi-2024.8.30-py3-none-any.whl", source: "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", sha256: "922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8" },
|
|
245115
|
+
{ distribution: "charset-normalizer", version: "3.4.0", importName: "charset_normalizer", filename: "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", source: "https://files.pythonhosted.org/packages/c2/72/12a7f0943dd71fb5b4e7b55c41327ac0a1663046a868ee4d0d8e9c369b85/charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", sha256: "40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca" },
|
|
245116
|
+
{ distribution: "idna", version: "3.10", importName: "idna", filename: "idna-3.10-py3-none-any.whl", source: "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", sha256: "946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" },
|
|
245117
|
+
{ distribution: "urllib3", version: "2.2.3", importName: "urllib3", filename: "urllib3-2.2.3-py3-none-any.whl", source: "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl", sha256: "ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac" }
|
|
245118
|
+
];
|
|
245119
|
+
var CLAP_PYTHON_REQUIREMENTS = CLAP_RUNTIME_WHEELS.map((wheel) => `${wheel.distribution}==${wheel.version}`);
|
|
245098
245120
|
|
|
245099
245121
|
// packages/execution/dist/speaker-embedding-runtime.js
|
|
245100
245122
|
init_jetson_monitor();
|