whosaid 1.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- whosaid-1.1.0/.cbmignore +76 -0
- whosaid-1.1.0/.github/workflows/prxref-review.yml +43 -0
- whosaid-1.1.0/.gitignore +11 -0
- whosaid-1.1.0/CHANGELOG.md +260 -0
- whosaid-1.1.0/LICENSE +21 -0
- whosaid-1.1.0/PKG-INFO +484 -0
- whosaid-1.1.0/README.md +456 -0
- whosaid-1.1.0/bootstrap.sh +152 -0
- whosaid-1.1.0/docs/demo.gif +0 -0
- whosaid-1.1.0/docs/design.md +160 -0
- whosaid-1.1.0/lib/__init__.py +3 -0
- whosaid-1.1.0/lib/diarize_sherpa.py +1342 -0
- whosaid-1.1.0/lib/mcp_server.py +958 -0
- whosaid-1.1.0/lib/samples.py +196 -0
- whosaid-1.1.0/lib/transcribe_mlx.py +70 -0
- whosaid-1.1.0/lib/workspace.py +924 -0
- whosaid-1.1.0/pyproject.toml +57 -0
- whosaid-1.1.0/recordings/.gitkeep +0 -0
- whosaid-1.1.0/test/anchor_test.py +400 -0
- whosaid-1.1.0/test/check_sidecar_schema.py +73 -0
- whosaid-1.1.0/test/diarize_absorb_test.py +243 -0
- whosaid-1.1.0/test/e2e.sh +541 -0
- whosaid-1.1.0/test/enroll_from_file_test.sh +260 -0
- whosaid-1.1.0/test/estimate_k_test.py +235 -0
- whosaid-1.1.0/test/install_guard_test.sh +142 -0
- whosaid-1.1.0/test/mcp_descriptions_test.py +126 -0
- whosaid-1.1.0/test/registry_matches_test.py +167 -0
- whosaid-1.1.0/test/samples_test.sh +277 -0
- whosaid-1.1.0/test/version_test.sh +105 -0
- whosaid-1.1.0/test/workspace_test.sh +861 -0
- whosaid-1.1.0/voices/.gitkeep +0 -0
- whosaid-1.1.0/whosaid +1278 -0
whosaid-1.1.0/.cbmignore
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Generated by /cbm-index — keeps the codebase-memory graph small and queries fast.
|
|
2
|
+
# Edit freely; this file is yours.
|
|
3
|
+
|
|
4
|
+
# VCS
|
|
5
|
+
.git/
|
|
6
|
+
.hg/
|
|
7
|
+
.svn/
|
|
8
|
+
|
|
9
|
+
# Build output
|
|
10
|
+
dist/
|
|
11
|
+
build/
|
|
12
|
+
out/
|
|
13
|
+
target/
|
|
14
|
+
.next/
|
|
15
|
+
.nuxt/
|
|
16
|
+
.svelte-kit/
|
|
17
|
+
.output/
|
|
18
|
+
|
|
19
|
+
# Caches
|
|
20
|
+
.cache/
|
|
21
|
+
.parcel-cache/
|
|
22
|
+
.turbo/
|
|
23
|
+
.swc/
|
|
24
|
+
tmp/
|
|
25
|
+
temp/
|
|
26
|
+
|
|
27
|
+
# Dependencies
|
|
28
|
+
node_modules/
|
|
29
|
+
bower_components/
|
|
30
|
+
.pnpm-store/
|
|
31
|
+
vendor/
|
|
32
|
+
|
|
33
|
+
# Coverage / reports
|
|
34
|
+
coverage/
|
|
35
|
+
.nyc_output/
|
|
36
|
+
htmlcov/
|
|
37
|
+
.coverage
|
|
38
|
+
*.lcov
|
|
39
|
+
|
|
40
|
+
# Editor / OS
|
|
41
|
+
.idea/
|
|
42
|
+
.vscode/
|
|
43
|
+
.DS_Store
|
|
44
|
+
*.swp
|
|
45
|
+
|
|
46
|
+
# Logs
|
|
47
|
+
*.log
|
|
48
|
+
|
|
49
|
+
# Minified / sourcemaps (low signal for graph)
|
|
50
|
+
*.min.js
|
|
51
|
+
*.min.css
|
|
52
|
+
*.bundle.js
|
|
53
|
+
*.map
|
|
54
|
+
|
|
55
|
+
# Lockfiles (not source code, but bulky)
|
|
56
|
+
pnpm-lock.yaml
|
|
57
|
+
yarn.lock
|
|
58
|
+
package-lock.json
|
|
59
|
+
poetry.lock
|
|
60
|
+
uv.lock
|
|
61
|
+
Gemfile.lock
|
|
62
|
+
composer.lock
|
|
63
|
+
|
|
64
|
+
# Python
|
|
65
|
+
__pycache__/
|
|
66
|
+
*.pyc
|
|
67
|
+
*.pyo
|
|
68
|
+
*.egg-info/
|
|
69
|
+
.pytest_cache/
|
|
70
|
+
.mypy_cache/
|
|
71
|
+
.ruff_cache/
|
|
72
|
+
.tox/
|
|
73
|
+
.venv/
|
|
74
|
+
venv/
|
|
75
|
+
env/
|
|
76
|
+
*.whl
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
name: prxref review
|
|
2
|
+
|
|
3
|
+
# pull_request_target runs in the BASE repository context, so it has access to
|
|
4
|
+
# secrets and a write token even for fork PRs. This workflow never checks out
|
|
5
|
+
# or executes the pull request's code: prxref fetches the diff over the GitHub
|
|
6
|
+
# API from the PR URL alone, running from PyPI.
|
|
7
|
+
on:
|
|
8
|
+
pull_request_target:
|
|
9
|
+
types: [opened, synchronize, reopened]
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
pull-requests: write
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
review:
|
|
17
|
+
name: prxref review
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
timeout-minutes: 10
|
|
20
|
+
# prxref is non-blocking by design (exits 0 on all network/LLM/forge errors),
|
|
21
|
+
# but continue-on-error provides belt-and-braces protection for CI continuity.
|
|
22
|
+
continue-on-error: true
|
|
23
|
+
|
|
24
|
+
steps:
|
|
25
|
+
- name: Install uv
|
|
26
|
+
uses: astral-sh/setup-uv@v5
|
|
27
|
+
with:
|
|
28
|
+
python-version: "3.12"
|
|
29
|
+
|
|
30
|
+
- name: Run prxref review
|
|
31
|
+
env:
|
|
32
|
+
PRXREF_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
33
|
+
PRXREF_LLM_BACKEND: http
|
|
34
|
+
PRXREF_LLM_BASE_URL: ${{ vars.PRXREF_LLM_BASE_URL }}
|
|
35
|
+
PRXREF_LLM_API_KEY: ${{ secrets.PRXREF_LLM_API_KEY }}
|
|
36
|
+
PRXREF_LLM_MODELS: ${{ vars.PRXREF_LLM_MODELS }}
|
|
37
|
+
PRXREF_LLM_REASONING_EFFORT: ${{ vars.PRXREF_LLM_REASONING_EFFORT }}
|
|
38
|
+
# Per-WORKER-CHUNK completion budget, not a per-run one. Needs the
|
|
39
|
+
# literal fallback because an unset repository variable renders as
|
|
40
|
+
# the empty string and prxref falls back to 4096, which truncates
|
|
41
|
+
# reasoning models before the findings JSON is emitted.
|
|
42
|
+
PRXREF_LLM_MAX_TOKENS: ${{ vars.PRXREF_LLM_MAX_TOKENS || '32000' }}
|
|
43
|
+
run: uvx prxref review --pr-url "${{ github.event.pull_request.html_url }}" -v
|
whosaid-1.1.0/.gitignore
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to whosaid are documented here. This project adheres to
|
|
4
|
+
[Semantic Versioning](https://semver.org/spec/v2.0.0.html) and the format of
|
|
5
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **PyPI packaging (`pyproject.toml`).** The `lib/` modules now ship as the importable `whosaid`
|
|
12
|
+
package (PEP 621, hatchling; wheel renames `lib/` → `whosaid/`), with a `whosaid-mcp` console
|
|
13
|
+
script that runs the MCP server (`python -m`-style entry `whosaid.mcp_server:main`) and the MCP
|
|
14
|
+
registry marker (`io.github.sblattj/whosaid`) in the README. Runtime deps are declared
|
|
15
|
+
(`mlx-whisper`, `mcp`, `numpy`, `sherpa-onnx`); ffmpeg/uv remain system prerequisites from
|
|
16
|
+
`whosaid setup`, and the full CLI is still installed from a checkout via `whosaid install`.
|
|
17
|
+
`pyproject.toml` carries the version — bump it together with `WHOSAID_VERSION` in the `whosaid`
|
|
18
|
+
script and `__version__` in `lib/mcp_server.py` at release time. `[tool.uv] managed = false`
|
|
19
|
+
keeps `uv run` in the checkout working exactly as before (ephemeral `--with` environments; no
|
|
20
|
+
project sync, no `.venv`, no lockfile).
|
|
21
|
+
- **`--expected-speakers` registry-anchored diarization (GitHub issue #1, part 4).** `whosaid <audio> --expected-speakers Alice,Bob` (comma-separated and repeatable; also on `ingest`, and as `expected_speakers` on the `whosaid_transcribe` MCP tool) anchors clustering to the enrolled voiceprints of the people you expect: each turn at cosine ≥ `--anchor-threshold` (default `0.70`, env `WHOSAID_ANCHOR_THRESHOLD`) is pinned to that person, the residual turns are clustered into new speakers as before, and a listed person who never speaks is dropped — so a recurring team with varying attendance no longer needs an exact `--speakers N` (which merges distinct people when fewer show up) or bare auto-detect (which over-segments and then leaves a major speaker unmatched). Unknown names are fatal and list the known voices. Per-anchor results land in the sidecar under `anchors` and in `registry_matches` with `pass: "anchor"`; the flag forces the chunked diarization path at any length, since anchoring needs per-turn voiceprints.
|
|
22
|
+
- **Temp-directory install guard (GitHub issue #10).** `whosaid install` (and `./bootstrap.sh`,
|
|
23
|
+
before its multi-minute model downloads) now refuse to install from a checkout under `/tmp`,
|
|
24
|
+
`/private/tmp`, `/var/tmp`, or `$TMPDIR` unless `--force` is given, since the installed symlink
|
|
25
|
+
would silently dangle once the OS cleans that directory up. `whosaid doctor` now also reports a
|
|
26
|
+
dangling or foreign install symlink so a "command not found" regression points at the real cause.
|
|
27
|
+
- **Match-threshold flag, machine-readable match confidence, source metadata (GitHub issue #1, parts 2–3).**
|
|
28
|
+
`whosaid <audio> --match-threshold F` (alias `--ref-threshold`, env `WHOSAID_MATCH_THRESHOLD`) now
|
|
29
|
+
passes the registry/reference match gate through from the CLI, `ingest`, `relabel` and the MCP
|
|
30
|
+
tools, and its default is raised from `0.40` to `0.50` — real meeting audio produced wrong
|
|
31
|
+
assertions in the 0.40–0.53 band, while genuine matches score far higher. Clusters below the
|
|
32
|
+
threshold keep their `SPEAKER_NN` label. Every naming decision, near-misses included, is written
|
|
33
|
+
to `<base>.diarization.json` as `registry_matches`
|
|
34
|
+
(`{cluster, name, similarity, threshold, matched, pass}`), refreshed by `relabel --auto`, and the
|
|
35
|
+
sidecar also gains `source` (`path`, `duration_seconds`, `creation_time`) so no separate `ffprobe`
|
|
36
|
+
is needed for per-meeting timestamps.
|
|
37
|
+
- **`whosaid enroll --from FILE` (GitHub issue #1, part 1).** Enroll a voice from an existing
|
|
38
|
+
recording instead of the mic: `--ss`/`--t`/`--to` cut a time window (seconds or `M:SS`/`H:MM:SS`),
|
|
39
|
+
the same ≥15s/non-silent check runs on the extracted clip, and `--force` allows overwriting an
|
|
40
|
+
existing `voices/<Name>.wav`.
|
|
41
|
+
- **`whosaid samples <base>` and the identity-mechanisms doc (GitHub issue #1, nice-to-haves).**
|
|
42
|
+
`whosaid samples <base> [-o DIR] [--audio FILE] [--per-speaker N] [--seconds S] [--json]` exports
|
|
43
|
+
one short representative WAV per speaker cluster (the longest segment, clamped to `--seconds`,
|
|
44
|
+
default 8) so you can listen and confirm an identity before trusting an auto-label or enrolling —
|
|
45
|
+
replacing the by-hand `ffmpeg` cut issue #1 described. Also exposed as the `whosaid_samples` MCP
|
|
46
|
+
tool. A new README section, "Speaker identity: enrollment clips vs. the registry", documents how
|
|
47
|
+
`WHOSAID_VOICE_REFS` enrollment clips and the speaker registry (`relabel`/`--save-speaker`) differ
|
|
48
|
+
and which one names a given cluster.
|
|
49
|
+
- **Meeting workspaces (GitHub issue #2).** A dated, auditable home for a recurring meeting
|
|
50
|
+
series (`lib/workspace.py`): every recording transcribed into a `YYYY-MM-DD-HHMM` folder,
|
|
51
|
+
per-meeting action items, and one roll-up across the whole workspace — still entirely offline.
|
|
52
|
+
- **`whosaid ingest` — dated, idempotent batch folders.** Transcribes a batch into folders named
|
|
53
|
+
from each recording's container `creation_time` (rendered in `--tz`, mtime fallback),
|
|
54
|
+
idempotent by source sha256, passing through all transcribe flags.
|
|
55
|
+
- **Pluggable action-items hook.** `--hook CMD` (or the `WHOSAID_ACTION_ITEMS_HOOK` environment
|
|
56
|
+
variable) receives the speaker-labeled transcript on stdin plus `WHOSAID_SPEAKERS` /
|
|
57
|
+
`WHOSAID_TRANSCRIPT_PATH`, and its stdout becomes the meeting's `action-items.md`. With no hook
|
|
58
|
+
a skeleton is written instead, so the default stays fully offline.
|
|
59
|
+
- **`whosaid roll-up` — coverage index with a nothing-missing audit.** `_INDEX.md` lists one row
|
|
60
|
+
per meeting (created, duration, transcribed/diarized/action-items) and flags orphan directories
|
|
61
|
+
and stale manifest entries, alongside a recurring-topics section.
|
|
62
|
+
- **Living, deduplicated action-item corpus.** With `--action-items`, `_ACTION-ITEMS.md` folds
|
|
63
|
+
every meeting's items into stable `AI-001` ids (never renumbered) with `first_seen` /
|
|
64
|
+
`last_seen`, occurrence lists, and open/ongoing/resolved statuses that survive re-runs.
|
|
65
|
+
Incremental and append-only by default (`--rebuild` to reset); state is plain JSON
|
|
66
|
+
(`_workspace.json`, `_action-items.json`) that is safe to hand-edit.
|
|
67
|
+
- **`whosaid version` / `--version` (GitHub issue #9).** Prints the installed version (and, from
|
|
68
|
+
a git checkout, a `git describe` suffix); also exposed as the `version` field in the MCP
|
|
69
|
+
`whosaid_doctor` tool's report.
|
|
70
|
+
|
|
71
|
+
### Fixed
|
|
72
|
+
|
|
73
|
+
- **Phantom speaker clusters on long recordings (GitHub issues #5, #6, #7, #8, #11).** Four
|
|
74
|
+
related fixes so a long meeting no longer fragments into duplicate/unidentified speakers:
|
|
75
|
+
- **Absorb pass.** After the registry one-best and `--ref` passes, every still-unnamed cluster
|
|
76
|
+
whose centroid cosine to a known voice (registry entry or `--ref` clip) is `>=`
|
|
77
|
+
`--absorb-threshold` (default `0.85`, env `WHOSAID_ABSORB_THRESHOLD`) is folded into that
|
|
78
|
+
person. A person split across several clusters is named on all of them, and the speaker cards
|
|
79
|
+
now render **one card per name** with the combined turns/talk time (was one card per cluster).
|
|
80
|
+
- **`--ref` no longer double-names.** The `--ref` pass only considers still-unnamed clusters and
|
|
81
|
+
skips any name the registry already assigned, so an enrolled voice plus a registry entry for the
|
|
82
|
+
same person can't produce two cards for them.
|
|
83
|
+
- **Auto speaker-count cap guard.** When farthest-first speaker-count estimation saturates at the
|
|
84
|
+
cap (20), it is re-estimated with progressively lower merge thresholds until the count drops
|
|
85
|
+
below the cap, instead of handing k-means a `k` of 20 that shatters real voices. The
|
|
86
|
+
over-segmentation WARN also now fires when the final count equals the cap.
|
|
87
|
+
- **Auto speaker-count estimation (GitHub issue #5, #6 header warning, #1 min/max speakers).**
|
|
88
|
+
Replaces the farthest-first count (and its cap-retry ladder) with average-linkage
|
|
89
|
+
agglomerative clustering of the per-turn voiceprints, cut at cosine `0.58` (env
|
|
90
|
+
`WHOSAID_COUNT_THRESHOLD`), using the nearest-neighbour-chain algorithm so it stays O(n^2):
|
|
91
|
+
1000 turns estimate in **0.009 s**. The cap of 20 is now a **bound, not a target** — an
|
|
92
|
+
estimate that lands on it is treated as a failed estimate rather than a result.
|
|
93
|
+
- Measured on a purpose-built 17.8-minute, 6-voice, 48-turn synthetic meeting (macOS `say`,
|
|
94
|
+
65-72 embedded turns): raw `say` audio is too clean to fragment, so base and new both
|
|
95
|
+
return the true 6. Re-mixed with 8 per-turn channel profiles (band-limiting, room/headset
|
|
96
|
+
EQ, level offsets, pink noise) the true count is still 6, base returns 6, and the new
|
|
97
|
+
estimator returns **6** — an earlier cut of 0.45, calibrated from the 0.6-0.8 same-speaker
|
|
98
|
+
figure quoted in the issue, returned 5 and was corrected by measurement.
|
|
99
|
+
- Real per-turn TitaNet-small similarity through this pipeline is **intra-speaker ~0.90,
|
|
100
|
+
inter-speaker ~0.25** (not the 0.6-0.8 the issue assumes, which is the low tail); k=6 holds
|
|
101
|
+
for any cut in `[0.55, 0.61]` on the channel-varied fixture and `[0.44, 0.61]` on the clean
|
|
102
|
+
one, and 0.58 is the midpoint of the intersection.
|
|
103
|
+
- **`--min-speakers N` / `--max-speakers N`** (issue #1) clamp the auto estimate on
|
|
104
|
+
`whosaid`, the diarizer and the `whosaid_transcribe` MCP tool; `--max-speakers` also lowers
|
|
105
|
+
the cap, `--speakers N` still forces an exact count, and `min > max` is rejected. The
|
|
106
|
+
whole-file (<15 min) path passes an exact count only when `min == max` and otherwise
|
|
107
|
+
reports the range as unenforced, since sherpa's `FastClustering` has no notion of a range.
|
|
108
|
+
- **The count warning now reaches the artifact (issue #6).** When the count is untrustworthy
|
|
109
|
+
a `# WARNING: ...` line is written into `<base>.speaker-cards.txt` directly under the count
|
|
110
|
+
line, and `count_warning` plus a `count_estimate` record (`method`, `threshold`, `k`,
|
|
111
|
+
`raw_k`, `cap`, `min`, `max`, `saturated`) appear in `<base>.diarization.json`, in the JSON
|
|
112
|
+
on stdout, and in the MCP result.
|
|
113
|
+
- **Registry entries computed with a different embedding model no longer mis-match** in
|
|
114
|
+
`relabel --auto`: candidate voiceprints are filtered to the sidecar's own embedding model.
|
|
115
|
+
|
|
116
|
+
### Added
|
|
117
|
+
|
|
118
|
+
- **`whosaid relabel <base> --auto` — re-apply naming with no re-diarization.** Reloads the cached
|
|
119
|
+
`<base>.diarization.json`, re-runs registry matching + the absorb pass, and rewrites
|
|
120
|
+
`<base>.speakers.txt` / `<base>.speaker-cards.txt` (and the sidecar's names). Picks up voices you
|
|
121
|
+
enrolled after the transcript was made and merges phantom splits. Accepts the meeting-workspace
|
|
122
|
+
layout (`base` = `transcript`). The MCP `whosaid_relabel` tool gains an `auto` parameter.
|
|
123
|
+
- **`--absorb-threshold F` transcribe/relabel flag** (env `WHOSAID_ABSORB_THRESHOLD`, default
|
|
124
|
+
`0.85`) controlling the absorb pass above.
|
|
125
|
+
|
|
126
|
+
## [1.1.0] — 2026-08-18
|
|
127
|
+
|
|
128
|
+
### Added
|
|
129
|
+
|
|
130
|
+
- **MCP server (`whosaid mcp`).** A stdio MCP (Model Context Protocol) server (`lib/mcp_server.py`,
|
|
131
|
+
built on the official `mcp` Python SDK's `FastMCP`) exposes whosaid to AI agents. Launched via
|
|
132
|
+
`whosaid mcp`, using the same ephemeral `uv run --with "mcp[cli]"` pattern as the rest of
|
|
133
|
+
whosaid — no persistent install.
|
|
134
|
+
- **Five tools**, all prefixed `whosaid_`: `whosaid_transcribe`, `whosaid_relabel`,
|
|
135
|
+
`whosaid_list_speakers` (read-only), `whosaid_doctor` (read-only), and
|
|
136
|
+
`whosaid_enroll_from_file`. Every tool shells the existing `whosaid` CLI rather than
|
|
137
|
+
reimplementing the pipeline, so the MCP surface and the CLI can't drift apart.
|
|
138
|
+
- **`whosaid://guide` resource** — an on-demand deep reference (full flag/env list, the long-audio
|
|
139
|
+
parallel path, the cosine-match threshold) an agent can read without it bloating every tool's
|
|
140
|
+
always-loaded description.
|
|
141
|
+
|
|
142
|
+
## [1.0.2] — 2026-08-18
|
|
143
|
+
|
|
144
|
+
Documentation-accuracy patch — no change to the transcription or diarization
|
|
145
|
+
pipeline. A `/cbm-atlas` architecture audit confirmed the README already matches
|
|
146
|
+
the code; the drift was in the design doc, now corrected.
|
|
147
|
+
|
|
148
|
+
### Documentation
|
|
149
|
+
|
|
150
|
+
- **`docs/design.md` refreshed to the current architecture.** The default
|
|
151
|
+
speaker-embedding model is corrected to NeMo TitaNet-small (it had named
|
|
152
|
+
3D-Speaker ERes2Net, now listed only as an opt-in `DIARIZE_EMB_NAME`
|
|
153
|
+
alternative). Added the `install` and `relabel` subcommands, the persistent
|
|
154
|
+
local speaker registry, `.speaker-cards.txt`, the `.diarization.json` sidecar,
|
|
155
|
+
and the parallel long-audio path so the doc matches the implementation.
|
|
156
|
+
- **README wording tightened.** The long-audio parallel path now says it
|
|
157
|
+
"recovers the same speakers" as a single-pass run rather than "the same
|
|
158
|
+
result" — the whole-file and chunked paths use different clustering algorithms,
|
|
159
|
+
so bit-identical output isn't guaranteed.
|
|
160
|
+
|
|
161
|
+
### Build
|
|
162
|
+
|
|
163
|
+
- `bootstrap.sh`'s disk-space check now distinguishes the ~4 GB recommended free
|
|
164
|
+
space from the ~1.5 GB Whisper model download.
|
|
165
|
+
- The generated `/cbm-atlas` output directory (`.cbm-atlas/`) is now gitignored.
|
|
166
|
+
|
|
167
|
+
## [1.0.1] — 2026-08-17
|
|
168
|
+
|
|
169
|
+
Documentation and test-coverage patch — no change to the transcription or
|
|
170
|
+
diarization pipeline.
|
|
171
|
+
|
|
172
|
+
### Documentation
|
|
173
|
+
|
|
174
|
+
- **README tuned for discoverability.** An above-the-fold summary now names the
|
|
175
|
+
terms people search for — speaker diarization, Whisper speech-to-text, "who
|
|
176
|
+
spoke when," offline/on-device, voice-based speaker recognition — and the
|
|
177
|
+
comparison section opens with a "MacWhisper / whisperX / aTrain alternative"
|
|
178
|
+
framing.
|
|
179
|
+
|
|
180
|
+
### Tests
|
|
181
|
+
|
|
182
|
+
- **Expanded `test/e2e.sh` coverage.** Asserts every output format and artifact
|
|
183
|
+
from a run (`.srt`, `.vtt`, `.tsv`, `.json`, `.rttm`, the speaker-cards file,
|
|
184
|
+
and the `.diarization.json` sidecar); smoke-tests `whosaid doctor`'s
|
|
185
|
+
embedding-model and registry report; and exercises `--no-diarize` (plain
|
|
186
|
+
transcript, no `.speakers.txt`).
|
|
187
|
+
|
|
188
|
+
## [1.0.0] — 2026-08-17
|
|
189
|
+
|
|
190
|
+
First stable release. whosaid is local, speaker-attributed transcription for
|
|
191
|
+
Apple Silicon: it turns an audio file into a transcript where every turn is
|
|
192
|
+
attributed to a person — who said what — with nothing ever leaving your Mac.
|
|
193
|
+
|
|
194
|
+
### Highlights
|
|
195
|
+
|
|
196
|
+
- **Speaker-attributed transcripts.** MLX Whisper (Metal GPU) transcribes,
|
|
197
|
+
sherpa-onnx diarization (CPU) finds who spoke when, and the two are merged into
|
|
198
|
+
`<base>.speakers.txt` with per-turn speaker labels and timestamps.
|
|
199
|
+
- **Names speakers by voice.** A one-time ~45s enrollment (`whosaid enroll`)
|
|
200
|
+
teaches whosaid your voice, so your turns read as your name instead of
|
|
201
|
+
`SPEAKER_00`.
|
|
202
|
+
- **Persistent cross-meeting speaker identity.** Identify someone once with
|
|
203
|
+
`whosaid relabel` and their voiceprint is saved to a private local registry
|
|
204
|
+
(`~/.config/whosaid/speakers.json`), so they are auto-named in every future
|
|
205
|
+
transcript — a persistence most transcription tools don't offer.
|
|
206
|
+
- **Tells you how many people spoke.** The distinct-speaker count is
|
|
207
|
+
auto-detected and reported up front, with per-speaker turn counts and talk
|
|
208
|
+
time.
|
|
209
|
+
- **Speaker cards.** For each speaker, `<base>.speaker-cards.txt` holds a few
|
|
210
|
+
representative snippets so you can read a couple of lines and know who each
|
|
211
|
+
cluster is — then name them with `whosaid relabel`.
|
|
212
|
+
- **Fast on long recordings.** Audio over ~15 min is diarized in parallel time
|
|
213
|
+
windows and stitched back into consistent speakers by a single global
|
|
214
|
+
clustering pass over every turn's voiceprint — several times faster, with the
|
|
215
|
+
same result as a single-pass run.
|
|
216
|
+
- **No accounts, no API keys, no Hugging Face token.** Every model comes from an
|
|
217
|
+
open, ungated source and is cached locally after the first download.
|
|
218
|
+
|
|
219
|
+
### Commands
|
|
220
|
+
|
|
221
|
+
- `whosaid <audio>…` — transcribe + diarize + label one or more files.
|
|
222
|
+
- `whosaid enroll [Name]` — record ~45s and name a voice.
|
|
223
|
+
- `whosaid record [--label L]` — capture from the mic, then transcribe.
|
|
224
|
+
- `whosaid relabel <base> SPEAKER_02=Name …` — name clusters from the speaker
|
|
225
|
+
cards and persist their voiceprints (no re-transcription).
|
|
226
|
+
- `whosaid doctor` — read-only environment / model / registry report.
|
|
227
|
+
- `whosaid setup` (`./bootstrap.sh`) — dependency check + model pre-download.
|
|
228
|
+
- `whosaid install` — install/update the `~/.local/bin/whosaid` command symlink.
|
|
229
|
+
|
|
230
|
+
### Notes
|
|
231
|
+
|
|
232
|
+
- **The default speaker-embedding model is NeMo TitaNet-small** (English-native,
|
|
233
|
+
~2.5× faster than ERes2Net in sherpa's benchmark). Set `DIARIZE_EMB_NAME` to
|
|
234
|
+
select the ERes2Net (en) or `…zh-cn…` (Mandarin) models from the same release.
|
|
235
|
+
- **`--chunk-seconds` is honored at any length.** Auto-chunking still only engages
|
|
236
|
+
past ~15 min, but passing an explicit `--chunk-seconds` forces the parallel
|
|
237
|
+
path on shorter audio too.
|
|
238
|
+
- **`whosaid doctor`** reports the active speaker-embedding model and the local
|
|
239
|
+
registry path + voiceprint count.
|
|
240
|
+
- **Hallucination-hardened transcription.** whosaid calls the `mlx-whisper`
|
|
241
|
+
library directly with the temperature-fallback ladder enabled,
|
|
242
|
+
`condition_on_previous_text` off, and a hallucination-silence threshold — the
|
|
243
|
+
configuration that avoids Whisper's repetition-collapse on long audio.
|
|
244
|
+
- **Output files:** `.txt`, `.srt`, `.vtt`, `.tsv`, `.json` (transcription), plus
|
|
245
|
+
`.rttm`, `.speakers.txt`, `.speaker-cards.txt`, and a `.diarization.json`
|
|
246
|
+
sidecar that makes `relabel` instant.
|
|
247
|
+
- **Privacy by construction:** audio, text, and voice embeddings never leave the
|
|
248
|
+
machine; the speaker registry lives outside the repo and is never uploaded.
|
|
249
|
+
|
|
250
|
+
### Requirements
|
|
251
|
+
|
|
252
|
+
- An Apple Silicon Mac (MLX runs on the GPU via Metal); macOS.
|
|
253
|
+
- `ffmpeg` and `uv` (Homebrew). Python is used only through ephemeral `uv`
|
|
254
|
+
environments — no persistent install is left behind.
|
|
255
|
+
|
|
256
|
+
[Unreleased]: https://github.com/sblattj/whosaid/compare/v1.1.0...HEAD
|
|
257
|
+
[1.1.0]: https://github.com/sblattj/whosaid/releases/tag/v1.1.0
|
|
258
|
+
[1.0.2]: https://github.com/sblattj/whosaid/releases/tag/v1.0.2
|
|
259
|
+
[1.0.1]: https://github.com/sblattj/whosaid/releases/tag/v1.0.1
|
|
260
|
+
[1.0.0]: https://github.com/sblattj/whosaid/releases/tag/v1.0.0
|
whosaid-1.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Stephen Blatt
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|