Releases: pretyflaco/millet
v0.12.5 — Title-aware schedule matching + collision guard for sync
Fixed
- Title-aware schedule matching.
detect_meeting_typenow considers the sessiontitle(when present in*.session.json). A titled session only auto-matches a scheduled meeting whosename/folderslug equals the title's slug; otherwise it returnsNoneso the caller files it under its own folder. This stops an ad-hoc meeting recorded inside a schedule window (e.g. a "post-scrum" at 09:03 inside the 06:30-09:30 standup window) from being misfiled as the scheduled meeting. Untitled sessions keep the prior pure time-window behavior (back-compat). - Collision guard: never silently overwrite a different meeting.
sync_sessionwrites a small local-only.session-idmarker into each synced folder and, before reusing a dated folder, checks it. If an existing folder belongs to a different session, the new meeting is filed into a disambiguated folder (<folder>-<sessionid-suffix>) instead of clobbering the existing one.
Notes
- The
.session-idmarker is kept strictly local: it is registered in the clone's.git/info/exclude, so it is never committed/pushed and never trips the "uncommitted changes" sync guard. - Pairs with vezir v0.7.16, which injects the session
titleinto*.session.jsonand adds an explicit "sync as" folder override.
v0.12.4 — robust language detection + sync exit-code
Fixed
- Wrong summary/transcript language from a misleading channel opener. whisperx detects language from only the first ~30s of each channel, so an opening word in another language (e.g. "Gracias") could mislabel an English meeting — even after the v0.12.3 dominant-channel fix. Detection now samples several windows across each channel via faster-whisper's
detect_language(language_detection_segments=N)(whisperx backend;--language-detection-segments, default 6). millet syncexited 0 even when the push failed. A failed sync (e.g.git pushrejected) now raisesSystemExit(1), so callers don't have to scrape the log to notice failures.
Added
- Soft default-language bias.
--default-language <lang>keeps a team/operator default unless a channel confidently detects another language (>= default_language_override_confidence, default 0.70). Feeds the dominant-channel selection so single-language teams don't drift.
Tests
- +default-language bias, +CLI sync exit-code. Full suite 295 pass; 7 pre-existing env-only failures.
v0.12.3 — summary language from dominant channel + per-language summaries
Fixed
- Summary generated in the wrong language for dual-channel meetings. The transcript/summary language was taken from the mic channel only, so a local speaker's minority-language asides (e.g. a few Portuguese phrases) made the whole summary that language even when the meeting was mostly English on the system channel. The language is now chosen from the channel with the most speech (
_dominant_channel_language; mic wins exact ties). - Each channel is word-aligned with its OWN detected language (
_align_channel) instead of sharing the mic's alignment model.
Added
- Additional-language summaries.
apply_labels(summary_language=...)regenerates the summary in a chosen language and saves it as an ADDITIONAL<base>.summary.<lang>.md(with suffixed sidecars), leaving the primary intact.MeetingSummary.savegainslang_suffix.
Changed
- Sync pushes
<base>.summary.<lang>.mdas a distinctsummary.<lang>.md;.frontmatter.jsonis now excluded from sync (also fixes a latent transcript.json collision).
Tests
- +8 (dominant-channel language, additional-language save/override). Full suite 285 pass; 7 pre-existing env-only failures.
v0.12.2 — suppress phantom remote speakers in dual-diarize
Fixed
- Phantom extra speakers in the dual-diarize path. pyannote can over-segment a single remote stream into multiple clusters (e.g. peeling short backchannel "yeah/cool/awesome" off the main speaker into a separate cluster), which voiceprint matching then mis-names from a weak, barely-over-threshold match. Observed on a real 2-speaker call that surfaced as 4 speakers (a false "Roark" @0.69 and a 0.4s "REMOTE" one-liner).
Added
- Voiceprint auto-apply gate. A match at/above
MATCH_THRESHOLDis auto-applied only when it has enough embeddable speech and is unambiguous — either a strong absolute confidence (MATCH_AUTOAPPLY_CONFIDENCE = 0.72) or a clear margin over the runner-up profile (MATCH_AUTOAPPLY_MARGIN = 0.15).SpeakerMatchnow carriesevidence_seconds+margin;identify_speakerscomputes the per-cluster margin. Weak/ambiguous matches stay raw and route to needs_labeling rather than mislabeling. The auto-id sidecar records only applied matches. - Remote-cluster consolidation (dual-diarize). After diarizing the system channel, merge same-speaker clusters (voiceprint cosine ≥
cluster_merge_similarity) and absorb thin clusters (<cluster_min_speech_secondsembeddable) into the dominant remote; attach trivial unassigned segments to the nearest remote so a brief one-liner no longer becomes a genericREMOTE. On by default; disable with--no-consolidate-remote-clusters.
Validated
- Real 2-speaker session: 4 → 2 named + 1 raw cluster, no false name, orphan merged.
- Real 13-speaker session: every legitimate speaker still auto-named (no over-suppression).
Tests
- +18 (consolidation merge/absorb/no-over-merge/orphan/config + auto-apply gate policy). Full suite 277 pass; the 7 failures are pre-existing env-only (device-default, Gdk import, offline diarization).
v0.12.1 — fix auto-label discarding matches in non-interactive runs
Fixed
label --autodiscarded all voiceprint matches when any speaker was unmatched in a non-interactive (worker) context. It auto-applied confident matches, then prompted for unrecognized speakers; with no TTY,click.prompthit EOF → Abort → matches never persisted. Meetings with fully-recognizable speakers were stuck inneeds_labelingwith raw SPEAKER_N ids. Now skips prompting when stdin isn't a TTY.
Added
*.autoid.jsonsidecar (name + confidence per speaker) so labeling UIs can pre-fill recognized names and show confidence. Excluded from sync + transcript resolution.
3 new tests.
v0.12.0 — dual-diarize: per-channel ASR + remote speaker diarization
New default for stereo: dual-diarize. Transcribes mic and system channels separately (local speaker = continuous YOU from mic, immune to overlap), then runs pyannote diarization on the system channel to split distinct remote speakers. Overlapping segments preserved.
Headline fix
Eliminates overlap-fragmentation: mono mixdown + diarization flickered words between speakers during talk-over. Now: each channel's speech is a continuous stream — no per-word channel guessing.
Also includes
- Channel-energy correction (mono path,
--channel-correct): per-segment/word RMS reassignment +--channel-correct-margintuning. On by default for--mixdown mono. - DNS-retry hardening for
millet syncgit operations (5x backoff). - 11 new tests; 256 pass, 0 regressions.
Validated on
- DEVSTANDUP (5 speakers, 62 min) — overlap-fragmentation eliminated
- LUKAS_2 (2 speakers, 45 min) — clean overlapping segments
- AB_BOARD (4 speakers, 88 min, .ogg) — distinct remotes preserved
--mixdown mono and --mixdown dual remain available as fallbacks.
v0.11.0 — opt-in Parakeet ASR backend
Adds an opt-in Parakeet ASR backend (NVIDIA Parakeet TDT via onnx-asr) alongside whisperx and mlx.
Highlights
--asr-backend parakeet(English, ONNX Runtime, pure-Python — no extra torch/transformers).- Long audio auto-chunked via Silero VAD; WhisperX-shaped output so alignment/diarization/dual-channel labeling are unchanged.
--parakeet-keep-alignmenttoggle (native timestamps vs WhisperX alignment).millet download parakeet(explicit, lazy model fetch).millet-pipeline[parakeet]extra (onnx-asr[hub]); installonnxruntime-gpufor CUDA.scripts/bench_asr.pyharness + results.
Notes
- Opt-in only;
autoselection unchanged. - On a 3090, whisperx is faster than Parakeet; Parakeet's value is finer segmentation, not speed. Stays opt-in pending further validation.
- 12 new tests; no behavior change for existing users.
v0.10.0 — tech-debt sweep (import fix, CI, ruff, cli split)
Code-health release. No user-facing behavior change; minor bump because internal cli.py became a cli/ package.
Fixed
- Latent clean-install crash: the
millet/{capture,audio,utils,languages}.pyshims importedfrom meet_record.*(pre-rename package). On a cleanpip install millet-pipeline(which depends onmillet-record→millet_record) every shim raisedModuleNotFoundError: meet_record; it only worked where the legacymeetscribe-recordwas co-installed. Now they importfrom millet_record.*. Newtests/test_shim_imports.pyguards against regression.
Changed
cli.py(1929 lines) split into acli/package — one module per command +cli/_helpers.py;cli/__init__.pydefines themaingroup and re-exports every command symbol so themillet.subcommands/meet.subcommandsentry points keep resolving.- CI fixed — was linting/testing dead
meet/paths and installingmeetscribe-record(old name). Now lintsmillet/+tests/, installsmillet-record, runs the no-torch/no-GTK suites. - Ruff config added (mirrors vezir's ruleset); ~120 findings cleaned. Legacy
meet.*test refs rewritten tomillet.*(recovered ~29 tests).
Tests: 233 passed, 7 environment-only fails (GTK/torch); ruff clean.
v0.9.2 — resilient Tinfoil (confidential) summarization
The confidential summary preset (Tinfoil TEE backend) could hard-fail on a single transient DNS/network blip: the Tinfoil SDK does a network fetch at client construction (router discovery, GET https://atc.tinfoil.sh/routers) and the init was outside the retry path, so one flaky lookup aborted the whole summarization.
Fixed
_summarize_tinfoilnow retries transient network/DNS errors with exponential backoff (3 attempts, ~2s/4s/8s), around BOTH client construction (router discovery) and the completion call.- Genuine auth/model errors still fail fast (no retry); a persistent outage surfaces a clear "Tinfoil TEE unreachable after N attempts" message.
- New
_is_transient_network_error()walks the exception cause chain (the SDK wrapsURLErrorinValueError("Failed to fetch router addresses…")) and matches common DNS/connection failure text.
Note: the Tinfoil SDK itself is current (0.12.1, latest on PyPI) — this was a host DNS-flakiness resilience fix. 6 new tests.
v0.9.1: team-aware paths + --team flag + MILLET_* env aliases
Team-aware path resolution and --team flag for millet sync/enroll/label, plus MILLET_* environment-variable aliases (one-release fallback to legacy MEETSCRIBE_*/MEET_* with a deprecation warning). On-disk ~/.config/meet and ~/meet-recordings paths are unchanged. See CHANGELOG.md for details.