接入 LiveKit 与 TurnSense 评分并改进模型曲线选择
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 84 KiB |
@@ -0,0 +1,3 @@
|
||||
.venv/
|
||||
__pycache__/
|
||||
cache/
|
||||
@@ -0,0 +1 @@
|
||||
3.11
|
||||
@@ -0,0 +1,42 @@
|
||||
# LiveKit Turn Detector v1-mini 韩泰评估
|
||||
|
||||
执行机器:本机 Apple M4 Mac mini,CPU,16 GB。Python 3.11.15;独立 uv 环境。官方 PyPI `livekit-local-inference==0.2.7`(内嵌模型)与 `livekit-agents==1.8.8`,完整依赖见 uv.lock。
|
||||
|
||||
## 复现
|
||||
|
||||
从项目根目录执行:
|
||||
|
||||
```bash
|
||||
cd experiments/livekit-v1-mini
|
||||
uv sync --locked
|
||||
uv run --locked python run_experiment.py smoke
|
||||
uv run --locked python run_experiment.py all
|
||||
```
|
||||
|
||||
`smoke` 使用种子 42 生成固定输入,重复推理必须一致,输出 `WITNESS`;`all` 生成 44 点 predictions.csv、summary.json 和六通录音的 score-timeline.json。`evaluate` 只跑标注点。所有输出位于 results,不修改人工标注。
|
||||
|
||||
## 输入与评分
|
||||
|
||||
- 只用用户左声道 channel 0;先裁取判断时间之前的 int16 音频,除以 32768,再用 scipy resample_poly 重采样到 16 kHz,取最近 19200 样本(1.2 秒),乘以 32767 转为 int16。短窗口由原生模型左补零,不使用未来音频或人工转写。
|
||||
- 使用 Agents `_LocalTransport._predict` 入口,严格锁定 SDK 版本;原生异常或框架默认回退分数会导致实验失败,不计成真实预测。
|
||||
- 每 200 ms 及每个原标注精确时刻计算原始分数;不加 VAD 停止门槛、静音等待、超时放行、会话结束信号或前一预测状态。
|
||||
- 默认阈值为锁定 SDK 的中文阈值 0.355,同时报告 0.5,未在本批数据上搜索最优阈值。
|
||||
- 每个判断点重复推理并验证分数一致;将该点之后的音频改为零,验证输入不变。该方法的前缀重采样与官方 benchmark 的 librosa 或在线重采样存在实现差异,因此不称为完整官方在线策略复现。
|
||||
- 单个评估请求依次执行。脚本请求 EOT_PARALLELISM=1;当前原生库实际报告 parallelism=2,记录于结果,不声称原生单线程。OMP/MKL/OPENBLAS 请求线程数均为 1。
|
||||
|
||||
## 环境与模型校验
|
||||
|
||||
macOS arm64 CPython 3.11 官方 wheel SHA256:`fa4cb2d495c80be6121642dba4def3a88b22a68d52c1b0affccd66c563de6622`。
|
||||
原生二进制 SHA256:`7afa5899367957328c9916153b5d672392e191c0bfadf1f49325af2e4f3055b4`。模型嵌于二进制,未暴露独立权重文件,不虚构独立权重哈希。结果还记录 Python、依赖、脚本、标注及各录音哈希。环境规范见 env-spec.json。
|
||||
|
||||
## 限制与许可
|
||||
|
||||
这 44 个手选判断点仅作探索性对比,不是独立大样本泛化评估。点级误接话率不等于整段对话的抢话率,用户声道也可能存在机器人回声。模型分数和 SDK 完整终点策略须分开讨论。
|
||||
|
||||
当前 wheel 附带的 MODEL_LICENSE.md 要求模型配合 LiveKit Agents 使用,并限制用其输出改进其他模型;本实验通过 Agents 官方本地传输入口执行。网页仅查看分数;不将其用作其他模型的训练标签。不要将该模型描述为 Apache-2.0 权重。
|
||||
|
||||
官方参考:
|
||||
- https://docs.livekit.io/agents/logic/turns/turn-detector/
|
||||
- https://github.com/livekit/eot-bench/blob/main/eot_harness/livekit_turn_detector_mini_adapter.py
|
||||
|
||||
结果解读见 results/summary.json 和 results/评估说明.md。
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"base": "macOS arm64 CPU + Python 3.11",
|
||||
"system_pkgs": [],
|
||||
"pip_phases": [
|
||||
[
|
||||
"livekit-local-inference==0.2.7",
|
||||
"numpy==2.2.6",
|
||||
"soundfile==0.13.1",
|
||||
"scipy==1.15.3",
|
||||
"livekit-agents==1.8.8"
|
||||
]
|
||||
],
|
||||
"env": {
|
||||
"OMP_NUM_THREADS": "1",
|
||||
"OPENBLAS_NUM_THREADS": "1",
|
||||
"MKL_NUM_THREADS": "1",
|
||||
"EOT_PARALLELISM": "1"
|
||||
},
|
||||
"resource": {
|
||||
"machine": "local Mac mini M4",
|
||||
"gpu": false,
|
||||
"model_workers": 1
|
||||
},
|
||||
"weight_dirs": {
|
||||
"eot": {
|
||||
"source": "official PyPI livekit-local-inference wheel",
|
||||
"gated": false
|
||||
}
|
||||
},
|
||||
"smoke": {
|
||||
"cmd": "uv run --locked python run_experiment.py smoke",
|
||||
"expect": "WITNESS"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
[project]
|
||||
name = "livekit-v1-mini-evaluation"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.11,<3.12"
|
||||
dependencies = ["livekit-local-inference==0.2.7", "livekit-agents==1.8.8", "numpy==2.2.6", "soundfile==0.13.1", "scipy==1.15.3"]
|
||||
@@ -0,0 +1,45 @@
|
||||
call_id,decision_ms,can_reply_now,probability,prediction,threshold,input_samples,available_end_ms,preparation_ms,inference_ms
|
||||
2c83253a-7977-4e3c-a387-0aa1ea06fbe1,21780,1,0.4333016276359558,1,0.355,19200,21780.0,2.7287499979138374,5.575624294579029
|
||||
2c83253a-7977-4e3c-a387-0aa1ea06fbe1,29860,1,0.6237799525260925,1,0.355,19200,29860.0,3.7509994581341743,6.744083017110825
|
||||
2c83253a-7977-4e3c-a387-0aa1ea06fbe1,48125,0,0.45254063606262207,1,0.355,19200,48125.0,5.758457817137241,7.209542207419872
|
||||
2c83253a-7977-4e3c-a387-0aa1ea06fbe1,64000,1,0.2848171889781952,0,0.355,19200,64000.0,7.960332557559013,6.5420838072896
|
||||
2c83253a-7977-4e3c-a387-0aa1ea06fbe1,76672,0,0.45449426770210266,1,0.355,19200,76672.0,9.708958677947521,7.211333140730858
|
||||
2c83253a-7977-4e3c-a387-0aa1ea06fbe1,88482,1,0.45394039154052734,1,0.355,19200,88482.0,11.292457580566406,5.600791424512863
|
||||
40ae686c-8196-4d7b-b7ad-3e12a427ebb6,22007,1,0.5854928493499756,1,0.355,19200,22007.0,2.911125309765339,8.196083828806877
|
||||
40ae686c-8196-4d7b-b7ad-3e12a427ebb6,31842,1,0.5878404378890991,1,0.355,19200,31842.0,4.0603335946798325,5.483958870172501
|
||||
40ae686c-8196-4d7b-b7ad-3e12a427ebb6,48536,0,0.2537097632884979,0,0.355,19200,48536.0,5.920499563217163,5.44358417391777
|
||||
40ae686c-8196-4d7b-b7ad-3e12a427ebb6,52348,1,0.557003378868103,1,0.355,19200,52348.0,6.229749880731106,7.242332212626934
|
||||
40ae686c-8196-4d7b-b7ad-3e12a427ebb6,65298,0,0.6249343752861023,1,0.355,19200,65298.0,7.885375060141087,6.663625128567219
|
||||
40ae686c-8196-4d7b-b7ad-3e12a427ebb6,67943,0,0.6181272268295288,1,0.355,19200,67943.0,8.195959031581879,7.16041773557663
|
||||
40ae686c-8196-4d7b-b7ad-3e12a427ebb6,70430,0,0.2462766468524933,0,0.355,19200,70430.0,8.394083939492702,6.635541096329689
|
||||
40ae686c-8196-4d7b-b7ad-3e12a427ebb6,82941,1,0.5786489844322205,1,0.355,19200,82941.0,10.166166350245476,6.077749654650688
|
||||
872a1e67-873a-4c49-a598-b38a670afcad,17153,1,0.5009272694587708,1,0.355,19200,17153.0,2.16870941221714,6.159832701086998
|
||||
872a1e67-873a-4c49-a598-b38a670afcad,24846,1,0.668940544128418,1,0.355,19200,24846.0,3.041500225663185,5.271041765809059
|
||||
872a1e67-873a-4c49-a598-b38a670afcad,33471,1,0.6602025628089905,1,0.355,19200,33471.0,4.0121255442500114,6.265167146921158
|
||||
872a1e67-873a-4c49-a598-b38a670afcad,44810,0,0.16742786765098572,0,0.355,19200,44810.0,5.381416529417038,7.350374944508076
|
||||
872a1e67-873a-4c49-a598-b38a670afcad,45581,0,0.3364622890949249,0,0.355,19200,45581.0,5.505583249032497,5.54287526756525
|
||||
872a1e67-873a-4c49-a598-b38a670afcad,47187,0,0.3902971148490906,1,0.355,19200,47187.0,5.704415962100029,6.308957934379578
|
||||
872a1e67-873a-4c49-a598-b38a670afcad,49500,1,0.6626229882240295,1,0.355,19200,49500.0,5.960375070571899,6.991500034928322
|
||||
872a1e67-873a-4c49-a598-b38a670afcad,52326,0,0.2527875006198883,0,0.355,19200,52326.0,6.491790525615215,5.456083454191685
|
||||
872a1e67-873a-4c49-a598-b38a670afcad,54318,1,0.14241522550582886,0,0.355,19200,54318.0,6.935875862836838,5.50837442278862
|
||||
a266b9ef-5e47-4434-a04c-ce4eacd52c19,18027,1,0.6745697259902954,1,0.355,19200,18027.0,2.313540317118168,7.023083046078682
|
||||
a266b9ef-5e47-4434-a04c-ce4eacd52c19,24768,1,0.6706604957580566,1,0.355,19200,24768.0,3.0613746494054794,5.433417856693268
|
||||
a266b9ef-5e47-4434-a04c-ce4eacd52c19,41219,0,0.5743900537490845,1,0.355,19200,41219.0,5.006374791264534,6.470665335655212
|
||||
a266b9ef-5e47-4434-a04c-ce4eacd52c19,51080,0,0.47520971298217773,1,0.355,19200,51080.0,6.177124567329884,7.069415412843227
|
||||
a266b9ef-5e47-4434-a04c-ce4eacd52c19,68313,0,0.6697428226470947,1,0.355,19200,68313.0,8.281541988253593,7.906915619969368
|
||||
cb3238dc-3946-42a5-aad8-9b9a54226363,13540,1,0.5981559753417969,1,0.355,19200,13540.0,1.6896668821573257,5.790833383798599
|
||||
cb3238dc-3946-42a5-aad8-9b9a54226363,19620,1,0.651459813117981,1,0.355,19200,19620.0,2.6979586109519005,5.748957395553589
|
||||
cb3238dc-3946-42a5-aad8-9b9a54226363,27520,1,0.6795960068702698,1,0.355,19200,27520.0,3.496958874166012,5.925584584474564
|
||||
cb3238dc-3946-42a5-aad8-9b9a54226363,41207,0,0.6371078491210938,1,0.355,19200,41207.0,5.00095821917057,5.397791974246502
|
||||
cb3238dc-3946-42a5-aad8-9b9a54226363,49204,0,0.6755564212799072,1,0.355,19200,49204.0,6.174792535603046,5.501791834831238
|
||||
cb3238dc-3946-42a5-aad8-9b9a54226363,58424,0,0.6663632392883301,1,0.355,19200,58424.0,7.433624938130379,5.379541777074337
|
||||
cb3238dc-3946-42a5-aad8-9b9a54226363,60790,0,0.25056231021881104,0,0.355,19200,60790.0,7.39095825701952,6.392749957740307
|
||||
cb3238dc-3946-42a5-aad8-9b9a54226363,70745,0,0.5162182450294495,1,0.355,19200,70745.0,8.840749971568584,5.87591715157032
|
||||
cb3238dc-3946-42a5-aad8-9b9a54226363,73640,0,0.387985497713089,1,0.355,19200,73640.0,8.819833397865295,5.357082933187485
|
||||
e304f740-85d4-427e-9fd6-c5474e9cd737,11531,1,0.6718385219573975,1,0.355,19200,11531.0,1.627875491976738,5.708709359169006
|
||||
e304f740-85d4-427e-9fd6-c5474e9cd737,18842,1,0.5978389978408813,1,0.355,19200,18842.0,2.3520831018686295,6.592665798962116
|
||||
e304f740-85d4-427e-9fd6-c5474e9cd737,29619,1,0.6137039065361023,1,0.355,19200,29619.0,3.6592911928892136,6.604207679629326
|
||||
e304f740-85d4-427e-9fd6-c5474e9cd737,40701,0,0.6689900755882263,1,0.355,19200,40701.0,4.9554575234651566,5.339334718883038
|
||||
e304f740-85d4-427e-9fd6-c5474e9cd737,54600,1,0.6682158708572388,1,0.355,19200,54600.0,6.610791198909283,6.827791221439838
|
||||
e304f740-85d4-427e-9fd6-c5474e9cd737,62828,0,0.7292628288269043,1,0.355,19200,62828.0,7.489999756217003,5.283292382955551
|
||||
e304f740-85d4-427e-9fd6-c5474e9cd737,65819,0,0.6575707197189331,1,0.355,19200,65819.0,8.478624746203423,5.405125208199024
|
||||
|
@@ -0,0 +1,8 @@
|
||||
WITNESS CPU seeded probability=0.580093086, repeat=0.580093086, parallelism=2
|
||||
2c83253a: 489 scores, 6 checkpoints; repeat/future-suffix checks passed
|
||||
40ae686c: 441 scores, 8 checkpoints; repeat/future-suffix checks passed
|
||||
872a1e67: 379 scores, 9 checkpoints; repeat/future-suffix checks passed
|
||||
a266b9ef: 421 scores, 5 checkpoints; repeat/future-suffix checks passed
|
||||
cb3238dc: 478 scores, 9 checkpoints; repeat/future-suffix checks passed
|
||||
e304f740: 365 scores, 7 checkpoints; repeat/future-suffix checks passed
|
||||
{"threshold": 0.355, "n": 44, "tn": 6, "fp": 16, "fn": 2, "tp": 20, "accuracy": 0.5909090909090909, "false_reply_rate": 0.7272727272727273, "false_hold_rate": 0.09090909090909091}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
|
||||
WITNESS CPU seeded probability=0.580093086, repeat=0.580093086, parallelism=2
|
||||
{"load_ms": 123.47654160112143, "provenance": {"machine": "macOS-26.4.1-arm64-arm-64bit", "hardware": "Apple M4 Mac mini CPU, 16 GB", "python": "3.11.15 (main, Mar 25 2026, 02:58:47) [Clang 22.1.1 ]", "dependencies": {"livekit-local-inference": "0.2.7", "livekit-agents": "1.8.8", "numpy": "2.2.6", "scipy": "1.15.3", "soundfile": "0.13.1"}, "native_binary_sha256": "7afa5899367957328c9916153b5d672392e191c0bfadf1f49325af2e4f3055b4", "weight_identity": "Weights embedded in pinned official binary; separate hash not exposed", "wheel_sha256": "fa4cb2d495c80be6121642dba4def3a88b22a68d52c1b0affccd66c563de6622", "native_parallelism": 2, "script_sha256": "e05a0408fcce2a8194966bf594d7324e17cd8025d0b7eb06152e58eb9b46f892", "annotation_sha256": "780e50f976daf039ab976ea155bdb5d1ca5fffc792f9db7adf8063ec3048ff8e"}}
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"metrics": {
|
||||
"threshold": 0.355,
|
||||
"n": 44,
|
||||
"tn": 6,
|
||||
"fp": 16,
|
||||
"fn": 2,
|
||||
"tp": 20,
|
||||
"accuracy": 0.5909090909090909,
|
||||
"false_reply_rate": 0.7272727272727273,
|
||||
"false_hold_rate": 0.09090909090909091
|
||||
},
|
||||
"reference_threshold_05": {
|
||||
"threshold": 0.5,
|
||||
"n": 44,
|
||||
"tn": 11,
|
||||
"fp": 11,
|
||||
"fn": 4,
|
||||
"tp": 18,
|
||||
"accuracy": 0.6590909090909091,
|
||||
"false_reply_rate": 0.5,
|
||||
"false_hold_rate": 0.18181818181818182
|
||||
},
|
||||
"model": "LiveKit Turn Detector v1-mini",
|
||||
"official_zh_threshold": 0.355,
|
||||
"load_ms": 119.18720882385969,
|
||||
"method": "Agents 1.8.8 local transport, native v1-mini 0.2.7. User channel 0; received int16 prefix /32768 cropped BEFORE scipy resample_poly to 16kHz; last 19200 samples converted with *32767. Native left-padding below 1.2s. No ASR, VAD gate, silence timeout or fallback scores. EOT_PARALLELISM=1. Prefix resampling differs from official librosa and online streaming resampling; not a full service replay.",
|
||||
"provenance": {
|
||||
"machine": "macOS-26.4.1-arm64-arm-64bit",
|
||||
"hardware": "Apple M4 Mac mini CPU, 16 GB",
|
||||
"python": "3.11.15 (main, Mar 25 2026, 02:58:47) [Clang 22.1.1 ]",
|
||||
"dependencies": {
|
||||
"livekit-local-inference": "0.2.7",
|
||||
"livekit-agents": "1.8.8",
|
||||
"numpy": "2.2.6",
|
||||
"scipy": "1.15.3",
|
||||
"soundfile": "0.13.1"
|
||||
},
|
||||
"native_binary_sha256": "7afa5899367957328c9916153b5d672392e191c0bfadf1f49325af2e4f3055b4",
|
||||
"weight_identity": "Weights embedded in pinned official binary; separate hash not exposed",
|
||||
"wheel_sha256": "fa4cb2d495c80be6121642dba4def3a88b22a68d52c1b0affccd66c563de6622",
|
||||
"native_parallelism": 2,
|
||||
"script_sha256": "e05a0408fcce2a8194966bf594d7324e17cd8025d0b7eb06152e58eb9b46f892",
|
||||
"annotation_sha256": "780e50f976daf039ab976ea155bdb5d1ca5fffc792f9db7adf8063ec3048ff8e"
|
||||
},
|
||||
"preparation_ms": {
|
||||
"p50": 5.839478690177202,
|
||||
"p95": 9.578727371990682
|
||||
},
|
||||
"inference_ms": {
|
||||
"p50": 6.118791177868843,
|
||||
"p95": 7.3341685347259045
|
||||
},
|
||||
"limitations": [
|
||||
"44 selected points: exploratory, not held-out generalization.",
|
||||
"Point false reply rate is not interval interruption rate; user channel may contain robot echo.",
|
||||
"Raw zh threshold without production VAD/endpointing gates.",
|
||||
"Wheel license restricts use to LiveKit Agents; do not use outputs to improve other models."
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
# LiveKit v1-mini 韩泰评估结果
|
||||
|
||||
本机 M4 CPU;6 通录音、44 个判断点,正负各 22。官方中文阈值来自锁定 SDK 的 LOCAL_LANGUAGES[zh],未对本批数据搜索最优阈值。
|
||||
|
||||
| 阈值 | 正确 / 总数 | 准确率 | 误接话(应等待却接话) | 漏接话(应接话却等待) |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 0.355 | 26 / 44 | 59.09% | 16 / 22(72.73%) | 2 / 22(9.09%) |
|
||||
| 0.5 | 29 / 44 | 65.91% | 11 / 22(50.00%) | 4 / 22(18.18%) |
|
||||
|
||||
模型已加载后,框架本地推理调用(包含异步派发)中位数 6.12 ms、P95 7.33 ms;输入准备/前缀重采样中位数 5.84 ms、P95 9.58 ms。模型构造耗时 119.19 ms,不含 Python/Agents 导入,不是完整进程冷启动时间。原生库报告 parallelism=2,不能称为单线程。耗时不等于端点等待时间。
|
||||
|
||||
连续曲线每 200 ms 独立计算最近 1.2 秒窗口,并包含精确判断点,非插值生成。全部 44 点验证了重复结果一致,以及修改未来音频不改变输入。本实验读取当前标注;已有其他模型结果来自之前的标注快照,已发现第二通录音有一个判断点从旧结果的 48348 ms 移至当前 48536 ms,其余 43 点时间和标签一致。因此不能直接拿旧 44 点汇总作严格横向排名;旧曲线空心点仍保留原快照时刻。
|
||||
|
||||
中文默认阈值下,16 个应等待点误判,仍不能认为解决了韩泰抢话问题。0.5 是预先选定参考值,不是独立验证的最优阈值。小样本手选点仅为探索性回测,点级误接话率不等于连续对话打断率,用户声道可能有机器人回声。
|
||||
|
||||
只评估原始模型分数,不含 LiveKit 会话静音、VAD、超时或故障放行策略。预处理采用判断前裁取再重采样,具体差异见上级 README。模型许可限制见上级 README,结果不用于其他模型训练。
|
||||
@@ -0,0 +1,111 @@
|
||||
"""Causal v1-mini evaluation using LiveKit Agents' official local transport."""
|
||||
import os
|
||||
for k in ('OMP_NUM_THREADS','OPENBLAS_NUM_THREADS','MKL_NUM_THREADS','EOT_PARALLELISM'): os.environ[k]='1'
|
||||
import argparse,asyncio,csv,hashlib,json,math,platform,sys,time,weakref
|
||||
import importlib.metadata as metadata
|
||||
from collections import Counter
|
||||
from pathlib import Path
|
||||
import numpy as np
|
||||
import soundfile as sf
|
||||
from scipy.signal import resample_poly
|
||||
from livekit.agents.inference import TurnDetector
|
||||
from livekit.agents.inference.eot.transports import _LocalTransport
|
||||
from livekit.agents.inference.eot.languages import LOCAL_LANGUAGES
|
||||
from livekit.local_inference import _native
|
||||
HERE=Path(__file__).resolve().parent
|
||||
ROOT=HERE.parents[1]
|
||||
OUT=HERE/'results'
|
||||
LABELS=ROOT/'labels/EOT标注/工作数据/eot_labels.csv'
|
||||
THRESHOLD=LOCAL_LANGUAGES['zh']
|
||||
def sha(p): return hashlib.sha256(p.read_bytes()).hexdigest()
|
||||
def pcm_at(user,sr,ms):
|
||||
end=min(len(user),ms*sr//1000)
|
||||
if end<=0: raise ValueError('No received audio')
|
||||
prefix=user[:end].astype(np.float32)/32768.
|
||||
g=math.gcd(sr,16000)
|
||||
audio=resample_poly(prefix,16000//g,sr//g) if sr!=16000 else prefix
|
||||
return np.ascontiguousarray((np.clip(audio[-19200:],-1,1)*32767).astype(np.int16))
|
||||
class Receiver:
|
||||
def _resolve_prediction(self,request_id,prob,**kwargs): self.value=prob
|
||||
class CheckedNative:
|
||||
"""Reject the framework's error fallback instead of counting it as a model score."""
|
||||
def __init__(self,native): self.native=native;self.success=False
|
||||
def predict(self,pcm):
|
||||
self.success=False
|
||||
self.value=float(self.native.predict(pcm))
|
||||
if not np.isfinite(self.value) or not 0<=self.value<=1: raise ValueError('Invalid score')
|
||||
self.success=True
|
||||
return self.value
|
||||
class Scorer:
|
||||
def __init__(self):
|
||||
self.detector=TurnDetector(version='v1-mini')
|
||||
self.transport=_LocalTransport(opts=self.detector._opts)
|
||||
self.checked=CheckedNative(self.transport._eot)
|
||||
self.transport._eot=self.checked
|
||||
self.receiver=Receiver()
|
||||
self.transport._stream_ref=weakref.ref(self.receiver)
|
||||
async def predict(self,pcm):
|
||||
self.checked.success=False;self.receiver.value=None
|
||||
await self.transport._predict('offline-evaluation',pcm)
|
||||
if not self.checked.success or self.receiver.value!=self.checked.value: raise RuntimeError('Inference fallback rejected')
|
||||
return self.receiver.value
|
||||
|
||||
def metrics(rows,threshold):
|
||||
c=Counter((int(r['can_reply_now']),int(r['probability']>=threshold)) for r in rows if r['can_reply_now'] in ('0','1'))
|
||||
tn,fp,fn,tp=c[0,0],c[0,1],c[1,0],c[1,1];n=tn+fp+fn+tp
|
||||
return dict(threshold=threshold,n=n,tn=tn,fp=fp,fn=fn,tp=tp,accuracy=(tn+tp)/n,
|
||||
false_reply_rate=fp/(tn+fp) if tn+fp else None,false_hold_rate=fn/(tp+fn) if tp+fn else None)
|
||||
def provenance():
|
||||
return dict(machine=platform.platform(),hardware='Apple M4 Mac mini CPU, 16 GB',python=sys.version,
|
||||
dependencies={p:metadata.version(p) for p in ['livekit-local-inference','livekit-agents','numpy','scipy','soundfile']},
|
||||
native_binary_sha256=sha(Path(_native.__file__)),weight_identity='Weights embedded in pinned official binary; separate hash not exposed',
|
||||
wheel_sha256='fa4cb2d495c80be6121642dba4def3a88b22a68d52c1b0affccd66c563de6622',
|
||||
native_parallelism=_native._parallelism(),script_sha256=sha(Path(__file__)),annotation_sha256=sha(LABELS))
|
||||
async def main(mode):
|
||||
OUT.mkdir(exist_ok=True)
|
||||
t=time.perf_counter();model=Scorer();load_ms=(time.perf_counter()-t)*1000
|
||||
rng=np.random.default_rng(42);x=(rng.normal(0,.005,19200)*32767).astype(np.int16)
|
||||
p=await model.predict(x);q=await model.predict(x);assert p==q
|
||||
print(f'WITNESS CPU seeded probability={p:.9f}, repeat={q:.9f}, parallelism={_native._parallelism()}',flush=True)
|
||||
if mode=='smoke':
|
||||
print(json.dumps(dict(load_ms=load_ms,provenance=provenance()),ensure_ascii=False));return
|
||||
with LABELS.open(encoding='utf-8-sig',newline='') as f: labels=list(csv.DictReader(f))
|
||||
calls,rows=[],[]
|
||||
for call in sorted({r['call_id'] for r in labels}):
|
||||
path=ROOT/'data/韩泰机器人抢话/音频'/f'{call}.mp3'
|
||||
pcm,sr=sf.read(path,dtype='int16',always_2d=True);user=pcm[:,0];duration=round(len(user)*1000/sr,3)
|
||||
exact={int(r['decision_ms']):r for r in labels if r['call_id']==call}
|
||||
if any(t>duration for t in exact): raise ValueError('Annotation exceeds audio')
|
||||
times=sorted(set(range(200,int(duration)+1,200))|set(exact)|{int(duration)}) if mode=='all' else sorted(exact)
|
||||
points=[];checkpoints=[]
|
||||
for ms in times:
|
||||
t=time.perf_counter();audio=pcm_at(user,sr,ms);prep=(time.perf_counter()-t)*1000
|
||||
t=time.perf_counter();score=await model.predict(audio);latency=(time.perf_counter()-t)*1000
|
||||
points.append([ms,round(score,7)])
|
||||
if ms in exact:
|
||||
assert score==await model.predict(audio)
|
||||
modified=user.copy();modified[ms*sr//1000:]=0
|
||||
assert np.array_equal(audio,pcm_at(modified,sr,ms))
|
||||
checkpoints.append([ms,round(score,7)]);row=exact[ms]
|
||||
rows.append(dict(call_id=call,decision_ms=ms,can_reply_now=row['can_reply_now'],probability=score,
|
||||
prediction=int(score>=THRESHOLD),threshold=THRESHOLD,input_samples=len(audio),
|
||||
available_end_ms=(ms*sr//1000)*1000/sr,preparation_ms=prep,inference_ms=latency))
|
||||
calls.append(dict(call_id=call,audio_path=f'韩泰机器人抢话/音频/{call}.mp3',audio_sha256=sha(path),
|
||||
duration_ms=duration,points=points,checkpoints=checkpoints))
|
||||
print(f'{call[:8]}: {len(points)} scores, {len(checkpoints)} checkpoints; repeat/future-suffix checks passed',flush=True)
|
||||
with (OUT/'predictions.csv').open('w',encoding='utf-8',newline='') as f:
|
||||
w=csv.DictWriter(f,fieldnames=list(rows[0]));w.writeheader();w.writerows(rows)
|
||||
method='Agents 1.8.8 local transport, native v1-mini 0.2.7. User channel 0; received int16 prefix /32768 cropped BEFORE scipy resample_poly to 16kHz; last 19200 samples converted with *32767. Native left-padding below 1.2s. No ASR, VAD gate, silence timeout or fallback scores. EOT_PARALLELISM=1. Prefix resampling differs from official librosa and online streaming resampling; not a full service replay.'
|
||||
summary=dict(metrics=metrics(rows,THRESHOLD),reference_threshold_05=metrics(rows,.5),model='LiveKit Turn Detector v1-mini',
|
||||
official_zh_threshold=THRESHOLD,load_ms=load_ms,method=method,provenance=provenance(),
|
||||
preparation_ms={k:float(np.percentile([r['preparation_ms'] for r in rows],v)) for k,v in [('p50',50),('p95',95)]},
|
||||
inference_ms={k:float(np.percentile([r['inference_ms'] for r in rows],v)) for k,v in [('p50',50),('p95',95)]},
|
||||
limitations=['44 selected points: exploratory, not held-out generalization.', 'Point false reply rate is not interval interruption rate; user channel may contain robot echo.', 'Raw zh threshold without production VAD/endpointing gates.', 'Wheel license restricts use to LiveKit Agents; do not use outputs to improve other models.'])
|
||||
(OUT/'summary.json').write_text(json.dumps(summary,ensure_ascii=False,indent=2)+'\n')
|
||||
if mode=='all':
|
||||
out=dict(schema_version=1,model='livekit_mini',name='LiveKit v1-mini',score='P(turn ended)',sample_interval_ms=200,
|
||||
method=method,provenance=provenance(),calls=calls)
|
||||
(OUT/'score-timeline.json').write_text(json.dumps(out,ensure_ascii=False,separators=(',',':'))+'\n')
|
||||
print(json.dumps(summary['metrics']),flush=True)
|
||||
if __name__=='__main__':
|
||||
p=argparse.ArgumentParser();p.add_argument('mode',choices=['smoke','evaluate','all']);asyncio.run(main(p.parse_args().mode))
|
||||
Generated
+1141
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,5 @@
|
||||
.venv/
|
||||
__pycache__/
|
||||
models/
|
||||
vendor/
|
||||
cache/
|
||||
@@ -0,0 +1 @@
|
||||
3.11
|
||||
@@ -0,0 +1,39 @@
|
||||
# TurnSense 1.0 FP32 韩泰测试
|
||||
|
||||
本机 Apple M4 Mac mini、CPU、16 GB,独立 Python 3.11 uv 环境。锁定依赖见 pyproject.toml / uv.lock,声明规范见 env-spec.json。ONNX Runtime CPU,intra/inter-op 各 1 线程。官方代码与权重版本、SHA256 见 model-manifest.json。
|
||||
|
||||
## 复现
|
||||
|
||||
从项目根目录运行:
|
||||
|
||||
```bash
|
||||
cd experiments/turnsense-1.0
|
||||
uv sync --locked
|
||||
uv run --locked python setup_assets.py
|
||||
uv run --locked python run_experiment.py smoke
|
||||
uv run --locked python run_experiment.py all
|
||||
```
|
||||
|
||||
本机 Hugging Face 直连失败时,使用已配置代理(示例为本机现有 7897 端口)下载公开权重:
|
||||
|
||||
```bash
|
||||
HTTPS_PROXY=http://127.0.0.1:7897 uv run --locked python setup_assets.py
|
||||
```
|
||||
|
||||
已有固定 vendor/ 与 models/ 时,可直接执行 smoke,不需联网。`smoke` 应输出 WITNESS、三项概率之和为 1、repeat identical;固定 seed=42 噪声输入应主要判为 invalid。`all` 跑全部六通录音的 200 ms 网格及当前 44 个精确判断点;`evaluate` 仅跑判断点。输出只写 results,不修改人工标注。
|
||||
|
||||
## 输入与规则
|
||||
|
||||
- 原录音用户左声道 channel 0,int16 / 32768;先裁取 decision_ms 之前已收到的前缀,再用官方依赖 librosa 重采样到 16 kHz,取末尾最多 8 秒。不用转写、未来音频、超时放行或 VAD 停止门槛。
|
||||
- 官方前端:kaldi-native-fbank,80 mel,25 ms Hamming 窗、10 ms 帧移,snip_edges=true,dither=0,LFR m=7/n=6,官方 am.mvn。LFR 边界重复的是已收到音频的最后特征,不读真实未来帧。官方完整前端未改动。
|
||||
- 保留 complete/incomplete/invalid 三项概率和原生 argmax 类别。主要二类映射为原生 complete=>can_reply=1,其余两类=>0;另报告 P(complete)>=0.5。网页曲线显示 P(complete),0.5 参考线不等同原生三分类 argmax。
|
||||
- 每个精确判断点重复推理须完全一致;将之后的音频置零,准备出的输入须保持一致。固定 200 ms 网格是逐次真实独立推理,非插值,也不是原生流式模型。前缀重采样与特征抽取均可重算,因此计算耗时不能声称为在线增量开销。
|
||||
- 实际环境、输入文件/脚本/前端/CMVN/权重哈希写入 summary.json。分开记录重采样、特征提取、ONNX 推理耗时;模型构造不含 Python 进程启动和 import。
|
||||
|
||||
官方来源:
|
||||
- https://github.com/Bairong-Xdynamics/TurnSense
|
||||
- https://huggingface.co/brgroup/TurnSense/tree/main/TurnSense1.0
|
||||
|
||||
供应代码 LICENSE 为 Apache-2.0,保留在 vendor/LICENSE;README 还提到附加条件,产品部署前应以所用版本实际许可为准。本目录只做公开模型的本地比较,不重分发权重。
|
||||
|
||||
44 个选定点仅为探索性测试,点级误接话率不等于连续对话抢话率,用户声道可能含机器人回声。旧模型若标注时间不同,不能直接用旧汇总排名。结果解读见 results/评估说明.md。
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"base": "macOS arm64 CPU, Python 3.11",
|
||||
"pip_phases": [
|
||||
[
|
||||
"numpy==2.2.6",
|
||||
"onnxruntime==1.24.4",
|
||||
"soundfile==0.13.1",
|
||||
"librosa==0.11.0",
|
||||
"kaldi-native-fbank==1.22.3"
|
||||
]
|
||||
],
|
||||
"env": {
|
||||
"OMP_NUM_THREADS": "1",
|
||||
"OPENBLAS_NUM_THREADS": "1",
|
||||
"MKL_NUM_THREADS": "1"
|
||||
},
|
||||
"resource": {
|
||||
"machine": "Apple M4 Mac mini / 16 GB",
|
||||
"device": "CPUExecutionProvider",
|
||||
"inter_op_threads": 1,
|
||||
"intra_op_threads": 1
|
||||
},
|
||||
"code_revision": "c6d83e5b0d1355125b05eaf1b4b5c6c1acabb338",
|
||||
"weights": {
|
||||
"repo": "brgroup/TurnSense",
|
||||
"directory": "TurnSense1.0",
|
||||
"variant": "FP32",
|
||||
"revision": "341dd68c9df87d74d35b9cee63a1dcf0fff24966",
|
||||
"sha256": {
|
||||
"model_fp32.onnx": "82256247dd38f0d9f2f213beec7ede1507347f84422fa48f29d565b613d89976",
|
||||
"am.mvn": "29b3c740a2c0cfc6b308126d31d7f265fa2be74f3bb095cd2f143ea970896ae5"
|
||||
}
|
||||
},
|
||||
"smoke": {
|
||||
"cmd": "uv run --locked python run_experiment.py smoke",
|
||||
"expect": "WITNESS"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"repo": "brgroup/TurnSense",
|
||||
"revision": "341dd68c9df87d74d35b9cee63a1dcf0fff24966",
|
||||
"version": "TurnSense1.0 FP32",
|
||||
"code_repo": "Bairong-Xdynamics/TurnSense",
|
||||
"code_revision": "c6d83e5b0d1355125b05eaf1b4b5c6c1acabb338",
|
||||
"files": {
|
||||
"model_fp32.onnx": "82256247dd38f0d9f2f213beec7ede1507347f84422fa48f29d565b613d89976",
|
||||
"am.mvn": "29b3c740a2c0cfc6b308126d31d7f265fa2be74f3bb095cd2f143ea970896ae5"
|
||||
},
|
||||
"verified_against": "Hugging Face fixed revision LFS SHA256 / Git blob SHA1"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
[project]
|
||||
name = "turnsense-1-0-evaluation"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.11,<3.12"
|
||||
dependencies = ["numpy==2.2.6", "onnxruntime==1.24.4", "soundfile==0.13.1", "librosa==0.11.0", "kaldi-native-fbank==1.22.3"]
|
||||
@@ -0,0 +1,45 @@
|
||||
call_id,decision_ms,can_reply_now,p_complete,p_incomplete,p_invalid,native_class,native_can_reply,prediction_05,input_samples,available_end_ms,preparation_ms,frontend_ms,inference_ms
|
||||
2c83253a-7977-4e3c-a387-0aa1ea06fbe1,21780,1,0.7006610631942749,0.29899370670318604,0.00034517815220169723,complete,1,1,128000,21780.0,0.6559574976563454,4.819874651730061,31.615042127668858
|
||||
2c83253a-7977-4e3c-a387-0aa1ea06fbe1,29860,1,0.6634299755096436,0.3349137306213379,0.0016562796663492918,complete,1,1,128000,29860.0,1.0954178869724274,5.1015829667449,32.756125554442406
|
||||
2c83253a-7977-4e3c-a387-0aa1ea06fbe1,48125,0,0.37549158930778503,0.624477744102478,3.058241418329999e-05,incomplete,0,0,128000,48125.0,1.650916412472725,5.288207903504372,31.92400000989437
|
||||
2c83253a-7977-4e3c-a387-0aa1ea06fbe1,64000,1,0.48844802379608154,0.5109370946884155,0.0006149054388515651,incomplete,0,0,128000,64000.0,2.638166770339012,5.002083256840706,35.11762525886297
|
||||
2c83253a-7977-4e3c-a387-0aa1ea06fbe1,76672,0,0.13564634323120117,0.8621996641159058,0.0021539577282965183,incomplete,0,0,128000,76672.0,3.0605001375079155,5.475125275552273,30.60787543654442
|
||||
2c83253a-7977-4e3c-a387-0aa1ea06fbe1,88482,1,0.942379891872406,0.05571956932544708,0.0019004124915227294,complete,1,1,128000,88482.0,3.272208385169506,4.725124686956406,35.046874545514584
|
||||
40ae686c-8196-4d7b-b7ad-3e12a427ebb6,22007,1,0.3481478691101074,0.651701033115387,0.00015111359243746847,incomplete,0,0,128000,22007.0,0.7836250588297844,4.815500229597092,31.588083133101463
|
||||
40ae686c-8196-4d7b-b7ad-3e12a427ebb6,31842,1,0.4427306354045868,0.5568790435791016,0.000390340865124017,incomplete,0,0,128000,31842.0,0.8989162743091583,4.637166857719421,31.384666450321674
|
||||
40ae686c-8196-4d7b-b7ad-3e12a427ebb6,48536,0,0.7467879056930542,0.2531856596469879,2.6438898203196004e-05,complete,1,1,128000,48536.0,1.652708277106285,4.660000093281269,29.796875081956387
|
||||
40ae686c-8196-4d7b-b7ad-3e12a427ebb6,52348,1,0.9037413597106934,0.09623286128044128,2.5730008928803727e-05,complete,1,1,128000,52348.0,2.6957085356116295,6.087250076234341,32.25929103791714
|
||||
40ae686c-8196-4d7b-b7ad-3e12a427ebb6,65298,0,0.16583986580371857,0.8341189026832581,4.125330087845214e-05,incomplete,0,0,128000,65298.0,2.300082705914974,4.840708337724209,29.82383221387863
|
||||
40ae686c-8196-4d7b-b7ad-3e12a427ebb6,67943,0,0.12022174149751663,0.8796190023422241,0.00015931774396449327,incomplete,0,0,128000,67943.0,2.689083106815815,5.102500319480896,31.059750355780125
|
||||
40ae686c-8196-4d7b-b7ad-3e12a427ebb6,70430,0,0.0029643976595252752,0.9970191717147827,1.6417250662925653e-05,incomplete,0,0,128000,70430.0,2.258916385471821,4.696916788816452,31.458708457648754
|
||||
40ae686c-8196-4d7b-b7ad-3e12a427ebb6,82941,1,0.5291794538497925,0.4702177941799164,0.0006027779309079051,complete,1,1,128000,82941.0,3.3505000174045563,5.25750033557415,31.063792295753956
|
||||
872a1e67-873a-4c49-a598-b38a670afcad,17153,1,0.6752944588661194,0.32434993982315063,0.0003555795119609684,complete,1,1,128000,17153.0,0.5732094869017601,4.804333671927452,31.661707907915115
|
||||
872a1e67-873a-4c49-a598-b38a670afcad,24846,1,0.19226661324501038,0.8076792359352112,5.4253305279416963e-05,incomplete,0,0,128000,24846.0,0.7762927561998367,5.662750452756882,31.10175020992756
|
||||
872a1e67-873a-4c49-a598-b38a670afcad,33471,1,0.670210599899292,0.3295625150203705,0.00022692007769364864,complete,1,1,128000,33471.0,1.130000688135624,4.772042855620384,31.292874366044998
|
||||
872a1e67-873a-4c49-a598-b38a670afcad,44810,0,0.613646924495697,0.3845664858818054,0.0017866046400740743,complete,1,1,128000,44810.0,1.7957082018256187,5.095167085528374,30.916291289031506
|
||||
872a1e67-873a-4c49-a598-b38a670afcad,45581,0,0.5043333172798157,0.4948233664035797,0.0008433183538727462,complete,1,1,128000,45581.0,1.8424158915877342,4.733665846288204,31.039416790008545
|
||||
872a1e67-873a-4c49-a598-b38a670afcad,47187,0,0.33440664410591125,0.664151668548584,0.001441665575839579,incomplete,0,0,128000,47187.0,1.7980830743908882,4.785459488630295,31.987749971449375
|
||||
872a1e67-873a-4c49-a598-b38a670afcad,49500,1,0.46493276953697205,0.5347657203674316,0.0003015460097230971,incomplete,0,0,128000,49500.0,2.1763751283288,5.169540643692017,29.42441590130329
|
||||
872a1e67-873a-4c49-a598-b38a670afcad,52326,0,0.31294915080070496,0.6869400143623352,0.00011081220145570114,incomplete,0,0,128000,52326.0,2.1302076056599617,4.98233363032341,30.02820909023285
|
||||
872a1e67-873a-4c49-a598-b38a670afcad,54318,1,0.7077078223228455,0.2920091450214386,0.00028306824970059097,complete,1,1,128000,54318.0,1.9198330119252205,4.737791605293751,30.707416124641895
|
||||
a266b9ef-5e47-4434-a04c-ce4eacd52c19,18027,1,0.19850707054138184,0.8014131784439087,7.97970496932976e-05,incomplete,0,0,128000,18027.0,0.5511250346899033,4.951875656843185,30.43895773589611
|
||||
a266b9ef-5e47-4434-a04c-ce4eacd52c19,24768,1,0.45438897609710693,0.5453333258628845,0.00027762504760175943,incomplete,0,0,128000,24768.0,0.8649574592709541,5.036665126681328,29.373792000114918
|
||||
a266b9ef-5e47-4434-a04c-ce4eacd52c19,41219,0,0.20029887557029724,0.798045814037323,0.001655340543948114,incomplete,0,0,128000,41219.0,1.262083649635315,4.710624925792217,30.250207521021366
|
||||
a266b9ef-5e47-4434-a04c-ce4eacd52c19,51080,0,0.6161020994186401,0.3807879388332367,0.003109981305897236,complete,1,1,128000,51080.0,1.7816657200455666,4.898290149867535,38.34949992597103
|
||||
a266b9ef-5e47-4434-a04c-ce4eacd52c19,68313,0,0.8574621677398682,0.1419398933649063,0.0005979320267215371,complete,1,1,128000,68313.0,1.8581673502922058,5.039874464273453,29.415292665362358
|
||||
cb3238dc-3946-42a5-aad8-9b9a54226363,13540,1,0.6023414134979248,0.39736831188201904,0.0002902977867051959,complete,1,1,128000,13540.0,0.6275000050663948,5.3015416488051414,31.05229139328003
|
||||
cb3238dc-3946-42a5-aad8-9b9a54226363,19620,1,0.3794628083705902,0.6204359531402588,0.00010125405970029533,incomplete,0,0,128000,19620.0,0.6028758361935616,5.842084065079689,35.181958228349686
|
||||
cb3238dc-3946-42a5-aad8-9b9a54226363,27520,1,0.5865507125854492,0.41297394037246704,0.0004753079265356064,complete,1,1,128000,27520.0,0.7745418697595596,4.71708457916975,30.025207437574863
|
||||
cb3238dc-3946-42a5-aad8-9b9a54226363,41207,0,0.44317537546157837,0.5567837953567505,4.084650208824314e-05,incomplete,0,0,128000,41207.0,1.503458246588707,4.907291382551193,31.758290715515614
|
||||
cb3238dc-3946-42a5-aad8-9b9a54226363,49204,0,0.4868903160095215,0.513083815574646,2.5880843168124557e-05,incomplete,0,0,128000,49204.0,1.718541607260704,4.778958857059479,30.595874413847923
|
||||
cb3238dc-3946-42a5-aad8-9b9a54226363,58424,0,0.13133850693702698,0.8686069250106812,5.45754301128909e-05,incomplete,0,0,128000,58424.0,1.8835840746760368,4.6873753890395164,29.813292436301708
|
||||
cb3238dc-3946-42a5-aad8-9b9a54226363,60790,0,0.1166829988360405,0.8832864761352539,3.057053982047364e-05,incomplete,0,0,128000,60790.0,2.817000262439251,4.758957773447037,29.99345865100622
|
||||
cb3238dc-3946-42a5-aad8-9b9a54226363,70745,0,0.22430983185768127,0.7756540179252625,3.617277616285719e-05,incomplete,0,0,128000,70745.0,2.755166031420231,5.143124610185623,32.967125065624714
|
||||
cb3238dc-3946-42a5-aad8-9b9a54226363,73640,0,0.46023279428482056,0.5397213697433472,4.5886350562796e-05,incomplete,0,0,128000,73640.0,3.018917515873909,5.13325072824955,31.059250235557556
|
||||
e304f740-85d4-427e-9fd6-c5474e9cd737,11531,1,0.4405680298805237,0.5592595338821411,0.0001724747708067298,incomplete,0,0,128000,11531.0,0.40116626769304276,4.7815414145588875,31.708291731774807
|
||||
e304f740-85d4-427e-9fd6-c5474e9cd737,18842,1,0.6414244771003723,0.35465940833091736,0.003916137386113405,complete,1,1,128000,18842.0,0.5581667646765709,4.73874993622303,29.53079156577587
|
||||
e304f740-85d4-427e-9fd6-c5474e9cd737,29619,1,0.5816889405250549,0.41641655564308167,0.0018945817137137055,complete,1,1,128000,29619.0,0.842750072479248,4.706874489784241,29.850250110030174
|
||||
e304f740-85d4-427e-9fd6-c5474e9cd737,40701,0,0.3338589072227478,0.6659473180770874,0.0001937897759489715,incomplete,0,0,128000,40701.0,1.445750705897808,5.079125054180622,31.952625140547752
|
||||
e304f740-85d4-427e-9fd6-c5474e9cd737,54600,1,0.8655244708061218,0.13437898457050323,9.655235044192523e-05,complete,1,1,128000,54600.0,2.032499760389328,4.749624989926815,31.001625582575798
|
||||
e304f740-85d4-427e-9fd6-c5474e9cd737,62828,0,0.4587578773498535,0.5411435961723328,9.847679757513106e-05,incomplete,0,0,128000,62828.0,2.3750411346554756,4.857249557971954,32.79420919716358
|
||||
e304f740-85d4-427e-9fd6-c5474e9cd737,65819,0,0.08307088166475296,0.9169193506240845,9.722626600705553e-06,incomplete,0,0,128000,65819.0,2.238958142697811,4.7214580699801445,32.456833869218826
|
||||
|
@@ -0,0 +1,30 @@
|
||||
WITNESS CPU seed=42 [2.4581515390309505e-05, 7.737393025308847e-05, 0.9998980760574341] repeat identical
|
||||
2c83253a: 100/489 scores
|
||||
2c83253a: 200/489 scores
|
||||
2c83253a: 300/489 scores
|
||||
2c83253a: 400/489 scores
|
||||
2c83253a DONE: 489 scores, 6 checkpoints; repeat/future-suffix checks passed
|
||||
40ae686c: 100/441 scores
|
||||
40ae686c: 200/441 scores
|
||||
40ae686c: 300/441 scores
|
||||
40ae686c: 400/441 scores
|
||||
40ae686c DONE: 441 scores, 8 checkpoints; repeat/future-suffix checks passed
|
||||
872a1e67: 100/379 scores
|
||||
872a1e67: 200/379 scores
|
||||
872a1e67: 300/379 scores
|
||||
872a1e67 DONE: 379 scores, 9 checkpoints; repeat/future-suffix checks passed
|
||||
a266b9ef: 100/421 scores
|
||||
a266b9ef: 200/421 scores
|
||||
a266b9ef: 300/421 scores
|
||||
a266b9ef: 400/421 scores
|
||||
a266b9ef DONE: 421 scores, 5 checkpoints; repeat/future-suffix checks passed
|
||||
cb3238dc: 100/478 scores
|
||||
cb3238dc: 200/478 scores
|
||||
cb3238dc: 300/478 scores
|
||||
cb3238dc: 400/478 scores
|
||||
cb3238dc DONE: 478 scores, 9 checkpoints; repeat/future-suffix checks passed
|
||||
e304f740: 100/365 scores
|
||||
e304f740: 200/365 scores
|
||||
e304f740: 300/365 scores
|
||||
e304f740 DONE: 365 scores, 7 checkpoints; repeat/future-suffix checks passed
|
||||
{"native_argmax": {"n": 44, "tn": 17, "fp": 5, "fn": 9, "tp": 13, "accuracy": 0.6818181818181818, "false_reply_rate": 0.22727272727272727, "false_hold_rate": 0.4090909090909091}, "threshold_05": {"n": 44, "tn": 17, "fp": 5, "fn": 9, "tp": 13, "accuracy": 0.6818181818181818, "false_reply_rate": 0.22727272727272727, "false_hold_rate": 0.4090909090909091}, "class_counts": {"complete": 18, "incomplete": 26}}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
|
||||
WITNESS CPU seed=42 [2.4581515390309505e-05, 7.737393025308847e-05, 0.9998980760574341] repeat identical
|
||||
{"load_ms": 576.1605408042669, "provenance": {"machine": "macOS-26.4.1-arm64-arm-64bit", "hardware": "Apple M4 Mac mini CPU / 16 GB", "python": "3.11.15 (main, Mar 25 2026, 02:58:47) [Clang 22.1.1 ]", "dependencies": {"numpy": "2.2.6", "onnxruntime": "1.24.4", "librosa": "0.11.0", "kaldi-native-fbank": "1.22.3", "soundfile": "0.13.1"}, "providers": ["CPUExecutionProvider"], "intra_op_threads": 1, "inter_op_threads": 1, "model_manifest": {"repo": "brgroup/TurnSense", "revision": "341dd68c9df87d74d35b9cee63a1dcf0fff24966", "version": "TurnSense1.0 FP32", "code_repo": "Bairong-Xdynamics/TurnSense", "code_revision": "c6d83e5b0d1355125b05eaf1b4b5c6c1acabb338", "files": {"model_fp32.onnx": "82256247dd38f0d9f2f213beec7ede1507347f84422fa48f29d565b613d89976", "am.mvn": "29b3c740a2c0cfc6b308126d31d7f265fa2be74f3bb095cd2f143ea970896ae5"}, "verified_against": "Hugging Face fixed revision LFS SHA256 / Git blob SHA1"}, "script_sha256": "23215a6db3c39f6819761adf1e6cd46a8f7c0c7767d1a3640bc928ecd548b2fb", "annotation_sha256": "780e50f976daf039ab976ea155bdb5d1ca5fffc792f9db7adf8063ec3048ff8e", "frontend_sha256": "22808c254ea2150e290e6bdcf115322a723b67fc4e53b1ef02b8f3f42697187d", "official_infer_sha256": "40295fb42a36ae4ecca30093d6bc37acbca9c75304b1a722dba73fd3b30498df"}}
|
||||
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"model": "TurnSense 1.0 FP32",
|
||||
"native_argmax": {
|
||||
"n": 44,
|
||||
"tn": 17,
|
||||
"fp": 5,
|
||||
"fn": 9,
|
||||
"tp": 13,
|
||||
"accuracy": 0.6818181818181818,
|
||||
"false_reply_rate": 0.22727272727272727,
|
||||
"false_hold_rate": 0.4090909090909091
|
||||
},
|
||||
"threshold_05": {
|
||||
"n": 44,
|
||||
"tn": 17,
|
||||
"fp": 5,
|
||||
"fn": 9,
|
||||
"tp": 13,
|
||||
"accuracy": 0.6818181818181818,
|
||||
"false_reply_rate": 0.22727272727272727,
|
||||
"false_hold_rate": 0.4090909090909091
|
||||
},
|
||||
"class_counts": {
|
||||
"complete": 18,
|
||||
"incomplete": 26
|
||||
},
|
||||
"load_ms": 657.7007500454783,
|
||||
"method": "TurnSense1.0 FP32, official code/frontend/CMVN and 3-class postprocessing. CPU one intra/inter-op thread. User channel 0 int16/32768, crop received prefix BEFORE librosa resample to 16kHz, last 8s, no future ASR/audio or silence/timeout gates. Score=P(complete), native argmax complete=>1 else0; separately report threshold0.5. Full-prefix resampling is offline causal-window evaluation, not an incremental frontend benchmark.",
|
||||
"provenance": {
|
||||
"machine": "macOS-26.4.1-arm64-arm-64bit",
|
||||
"hardware": "Apple M4 Mac mini CPU / 16 GB",
|
||||
"python": "3.11.15 (main, Mar 25 2026, 02:58:47) [Clang 22.1.1 ]",
|
||||
"dependencies": {
|
||||
"numpy": "2.2.6",
|
||||
"onnxruntime": "1.24.4",
|
||||
"librosa": "0.11.0",
|
||||
"kaldi-native-fbank": "1.22.3",
|
||||
"soundfile": "0.13.1"
|
||||
},
|
||||
"providers": [
|
||||
"CPUExecutionProvider"
|
||||
],
|
||||
"intra_op_threads": 1,
|
||||
"inter_op_threads": 1,
|
||||
"model_manifest": {
|
||||
"repo": "brgroup/TurnSense",
|
||||
"revision": "341dd68c9df87d74d35b9cee63a1dcf0fff24966",
|
||||
"version": "TurnSense1.0 FP32",
|
||||
"code_repo": "Bairong-Xdynamics/TurnSense",
|
||||
"code_revision": "c6d83e5b0d1355125b05eaf1b4b5c6c1acabb338",
|
||||
"files": {
|
||||
"model_fp32.onnx": "82256247dd38f0d9f2f213beec7ede1507347f84422fa48f29d565b613d89976",
|
||||
"am.mvn": "29b3c740a2c0cfc6b308126d31d7f265fa2be74f3bb095cd2f143ea970896ae5"
|
||||
},
|
||||
"verified_against": "Hugging Face fixed revision LFS SHA256 / Git blob SHA1"
|
||||
},
|
||||
"script_sha256": "23215a6db3c39f6819761adf1e6cd46a8f7c0c7767d1a3640bc928ecd548b2fb",
|
||||
"annotation_sha256": "780e50f976daf039ab976ea155bdb5d1ca5fffc792f9db7adf8063ec3048ff8e",
|
||||
"frontend_sha256": "22808c254ea2150e290e6bdcf115322a723b67fc4e53b1ef02b8f3f42697187d",
|
||||
"official_infer_sha256": "40295fb42a36ae4ecca30093d6bc37acbca9c75304b1a722dba73fd3b30498df"
|
||||
},
|
||||
"latency_ms": {
|
||||
"preparation_ms": {
|
||||
"p50": 1.7886869609355927,
|
||||
"p95": 3.0542627442628145
|
||||
},
|
||||
"frontend_ms": {
|
||||
"p50": 4.848978947848082,
|
||||
"p95": 5.63460667617619
|
||||
},
|
||||
"inference_ms": {
|
||||
"p50": 31.06177132576704,
|
||||
"p95": 35.107012651860714
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
# TurnSense 1.0 FP32 韩泰评估
|
||||
|
||||
本机 Apple M4 Mac mini、CPU;六通录音,当前 44 个判断点,正负各 22。只使用判断时已收到的用户左声道音频、末尾最多 8 秒,使用官方特征前端与 CMVN,未输入标注转写。
|
||||
|
||||
| 判断规则 | 正确 / 总数 | 准确率 | 误接话(应等待却接话) | 漏接话(应接话却等待) |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 原生 argmax:complete=>1,其余=>0 | 30 / 44 | 68.18% | 5 / 22(22.73%) | 9 / 22(40.91%) |
|
||||
| P(complete) ≥ 0.5 | 30 / 44 | 68.18% | 5 / 22(22.73%) | 9 / 22(40.91%) |
|
||||
|
||||
本批两种规则得到相同二分类结果,但它们在一般情况下并不等价。原生类别:complete 18 点、incomplete 26 点、invalid 0 点。invalid 输出仍保留,不能据此评估噪声识别能力,因为本批没有独立 invalid 真值。
|
||||
|
||||
## 计算耗时
|
||||
|
||||
| 阶段 | p50 | p95 |
|
||||
| --- | --- | --- |
|
||||
| 裁取/前缀重采样 | 1.79 ms | 3.05 ms |
|
||||
| 官方特征前端 | 4.85 ms | 5.63 ms |
|
||||
| ONNX 模型推理 | 31.06 ms | 35.11 ms |
|
||||
|
||||
模型构造 657.70 ms,不含 Python 启动/import,不能称为完整冷启动。CPU intra/inter-op 各 1 线程,已预热;前缀重采样和特征重算不等于在线增量开销。阶段分位数不应相加成总耗时分位数。
|
||||
|
||||
## 解读与比较边界
|
||||
|
||||
同一当前标注哈希的 LiveKit v1-mini 在 0.5 下为 29/44,误接话 11、漏接话 4;TurnSense 在 0.5 下为 30/44,误接话 5、漏接话 9。可见这批点上更倾向等待:少误接话,但更多应接话时未接话。样本小、上下文长度不同(8 秒 vs 1.2 秒),不能把这 1 点正确率差异当成稳定优势,更不能宣布已解决抢话。
|
||||
|
||||
当前标注第二通一个点已由旧 VAP / Smart Turn 快照的 48348 ms 改为 48536 ms,不能直接用其旧 44 点汇总严格排名。原曲线空心点保留各自评分时刻。
|
||||
|
||||
## 可核验性
|
||||
|
||||
- 全部判断点重复模型推理结果完全一致。
|
||||
- 将判断点之后的音频置零,准备出的模型输入不变。
|
||||
- 权重 SHA256 与固定 Hugging Face 修订的 LFS SHA256 一致;CMVN 用 Git blob SHA1 核对,并另记 SHA256。
|
||||
- 模型、CMVN、脚本、前端、标注、录音哈希与环境版本均写入结果,独立环境文档已由另一代理按说明复现 smoke。
|
||||
- 连续曲线为每 200 ms 实际推理及每个判断点的精确分数,共 2573 个采样点,非插值生成。
|
||||
|
||||
44 个手选判断点仅为探索性评估;点级误接话率不等于整段对话抢话率,用户声道可能有机器人回声。不加 VAD 门槛、静音延迟或超时策略。模型为 1.0 FP32,本次没有测试 INT8 或 1.1。
|
||||
@@ -0,0 +1,105 @@
|
||||
"""TurnSense 1.0 FP32: real causal-prefix inference with the official frontend."""
|
||||
import os
|
||||
for k in ('OMP_NUM_THREADS','MKL_NUM_THREADS','OPENBLAS_NUM_THREADS'): os.environ[k]='1'
|
||||
os.environ['NUMBA_CACHE_DIR']=str(__import__('pathlib').Path(__file__).resolve().parent/'cache/numba')
|
||||
import argparse,csv,hashlib,json,platform,sys,time
|
||||
import importlib.metadata as md
|
||||
from pathlib import Path
|
||||
from collections import Counter
|
||||
import numpy as np
|
||||
import soundfile as sf
|
||||
import librosa
|
||||
import onnxruntime as ort
|
||||
HERE=Path(__file__).resolve().parent
|
||||
ROOT=HERE.parents[1]
|
||||
OUT=HERE/'results'
|
||||
LABEL_PATH=ROOT/'labels/EOT标注/工作数据/eot_labels.csv'
|
||||
sys.path.insert(0,str(HERE/'vendor'))
|
||||
from frontend import AudioFrontend
|
||||
from infer import DEFAULT_FRONTEND_CONF,process_predictions,LABELS
|
||||
|
||||
def sha(p): return hashlib.sha256(p.read_bytes()).hexdigest()
|
||||
def prepare(user,sr,ms):
|
||||
end=min(len(user),ms*sr//1000)
|
||||
if end<=0: raise ValueError('No audio available')
|
||||
prefix=user[:end].astype(np.float32)/32768.
|
||||
if sr!=16000: prefix=librosa.resample(prefix,orig_sr=sr,target_sr=16000)
|
||||
return np.ascontiguousarray(prefix[-128000:])
|
||||
class Model:
|
||||
def __init__(self):
|
||||
manifest=json.loads((HERE/'model-manifest.json').read_text())
|
||||
for name,digest in manifest['files'].items():
|
||||
if sha(HERE/'models'/name)!=digest: raise ValueError('Weight/CMVN checksum mismatch')
|
||||
opts=ort.SessionOptions();opts.intra_op_num_threads=1;opts.inter_op_num_threads=1
|
||||
opts.graph_optimization_level=ort.GraphOptimizationLevel.ORT_ENABLE_ALL
|
||||
self.session=ort.InferenceSession(str(HERE/'models/model_fp32.onnx'),sess_options=opts,providers=['CPUExecutionProvider'])
|
||||
self.frontend=AudioFrontend(**{**DEFAULT_FRONTEND_CONF,'cmvn_file':str(HERE/'models/am.mvn')})
|
||||
assert LABELS==['complete','incomplete','invalid']
|
||||
def predict(self,audio):
|
||||
t=time.perf_counter();feats,n=self.frontend.extract_features(audio)
|
||||
inputs={'feats':np.asarray(feats,dtype=np.float32)[None,...],'feat_lengths':np.asarray([int(n)],dtype=np.int64)}
|
||||
frontend_ms=(time.perf_counter()-t)*1000
|
||||
t=time.perf_counter();raw=self.session.run(None,inputs)[0];inference_ms=(time.perf_counter()-t)*1000
|
||||
probs,pred=process_predictions(raw)
|
||||
if probs.shape!=(1,3) or not np.isfinite(probs).all() or not np.isclose(probs.sum(),1): raise ValueError('Invalid 3-class output')
|
||||
return probs[0],int(pred[0]),frontend_ms,inference_ms
|
||||
|
||||
def metrics(rows,rule):
|
||||
c=Counter((int(r['can_reply_now']),int(r[rule])) for r in rows if r['can_reply_now'] in ('0','1'))
|
||||
tn,fp,fn,tp=c[0,0],c[0,1],c[1,0],c[1,1];n=tn+fp+fn+tp
|
||||
return dict(n=n,tn=tn,fp=fp,fn=fn,tp=tp,accuracy=(tn+tp)/n,false_reply_rate=fp/(tn+fp),false_hold_rate=fn/(tp+fn))
|
||||
def prov():
|
||||
return dict(machine=platform.platform(),hardware='Apple M4 Mac mini CPU / 16 GB',python=sys.version,
|
||||
dependencies={p:md.version(p) for p in ['numpy','onnxruntime','librosa','kaldi-native-fbank','soundfile']},
|
||||
providers=['CPUExecutionProvider'],intra_op_threads=1,inter_op_threads=1,
|
||||
model_manifest=json.loads((HERE/'model-manifest.json').read_text()),script_sha256=sha(Path(__file__)),
|
||||
annotation_sha256=sha(LABEL_PATH),frontend_sha256=sha(HERE/'vendor/frontend/audio_frontend.py'),
|
||||
official_infer_sha256=sha(HERE/'vendor/infer.py'))
|
||||
def main(mode):
|
||||
OUT.mkdir(exist_ok=True)
|
||||
t=time.perf_counter();model=Model();load_ms=(time.perf_counter()-t)*1000
|
||||
rng=np.random.default_rng(42);test=rng.normal(0,.005,16000).astype(np.float32)
|
||||
a=model.predict(test)[0];b=model.predict(test)[0];assert np.array_equal(a,b)
|
||||
print('WITNESS CPU seed=42',a.tolist(),'repeat identical',flush=True)
|
||||
if mode=='smoke':
|
||||
print(json.dumps(dict(load_ms=load_ms,provenance=prov()),ensure_ascii=False));return
|
||||
with LABEL_PATH.open(encoding='utf-8-sig',newline='') as f: labels=list(csv.DictReader(f))
|
||||
rows,calls=[],[]
|
||||
for call in sorted({r['call_id'] for r in labels}):
|
||||
path=ROOT/'data/韩泰机器人抢话/音频'/f'{call}.mp3'
|
||||
pcm,sr=sf.read(path,dtype='int16',always_2d=True);user=pcm[:,0];duration=round(len(user)*1000/sr,3)
|
||||
exact={int(r['decision_ms']):r for r in labels if r['call_id']==call}
|
||||
if any(t>duration for t in exact): raise ValueError('Label beyond recording')
|
||||
times=sorted(set(range(200,int(duration)+1,200))|set(exact)|{int(duration)}) if mode=='all' else sorted(exact)
|
||||
points=[];checkpoints=[]
|
||||
for i,ms in enumerate(times):
|
||||
t=time.perf_counter();audio=prepare(user,sr,ms);prep=(time.perf_counter()-t)*1000
|
||||
probs,pred,front,inf=model.predict(audio);points.append([ms,round(float(probs[0]),7)])
|
||||
if ms in exact:
|
||||
assert np.array_equal(probs,model.predict(audio)[0])
|
||||
modified=user.copy();modified[ms*sr//1000:]=0
|
||||
assert np.array_equal(audio,prepare(modified,sr,ms))
|
||||
checkpoints.append([ms,round(float(probs[0]),7)])
|
||||
rows.append(dict(call_id=call,decision_ms=ms,can_reply_now=exact[ms]['can_reply_now'],
|
||||
p_complete=float(probs[0]),p_incomplete=float(probs[1]),p_invalid=float(probs[2]),
|
||||
native_class=LABELS[pred],native_can_reply=int(pred==0),prediction_05=int(probs[0]>=.5),
|
||||
input_samples=len(audio),available_end_ms=(ms*sr//1000)*1000/sr,
|
||||
preparation_ms=prep,frontend_ms=front,inference_ms=inf))
|
||||
if (i+1)%100==0: print(f'{call[:8]}: {i+1}/{len(times)} scores',flush=True)
|
||||
calls.append(dict(call_id=call,audio_path=f'韩泰机器人抢话/音频/{call}.mp3',audio_sha256=sha(path),
|
||||
duration_ms=duration,points=points,checkpoints=checkpoints))
|
||||
print(f'{call[:8]} DONE: {len(points)} scores, {len(checkpoints)} checkpoints; repeat/future-suffix checks passed',flush=True)
|
||||
with (OUT/'predictions.csv').open('w',encoding='utf-8',newline='') as f:
|
||||
w=csv.DictWriter(f,fieldnames=list(rows[0]));w.writeheader();w.writerows(rows)
|
||||
method='TurnSense1.0 FP32, official code/frontend/CMVN and 3-class postprocessing. CPU one intra/inter-op thread. User channel 0 int16/32768, crop received prefix BEFORE librosa resample to 16kHz, last 8s, no future ASR/audio or silence/timeout gates. Score=P(complete), native argmax complete=>1 else0; separately report threshold0.5. Full-prefix resampling is offline causal-window evaluation, not an incremental frontend benchmark.'
|
||||
summary=dict(model='TurnSense 1.0 FP32',native_argmax=metrics(rows,'native_can_reply'),threshold_05=metrics(rows,'prediction_05'),
|
||||
class_counts=dict(Counter(r['native_class'] for r in rows)),load_ms=load_ms,method=method,provenance=prov(),
|
||||
latency_ms={key:{name:float(np.percentile([r[key] for r in rows],q)) for name,q in [('p50',50),('p95',95)]} for key in ['preparation_ms','frontend_ms','inference_ms']})
|
||||
(OUT/'summary.json').write_text(json.dumps(summary,ensure_ascii=False,indent=2)+'\n')
|
||||
if mode=='all':
|
||||
output=dict(schema_version=1,model='turnsense',name='TurnSense 1.0',score='P(complete), 3-class output',sample_interval_ms=200,
|
||||
method=method,provenance=prov(),calls=calls)
|
||||
(OUT/'score-timeline.json').write_text(json.dumps(output,ensure_ascii=False,separators=(',',':'))+'\n')
|
||||
print(json.dumps({k:summary[k] for k in ['native_argmax','threshold_05','class_counts']}),flush=True)
|
||||
if __name__=='__main__':
|
||||
p=argparse.ArgumentParser();p.add_argument('mode',choices=['smoke','evaluate','all']);main(p.parse_args().mode)
|
||||
@@ -0,0 +1,28 @@
|
||||
"""Restore fixed official assets; no speech or labels are transmitted."""
|
||||
import hashlib,json,tarfile,urllib.request,io
|
||||
from pathlib import Path
|
||||
HERE=Path(__file__).resolve().parent
|
||||
m=json.loads((HERE/'model-manifest.json').read_text())
|
||||
|
||||
def download(url):
|
||||
with urllib.request.urlopen(url,timeout=120) as r: return r.read()
|
||||
|
||||
def main():
|
||||
vendor=HERE/'vendor';vendor.mkdir(exist_ok=True)
|
||||
revision=m['code_revision'];archive=download(f'https://codeload.github.com/{m["code_repo"]}/tar.gz/{revision}')
|
||||
# Extract only the official inference code, frontend sources and license.
|
||||
with tarfile.open(fileobj=io.BytesIO(archive),mode='r:gz') as tar:
|
||||
for member in tar.getmembers():
|
||||
rel=Path(*Path(member.name).parts[1:])
|
||||
if not member.isfile() or rel.is_absolute() or '..' in rel.parts: continue
|
||||
if str(rel) not in ('infer.py','LICENSE') and not (str(rel).startswith('frontend/') and rel.suffix=='.py'): continue
|
||||
target=vendor/rel;target.parent.mkdir(parents=True,exist_ok=True);target.write_bytes(tar.extractfile(member).read())
|
||||
models=HERE/'models';models.mkdir(exist_ok=True)
|
||||
for name,sha in m['files'].items():
|
||||
target=models/name
|
||||
if target.exists() and hashlib.sha256(target.read_bytes()).hexdigest()==sha: continue
|
||||
data=download(f'https://huggingface.co/{m["repo"]}/resolve/{m["revision"]}/TurnSense1.0/{name}')
|
||||
if hashlib.sha256(data).hexdigest()!=sha: raise ValueError('Checksum mismatch')
|
||||
target.write_bytes(data)
|
||||
print('Official code and weight checksums restored')
|
||||
if __name__=='__main__': main()
|
||||
Generated
+498
@@ -0,0 +1,498 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = "==3.11.*"
|
||||
|
||||
[[package]]
|
||||
name = "audioread"
|
||||
version = "3.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a1/4a/874ecf9b472f998130c2b5e145dcdb9f6131e84786111489103b66772143/audioread-3.1.0.tar.gz", hash = "sha256:1c4ab2f2972764c896a8ac61ac53e261c8d29f0c6ccd652f84e18f08a4cab190", size = 20082, upload-time = "2025-10-26T19:44:13.484Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/16/fbe8e1e185a45042f7cd3a282def5bb8d95bb69ab9e9ef6a5368aa17e426/audioread-3.1.0-py3-none-any.whl", hash = "sha256:b30d1df6c5d3de5dcef0fb0e256f6ea17bdcf5f979408df0297d8a408e2971b4", size = 23143, upload-time = "2025-10-26T19:44:12.016Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2026.10.19"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a6/bd/1436c50cc46699de0230fbaacc1d1de7bfec413176099949e9d5112e88f2/certifi-2026.10.19.tar.gz", hash = "sha256:035c1e85572b141bf1c3a3eff0e33a8eb74917522f2559075ff8d2fea780a1ae", size = 137598, upload-time = "2026-10-20T02:24:16.604Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/51/29046e48f693c54e5b7412216d447a1d8362c9653148d98c02cdf65fa733/certifi-2026.10.19-py3-none-any.whl", hash = "sha256:42d35340cc5b9ba78e818ddbff0c5b1c2b779de448a340fd1b989f5fa8670e1b", size = 136464, upload-time = "2026-10-20T02:24:15.072Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cffi"
|
||||
version = "2.1.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pycparser", marker = "implementation_name != 'PyPy'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9e/ef/008a1939e372c06329a3fce4279c02f328488f3526744906eeec3da7ad5f/cffi-2.1.1.tar.gz", hash = "sha256:dd31f52ea1086513bb9df30f8fcee9b8918323ae067a3d5b78bc826a000712be", size = 530807, upload-time = "2026-08-03T21:21:18.939Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/70/d2/16d99a0c4948febc0ebd133a13b2f688ff7f8cb04da971e1128872ce0c03/cffi-2.1.1-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:c8d2c9fd1f2d16f780d15127abb050d13d1a76c03a4bd87d7e4980e45e511e12", size = 183838, upload-time = "2026-08-03T21:19:29.637Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/95/31b535a9f0220ae9f357de4a08d57ce89cb417653c2fd9f075f50822a388/cffi-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:398aff33cee2767e3e781d2554c54bd0dff386bb437581e0d8011fde1a942ec1", size = 184168, upload-time = "2026-08-03T21:19:30.764Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/5a/4707a0dc1f203f5dde5a907b0d4e3c25d71120241048bd5bc6f1bb9d4e71/cffi-2.1.1-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:154852545011f779917b11c78db2358d095da62a9a172b78ad0a583ee5adc0d0", size = 211805, upload-time = "2026-08-03T21:19:31.867Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/66/c19feabb28485b6e0bbaaafa90837a1ef5d302e90f2178bd33f17a49879b/cffi-2.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3311ed60d36f83378794e1009ac6258bafbf81f7888b4caa7b35a521e3f95813", size = 218716, upload-time = "2026-08-03T21:19:32.896Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/92/500760486c8baab49a7a8a58ba7fc3355ec3974b454b8a09e528efde9e1d/cffi-2.1.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6e192623c49c94421616a5778fba35cf0d5a8d000650c1967ef4448ee5cdd990", size = 205569, upload-time = "2026-08-03T21:19:34.142Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/a7/a67c733254d6e7373f7822f8082d8d6beade791e0cf12a7611f376fa61c7/cffi-2.1.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a6e721d4b0e45d5b65e87534470e67b18dcd092c83f68fba09f152b9cbc061af", size = 204907, upload-time = "2026-08-03T21:19:35.174Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/a4/4399daaf8f7dfee9d7c3327fdb0426ee041cc63edc358b93911ceb2bfc7a/cffi-2.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:34e261f78cb6ceaaa36f42f2613f4380d94d9c759a9c73c769ee6e0247364632", size = 217807, upload-time = "2026-08-03T21:19:36.286Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/f7/dabe6da2466ecbd82dc62e7342dc6b1065dad990c06f00f0ede9ebf2a0ed/cffi-2.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7225e4514edb64eb6740324353e0da0711954fd8d7da4576755b1c6e09b697cd", size = 221252, upload-time = "2026-08-03T21:19:37.416Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/87/616202d8e51342c07d2534c510111c4cc37201775ce8f60802c9335d1edd/cffi-2.1.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:df913725b79db7bcf03448f36b7bf8815363417d5b58deecf9305e3e30f0f21a", size = 214214, upload-time = "2026-08-03T21:19:38.507Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/c6/ab025d75d2c26c19b087c0124e75ee31cb65032f4fe345d356d8c507ab97/cffi-2.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f5cfbc5fe74540d335175b656c725d74d90e3730c626d92575eea35029d9afaa", size = 219408, upload-time = "2026-08-03T21:19:39.809Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/e2/7e8109f65445bdc673a7b54f02c677de462db75674220fd1335efc8eb598/cffi-2.1.1-cp311-cp311-win32.whl", hash = "sha256:f8ec5e643a9a937f64e1999eb9f75d072263751912dc5cd06d3c85f8f44be7c3", size = 174470, upload-time = "2026-08-03T21:19:41.246Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/c0/77ba02423c2f7d7091143c45cd49e0e6575c4c1967394bb542bd923a9b74/cffi-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:42f6930c31dc7f50732c9ae793c2786c7b6b044195967bbdde40bb9be81c4cc0", size = 185096, upload-time = "2026-08-03T21:19:42.615Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/47/9f1f85f9672ceda4984dc6c4f8824e8558992a2972c3d3c81fb8eb28d4ba/cffi-2.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:c7659f22557c5a0bc4855cd635f55edec690cc008a40768527762cb9fb263455", size = 179941, upload-time = "2026-08-03T21:19:43.747Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "3.5.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/33/1c/f41d4e74c28ab327ff3acd36053f7ea506c55872d7a90b0fa71aa3ab0c89/charset_normalizer-3.5.2.tar.gz", hash = "sha256:39de2a259fc954455c57274dc94c79d5842774e1247a016aff30bc0efed0f4ef", size = 172659, upload-time = "2026-09-30T04:39:23.398Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/22/67/6a0b94a7960d5e1b5eacd2fb529f3fccc47db4644f7f0a7cfdcfc3be578a/charset_normalizer-3.5.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d21b8b13c7592db2ac5e544a6d83187b995257472b0c9e8351b6d507ae37ed6", size = 370642, upload-time = "2026-09-30T04:35:06.91Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/94/01009e13b94041599004edf32e56e382c24e570f60f79bab8efe45cfe1eb/charset_normalizer-3.5.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d760fe2a4d7c3b226cb9026d6a842868d52a7901bd98420e1baf14e80da85cf5", size = 259280, upload-time = "2026-09-30T04:35:08.448Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/85/3b5358f60a13210f0b67d3755c168ef758701b021e655d88d4da28554467/charset_normalizer-3.5.2-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c9790464842f85f437dbbb54417eda1e0e6bfc52dd8d22d6fd1c994b73b2dc74", size = 245335, upload-time = "2026-09-30T04:35:10.104Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/75/77c1c479b09ecd751d1e767b251ea5c14d4d50ff757bf404afab2692f600/charset_normalizer-3.5.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4685902cf26edf013ed7a3da0f426ebba7a00ebb9541386d835afbf002c11cab", size = 291707, upload-time = "2026-09-30T04:35:11.575Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/0d/363f78cacb70f58f15f4b083961bbd9d292f335d3f5c66fc4f1cfe69cb90/charset_normalizer-3.5.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4495c5002a7b28557e7e222e77e0b661183e432b7d6d2e788101e3f240e05b8c", size = 286747, upload-time = "2026-09-30T04:35:13.022Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/ed/cf505d3011ffceb12c2067a7a5d3cfe92b875d4d44bb0ff0d69375e2c184/charset_normalizer-3.5.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:211d5a3eb6af8f513b8d4ca19a8c1b7accab1b5f0d3175f9826b03c1a920dc1f", size = 269972, upload-time = "2026-09-30T04:35:14.606Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/d8/f0a93a431d170e7ca681d4f6650fee3de934d18560e474e7267eb4b0f987/charset_normalizer-3.5.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ef4fcbf3327382cd4c9f540babd61248208af7b93eec4de397b4d5f58a09e288", size = 269339, upload-time = "2026-09-30T04:35:16.087Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/bd/9b2bd1c5b7af02462c9752d33994834ff972a96b4c483eefde9e594488e2/charset_normalizer-3.5.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd16aabe4a02a297c23417aa17ac6299dbd8c49f673bcd645b4929b11f5a4400", size = 261683, upload-time = "2026-09-30T04:35:17.488Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/a5/cac540ab0fd61f3fec88ad3dbb64509e71424593d73cfdfff5ab3e4db279/charset_normalizer-3.5.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:fb9e68df06293761f9fe66ade60a9bc6d0f5e42b8acf2939a9158af86ab0e5bd", size = 248143, upload-time = "2026-09-30T04:35:18.849Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/7a/ff467301deef2089fad87f72df9e000a26a78fec7acbb18e1999371b8369/charset_normalizer-3.5.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:59f63901b0031c3136cf64704dcb21de0bbae62ce2c9529bc39d27665463de37", size = 292358, upload-time = "2026-09-30T04:35:20.326Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/77/22d7e785d1e210afc2e2f58600dd1799d17a35665faf84383f002826c5f8/charset_normalizer-3.5.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:304d5463e65a35d7bb0850550e0780395395f6fcf452f04db7d5ca7cecc425ac", size = 268766, upload-time = "2026-09-30T04:35:21.72Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/91/e8e946267f1c2d9e2bd651726e2fbd2addf02c4d36cea5069e32ca9d7bb5/charset_normalizer-3.5.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9cf9b1a857e25c4baceeb3624e92a56df3668f398c4acba74e174d81fb4d1d3a", size = 287432, upload-time = "2026-09-30T04:35:23.273Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/88/7561d8a88d555e7df6623abe7c0070b4baf47549b9408783a2ae0a1a6cf7/charset_normalizer-3.5.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:114e4d0c92d618409ed82a99e22b5c5e768fe995f2973f78265f4524f49d4640", size = 272470, upload-time = "2026-09-30T04:35:24.655Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/7e/578c702301ec036f01455f30744a08d2b42f6ab35b9b2d4bf8cae0ef2a80/charset_normalizer-3.5.2-cp311-cp311-win32.whl", hash = "sha256:2625388c6c754520c37abaf3b41eb34d1cc4a373f457898f08606c8e362b891d", size = 186688, upload-time = "2026-09-30T04:35:26.225Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/fc/fdf8cf52ff21cd5bf158f20978991cf985325842f74283eb6df26c8a39d8/charset_normalizer-3.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:87e50a3e7cb90af586b6c5faf23e302a970415ac73bd7bd90a515a04b427ef96", size = 214932, upload-time = "2026-09-30T04:35:27.796Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/66/3e45a506d8110b632541faf9a9470185aa9878f1ed44020f31346c1c5e5b/charset_normalizer-3.5.2-cp311-cp311-win_arm64.whl", hash = "sha256:254eb48b9fa5ee9898a3c445825a1f340fe53712a098904b39b0bddba8ea3cb1", size = 202828, upload-time = "2026-09-30T04:35:29.259Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/ab/176fbfd5b64939c55d652366aa5b9ef1d767af207a3aa6ebeb0d226c484d/charset_normalizer-3.5.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:4275811936e2f06feff5e598fb42a1b7ae852da8e39605211892b56b81a34efd", size = 331815, upload-time = "2026-09-30T04:38:26.216Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/84/371eac6b30bdbcbf2d632a1a01809103459216fcaae61b8b8d922c1bfb8a/charset_normalizer-3.5.2-cp37-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1c50fe28bbc2ced33386f298650d91218076c05420e6cbd790b913adc41659e7", size = 253276, upload-time = "2026-09-30T04:38:28.032Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/6f/c4fbae58febff71709c51bc7e18fdfa55341dc382704740f9f0cbf03817b/charset_normalizer-3.5.2-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d19fbd981a488e22cd04883659ca6b08f50b5974f9fd7c95655ef6a043e5893f", size = 241239, upload-time = "2026-09-30T04:38:29.732Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/71/458c3f42164a07d0c5210798e9e704b39e540a6793b05aba67f3a35243a9/charset_normalizer-3.5.2-cp37-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0fed1d06615f022ee3b13caf5e8b180cfea32bb2c5aded8a9d44277afc040f93", size = 231121, upload-time = "2026-09-30T04:38:31.462Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/54/ab9e89367076f6331bb6c65c4bf14a5361fa5191cb6561bf534f18504e1b/charset_normalizer-3.5.2-cp37-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:838dcc90063569a0448120554591a1d6c4a4ffe11babf048908793154ab86ade", size = 260350, upload-time = "2026-09-30T04:38:33.239Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/c1/061431ecc688d9d76602502cb57cc01e691e682c18f1beb45f9673b5bbd2/charset_normalizer-3.5.2-cp37-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2ce45c6627b22c47e390bc91a41c3d13032192e699fa0bea96e9671b373d69b0", size = 255430, upload-time = "2026-09-30T04:38:34.865Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/1f/20c8949f0676f7ab811abdeb7f4d7f1cbc6e61ff20bef08b44edeb092bc8/charset_normalizer-3.5.2-cp37-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0774bf9bf620249fee3e0b8b9fd3065de213be30f3aa94ce2494b3b638949e26", size = 250612, upload-time = "2026-09-30T04:38:36.649Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/9e/46f2fa4c431fc98c4ae76a8cb5bdca54e0341e3cfc3fcfd8e82740250818/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1db38f4c5496827c1a501846d64d14c3b80c7e6714e406cd7dc36a9899fa1011", size = 242083, upload-time = "2026-09-30T04:38:38.26Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/39/559be29a0c0f086e0bba6922babd38916cc5e0b58ced4de13ee01ea05508/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:304d8e4d493af723536393eee0c689eb7813f4a474c8b479dee63f1fdd98f621", size = 232738, upload-time = "2026-09-30T04:38:39.81Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/6c/387b0e4f756a282831c1d9fc6aeb6c51ca4507ca202767c8de15ce9b12e2/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:9b7f416ff0978e2f2249330527f0ad6fa02f4932e6199692d3b52da2048c19e4", size = 260703, upload-time = "2026-09-30T04:38:41.346Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/92/1fdf015f09ef449f50d3ac4b67c90887c9c318b727daa95cc4f866e6521d/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:01077390b03f7988f11d700a2194e69b119741a86b1a638b1db88891e3eced8e", size = 247622, upload-time = "2026-09-30T04:38:42.937Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/3c/8e7b8a5671ad5d433669fb2a76f1a0164df2d9b1718b0206bc2a16d840cc/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_s390x.whl", hash = "sha256:7e841fb9010836c992c9f12fcbd43a831de93a5f726fc1ccd8ca1d0268c5014c", size = 257500, upload-time = "2026-09-30T04:38:44.604Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/f0/45b579df5cabc1d5d53ea1cc35e8437d3ca768c0acccc7041517cb6fbb32/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9cae88599c7219005d879f98e5ed53341e9a122af585e1091200358a3003d2a0", size = 255100, upload-time = "2026-09-30T04:38:46.289Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/68/fdec18a343f5fb3f310588dd478b09ac4799e0b187dbade3a8cd776f03ef/charset_normalizer-3.5.2-cp37-abi3-win32.whl", hash = "sha256:01b0c0d2262a9e28e8484a278c7e1b5d650e3ac8cf2683d2967e25899f208bdf", size = 174499, upload-time = "2026-09-30T04:38:47.999Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/8a/b618149cc5207943a0242068d7a27897f56a62947b5a039085f2a22029f8/charset_normalizer-3.5.2-cp37-abi3-win_amd64.whl", hash = "sha256:9f56f72050826f63dcee7a7f55b0a77168cb3bfc553fd405e7f8f9ece75a4036", size = 200092, upload-time = "2026-09-30T04:38:49.707Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/cf/4c66866fa9e2b1c78e3c911516d1de497a677b7ac60f1eceda74ce777ca3/charset_normalizer-3.5.2-cp37-abi3-win_arm64.whl", hash = "sha256:40ab6bffa02ae10a0581e6c198be7d2d8ca5c2a0c64e4ed3465d766df457573e", size = 294363, upload-time = "2026-09-30T04:38:51.312Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/ad/d07d7862a62ffa6d79d68074d14823243dd235a77c45262acbf6adeb28bf/charset_normalizer-3.5.2-py3-none-any.whl", hash = "sha256:b6b751274acb69d77b3323d6b7dbaa3c7fdfc1eb829b7eb61d262f32e1af9685", size = 68872, upload-time = "2026-09-30T04:39:21.828Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cloudpickle"
|
||||
version = "3.1.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/27/fb/576f067976d320f5f0114a8d9fa1215425441bb35627b1993e5afd8111e5/cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414", size = 22330, upload-time = "2025-11-03T09:25:26.604Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a", size = 22228, upload-time = "2025-11-03T09:25:25.534Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "decorator"
|
||||
version = "5.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/60/8b/32f9823da46cde7df2087faa08cd98d01b908f8dcab982cdba9c84e85355/decorator-5.3.1.tar.gz", hash = "sha256:4cbcdd55a6efadb9dbea26b858f4fb3264567b52d69ca0d25b721b553f60ea82", size = 58084, upload-time = "2026-05-18T06:03:28.057Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/05/7f/798705f5296a58ca505d600456748d1be48078eac8a7050d8a98bc9edb89/decorator-5.3.1-py3-none-any.whl", hash = "sha256:f47fe6fdbd2edd623ecfe36875d37aba411624e2670dd395dddae1358689bb3c", size = 10365, upload-time = "2026-05-18T06:03:26.517Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flatbuffers"
|
||||
version = "25.12.19"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/2d/d2a548598be01649e2d46231d151a6c56d10b964d94043a335ae56ea2d92/flatbuffers-25.12.19-py2.py3-none-any.whl", hash = "sha256:7634f50c427838bb021c2d66a3d1168e9d199b0607e6329399f04846d42e20b4", size = 26661, upload-time = "2025-12-19T23:16:13.622Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.20"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f5/08/8eea9d4b8302028f3abb2c0813953f7aec26d33b7a8960ed760e65ff29fa/idna-3.20.tar.gz", hash = "sha256:a7db850025b95ded1eae8a46181a1a6c56c92c96f0e2b005d9ff8dc0210cab44", size = 216463, upload-time = "2026-09-17T14:11:04.752Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/58/a2/bb081bab032533a855d44de1d56f8e8426114ff1ba5d1f07a438a0a654f8/idna-3.20-py3-none-any.whl", hash = "sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c", size = 69583, upload-time = "2026-09-17T14:11:03.168Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "joblib"
|
||||
version = "1.6.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cloudpickle" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d5/1d/537ab090f302b838943a1b56497dd53059b9a9b46a074936470173a2e207/joblib-1.6.0.tar.gz", hash = "sha256:2ccc96785b12046c08fd6d55839c12857831b54a3c1673ffadd2f04bfc4eda03", size = 327903, upload-time = "2026-08-31T09:39:04.122Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/18/53/84099323c2ec4be98d935f63c033ac4151ee83836ca1050ede3b3aadf155/joblib-1.6.0-py3-none-any.whl", hash = "sha256:3dbbf9f6e4b592a2357b854608e980fe6390d131d7a82f011a377ef2ebef7aba", size = 306115, upload-time = "2026-08-31T09:39:02.298Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kaldi-native-fbank"
|
||||
version = "1.22.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3a/2c/84076b352107ce12d56f28c313f1aca1be332d953dd96aec7b84976e6d53/kaldi-native-fbank-1.22.3.tar.gz", hash = "sha256:387bf87225c6b83c93ae652eeaef1b4d531994b6e398e7a77189de340674f9af", size = 71013, upload-time = "2025-10-09T02:31:21.487Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/d0/07ab65d7c8389f56f8c772a55f8846a81c24d973abecfc0275c2c833f63e/kaldi_native_fbank-1.22.3-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:6b9ef5b6302ee45628a51a4484cb4f41006af02141508939c09ce36899fb3f41", size = 245879, upload-time = "2025-10-09T02:28:04.7Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/2b/3132083b930fa6411f14469f36c465b7d2fba29a8a3e121d8fd6baffc8ea/kaldi_native_fbank-1.22.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:29452f2900e771086e9022dde17a92d191217ab3e34ca7dc361bd9be53e94fb4", size = 229180, upload-time = "2025-10-09T02:29:35.356Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/53/720ffbe8b30de203570f397866334eb4c6364c9214699010f2086de911ff/kaldi_native_fbank-1.22.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48e5dd8e897bf4509be2c6eeb4bbab728eaaef1f214ae0510c96219c4253d17", size = 299054, upload-time = "2025-10-09T02:28:42.011Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/3f/beb161e4fdf6710938ccf18418c147d87ba8f102903d6c6e4eda25588e22/kaldi_native_fbank-1.22.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ce84c65779c9eed6ec02699797a4ba1859451977537a993be3ea8167a210ec3e", size = 321921, upload-time = "2025-10-09T02:31:21.646Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/bb/ee42418b77dbfc5ff619857b8eb372af98a88d47c8ca8b9a2d3ca2936c96/kaldi_native_fbank-1.22.3-cp311-cp311-win32.whl", hash = "sha256:516bce595eb5e5899a91dfec1142bea56a2fa232e53425e9966785aee8cd024e", size = 273018, upload-time = "2025-10-09T02:30:31.979Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/68/da630b035cd343311168e5fe02c39fe7b192638717e3202de92ccf8ae18e/kaldi_native_fbank-1.22.3-cp311-cp311-win_amd64.whl", hash = "sha256:bd225d0624d45b533c1780094b3c59666276a6e9f20222943441212cdf301c9e", size = 303342, upload-time = "2025-10-09T02:28:17.429Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy-loader"
|
||||
version = "0.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "packaging" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/19/8c/0f2ff2a8b7513e68871a74740c17a504b0488c679b379e6445ebb7bd78dc/lazy_loader-0.6.tar.gz", hash = "sha256:2f4b7824d6401958639008a0cae20c776b61dff619accd6890693e2de8260167", size = 18438, upload-time = "2026-09-21T21:31:32.982Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/23/93/4e45e37f26c820216704b36b8e67cc55c490a7abd287a78a26a2c0e21386/lazy_loader-0.6-py3-none-any.whl", hash = "sha256:77253be3391b06124a0e16105bd663b6c54470af1a9ca8e1cf026f38d58ed056", size = 8791, upload-time = "2026-09-21T21:31:31.632Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "librosa"
|
||||
version = "0.11.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "audioread" },
|
||||
{ name = "decorator" },
|
||||
{ name = "joblib" },
|
||||
{ name = "lazy-loader" },
|
||||
{ name = "msgpack" },
|
||||
{ name = "numba" },
|
||||
{ name = "numpy" },
|
||||
{ name = "pooch" },
|
||||
{ name = "scikit-learn" },
|
||||
{ name = "scipy" },
|
||||
{ name = "soundfile" },
|
||||
{ name = "soxr" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/64/36/360b5aafa0238e29758729e9486c6ed92a6f37fa403b7875e06c115cdf4a/librosa-0.11.0.tar.gz", hash = "sha256:f5ed951ca189b375bbe2e33b2abd7e040ceeee302b9bbaeeffdfddb8d0ace908", size = 327001, upload-time = "2025-03-11T15:09:54.884Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/ba/c63c5786dfee4c3417094c4b00966e61e4a63efecee22cb7b4c0387dda83/librosa-0.11.0-py3-none-any.whl", hash = "sha256:0b6415c4fd68bff4c29288abe67c6d80b587e0e1e2cfb0aad23e4559504a7fa1", size = 260749, upload-time = "2025-03-11T15:09:52.982Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "llvmlite"
|
||||
version = "0.50.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/11/c5/907cec40688a34eb489cded74d555e1ee4af8cf49d83e03dba2c2d4cfe27/llvmlite-0.50.0.tar.gz", hash = "sha256:f2a2cd6ec9ffcc1b7147dea0d7a49efebf17a2b434e0c2844fe175999d571eb4", size = 194522, upload-time = "2026-09-29T18:44:46.782Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/ae/9c41313563a860a69d5c67fb4098ce9b40a09c00b68a177407b7c10950fb/llvmlite-0.50.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:818b3d4845ac8e126e23cb500867570d0602a42a43e67b14acec31f046e03130", size = 40534276, upload-time = "2026-09-29T18:42:40.983Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/60/99c692a447cb6e148d4ecc30067d5f4ba8a980f1081472103ed0c79b4890/llvmlite-0.50.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0225351ad77ea30501fc5b4c09ff6868169fde50c5a576cdfda1645091157616", size = 58344485, upload-time = "2026-09-29T18:42:44.679Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/b2/a5234f59ccf69cc90d29c62e01cacd1d60403fc5dfac77b38e019237d301/llvmlite-0.50.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a6ffde00d4be8772a24e3e8b3af6bf86a79e7cf066d944ef56136b3957d707dc", size = 59696588, upload-time = "2026-09-29T18:42:48.871Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/15/db28c1cb84314bdc416f7dbe7688aa9565d36d76c8244a1c8fbf6adf37bf/llvmlite-0.50.0-cp311-cp311-win_amd64.whl", hash = "sha256:ffe46ef508df226e54b5fe1f7bf11122e5297bcdbb3902cc5b670a429d56ff47", size = 41865266, upload-time = "2026-09-29T18:42:52.699Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mpmath"
|
||||
version = "1.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "msgpack"
|
||||
version = "1.2.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0a/e7/bb605a7bab2d8425a64b3fa762b39dc1bf1c7e3f11ba6fb5413d6db0ff8c/msgpack-1.2.3.tar.gz", hash = "sha256:32edb81a2b5eb7cd7c9d941b2bfbbb082fd2cd09e0e725930316af6b708db186", size = 196517, upload-time = "2026-09-29T02:33:52.276Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/95/b9c651ccb9d720b2e2c8d537954dff528ab869a03bf89598145716db823c/msgpack-1.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ec90a9ae3e1169fa1171147340f0e97d941aa19fcd3b34e8339a55933ed042af", size = 90404, upload-time = "2026-09-29T02:31:44.826Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/cd/fc9e2e367e80f1493e2ec5f610dda558b344eeede296f88976db133e8f2c/msgpack-1.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9d7e9cbb0998bbfd363fd9a09c330520d5e9cb323c05b5a1a05865d23ccf2226", size = 89683, upload-time = "2026-09-29T02:31:46.413Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/9e/1028485c6886c1c117f777cc9b053e541eff0fedb3292dfb1da95040edb5/msgpack-1.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6707d2fa2aa1bb5424ea0b05f44ffc989b15ab41a73ff5855bff4944fec7c8ac", size = 465347, upload-time = "2026-09-29T02:31:47.934Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/83/800570e6a22376eb8d599920f70aead4779a63611696f567477c4e85a70f/msgpack-1.2.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:382b219de3d436de3baba0f4b0c6d4336e8f5858d0eb047918b13b69a71c6c55", size = 477820, upload-time = "2026-09-29T02:31:49.479Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/ff/817e4a2052f848d3fb67726908d6e4e7c19f68ee7c19553a82ce7b0ed415/msgpack-1.2.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:186e6c602b8a9968b8e864c67d622a69279f7d1e55ae25f40e3bff7e815b2b62", size = 436656, upload-time = "2026-09-29T02:31:51.18Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/42/040cc55dde6a7d92057baac8d1fc9cfb9f4fd4162900e2ec16dc33917a7d/msgpack-1.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9276ba88891338f2617044429dfd080ae008c9868a25f6f1a7d004a35dc9ac0a", size = 460939, upload-time = "2026-09-29T02:31:53.026Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/93/4dc007bdef930eed247346773bc0189b710078961d3218d5ee7ba59f322c/msgpack-1.2.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:c942c21a93f36b3a69e828c8945bb72c94dc2ffe488a2086950c812f3edf046c", size = 433608, upload-time = "2026-09-29T02:31:54.981Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/97/a1b944046f283ec89445cb2a982c42233b5b07cc630f9be739f4f1d469a3/msgpack-1.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:18a6ed513023001b28dcd3ba54966f6bb90a38274ba8d2640464bcab3a1b81d4", size = 477373, upload-time = "2026-09-29T02:31:56.713Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/79/ab411d0d172743732ab2503f4c32a22dd1a7d1436a6feecbb160e4b6376a/msgpack-1.2.3-cp311-cp311-win32.whl", hash = "sha256:d0238cd05dec9ffbe0de1071df685ba63e30a36ac155285b1a094e727c38cbe9", size = 67514, upload-time = "2026-09-29T02:31:58.267Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/8d/6f0cb2b84e484e96278455c26870196d025bb0cec312b226a663f1fa9000/msgpack-1.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:30e1522e4173230dca4d9ad896f038f73c0da6c1edd42f4dbad88ac583cf5d46", size = 75850, upload-time = "2026-09-29T02:31:59.449Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/25/f99e13a2c1d3f5a1dcaa5aab27f474e8c4358188bbc68ad79fecb0d1aefe/msgpack-1.2.3-cp311-cp311-win_arm64.whl", hash = "sha256:8ca67f77938ea6a3663aa9bd22b3e031f6da84d665be850abab910ee90728dfd", size = 72338, upload-time = "2026-09-29T02:32:00.885Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "narwhals"
|
||||
version = "2.27.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bf/21/f64d6b2dbea7bf3f8c38cdc786dcc6ef012ca3d173ad208c782c9a7bedf6/narwhals-2.27.1.tar.gz", hash = "sha256:aed93076a3ea42d9c32c88e4eb5ea422a21937011cbe1f480f9572a523c82094", size = 735013, upload-time = "2026-10-10T06:52:18.113Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/89/5d4c86da1130d9059681e5b6cd7645df5c10279a6a079c5c37dcb2cc6f3f/narwhals-2.27.1-py3-none-any.whl", hash = "sha256:d057df13f5852b8e157596e82eb5e955fad267425df5e420e0ee9863da483b31", size = 483211, upload-time = "2026-10-10T06:52:16.32Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "numba"
|
||||
version = "0.68.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "llvmlite" },
|
||||
{ name = "numpy" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4e/cd/e8280f9ffa30fea9fabc5341223701231fcc5d53a31f51419d42d4bec3a6/numba-0.68.0.tar.gz", hash = "sha256:8a781de54b980b98f43bff7f1093701b5f07c80d031c7cfa8a87493d8bf73f2d", size = 2855363, upload-time = "2026-09-30T15:05:44.721Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/54/fc/57b1ce7b92cadbb4084a2ca30d9cfc8937a45ece9a64bc6050e527cbc14b/numba-0.68.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:50399af9d3799a4677044294861169c614bd7e1d8bbfc9479f78a67ab28ff427", size = 2759814, upload-time = "2026-09-30T15:04:44.039Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/14/2ecbe9a046c611077b7b9ac267e9829aec473cf4f4314d181bd043c76fcf/numba-0.68.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:954e2684bca3ea11235272df28e8ef40f18a682c1c635a2398032b404675d8fa", size = 3547920, upload-time = "2026-09-30T15:04:46.364Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/dc/ba4eaf844972bf9647314079f3a4cad79f63614b388b667103a2e7f521df/numba-0.68.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:68f92839637a2aaca8ae124c3abf91f648d2fade50953ea8e81ec604ac05a771", size = 3834537, upload-time = "2026-09-30T15:04:48.61Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/0e/369fc577564e07820d5f8ddddf9648cf3e31415313c323cbd611f7905101/numba-0.68.0-cp311-cp311-win_amd64.whl", hash = "sha256:d36f7c6a07c27fa175f5a4683083c6a830f7791fbda592a8676ce47a444965f7", size = 2830973, upload-time = "2026-09-30T15:04:50.863Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
version = "2.2.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "onnxruntime"
|
||||
version = "1.24.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "flatbuffers" },
|
||||
{ name = "numpy" },
|
||||
{ name = "packaging" },
|
||||
{ name = "protobuf" },
|
||||
{ name = "sympy" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/60/69/6c40720201012c6af9aa7d4ecdd620e521bd806dc6269d636fdd5c5aeebe/onnxruntime-1.24.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0bdfce8e9a6497cec584aab407b71bf697dac5e1b7b7974adc50bf7533bdb3a2", size = 17332131, upload-time = "2026-03-17T22:05:49.005Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/e9/8c901c150ce0c368da38638f44152fb411059c0c7364b497c9e5c957321a/onnxruntime-1.24.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:046ff290045a387676941a02a8ae5c3ebec6b4f551ae228711968c4a69d8f6b7", size = 15152472, upload-time = "2026-03-17T22:03:26.176Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/b6/7a4df417cdd01e8f067a509e123ac8b31af450a719fa7ed81787dd6057ec/onnxruntime-1.24.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e54ad52e61d2d4618dcff8fa1480ac66b24ee2eab73331322db1049f11ccf330", size = 17222993, upload-time = "2026-03-17T22:04:34.485Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/59/8febe015f391aa1757fa5ba82c759ea4b6c14ef970132efb5e316665ba61/onnxruntime-1.24.4-cp311-cp311-win_amd64.whl", hash = "sha256:b43b63eb24a2bc8fc77a09be67587a570967a412cccb837b6245ccb546691153", size = 12594863, upload-time = "2026-03-17T22:05:38.749Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/84/4155fcd362e8873eb6ce305acfeeadacd9e0e59415adac474bea3d9281bb/onnxruntime-1.24.4-cp311-cp311-win_arm64.whl", hash = "sha256:e26478356dba25631fb3f20112e345f8e8bf62c499bb497e8a559f7d69cf7e7b", size = 12259895, upload-time = "2026-03-17T22:05:28.812Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "26.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "4.13.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/58/e6/d0abbb256b44e6a05748c6942511ff67db8dbb068de0c35b24bf1b395b09/platformdirs-4.13.5.tar.gz", hash = "sha256:05831adf87d392760f4b5c3a0a95579ab5a10b18b9b9bd43680dcb47544788a0", size = 62362, upload-time = "2026-10-19T01:02:11.778Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/8a/1d8cb3842909dc193a73835adedf412e32e18f8ffd59f43983949622fe6f/platformdirs-4.13.5-py3-none-any.whl", hash = "sha256:53d3ec4280b815c1e0b02edad7f9ee8fda1ba9c0865dd9c2e6e63849ce82fc1e", size = 32779, upload-time = "2026-10-19T01:02:10.17Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pooch"
|
||||
version = "1.9.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "packaging" },
|
||||
{ name = "platformdirs" },
|
||||
{ name = "requests" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/83/43/85ef45e8b36c6a48546af7b266592dc32d7f67837a6514d111bced6d7d75/pooch-1.9.0.tar.gz", hash = "sha256:de46729579b9857ffd3e741987a2f6d5e0e03219892c167c6578c0091fb511ed", size = 61788, upload-time = "2026-01-30T19:15:09.649Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl", hash = "sha256:f265597baa9f760d25ceb29d0beb8186c243d6607b0f60b83ecf14078dbc703b", size = 67175, upload-time = "2026-01-30T19:15:08.36Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "protobuf"
|
||||
version = "7.36.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d9/89/5b8517baa72f84a67b8a307ba953c91057af618bf40bf676f3c03551f8f0/protobuf-7.36.2.tar.gz", hash = "sha256:497d0463ff3316681da6c0b9e8d06cb465d61abce00b613ab42226175644d1bb", size = 512737, upload-time = "2026-09-17T20:07:59.326Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/32/72/98342feb672507c8f3a69e34b4fa8961f608edba5c1a48a6f47156d92cb5/protobuf-7.36.2-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:cbc70b17ee27e28894c7fee8bb04be1abead49e936bc70eb60052531eee2079e", size = 456039, upload-time = "2026-09-17T20:07:51.542Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/ea/91fdf7c2b8bbd49cde056f00a9df6773532987e1c00fe2830b895af95c7e/protobuf-7.36.2-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:e11e1f0180583a2af89db6a2ecd9e8dc40aa6d2988ca175bfd0e6d12ea72d74e", size = 344219, upload-time = "2026-09-17T20:07:52.914Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/ab/5fd5f8ece73fad885c5a09aa849b32d70472f954ba3a92d3bb5974ea953b/protobuf-7.36.2-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:f4fee11ec330d238b34a05c9b675f693c20415d1c5bd7d5320cc2f8a798eb9cf", size = 357223, upload-time = "2026-09-17T20:07:53.985Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/f3/3996583dd2906297a637af12114deddf7658af6e683fedb83be061983fb5/protobuf-7.36.2-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:89f23aa53c24553a2416fd4fd1ec06f74fa42b14b546d8883128813f775bbfd2", size = 343223, upload-time = "2026-09-17T20:07:54.931Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/1b/dcc64f358fcb51811b58ae40b3d28f820725f116d86487cc20bd4b130701/protobuf-7.36.2-cp310-abi3-win32.whl", hash = "sha256:912c1221170e16c08d1f086762f563dd61ff83c18b5fa6652952dfaded66f728", size = 442998, upload-time = "2026-09-17T20:07:55.826Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/55/b77bda4e5e5f5971fb51b07663694690e9afdb9402136c16a522bd621cad/protobuf-7.36.2-cp310-abi3-win_amd64.whl", hash = "sha256:a300819d441e078a5608c0d3c709796bb548136058fda017ae51d425b44fd353", size = 456514, upload-time = "2026-09-17T20:07:57.188Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/04/d52c7016b04b6c5108f26691f9d33ec82a9b65d041f1a9c771137693d618/protobuf-7.36.2-py3-none-any.whl", hash = "sha256:bdb3a345d48db958e6ce1f18e508beb0cc981d64f24088427549c866cd039f1e", size = 179806, upload-time = "2026-09-17T20:07:58.211Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pycparser"
|
||||
version = "3.12"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cb/27/eeb9f307bf07060f3d9f9aa0fb8c15b01bdfded0a941e4b2719c7f94cacd/pycparser-3.12.tar.gz", hash = "sha256:f03387877eab82ce01bbdfd0ba7252ee55a90b1c708d5129e2ce915475cf1db6", size = 158785, upload-time = "2026-10-22T13:40:03.56Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/ae/315bbeee126a769cbde43cf4908acb4b40408d8bf8bbf334dd7368d8dadf/pycparser-3.12-py3-none-any.whl", hash = "sha256:a076f3f489b39b43ed13563a1dafe42447445bec2e294154ec5c57fcdda4c85e", size = 62600, upload-time = "2026-10-22T13:40:02.021Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.34.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "charset-normalizer" },
|
||||
{ name = "idna" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scikit-learn"
|
||||
version = "1.9.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "joblib" },
|
||||
{ name = "narwhals" },
|
||||
{ name = "numpy" },
|
||||
{ name = "scipy" },
|
||||
{ name = "threadpoolctl" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d2/eb/eaf5e07fcc0da7149b0e084f24e54edd7441b9a89ce7e034032ae97fe3a0/scikit_learn-1.9.1.tar.gz", hash = "sha256:629cada3e33e2b9bf376cdc7614a47a4140b8aedc1d836579e359736fbd82977", size = 7786908, upload-time = "2026-09-10T18:34:04.679Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/79/67/1904bf57ab9ea77b5ba3899178bcb35ff8d814fc38ef6d1283bcddeaf04a/scikit_learn-1.9.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:326c188f92084bf58664229f4578eeab6176313b37cd5dfc85abd92b94130c58", size = 8823771, upload-time = "2026-09-10T18:32:29.891Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/31/dc50d8780ac647bed9417f10a8b15ae3cf990ad7edb4f4b468f35030f8c5/scikit_learn-1.9.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:b48b2b5b41d9c5fbafef5f37b042f61110df3318ad2a45baf57287ea5b9ba5a2", size = 8290892, upload-time = "2026-09-10T18:32:32.441Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/aa/a7a61517eaaa8acbf6061d255a625034e4afa277caff47e829574cc21d1c/scikit_learn-1.9.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4298fcc01b3d8fa9768d36894e99cce0747b3b2dd73bfd80779e393769d0afab", size = 9026402, upload-time = "2026-09-10T18:32:34.482Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/69/46/f8b95a7a16fd13b458401d5e85a63d88ecb3679f3649812d5fdbea4c8357/scikit_learn-1.9.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:52a0703bbc07ad27f560fa63fa68e4c54dd735bfbbf65b4dd3c225dc7547b6df", size = 9315422, upload-time = "2026-09-10T18:32:36.581Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/8a/8d1e888fe3d0dd4e2a1f3356dad703eed569aaf673e222b6f647005ad30f/scikit_learn-1.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:220fa18152852a5ce29c49e1eaba9d44ec44631cd2e5cf65f5a40eafa5ab3412", size = 8329877, upload-time = "2026-09-10T18:32:38.729Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/44/8f43f8de3e025c505efbd88b9264008f39490fbaf15a5ca8dbd8dae0cb74/scikit_learn-1.9.1-cp311-cp311-win_arm64.whl", hash = "sha256:8218cb8938d3031e0d23842838425490c229ecf3e99f666776e09d12ed902352", size = 7954229, upload-time = "2026-09-10T18:32:40.661Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scipy"
|
||||
version = "1.17.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "numpy" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/df/75/b4ce781849931fef6fd529afa6b63711d5a733065722d0c3e2724af9e40a/scipy-1.17.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:1f95b894f13729334fb990162e911c9e5dc1ab390c58aa6cbecb389c5b5e28ec", size = 31613675, upload-time = "2026-02-23T00:16:00.13Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/58/bccc2861b305abdd1b8663d6130c0b3d7cc22e8d86663edbc8401bfd40d4/scipy-1.17.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:e18f12c6b0bc5a592ed23d3f7b891f68fd7f8241d69b7883769eb5d5dfb52696", size = 28162057, upload-time = "2026-02-23T00:16:09.456Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/ee/18146b7757ed4976276b9c9819108adbc73c5aad636e5353e20746b73069/scipy-1.17.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a3472cfbca0a54177d0faa68f697d8ba4c80bbdc19908c3465556d9f7efce9ee", size = 20334032, upload-time = "2026-02-23T00:16:17.358Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/e6/cef1cf3557f0c54954198554a10016b6a03b2ec9e22a4e1df734936bd99c/scipy-1.17.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:766e0dc5a616d026a3a1cffa379af959671729083882f50307e18175797b3dfd", size = 22709533, upload-time = "2026-02-23T00:16:25.791Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/60/8804678875fc59362b0fb759ab3ecce1f09c10a735680318ac30da8cd76b/scipy-1.17.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:744b2bf3640d907b79f3fd7874efe432d1cf171ee721243e350f55234b4cec4c", size = 33062057, upload-time = "2026-02-23T00:16:36.931Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43af8d1f3bea642559019edfe64e9b11192a8978efbd1539d7bc2aaa23d92de4", size = 35349300, upload-time = "2026-02-23T00:16:49.108Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/3d/7ccbbdcbb54c8fdc20d3b6930137c782a163fa626f0aef920349873421ba/scipy-1.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd96a1898c0a47be4520327e01f874acfd61fb48a9420f8aa9f6483412ffa444", size = 35127333, upload-time = "2026-02-23T00:17:01.293Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/19/f926cb11c42b15ba08e3a71e376d816ac08614f769b4f47e06c3580c836a/scipy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4eb6c25dd62ee8d5edf68a8e1c171dd71c292fdae95d8aeb3dd7d7de4c364082", size = 37741314, upload-time = "2026-02-23T00:17:12.576Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:d30e57c72013c2a4fe441c2fcb8e77b14e152ad48b5464858e07e2ad9fbfceff", size = 36607512, upload-time = "2026-02-23T00:17:23.424Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/7f/bdd79ceaad24b671543ffe0ef61ed8e659440eb683b66f033454dcee90eb/scipy-1.17.1-cp311-cp311-win_arm64.whl", hash = "sha256:9ecb4efb1cd6e8c4afea0daa91a87fbddbce1b99d2895d151596716c0b2e859d", size = 24599248, upload-time = "2026-02-23T00:17:34.561Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "soundfile"
|
||||
version = "0.13.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cffi" },
|
||||
{ name = "numpy" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e1/41/9b873a8c055582859b239be17902a85339bec6a30ad162f98c9b0288a2cc/soundfile-0.13.1.tar.gz", hash = "sha256:b2c68dab1e30297317080a5b43df57e302584c49e2942defdde0acccc53f0e5b", size = 46156, upload-time = "2025-01-25T09:17:04.831Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/64/28/e2a36573ccbcf3d57c00626a21fe51989380636e821b341d36ccca0c1c3a/soundfile-0.13.1-py2.py3-none-any.whl", hash = "sha256:a23c717560da2cf4c7b5ae1142514e0fd82d6bbd9dfc93a50423447142f2c445", size = 25751, upload-time = "2025-01-25T09:16:44.235Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/ab/73e97a5b3cc46bba7ff8650a1504348fa1863a6f9d57d7001c6b67c5f20e/soundfile-0.13.1-py2.py3-none-macosx_10_9_x86_64.whl", hash = "sha256:82dc664d19831933fe59adad199bf3945ad06d84bc111a5b4c0d3089a5b9ec33", size = 1142250, upload-time = "2025-01-25T09:16:47.583Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/e5/58fd1a8d7b26fc113af244f966ee3aecf03cb9293cb935daaddc1e455e18/soundfile-0.13.1-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:743f12c12c4054921e15736c6be09ac26b3b3d603aef6fd69f9dde68748f2593", size = 1101406, upload-time = "2025-01-25T09:16:49.662Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/ae/c0e4a53d77cf6e9a04179535766b3321b0b9ced5f70522e4caf9329f0046/soundfile-0.13.1-py2.py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:9c9e855f5a4d06ce4213f31918653ab7de0c5a8d8107cd2427e44b42df547deb", size = 1235729, upload-time = "2025-01-25T09:16:53.018Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/5e/70bdd9579b35003a489fc850b5047beeda26328053ebadc1fb60f320f7db/soundfile-0.13.1-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:03267c4e493315294834a0870f31dbb3b28a95561b80b134f0bd3cf2d5f0e618", size = 1313646, upload-time = "2025-01-25T09:16:54.872Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/df/8c11dc4dfceda14e3003bb81a0d0edcaaf0796dd7b4f826ea3e532146bba/soundfile-0.13.1-py2.py3-none-win32.whl", hash = "sha256:c734564fab7c5ddf8e9be5bf70bab68042cd17e9c214c06e365e20d64f9a69d5", size = 899881, upload-time = "2025-01-25T09:16:56.663Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/e9/6b761de83277f2f02ded7e7ea6f07828ec78e4b229b80e4ca55dd205b9dc/soundfile-0.13.1-py2.py3-none-win_amd64.whl", hash = "sha256:1e70a05a0626524a69e9f0f4dd2ec174b4e9567f4d8b6c11d38b5c289be36ee9", size = 1019162, upload-time = "2025-01-25T09:16:59.573Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "soxr"
|
||||
version = "1.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "numpy" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ed/11/27cebce4a108f77afea7c80545115536b45e3f11ebfb914f638fdd9ba847/soxr-1.1.0.tar.gz", hash = "sha256:9f228ae21c78fa9359ca98d8a5e8e91f30639e438e574133dace62c5b5309e44", size = 173067, upload-time = "2026-05-03T00:15:18.214Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/49/3e6bc84f87439f222f40b616e9a29a170f41fb564710ea510df19dc26907/soxr-1.1.0-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:34cc92208c3c412c046813e69da639c04a792c6a41fbfd7d909d359cd3e97a2d", size = 205699, upload-time = "2026-05-03T00:14:46.67Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/94/216f46096a85b07d1e6ba7fd44491402e912a3d688cd4f36f0a600ca155f/soxr-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bd30f7201eac896ebf5db7b09156e6f1a1b82601900d29d9c8449bdad8365b11", size = 167381, upload-time = "2026-05-03T00:14:48.012Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/cb/06caa463b8181ec1981bd6376d4a873748b7008193188b8cfb60391eb131/soxr-1.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1577865e993f98ffb261257c3060fa76ec3db44ed3f181b16464268000424464", size = 210938, upload-time = "2026-05-03T00:14:49.768Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/47/d5964551ca818b7f0c7ef7f3899056263b60ef098a801066350a9672ca8f/soxr-1.1.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3da87e3ffa3e41823d873b051c7ecb2acebd8d1b6b46b752f5facf10a0d84ab9", size = 245268, upload-time = "2026-05-03T00:14:51.422Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/29/371467eb86c7ba6810df0bfe9409bcd9c52ec5615b111190fafe23e4d2e1/soxr-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:ae30c48ac795378cf23ba3c7c640b8ff794af714ac388b9fd6b31a40b39e6e86", size = 176779, upload-time = "2026-05-03T00:14:53.09Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/8a/f3da7973b5f1b05d2d7e94d5376b881dcbc05297900cae6c3d33d95b209b/soxr-1.1.0-cp312-abi3-macosx_10_14_x86_64.whl", hash = "sha256:e0e09fa633ce2e67df08b298afced4d184f6e753fc330f241022250f1d0d61da", size = 204124, upload-time = "2026-05-03T00:14:54.505Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/dc/200013a74641f8774664bbcd2346c695c05c2e300ea792adcb40a293eed0/soxr-1.1.0-cp312-abi3-macosx_11_0_arm64.whl", hash = "sha256:d6a7ad82b8d5f3fcc04b1d2ca055562b96af571e1d4fa7c6c61d0fb509ac43b4", size = 165457, upload-time = "2026-05-03T00:14:56.007Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/2b/2e5eba817a762a2ec589ff165b8bc5955b25a0ad140045f7cd8e45410543/soxr-1.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf98c0d7b7d5ef5bf072fee8d3020e8b664f2d195933ea7bc5089267c2e22a06", size = 206529, upload-time = "2026-05-03T00:14:57.646Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/f1/0e55195893228609c9a08c3b13b7a83a46c3a992cd00d3304f0f320cfb07/soxr-1.1.0-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b033078e86f3c4a658e5697fac8995764fad9e799563616b630136b613167f1", size = 240413, upload-time = "2026-05-03T00:14:59.363Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/4d/621e4150e4815246ad552d215a8a294a90143fedd19ee442cf82d3b3abc8/soxr-1.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:6ae2a174bffea94e8ead857dad85999d3f49f091774dbad5b046c0417d7092f4", size = 174357, upload-time = "2026-05-03T00:15:00.724Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sympy"
|
||||
version = "1.14.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "mpmath" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "threadpoolctl"
|
||||
version = "3.7.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/00/dc/6c58154c1c65f758ea979e7139cb76993a9cfc662d14e9be3c4a667cfb77/threadpoolctl-3.7.0.tar.gz", hash = "sha256:61348cfb77d53b9242e0017029244b559b810c142ced65b4e21eeca1843959a7", size = 31961, upload-time = "2026-09-15T15:46:20.263Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/43/3f/f88a53f60a472b46f4023f56d204dd7de33d34c5d2acbfa0d70a674e639e/threadpoolctl-3.7.0-py3-none-any.whl", hash = "sha256:cd8b60b5641b45c67bbf73c64c843235fc2d8a480c87389f52f5dbee893b86be", size = 26362, upload-time = "2026-09-15T15:46:19.168Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "turnsense-1-0-evaluation"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "kaldi-native-fbank" },
|
||||
{ name = "librosa" },
|
||||
{ name = "numpy" },
|
||||
{ name = "onnxruntime" },
|
||||
{ name = "soundfile" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "kaldi-native-fbank", specifier = "==1.22.3" },
|
||||
{ name = "librosa", specifier = "==0.11.0" },
|
||||
{ name = "numpy", specifier = "==2.2.6" },
|
||||
{ name = "onnxruntime", specifier = "==1.24.4" },
|
||||
{ name = "soundfile", specifier = "==0.13.1" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.16.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.8.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e3/05/b17359e1cefb4f909b5e40b1b90a496d987258916dbbf88e842c729f510e/urllib3-2.8.0.tar.gz", hash = "sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63", size = 458972, upload-time = "2026-09-15T19:29:36.253Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/92/9d/c4e665119135114480843e7ab388fa94d8480650450e6f8e26b70d323a4c/urllib3-2.8.0-py3-none-any.whl", hash = "sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3", size = 135717, upload-time = "2026-09-15T19:29:34.577Z" },
|
||||
]
|
||||
@@ -26,14 +26,14 @@ npm run dev
|
||||
|
||||
## 模型分数对比
|
||||
|
||||
点击时间轴上方的“模型分数对比”展开对比,默认收起。VAP / MaAI(紫色)、Smart Turn(青色)和 Silero VAD(橙色)共用录音的时间刻度、缩放、播放头及判断点参考线。点击或拖动曲线区域定位播放,悬停查看当时已产生的分数;点击空心评分点定位到原标注时刻。顶部显示播放位置最近的有效评分,暂无评分时显示“—”,不将缺失数据当作 0。
|
||||
点击时间轴上方的“模型分数对比”展开对比,默认收起。VAP / MaAI(紫色)、Smart Turn(青色)、Silero VAD(橙色)、LiveKit v1-mini(蓝色)和 TurnSense 1.0(玫红色)共用录音的时间刻度、缩放、播放头及判断点参考线。点击或拖动曲线区域定位播放,悬停查看当时已产生的分数;点击空心评分点定位到原标注时刻。顶部显示播放位置最近的有效评分,暂无评分时显示“—”,不将缺失数据当作 0。
|
||||
|
||||
- 实线阶梯曲线来自对六通完整录音的真实推理,不是用44个点插值。MaAI每100ms输入一块音频,因流式重采样缓冲,部分评分间隔为200ms;Smart Turn每200ms计算一次,并包含原标注的精确时刻。
|
||||
- MaAI使用双声道连续回放、20秒KV缓存,分数为 `1 - p_now[user]`,是接话候选分数,不是校准的EOT概率。Smart Turn沿用原回测的用户声道、过去ASR定位、500ms前置音频、3秒内续说及最多8秒窗口;没有额外的结束信号、VAD停止门槛或超时放行。
|
||||
- 空心点保留原44点的独立窗口回测分数。尤其MaAI的连续状态与独立窗口不同,空心点可能不落在连续曲线上。原始44点指标不会被这次时间序列覆盖。
|
||||
- Silero VAD 为用户声道逐个完整 32 ms 帧的真实 ONNX 推理,使用 `1 - speech_probability`,仅为非发声基线,不是语义 EOT 概率。时间戳为帧末;空心点取原判断时刻最后可用帧,不读取未来音频。独立环境和复现方法见 [Silero 实验说明](../../experiments/silero-vad/README.md)。
|
||||
- 参考阈值只改变虚线位置,不修改人工标签。MaAI可选0.11、0.5、0.8,Smart Turn和Silero可选0.5、0.8。保存的分数不会随标注或转写编辑而重新计算。
|
||||
- 三个模型名称旁各有独立显示开关,默认都打开。关闭后隐藏该模型的曲线、原标注点评分、参考线及分数读数;重新打开保留原阈值。全部关闭时显示提示,切换通话或收起再展开会保留当前显示选择。
|
||||
- “选择模型”打开可搜索、按类型分组的多选面板,支持全选可用 / 清空;无评分模型保留在列表并说明状态。首次显示 VAP、Smart Turn、TurnSense 三条,之后记住显示选择和参考阈值,切换通话、收起和刷新均保留。紧凑图例只显示已选模型与当前分数,悬停或键盘聚焦会突出对应曲线。点击图例打开阈值和模型说明,可单独查看一个模型并恢复原选择。
|
||||
- 仅在展开时读取当前项目录音的评分,并核对音频SHA256。更换为本地新录音、音频变化或没有对应推理数据时,不显示旧曲线。切换通话会取消旧请求,避免前一段数据覆盖当前段。
|
||||
|
||||
评分数据位于各模型实验目录的 `results/score-timeline.json`。更新数据时,在两个各自的本机uv环境中运行:
|
||||
@@ -76,3 +76,11 @@ demo,5200,我明天下午,0,high,mid_turn_pause,A
|
||||
- `GET /api/audio?path=...`:仅提供 `data` 内的 MP3、WAV、M4A、OGG、FLAC 和 WebM 录音,支持 Range 请求和 HEAD,便于跳转播放。路径相对 `data` 目录,不能通过 `..` 或符号链接访问目录之外的文件。
|
||||
|
||||
录音 `call_id` 默认取文件名去除扩展名后的部分;同一通话有多个项目录音时,默认使用发现的第一条,可点击“更换录音”重新关联本地文件。
|
||||
|
||||
### LiveKit v1-mini
|
||||
|
||||
新增蓝色 LiveKit v1-mini 曲线,默认显示、每 200 ms 更新,并包含 44 个判断点的精确分数。只接收判断前的用户声道,16 kHz 最近 1.2 秒音频。参考阈值默认采用锁定 SDK 的中文阈值 0.355,可切换 0.5 / 0.8。它通过 LiveKit Agents 本地推理入口运行,不加服务层 VAD 门槛、静音等待或超时;不将这些曲线称为完整在线策略表现。模型许可与复现说明见 [实验说明](../../experiments/livekit-v1-mini/README.md)。
|
||||
|
||||
### TurnSense 1.0 FP32
|
||||
|
||||
玫红色曲线为真实 ONNX 推理的 P(complete),每 200 ms 及原判断点精确时刻更新。输入仅为判断前的用户左声道,重采样到 16 kHz 后取最近 8 秒;官方前端/CMVN不改动。支持独立开关和 0.5 / 0.8 参考线。原生输出为 complete/incomplete/invalid 三分类,完整三项概率保存在实验 predictions.csv,原生 argmax complete 映射为可接话;网页 0.5 参考线不是原生 argmax 的等价规则。复现和评估结果见 [TurnSense 实验说明](../../experiments/turnsense-1.0/README.md)。
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
import { promises as fs } from "node:fs";
|
||||
import { createHash } from "node:crypto";
|
||||
import path from "node:path";
|
||||
import { parseScoreExport, type ModelId, type ScoreResponse } from "@/lib/model-scores";
|
||||
import { parseScoreExport, type ScoreResponse } from "@/lib/model-scores";
|
||||
|
||||
import { SCORE_MODELS, SCORE_MODEL_IDS } from "@/lib/score-models";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
export const dynamic = "force-dynamic";
|
||||
const root = path.resolve(process.cwd(), "..", "..");
|
||||
const sources: { model: ModelId; directory: string; name: string }[] = [
|
||||
{ model: "maai", directory: "maai-ch-kyoto", name: "MaAI" },
|
||||
{ model: "smart_turn", directory: "smart-turn-v3.2", name: "Smart Turn" },
|
||||
{ model: "silero_vad", directory: "silero-vad", name: "Silero VAD" },
|
||||
];
|
||||
const sources = SCORE_MODEL_IDS.map(model => ({ model, ...SCORE_MODELS[model] }));
|
||||
const json = (data: unknown, status = 200) => Response.json(data, { status, headers: { "Cache-Control": "no-store" } });
|
||||
|
||||
export async function GET(request: Request) {
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useEffect, useMemo, useRef, useState, useId } from "react";
|
||||
import { scoreAt, scorePath, type ModelId, type ScoreResponse, type ScoreSeries } from "@/lib/model-scores";
|
||||
import { createPortal } from "react-dom";
|
||||
import { SCORE_MODELS, SCORE_MODEL_IDS, readScorePreferences } from "@/lib/score-models";
|
||||
import type { TimelineAnnotation } from "./timeline";
|
||||
import styles from "./timeline.module.css";
|
||||
|
||||
const EMPTY: ScoreSeries[] = [];
|
||||
const COLORS: Record<ModelId, string> = { maai: "#8866b2", smart_turn: "#278d95", silero_vad: "#c47b32" };
|
||||
const COLORS = Object.fromEntries(SCORE_MODEL_IDS.map(id => [id, SCORE_MODELS[id].color])) as Record<ModelId, string>;
|
||||
export const SCORE_HEIGHT = 172;
|
||||
export type ScoreThresholds = Record<ModelId, number>;
|
||||
export type ScoreVisibility = Record<ModelId, boolean>;
|
||||
@@ -44,33 +46,125 @@ export function useModelScores(callId: string, recordingPath: string | null, exp
|
||||
};
|
||||
}
|
||||
|
||||
export function ScoreControls({ series, currentMs, thresholds, onThreshold, visibility, onVisibility }: {
|
||||
export function useScorePreferences() {
|
||||
const [preferences, setPreferences] = useState(() => readScorePreferences(null));
|
||||
const [ready, setReady] = useState(false);
|
||||
useEffect(() => {
|
||||
try { setPreferences(readScorePreferences(JSON.parse(localStorage.getItem("eot-score-preferences-v1") || "null"))); } catch { /* Defaults survive unavailable or invalid browser storage. */ }
|
||||
setReady(true);
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
if (!ready) return;
|
||||
try { localStorage.setItem("eot-score-preferences-v1", JSON.stringify({ version: 1, ...preferences })); } catch { /* Preferences are optional. */ }
|
||||
}, [ready, preferences]);
|
||||
return { ...preferences,
|
||||
setVisibility: (visibility: ScoreVisibility) => setPreferences(previous => ({ ...previous, visibility })),
|
||||
setThreshold: (id: ModelId, value: number) => setPreferences(previous => ({ ...previous, thresholds: { ...previous.thresholds, [id]: value } })),
|
||||
};
|
||||
}
|
||||
|
||||
export function ScoreControls({ series, currentMs, thresholds, onThreshold, visibility, onVisibility, onFocus, focusedModel, loading }: {
|
||||
series: ScoreSeries[]; currentMs: number; thresholds: ScoreThresholds;
|
||||
onThreshold: (model: ModelId, value: number) => void;
|
||||
visibility: ScoreVisibility; onVisibility: (model: ModelId, visible: boolean) => void;
|
||||
visibility: ScoreVisibility; onVisibility: (visibility: ScoreVisibility) => void;
|
||||
onFocus: (model: ModelId | null) => void; focusedModel: ModelId | null; loading: boolean;
|
||||
}) {
|
||||
const [panel, setPanel] = useState<ModelId | "choose" | null>(null);
|
||||
const [query, setQuery] = useState("");
|
||||
const [position, setPosition] = useState({ top: 0, left: 0 });
|
||||
const [previousSelection, setPreviousSelection] = useState<ScoreVisibility | null>(null);
|
||||
const anchor = useRef<HTMLButtonElement | null>(null);
|
||||
const popup = useRef<HTMLDivElement>(null);
|
||||
const search = useRef<HTMLInputElement>(null);
|
||||
const panelId = useId();
|
||||
const available = new Set(series.map(row => row.model));
|
||||
const selected = series.filter(row => visibility[row.model]);
|
||||
function close() { setPanel(null); anchor.current?.focus({ preventScroll: true }); }
|
||||
function open(kind: ModelId | "choose", button: HTMLButtonElement) {
|
||||
anchor.current = button;
|
||||
if (panel === kind) { close(); return; }
|
||||
setQuery(""); setPanel(kind);
|
||||
}
|
||||
useEffect(() => {
|
||||
if (!panel) return;
|
||||
const place = () => {
|
||||
const r = anchor.current?.getBoundingClientRect(); if (!r) return;
|
||||
const width = Math.min(360, window.innerWidth - 24);
|
||||
const height = Math.min(popup.current?.getBoundingClientRect().height || 460, window.innerHeight - 24);
|
||||
setPosition({ left: Math.max(12, Math.min(r.left, window.innerWidth - width - 12)),
|
||||
top: Math.max(12, Math.min(r.bottom + 8, window.innerHeight - height - 12)) });
|
||||
};
|
||||
place();
|
||||
(panel === "choose" ? search.current : popup.current?.querySelector<HTMLElement>("button,select"))?.focus({ preventScroll: true });
|
||||
const outside = (event: PointerEvent) => {
|
||||
if (!popup.current?.contains(event.target as Node) && !anchor.current?.contains(event.target as Node)) setPanel(null);
|
||||
};
|
||||
const escape = (event: KeyboardEvent) => {
|
||||
if (event.key === "Escape") { event.preventDefault(); event.stopPropagation(); close(); }
|
||||
// Keep workbench hotkeys from changing annotations while browsing settings.
|
||||
else if (popup.current?.contains(event.target as Node)) event.stopPropagation();
|
||||
};
|
||||
document.addEventListener("pointerdown", outside); document.addEventListener("keydown", escape, true);
|
||||
window.addEventListener("resize", place); window.addEventListener("scroll", place, true);
|
||||
return () => { document.removeEventListener("pointerdown", outside); document.removeEventListener("keydown", escape, true);
|
||||
window.removeEventListener("resize", place); window.removeEventListener("scroll", place, true); };
|
||||
}, [panel]);
|
||||
const manualSelection = (next: ScoreVisibility) => { setPreviousSelection(null); onVisibility(next); };
|
||||
const metadata = panel && panel !== "choose" ? SCORE_MODELS[panel] : null;
|
||||
return <div className={styles.scoreControls}>
|
||||
{series.map(row => {
|
||||
const visible = visibility[row.model];
|
||||
const point = visible ? scoreAt(row.points, currentMs, row.sample_interval_ms * 2) : null;
|
||||
return <div className={styles.modelControl} key={row.model} style={{ color: COLORS[row.model] }}>
|
||||
<button type="button" role="switch" aria-checked={visible} aria-label={`显示 ${row.name} 分数`}
|
||||
className={styles.modelVisibility} onClick={() => onVisibility(row.model, !visible)}>
|
||||
<span className={styles.modelSwitch} aria-hidden="true" /><strong>{row.name}</strong>
|
||||
</button>
|
||||
<output aria-live="off" title={!visible ? "已隐藏" : point ? `评分时刻 ${time(point[0])}` : "当前位置暂无评分"}>{point ? point[1].toFixed(3) : "—"}</output>
|
||||
<label>参考线<select aria-label={`${row.name} 参考阈值`} value={thresholds[row.model]} disabled={!visible}
|
||||
onChange={event => onThreshold(row.model, Number(event.target.value))}>
|
||||
{(row.model === "maai" ? [.11, .5, .8] : [.5, .8]).map(value => <option key={value} value={value}>{value.toFixed(2)}</option>)}
|
||||
<div className={styles.modelPickerBar}>
|
||||
<button type="button" className={styles.modelPickerButton} aria-expanded={panel === "choose"} aria-controls={panel === "choose" ? panelId : undefined}
|
||||
aria-haspopup="dialog" onClick={event => open("choose", event.currentTarget)}>选择模型 <span>{selected.length}/{SCORE_MODEL_IDS.length}</span><span aria-hidden="true">⌄</span></button>
|
||||
{previousSelection && <button type="button" className={styles.restoreSelection} onClick={() => { onVisibility(previousSelection); setPreviousSelection(null); }}>恢复之前选择</button>}
|
||||
</div>
|
||||
<div className={styles.modelLegend} aria-label="已显示模型图例">
|
||||
{selected.map(row => {
|
||||
const model = SCORE_MODELS[row.model];
|
||||
const point = scoreAt(row.points, currentMs, row.sample_interval_ms * 2);
|
||||
return <div key={row.model} className={styles.legendItem} style={{ color: model.color, opacity: focusedModel && focusedModel !== row.model ? .45 : 1 }}
|
||||
onPointerEnter={() => onFocus(row.model)} onPointerLeave={() => onFocus(null)}>
|
||||
<button type="button" className={styles.legendName} aria-label={`${model.name} 曲线设置`} aria-haspopup="dialog" aria-expanded={panel === row.model}
|
||||
onFocus={() => onFocus(row.model)} onBlur={() => onFocus(null)} onClick={event => open(row.model, event.currentTarget)}>
|
||||
<i className={styles.modelColor} /><span>{model.name}</span><output title={point ? `评分时刻 ${time(point[0])}` : "当前位置暂无评分"}>{point ? point[1].toFixed(3) : "—"}</output><span className={styles.legendMore} aria-hidden="true">⋯</span>
|
||||
</button>
|
||||
</div>;
|
||||
})}
|
||||
{!selected.length && <span className={styles.noModels}>{loading ? "正在载入评分…" : "尚未选择可用模型"}</span>}
|
||||
</div>
|
||||
{panel && createPortal(<div className={styles.modelPopover} ref={popup} id={panelId} role="dialog" aria-label={panel === "choose" ? "选择对比模型" : `${metadata!.name} 曲线设置`}
|
||||
style={{ top: position.top, left: position.left }}>
|
||||
<div className={styles.popoverHeading}><strong>{panel === "choose" ? "选择对比模型" : metadata!.name}</strong><button type="button" aria-label="关闭模型面板" onClick={close}>×</button></div>
|
||||
{panel === "choose" ? <>
|
||||
<input ref={search} className={styles.modelSearch} aria-label="搜索模型" placeholder="搜索模型名称或类型" value={query} onChange={event => setQuery(event.target.value)} />
|
||||
<div className={styles.pickerActions}><span>已显示 {selected.length} 个</span><button type="button" disabled={!series.length} onClick={() => manualSelection({ ...visibility, ...Object.fromEntries(series.map(row => [row.model, true])) })}>全选可用</button><button type="button" onClick={() => manualSelection(Object.fromEntries(SCORE_MODEL_IDS.map(id => [id, false])) as ScoreVisibility)}>清空</button></div>
|
||||
<div className={styles.modelOptions}>
|
||||
{Array.from(new Set(SCORE_MODEL_IDS.map(id => SCORE_MODELS[id].family))).reverse().map(family => {
|
||||
const models = SCORE_MODEL_IDS.filter(id => SCORE_MODELS[id].family === family && `${SCORE_MODELS[id].name} ${family}`.toLowerCase().includes(query.trim().toLowerCase()));
|
||||
return models.length ? <fieldset key={family}><legend>{family}</legend>{models.map(id => <label key={id} className={styles.modelOption}>
|
||||
<input type="checkbox" aria-label={`显示 ${SCORE_MODELS[id].name} 分数`} checked={visibility[id]} disabled={!available.has(id)} onChange={event => manualSelection({ ...visibility, [id]: event.target.checked })} />
|
||||
<i className={styles.modelColor} style={{ background: SCORE_MODELS[id].color }} /><span><strong>{SCORE_MODELS[id].name}</strong><small>{available.has(id) ? SCORE_MODELS[id].description.split(";")[0] : loading ? "正在读取评分…" : "当前录音暂无评分"}</small></span>
|
||||
</label>)}</fieldset> : null;
|
||||
})}
|
||||
{!SCORE_MODEL_IDS.some(id => `${SCORE_MODELS[id].name} ${SCORE_MODELS[id].family}`.toLowerCase().includes(query.trim().toLowerCase())) && <p className={styles.noSearchResults}>没有匹配的模型</p>}
|
||||
</div><p className={styles.pickerHint}>选择会自动记住。建议同时比较 2–3 条曲线。</p>
|
||||
</> : <>
|
||||
<p className={styles.modelExplanation}>{metadata!.description}</p>
|
||||
<label className={styles.thresholdSetting}>参考阈值<select aria-label={`${metadata!.name} 参考阈值`} value={thresholds[panel]} onChange={event => onThreshold(panel, Number(event.target.value))}>
|
||||
{metadata!.thresholds.map(value => <option key={value} value={value}>{value === .355 ? "0.355(中文)" : value.toFixed(2)}</option>)}
|
||||
</select></label>
|
||||
</div>;
|
||||
})}
|
||||
<button type="button" className={styles.modelMenuAction} onClick={() => {
|
||||
if (!previousSelection) setPreviousSelection(visibility);
|
||||
onVisibility(Object.fromEntries(SCORE_MODEL_IDS.map(id => [id, id === panel])) as ScoreVisibility); close();
|
||||
}}>仅显示此模型</button>
|
||||
<button type="button" className={styles.modelMenuAction} onClick={() => { manualSelection({ ...visibility, [panel]: false }); close(); }}>隐藏此模型</button>
|
||||
</>}
|
||||
</div>, document.body)}
|
||||
</div>;
|
||||
}
|
||||
|
||||
export default function ModelScoreTrack({ series, width, durationMs, currentMs, thresholds, annotations, onSeek, onSelectAnnotation }: {
|
||||
export default function ModelScoreTrack({ series, width, durationMs, currentMs, thresholds, annotations, onSeek, onSelectAnnotation, focusedModel }: {
|
||||
series: ScoreSeries[]; width: number; durationMs: number; currentMs: number;
|
||||
thresholds: ScoreThresholds; annotations: TimelineAnnotation[];
|
||||
thresholds: ScoreThresholds; annotations: TimelineAnnotation[]; focusedModel: ModelId | null;
|
||||
onSeek: (ms: number) => void; onSelectAnnotation: (id: string, seekToPoint?: boolean) => void;
|
||||
}) {
|
||||
const [hoverMs, setHoverMs] = useState<number | null>(null);
|
||||
@@ -86,7 +180,7 @@ export default function ModelScoreTrack({ series, width, durationMs, currentMs,
|
||||
};
|
||||
return <div className={styles.scoreTrack} onPointerLeave={() => setHoverMs(null)}>
|
||||
<svg width={width} height={SCORE_HEIGHT} viewBox={`0 0 ${width} ${SCORE_HEIGHT}`}
|
||||
role="group" aria-label="VAP、Smart Turn 与 Silero VAD 分数对比,纵轴 0 到 1,横轴与录音时间一致"
|
||||
role="group" aria-label="VAP、Smart Turn、Silero VAD、LiveKit v1-mini 与 TurnSense 分数对比,纵轴 0 到 1,横轴与录音时间一致"
|
||||
onPointerMove={event => {
|
||||
const bounds = event.currentTarget.getBoundingClientRect();
|
||||
setHoverMs(Math.max(0, Math.min(durationMs, (event.clientX - bounds.left) / bounds.width * durationMs)));
|
||||
@@ -94,10 +188,10 @@ export default function ModelScoreTrack({ series, width, durationMs, currentMs,
|
||||
{[0, .25, .5, .75, 1].map(p => <line key={p} x1={0} x2={width} y1={y(p)} y2={y(p)} stroke="#e8e9e4" strokeDasharray={p === 0 || p === 1 ? undefined : "2 4"} />)}
|
||||
{annotations.map(row => <line key={row.id} x1={x(row.decision_ms)} x2={x(row.decision_ms)} y1={0} y2={SCORE_HEIGHT}
|
||||
stroke={row.can_reply_now === "0" ? "#c68c6b" : "#96a781"} strokeDasharray="2 5" opacity=".4" />)}
|
||||
{paths.map(row => <g key={row.model}>
|
||||
{paths.map(row => <g key={row.model} data-model={row.model} opacity={focusedModel && focusedModel !== row.model ? .18 : 1}>
|
||||
<line x1={0} x2={width} y1={y(thresholds[row.model])} y2={y(thresholds[row.model])}
|
||||
stroke={COLORS[row.model]} strokeDasharray={row.model === "maai" ? "6 5" : "2 4"} opacity=".55" />
|
||||
<path d={row.path} fill="none" stroke={COLORS[row.model]} strokeWidth={1.6} strokeLinejoin="round" pointerEvents="none" />
|
||||
<path d={row.path} fill="none" stroke={COLORS[row.model]} strokeWidth={focusedModel === row.model ? 2.8 : 1.6} strokeLinejoin="round" pointerEvents="none" />
|
||||
{row.checkpoints.map(([ms, score]) => <circle key={ms} cx={x(ms)} cy={y(score)} r={3.8}
|
||||
fill="#fffefb" stroke={COLORS[row.model]} strokeWidth={1.8} tabIndex={0} role="button"
|
||||
aria-label={`${row.name} 原标注点评分 ${time(ms)},${score.toFixed(3)},点击定位`}
|
||||
|
||||
@@ -124,19 +124,7 @@
|
||||
.scoreToggle > span:last-child { color: #929b89; font-size: 10px; }
|
||||
.chevron, .chevronOpen { display: inline-block; width: 12px; font-size: 23px; line-height: 16px; text-align: center; }
|
||||
.chevronOpen { transform: rotate(90deg); }
|
||||
.scoreControls { display: flex; flex-wrap: wrap; align-items: center; gap: 9px; }
|
||||
.modelControl { display: flex; align-items: center; gap: 6px; padding: 5px 8px; border: 1px solid #e9e9e2; border-radius: 7px; background: #fafbf7; font-size: 10px; }
|
||||
.modelControl strong { font-weight: 600; }
|
||||
.modelControl output { width: 37px; font-family: monospace; font-variant-numeric: tabular-nums; text-align: center; }
|
||||
.modelControl label { display: flex; align-items: center; gap: 4px; color: #88927e; padding-left: 4px; border-left: 1px solid #e2e5db; }
|
||||
.modelControl select { padding: 1px 0; border: 0; background: transparent; font-size: 10px; color: #637258; cursor: pointer; }
|
||||
.modelVisibility { display: inline-flex; align-items: center; gap: 6px; padding: 3px 0; border: 0; background: transparent; color: inherit; font: inherit; cursor: pointer; }
|
||||
.modelSwitch { display: inline-block; position: relative; width: 26px; height: 15px; border-radius: 20px; background: currentColor; }
|
||||
.modelSwitch::after { content: ""; position: absolute; top: 3px; left: 14px; width: 9px; height: 9px; border-radius: 50%; background: white; transition: left .15s ease; }
|
||||
.modelVisibility[aria-checked="false"] { color: #92998c; }
|
||||
.modelVisibility[aria-checked="false"] .modelSwitch { background: #c6cbbe; }
|
||||
.modelVisibility[aria-checked="false"] .modelSwitch::after { left: 3px; }
|
||||
.modelControl select:disabled { opacity: .45; cursor: default; }
|
||||
.scoreControls { width: 100%; display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px; }
|
||||
.scoreDescription { padding: 0 22px 10px; font-size: 10px; line-height: 1.7; color: #88927f; }
|
||||
.scoreDescription button { border: 0; background: none; color: #536f40; cursor: pointer; text-decoration: underline; font-size: inherit; }
|
||||
.trackNames > .scoreAxis { padding: 12px 8px; flex-direction: column; justify-content: space-between; font-size: 10px; color: #929a87; border-top: 1px solid #e7e9e0; }
|
||||
@@ -150,10 +138,51 @@
|
||||
.scoreInspection b { font-weight: 500; }
|
||||
.scoreEmpty { position: sticky; left: 0; display: grid; place-items: center; color: #929b88; font-size: 11px; background: #fdfefb; border-top: 1px solid #e7e9e0; }
|
||||
.scoreFootnote { margin: 0; padding: 9px 22px 0; color: #8c9583; font-size: 10px; line-height: 1.7; }
|
||||
.scoreToggle:focus-visible, .modelVisibility:focus-visible, .modelControl select:focus-visible { outline: 2px solid #758962; outline-offset: 3px; border-radius: 3px; }
|
||||
.scoreToggle:focus-visible, .modelPickerButton:focus-visible, .legendName:focus-visible { outline: 2px solid #758962; outline-offset: 3px; border-radius: 3px; }
|
||||
@media (max-width: 640px) {
|
||||
.scorePanelHeader { padding-inline: 12px; gap: 5px; }
|
||||
.scoreDescription, .scoreFootnote { padding-inline: 12px; }
|
||||
.scoreControls { gap: 5px; }
|
||||
.modelControl { padding: 4px 5px; }
|
||||
}
|
||||
|
||||
.modelPickerBar { display: flex; align-items: center; gap: 9px; }
|
||||
.modelPickerButton { display: inline-flex; align-items: center; gap: 7px; padding: 6px 9px; border: 1px solid #dfe5d7; background: #f8faf3; border-radius: 6px; font: inherit; font-size: 11px; color: #526448; cursor: pointer; white-space: nowrap; }
|
||||
.modelPickerButton > span:first-child { padding: 1px 5px; border-radius: 4px; background: #e9efdf; color: #69775f; font: 10px monospace; }
|
||||
.restoreSelection { border: 0; background: none; color: #647d50; font: inherit; font-size: 10px; cursor: pointer; text-decoration: underline; text-underline-offset: 3px; }
|
||||
.modelLegend { display: flex; align-items: center; flex-wrap: wrap; gap: 3px 10px; min-width: 0; }
|
||||
.legendItem { transition: opacity .12s; }
|
||||
.legendName { display: flex; align-items: center; gap: 5px; border: 0; border-radius: 5px; padding: 5px 2px; background: none; color: inherit; font-size: 10px; cursor: pointer; }
|
||||
.legendName:hover, .legendName[aria-expanded="true"] { background: #f0f3e9; }
|
||||
.legendName output { font: 10px monospace; font-variant-numeric: tabular-nums; min-width: 32px; }
|
||||
.legendMore { color: #9aa18e; font-size: 15px; line-height: 10px; }
|
||||
.modelColor { display: inline-block; width: 7px; height: 7px; flex-shrink: 0; background: currentColor; border-radius: 50%; }
|
||||
.noModels { color: #929b88; font-size: 10px; }
|
||||
.modelPopover { position: fixed; z-index: 100; width: min(360px, calc(100vw - 24px)); max-height: calc(100vh - 24px); overflow-y: auto; padding: 14px; border: 1px solid #dfe4d6; border-radius: 12px; background: #fffefb; box-shadow: 0 12px 40px #33402a26, 0 2px 8px #33402a0d; color: #46563e; font-family: inherit; font-size: 12px; }
|
||||
.popoverHeading { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
|
||||
.popoverHeading strong { font-size: 13px; font-weight: 600; }
|
||||
.popoverHeading button { display: grid; place-items: center; width: 26px; height: 26px; border: 0; border-radius: 5px; background: #f4f6ee; font-size: 19px; color: #7c8872; cursor: pointer; }
|
||||
.modelSearch { box-sizing: border-box; width: 100%; padding: 8px 10px; border: 1px solid #dfe5d7; border-radius: 7px; color: #526448; background: #fbfcf7; font: inherit; font-size: 12px; }
|
||||
.modelSearch:focus-visible { outline: 2px solid #9aac87; outline-offset: 1px; }
|
||||
.pickerActions { display: flex; align-items: center; gap: 9px; padding: 11px 0 7px; font-size: 10px; color: #8b9580; }
|
||||
.pickerActions > span { margin-right: auto; }
|
||||
.pickerActions button { padding: 2px 0; border: 0; background: none; color: #637e4d; cursor: pointer; font: inherit; }
|
||||
.pickerActions button:disabled { opacity: .4; cursor: default; }
|
||||
.modelOptions { max-height: min(360px, 50vh); overflow-y: auto; scrollbar-width: thin; }
|
||||
.modelOptions fieldset { border: 0; margin: 6px 0 0; padding: 0; min-width: 0; }
|
||||
.modelOptions legend { padding: 5px 0; color: #8c9781; font-size: 10px; }
|
||||
.modelOption { display: flex; align-items: center; gap: 9px; padding: 8px 6px; border-radius: 6px; cursor: pointer; }
|
||||
.modelOption:hover { background: #f4f7ed; }
|
||||
.modelOption:has(input:disabled) { opacity: .55; cursor: default; }
|
||||
.modelOption input { accent-color: #748c5f; margin: 0; width: 14px; height: 14px; }
|
||||
.modelOption > span { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
|
||||
.modelOption strong { font-size: 11px; font-weight: 550; }
|
||||
.modelOption small { font-size: 10px; color: #8c9781; }
|
||||
.pickerHint { margin: 12px 0 0; border-top: 1px solid #ecede5; padding-top: 10px; color: #929b88; font-size: 10px; }
|
||||
.noSearchResults { padding: 18px 0; text-align: center; color: #929b88; }
|
||||
.modelExplanation { margin: 0 0 14px; line-height: 1.8; color: #839175; font-size: 11px; }
|
||||
.thresholdSetting { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 8px 0 14px; color: #66775a; font-size: 11px; }
|
||||
.thresholdSetting select { border: 1px solid #dfe5d7; border-radius: 6px; padding: 6px 8px; background: #f9fbf4; color: #526448; font: inherit; }
|
||||
.modelMenuAction { display: block; width: 100%; padding: 9px 6px; text-align: left; border: 0; border-top: 1px solid #eef0e6; color: #637a50; background: none; cursor: pointer; font: inherit; font-size: 11px; }
|
||||
.modelMenuAction:hover { background: #f4f7ed; }
|
||||
.modelPopover button:focus-visible, .modelPopover select:focus-visible { outline: 2px solid #91a67c; outline-offset: 2px; }
|
||||
@media (prefers-reduced-motion: reduce) { .legendItem { transition: none; } }
|
||||
|
||||
@@ -11,8 +11,9 @@ import {
|
||||
type PointerEvent,
|
||||
} from "react";
|
||||
import styles from "./timeline.module.css";
|
||||
import type { ModelId } from "@/lib/model-scores";
|
||||
import { resizeTranscriptRange, snapTimelineTime, type SnapPoint } from "@/lib/timeline-edit";
|
||||
import ModelScoreTrack, { ScoreControls, SCORE_HEIGHT, useModelScores, type ScoreThresholds, type ScoreVisibility } from "./model-score-track";
|
||||
import ModelScoreTrack, { ScoreControls, SCORE_HEIGHT, useModelScores, useScorePreferences } from "./model-score-track";
|
||||
|
||||
export interface TimelineTranscript {
|
||||
index: number;
|
||||
@@ -90,8 +91,8 @@ export default function Timeline({
|
||||
const helpId = useId();
|
||||
const scoreRegionId = useId();
|
||||
const [scoresOpen, setScoresOpen] = useState(false);
|
||||
const [thresholds, setThresholds] = useState<ScoreThresholds>({ maai: .5, smart_turn: .5, silero_vad: .5 });
|
||||
const [scoreVisibility, setScoreVisibility] = useState<ScoreVisibility>({ maai: true, smart_turn: true, silero_vad: true });
|
||||
const { thresholds, visibility: scoreVisibility, setVisibility, setThreshold } = useScorePreferences();
|
||||
const [focusedModel, setFocusedModel] = useState<ModelId | null>(null);
|
||||
const scores = useModelScores(callId, recordingPath, scoresOpen);
|
||||
const visibleScores = useMemo(() => scores.series.filter(row => scoreVisibility[row.model]), [scores.series, scoreVisibility]);
|
||||
const viewportRef = useRef<HTMLDivElement>(null);
|
||||
@@ -336,15 +337,15 @@ export default function Timeline({
|
||||
<button type="button" className={styles.scoreToggle} aria-expanded={scoresOpen} aria-controls={scoreRegionId}
|
||||
onClick={() => setScoresOpen(value => !value)}>
|
||||
<span aria-hidden="true" className={scoresOpen ? styles.chevronOpen : styles.chevron}>›</span>
|
||||
<strong>模型分数对比</strong><span>{scoresOpen ? "收起" : "展开 VAP / Smart Turn / Silero VAD 对比"}</span>
|
||||
<strong>模型分数对比</strong><span>{scoresOpen ? "收起" : "展开评分曲线"}</span>
|
||||
</button>
|
||||
{scoresOpen && <ScoreControls series={scores.series} currentMs={current}
|
||||
visibility={scoreVisibility} onVisibility={(model, visible) => setScoreVisibility(previous => ({ ...previous, [model]: visible }))}
|
||||
thresholds={thresholds} onThreshold={(model, value) => setThresholds(previous => ({ ...previous, [model]: value }))} />}
|
||||
visibility={scoreVisibility} onVisibility={setVisibility}
|
||||
thresholds={thresholds} onThreshold={setThreshold} focusedModel={focusedModel} onFocus={setFocusedModel} loading={scores.status === "loading"} />}
|
||||
</div>
|
||||
{scoresOpen && <div className={styles.scoreDescription} role="status">
|
||||
{scores.status === "loading" ? "正在读取模型分数…" : scores.status === "error" ? <>{scores.messages.join(" ")} <button type="button" onClick={scores.retry}>重新读取</button></>
|
||||
: <>实线:连续回放评分 · 空心点:原标注点评分 · 虚线:参考阈值。Smart Turn 表示完成倾向;VAP 使用 1 − 用户近期发声分数,Silero 使用 1 − 当前发声分数,仅为非发声基线,不是语义 EOT 概率。{scores.messages.join(" ")}</>}
|
||||
: <>实线:连续回放评分 · 空心点:原标注点评分 · 虚线:参考阈值。Smart Turn、LiveKit 表示完成倾向,TurnSense 为 P(complete);VAP 使用 1 − 用户近期发声分数,Silero 使用 1 − 当前发声分数,仅为非发声基线,不是语义 EOT 概率。{scores.messages.join(" ")}</>}
|
||||
</div>}
|
||||
|
||||
<div className={styles.trackLayout}>
|
||||
@@ -452,9 +453,9 @@ export default function Timeline({
|
||||
</div>
|
||||
|
||||
{scoresOpen && <div id={scoreRegionId}>
|
||||
{visibleScores.length ? <ModelScoreTrack series={visibleScores} width={canvasWidth} durationMs={duration}
|
||||
{visibleScores.length ? <ModelScoreTrack focusedModel={focusedModel && visibleScores.some(row => row.model === focusedModel) ? focusedModel : null} series={visibleScores} width={canvasWidth} durationMs={duration}
|
||||
currentMs={current} thresholds={thresholds} annotations={annotations} onSeek={onSeek} onSelectAnnotation={onSelectAnnotation} />
|
||||
: <div className={styles.scoreEmpty} style={{ height: SCORE_HEIGHT, width: viewportWidth }}>{scores.status === "loading" ? "正在载入评分曲线…" : scores.series.length ? "已隐藏全部模型分数,请打开上方开关查看" : "当前录音暂无可显示的模型分数"}</div>}
|
||||
: <div className={styles.scoreEmpty} style={{ height: SCORE_HEIGHT, width: viewportWidth }}>{scores.status === "loading" ? "正在载入评分曲线…" : scores.series.length ? "已隐藏全部模型分数,请在「选择模型」中添加" : "当前录音暂无可显示的模型分数"}</div>}
|
||||
</div>}
|
||||
|
||||
{activeSnap && <div className={styles.snapGuide} style={{ left: position(activeSnap.ms) }} aria-hidden="true" />}
|
||||
@@ -473,7 +474,7 @@ export default function Timeline({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{scoresOpen && scores.series.length > 0 && <p className={styles.scoreFootnote}>点击曲线定位录音;悬停查看分数。VAP 约每 100 ms、Smart Turn 每 200 ms、Silero 每 32 ms 更新。Silero 空心点取判断前最后完整帧;其他模型原标注点评分使用独立历史窗口,与连续曲线可能不同;编辑标注或转写不会重算已有分数。</p>}
|
||||
{scoresOpen && scores.series.length > 0 && <p className={styles.scoreFootnote}>点击曲线定位录音;悬停查看分数。VAP 约每 100 ms、Smart Turn 每 200 ms、Silero 每 32 ms、LiveKit、TurnSense 每 200 ms 更新。TurnSense 使用过去 8 秒用户音频;LiveKit 使用过去 1.2 秒用户音频,中文参考阈值 0.355;Silero 空心点取判断前最后完整帧;VAP 和 Smart Turn 原标注点评分使用独立历史窗口,与连续曲线可能不同;TurnSense 的 0.5 参考线不等同于原生三分类取最大值规则;编辑标注或转写不会重算已有分数。</p>}
|
||||
|
||||
<div className={styles.bottomBar}>
|
||||
<div className={styles.timeReadout}><span className={styles.playheadDot} /> 播放 <strong>{timeLabel(current, true)}</strong></div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export type ModelId = "maai" | "smart_turn" | "silero_vad";
|
||||
export type ModelId = "maai" | "smart_turn" | "silero_vad" | "livekit_mini" | "turnsense";
|
||||
export type ScorePoint = [timeMs: number, score: number];
|
||||
|
||||
export interface ScoreCall {
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import type { ModelId } from "./model-scores";
|
||||
|
||||
export const SCORE_MODELS: Record<ModelId, {
|
||||
name: string; family: string; color: string; directory: string;
|
||||
thresholds: readonly number[]; defaultThreshold: number; defaultVisible: boolean; description: string;
|
||||
}> = {
|
||||
maai: { name: "VAP / MaAI", family: "活动预测", color: "#8866b2", directory: "maai-ch-kyoto", thresholds: [.11, .5, .8], defaultThreshold: .5, defaultVisible: true, description: "1 − 用户近期发声分数;约每 100 ms 更新,不是校准的 EOT 概率。" },
|
||||
smart_turn: { name: "Smart Turn", family: "话轮检测", color: "#278d95", directory: "smart-turn-v3.2", thresholds: [.5, .8], defaultThreshold: .5, defaultVisible: true, description: "完成倾向;最近最多 8 秒用户音频,每 200 ms 更新。" },
|
||||
silero_vad: { name: "Silero VAD", family: "活动预测", color: "#c47b32", directory: "silero-vad", thresholds: [.5, .8], defaultThreshold: .5, defaultVisible: false, description: "1 − 发声概率;每 32 ms 更新,仅为非发声基线,不是语义 EOT 概率。" },
|
||||
livekit_mini: { name: "LiveKit v1-mini", family: "话轮检测", color: "#5378c4", directory: "livekit-v1-mini", thresholds: [.355, .5, .8], defaultThreshold: .355, defaultVisible: false, description: "P(turn ended);最近 1.2 秒用户音频,每 200 ms 更新。0.355 为中文参考阈值。" },
|
||||
turnsense: { name: "TurnSense 1.0", family: "话轮检测", color: "#b65378", directory: "turnsense-1.0", thresholds: [.5, .8], defaultThreshold: .5, defaultVisible: true, description: "FP32 · P(complete);最近 8 秒用户音频,每 200 ms 更新。参考线不等同原生三分类 argmax。" },
|
||||
};
|
||||
export const SCORE_MODEL_IDS = Object.keys(SCORE_MODELS) as ModelId[];
|
||||
export interface ScorePreferences { visibility: Record<ModelId, boolean>; thresholds: Record<ModelId, number> }
|
||||
export function readScorePreferences(value: unknown): ScorePreferences {
|
||||
const saved = value && typeof value === "object" && "version" in value && value.version === 1
|
||||
? value as { visibility?: Record<string, unknown>; thresholds?: Record<string, unknown> } : {};
|
||||
return {
|
||||
visibility: Object.fromEntries(SCORE_MODEL_IDS.map(id => [id, typeof saved.visibility?.[id] === "boolean" ? saved.visibility[id] : SCORE_MODELS[id].defaultVisible])) as Record<ModelId, boolean>,
|
||||
thresholds: Object.fromEntries(SCORE_MODEL_IDS.map(id => [id, SCORE_MODELS[id].thresholds.includes(saved.thresholds?.[id] as number) ? saved.thresholds![id] : SCORE_MODELS[id].defaultThreshold])) as Record<ModelId, number>,
|
||||
};
|
||||
}
|
||||
@@ -38,13 +38,15 @@ test("curve gaps are not connected and steps remain on the audio clock", () => {
|
||||
|
||||
test("recorded inference exports agree on audio identity and retain all 44 checkpoints", async () => {
|
||||
const files = [
|
||||
["maai", "maai-ch-kyoto"], ["smart_turn", "smart-turn-v3.2"], ["silero_vad", "silero-vad"],
|
||||
["maai", "maai-ch-kyoto"], ["smart_turn", "smart-turn-v3.2"], ["silero_vad", "silero-vad"], ["livekit_mini", "livekit-v1-mini"], ["turnsense", "turnsense-1.0"],
|
||||
] as const;
|
||||
const exports = await Promise.all(files.map(async ([model, directory]) => parseScoreExport(
|
||||
JSON.parse(await readFile(new URL(`../../../experiments/${directory}/results/score-timeline.json`, import.meta.url), "utf8")), model)));
|
||||
const identity = (data: typeof exports[number]) => data.calls.map(call => [call.call_id, call.audio_path, call.audio_sha256, call.duration_ms]);
|
||||
assert.deepEqual(identity(exports[0]), identity(exports[1]));
|
||||
assert.deepEqual(identity(exports[0]), identity(exports[2]));
|
||||
assert.deepEqual(identity(exports[0]), identity(exports[3]));
|
||||
assert.deepEqual(identity(exports[0]), identity(exports[4]));
|
||||
for (const data of exports) {
|
||||
assert.equal(data.calls.reduce((n, call) => n + call.checkpoints.length, 0), 44);
|
||||
for (const call of data.calls) {
|
||||
@@ -69,3 +71,25 @@ test("Silero checkpoints use only the last available frame, and speech is invert
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
test("LiveKit original checkpoints are real exact-time curve scores", async () => {
|
||||
const data = parseScoreExport(JSON.parse(await readFile(new URL(
|
||||
"../../../experiments/livekit-v1-mini/results/score-timeline.json", import.meta.url), "utf8")), "livekit_mini");
|
||||
assert.equal(data.sample_interval_ms, 200);
|
||||
for (const call of data.calls) {
|
||||
for (const [ms, score] of call.checkpoints) {
|
||||
assert.deepEqual(call.points.find(point => point[0] === ms), [ms, score]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
test("TurnSense curve checkpoints retain the complete probability at the exact decision", async () => {
|
||||
const data = parseScoreExport(JSON.parse(await readFile(new URL(
|
||||
"../../../experiments/turnsense-1.0/results/score-timeline.json", import.meta.url), "utf8")), "turnsense");
|
||||
assert.equal(data.sample_interval_ms, 200);
|
||||
for (const call of data.calls) {
|
||||
for (const point of call.checkpoints) assert.deepEqual(call.points.find(p => p[0] === point[0]), point);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
// @ts-expect-error Node's native TypeScript runner requires explicit extensions.
|
||||
import { readScorePreferences } from "../lib/score-models.ts";
|
||||
|
||||
test("first visit starts with three comparison curves and each model's reference threshold", () => {
|
||||
const defaults = readScorePreferences(null);
|
||||
assert.deepEqual(Object.entries(defaults.visibility).filter(([, value]) => value).map(([id]) => id), ["maai", "smart_turn", "turnsense"]);
|
||||
assert.equal(defaults.thresholds.livekit_mini, .355);
|
||||
});
|
||||
|
||||
test("saved choices preserve clear-all and accept only supported reference thresholds", () => {
|
||||
const saved = readScorePreferences({ version: 1, visibility: { maai: false, smart_turn: false, silero_vad: false, livekit_mini: false, turnsense: false },
|
||||
thresholds: { maai: .11, livekit_mini: .355, smart_turn: 99, turnsense: "0.8" } });
|
||||
assert.equal(Object.values(saved.visibility).some(Boolean), false);
|
||||
assert.equal(saved.thresholds.maai, .11);
|
||||
assert.equal(saved.thresholds.smart_turn, .5);
|
||||
assert.equal(saved.thresholds.turnsense, .5);
|
||||
});
|
||||
|
||||
test("partial and invalid preferences leave future models on their defaults", () => {
|
||||
const saved = readScorePreferences({ version: 1, visibility: { maai: true, smart_turn: "false", unknown: true }, thresholds: { turnsense: .8 } });
|
||||
assert.equal(saved.visibility.smart_turn, true);
|
||||
assert.equal(saved.visibility.turnsense, true);
|
||||
assert.equal(saved.thresholds.turnsense, .8);
|
||||
assert.equal("unknown" in saved.visibility, false);
|
||||
assert.deepEqual(readScorePreferences({ version: 999, visibility: { maai: false } }), readScorePreferences(null));
|
||||
});
|
||||
Reference in New Issue
Block a user