Skip to content

feat: optional speaker-diarization hook (diarize.py)#190

Open
seonghobae wants to merge 1 commit into
mainfrom
feat/diarization-hook
Open

feat: optional speaker-diarization hook (diarize.py)#190
seonghobae wants to merge 1 commit into
mainfrom
feat/diarization-hook

Conversation

@seonghobae

Copy link
Copy Markdown
Contributor

Summary

Adds an optional speaker-diarization hook (diarize.py) — a new standalone module that answers "who spoke when?" for an audio file and attributes transcript segments to speakers. It follows the repo's optional-AI pattern: zero heavy dependencies at import, an injectable backend, lazy pyannote.audio import, and a graceful, actionable error when the optional dependency is missing.

What's included

  • SpeakerTurn(start, end, speaker) and DiarizationResult(turns, speaker_count) dataclasses
  • DiarizationUnavailableError raised with pip install pyannote.audio instructions (and a pointer to the injectable-backend escape hatch) when the optional dependency is absent
  • diarize_file(audio_path, *, backend=None) — default backend lazily imports pyannote.audio inside the function; any callable mapping a path to speaker turns can be injected instead
  • merge_with_transcript(turns, segments) — pure function assigning each duck-typed segment (.start/.end/.text) the speaker with maximum accumulated time overlap, falling back to SPEAKER_1 when nothing overlaps
  • to_text(merged) — renders [speaker] text lines

Tests

tests/test_diarize.py — 21 tests, all offline (no model download, no network):

  • Injectable fake backend: turn ordering, speaker counting, empty/iterable inputs
  • Forced-unavailable path via sys.modules patch: error type, actionable message, ImportError chaining
  • Overlap matrix: exact, partial, accumulated multi-turn, boundary-touch, no-overlap fallback, multi-speaker
  • to_text formatting including merge round-trip

Gates

  • python3.14 -m unittest discover -s tests: 134 tests, no new failures (only the 5 pre-existing macOS os.listxattr baseline errors)
  • python3.14 -m interrogate -c pyproject.toml diarize.py: 100%
  • New files only — no existing files touched

🤖 Generated with Claude Code

New standalone module following the repo's optional-AI pattern:
dependency-free at import, injectable backend, lazy pyannote.audio
import, and a clear DiarizationUnavailableError with install
instructions when the optional dependency is missing.

- SpeakerTurn / DiarizationResult / AttributedSegment dataclasses
- diarize_file(audio_path, *, backend=None) with injectable backend
- merge_with_transcript: pure max-overlap speaker assignment with
  SPEAKER_1 fallback for non-overlapping segments
- to_text: renders '[speaker] text' lines
- 21 offline tests (fake backend, sys.modules import block, overlap
  matrix, formatting) — no model download or network

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CJRVbDrp1vGYkJgNHMGPpG
@seonghobae seonghobae enabled auto-merge (squash) July 6, 2026 14:26
@opencode-agent

opencode-agent Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

OpenCode Review Overview

  • Head SHA: 020e3586ec268405b48c40b750f62c582ed71b3f
  • Workflow run: 28760629866
  • Workflow attempt: 1
  • Gate result: APPROVE (exit 0)

Changed-File Evidence Map

flowchart LR
  PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
  Evidence --> S1["Changed file: diarize.py"]
  S1 --> I1["repository behavior"]
  I1 --> R1["Review risk: Changed file: diarize.py"]
  R1 --> V1["required checks"]
  Evidence --> S2["Test: test_diarize.py"]
  S2 --> I2["regression suite"]
  I2 --> R2["Review risk: Test: test_diarize.py"]
  R2 --> V2["targeted test run"]
Loading

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant