"Name anonymous diarized SRT speakers from a persistent local voiceprint library and repair speaker drift; local, CPU-only, no API key. Triggers \"who is speaker 2\", \"put names on the transcript\", \"label the meeting SRT\", \"the diarization split one person into three\", \"enroll my voice\". Backed by the pi-voiceid CLI."
Resources
2Install
npx skillscat add blackbelttechnology/pi-agent-dashboard/speaker-id Install via the SkillsCat registry.
Speaker ID enrollment
Give diarized transcripts real names, and repair the drift that clustering
diarizers produce on long recordings. Post-hoc relabeling — it does not
create diarization, it relabels an existing one.
pi-voiceid analyze --srt talk.srt # drift report, no enrollment needed
pi-voiceid enroll --name "Alice" --srt talk.srt --label "Speaker 1"
pi-voiceid label --srt talk.srt --dry-run # always dry-run first
pi-voiceid label --srt talk.srt # writes talk.named.srtThe source SRT is never overwritten. Everything runs locally on CPU viasherpa-onnx; no audio or embedding leaves the machine.
How it works
- Each anonymous cluster is profiled with a speaker-embedding model, sampling
segments across the whole timeline (drift is temporal). - Cluster centroids are centered (a multi-speaker mean subtracted) and compared
by cosine to the enrolled voiceprints. - A cluster is named only when the absolute threshold, the margin over the
runner-up, and a minimum share of agreeing segments all pass. Otherwise the
anonymous label is kept — never guessed. - Several clusters may map to one name: that is the drift repair.
Setup (one time)
The embedding model is not vendored. On first enroll/label it is fetched
into ~/.pi/models/speaker/ (28 MB). Model choice is the single biggest
accuracy factor — see BENCHMARK.md. Do not "upgrade" to a
bigger model without re-running the benchmark: the 114 MB VoxCeleb leader
scored worse than the 28 MB default on real meeting audio.
The native binding sherpa-onnx-node is an optional dependency. When it is
absent, transcription still works and pi-voiceid fails with an actionable
message naming the dependency and the install command.
Voiceprint library
Default store: ~/.pi/voiceprints/voiceprints.json. Override with --store or
the PI_VOICEPRINT_STORE env var. This is biometric-derived data about
identifiable people: it lives outside the repo by default, it is overridable,
and it must never be committed. If you point --store inside a repository, add
it to that repo's .gitignore.
enroll also embeds the source recording's other speakers, so the centering
mean stays multi-speaker (centering one speaker by their own mean cancels the
signal). Those third parties therefore end up in the store as unnamed
contributions. Erase with forget:
pi-voiceid forget --name "Alice" # drops Alice's voiceprint + her contributions
pi-voiceid forget --recording <id> # drops a whole recording's contributions
pi-voiceid list # shows the cohort and which clusters are unnamedA never-enrolled speaker swept into the pool has no name, so onlyforget --recording <id> reaches them. list makes that visible.
Procedure
1. Check for drift first
pi-voiceid analyze --srt talk.srtPrints per-cluster coherence and a channel-centered cluster-to-cluster cosine
matrix. Pairs at or above --drift-threshold (default 0.55) are flagged as
likely the same person split in two. analyze needs no library. The 0.55
default is weakly calibrated (n = 2) — treat it as a starting point.
2. Enroll a voice
Best: from a cluster you have already identified in an SRT (plenty of audio).
pi-voiceid enroll --name "Csákány Róbert" --srt talk.srt --label "Speaker 1"From a standalone clip:
pi-voiceid enroll --name "Kovács Dániel" --audio sample.m4a --start 12 --end 75Re-running the same name merges into the existing voiceprint (segment-count
weighted); --replace resets it instead. Enroll the same person from several
recordings — the cheapest accuracy win.
3. Label a transcript
pi-voiceid label --srt talk.srt --dry-run
pi-voiceid label --srt talk.srtAlways read the dry-run decision table. --output overrides the sibling*.named.srt (and is required for the same-file refusal to be reachable).--relabel allows an already-named transcript and leaves clusters whose name is
already in the library untouched, so the iterative workflow (label, enroll one
more person, label again) is safe.
Reading the output
| Column | Meaning |
|---|---|
cos |
centered cosine of the cluster centroid to the best voiceprint |
2nd / margin |
runner-up and the gap — a small gap means "could be either" |
vote |
share of individual segments agreeing with the centroid's pick |
space |
whether the cohort mean or the recording fallback produced the score |
Healthy cross-recording match: cos 0.6–0.9, margin > 0.5, vote > 80 %.
Correct rejection of an un-enrolled person looks like cos ≈ 0.0–0.25.
Pitfalls
- Centering needs a cohort. Below 40 embeddings the library-wide mean is not
trusted; the tool falls back to the recording's own mean, raises the threshold
by one margin minimum, and says so. Enroll ≥ 3 voices. - Never center by one speaker's own mean — the tool refuses a pool with fewer
than two speakers, or one speaker above 70 % of the pool's audio duration. - Channel mismatch is the dominant failure mode. A voiceprint from a phone
memo scores poorly against a conference capture. Enroll per capture setup. - Model mismatch is refused. Voiceprints and cohort contributions record the
model name and dimension; switching models requires re-enrolling. - Short segments are noise (
--min-segdefaults to 1.2 s; do not lower it). pi-voiceidneeds the source media beside the SRT (or--audio). Media
shorter than the last cue is refused — never embed ranges that do not exist.
Limitations
This is approach (A): post-hoc cluster relabeling. It cannot:
- separate overlapping speech (two people at once);
- fix a cluster that already merges two speakers (check
analyzecoherence —
below ~0.55 the cluster itself is impure); - recover a speaker the diarizer never separated at all.
Those need approach (B), true TS-VAD / personal VAD, where the enrollment
embedding feeds a frame-level model. Requires training; out of scope.
Accuracy on real audio is a lower bound: it was measured against another
diarizer's labels, not human annotation. Do not quote the segment-level figure
as the product's accuracy.
Verification
pi-voiceid label --srt "<enrollment source>.srt" --dry-run # expect a 1:1 map, high cos
pi-voiceid label --srt "<unrelated recording>.srt" --dry-run # expect mostly UNKNOWN
pi-voiceid list # enrolled voices must not resemble each otherMeasured on the reference archive: self-consistency 3/3 at cos 0.89–0.99 with a
4th un-enrolled cluster rejected at 0.252; cross-recording matches at cos
0.72–0.89. Full numbers and the six-model comparison are in
BENCHMARK.md.