Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 30 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,15 +103,22 @@ stream_from_file(streamer, "sample.wav")

### Speech Diarization (Who Spoke When)

Detect speaker turns and timestamps using ONNX:
Detect speaker turns and timestamps using ONNX (defaults to NVIDIA Nemotron-3 Diarization, also supports Pyannote Segmentation 3.0):

```python
from pythaiasr import diarize
from pythaiasr import diarize, segments_to_rttm

# Identify who spoke when
# NVIDIA Nemotron-3 Diarization (default: fast INT8 ONNX, up to 8 speakers)
segments = diarize("meeting.wav")
for seg in segments:
print(f"[{seg['start']:.2f}s - {seg['end']:.2f}s] {seg['speaker']}")

# Pyannote Segmentation 3.0 (optional)
segments_pyannote = diarize("meeting.wav", model="pyannote_segmentation")

# Export to standard NIST RTTM format
rttm_str = segments_to_rttm(segments, uri="meeting")
print(rttm_str)
```

### Speech Diarization + ASR (`asr_diarize`)
Expand All @@ -121,7 +128,7 @@ Detect speakers and transcribe each speaker turn with ASR:
```python
from pythaiasr import asr_diarize

# Attributed transcription per speaker
# Attributed transcription per speaker with Typhoon ASR and Nemotron Diarization (default)
turns = asr_diarize("meeting.wav", asr_model="typhoon_asr")
for turn in turns:
print(f"[{turn['start']:.2f}s - {turn['end']:.2f}s] {turn['speaker']}: {turn['text']}")
Expand All @@ -134,19 +141,19 @@ See examples/diarize_example.py
1. Speech Diarization (Who Spoke When)
============================================================
Processing: examples/../tests/test-diarize.wav ...
[ 0.17s -> 1.87s] SPEAKER_02
[ 1.97s -> 4.30s] SPEAKER_01
[ 4.83s -> 6.49s] SPEAKER_02
[ 6.78s -> 8.36s] SPEAKER_01
[ 0.15s -> 1.82s] SPEAKER_00
[ 1.95s -> 4.34s] SPEAKER_01
[ 4.81s -> 6.45s] SPEAKER_00
[ 6.81s -> 8.39s] SPEAKER_01

============================================================
2. Diarization + Speech Recognition (ASR Diarize)
============================================================
Transcribing turns with Typhoon ASR: examples/../tests/test-diarize.wav ...
[ 0.17s -> 1.87s] SPEAKER_02: สวัสดีชาวโลกทุกท่าน
[ 1.97s -> 4.30s] SPEAKER_01: แล้วระบบนี้ทํางานอย่างไร
[ 4.83s -> 6.49s] SPEAKER_02: ใช้ปัญญาประดิษฐ์ในการทดสอบ
[ 6.78s -> 8.36s] SPEAKER_01: ใช้งานได้ดีทีเดียว
[ 0.15s -> 1.82s] SPEAKER_00: สวัสดีชาวโลกทุกท่าน
[ 1.95s -> 4.34s] SPEAKER_01: แล้วระบบนี้ทํางานอย่างไร
[ 4.81s -> 6.45s] SPEAKER_00: ใช้ปัญญาประดิษฐ์ในการทดสอบ
[ 6.81s -> 8.39s] SPEAKER_01: ใช้งานได้ดีทีเดียว
```

### API
Expand Down Expand Up @@ -217,14 +224,16 @@ You can read about models from the list:
- [*biodatlab/whisper-th-medium-combined* - Thai Whisper medium model](https://huggingface.co/biodatlab/whisper-th-medium-combined)
- [*biodatlab/whisper-th-large-combined* - Thai Whisper large model](https://huggingface.co/biodatlab/whisper-th-large-combined)
- [*biodatlab/whisper-th-medium-timestamp* - Thai Whisper medium model with timestamp support](https://huggingface.co/biodatlab/whisper-th-medium-timestamp)
- [*joosthel/Nemotron-3-Diarization-ONNX* - NVIDIA Nemotron-3 Diarization ONNX model](https://huggingface.co/joosthel/Nemotron-3-Diarization-ONNX)

#### diarize

```python
diarize(
data: Union[str, Path, np.ndarray],
model: str = "pyannote_segmentation",
model: str = "nemotron-3-diarization",
device: Optional[str] = None,
precision: str = "int8",
sampling_rate: int = 16_000,
num_speakers: Optional[int] = None,
min_speakers: Optional[int] = None,
Expand All @@ -238,8 +247,11 @@ diarize(
```

- `data`: Audio file path or 1D numpy array of audio waveform.
- `model`: Diarization model identifier (default: `"pyannote_segmentation"`).
- `model`: Diarization model identifier:
- `"nemotron-3-diarization"` (default) / `"joosthel/Nemotron-3-Diarization-ONNX"` - NVIDIA Nemotron-3 Diarization (streaming cache, up to 8 speakers)
- `"pyannote_segmentation"` - Pyannote Segmentation 3.0 ONNX
- `device`: Device to run inference on (`"auto"`, `"cpu"`, `"cuda"`).
- `precision`: Model precision for Nemotron (`"int8"` default, or `"fp32"`).
- `sampling_rate`: Audio sampling rate (default: 16000).
- `num_speakers`: Exact number of speakers if known.
- `onset`: Speech onset probability threshold (default: 0.5).
Expand All @@ -255,8 +267,9 @@ diarize(
asr_diarize(
data: Union[str, Path, np.ndarray],
asr_model: str = "typhoon_asr",
diarize_model: str = "pyannote_segmentation",
diarize_model: str = "nemotron-3-diarization",
device: Optional[str] = None,
precision: str = "int8",
sampling_rate: int = 16_000,
lm: bool = False,
num_speakers: Optional[int] = None,
Expand All @@ -269,7 +282,8 @@ asr_diarize(

- `data`: Audio file path or 1D numpy array of audio waveform.
- `asr_model`: The ASR model name (default: `"typhoon_asr"`).
- `diarize_model`: Diarization model name (default: `"pyannote_segmentation"`).
- `diarize_model`: Diarization model name (default: `"nemotron-3-diarization"`, or `"pyannote_segmentation"`).
- `precision`: Model precision for Nemotron (`"int8"` default, or `"fp32"`).
- `merge_same_speaker`: Whether to merge adjacent speech turns from the same speaker (default: `True`).
- `max_merge_gap`: Maximum gap in seconds between same-speaker segments to merge (default: 0.5).
- `backend`: Diarization engine (`"onnx"` or `"sherpa-onnx"`, default: `"onnx"`).
Expand Down
16 changes: 12 additions & 4 deletions examples/diarize_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# Ensure pythaiasr package in current repo can be imported directly
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))

from pythaiasr import diarize, asr_diarize
from pythaiasr import diarize, asr_diarize, segments_to_rttm


def main():
Expand All @@ -27,7 +27,7 @@ def main():
sys.exit(1)

print("=" * 60)
print("1. Speech Diarization (Who Spoke When)")
print("1. Speech Diarization (defaults to Nemotron-3 INT8 ONNX)")
print("=" * 60)
print(f"Processing: {test_audio} ...")
segments = diarize(test_audio, device="auto")
Expand All @@ -38,11 +38,18 @@ def main():
for seg in segments:
print(f"[{seg['start']:6.2f}s -> {seg['end']:6.2f}s] {seg['speaker']}")

print("\n" + "=" * 60)
print("\nNIST RTTM Format:")
print(segments_to_rttm(segments, uri=os.path.basename(test_audio)))

print("=" * 60)
print("2. Diarization + Speech Recognition (ASR Diarize)")
print("=" * 60)
print(f"Transcribing turns with Typhoon ASR: {test_audio} ...")
turns = asr_diarize(test_audio, asr_model="typhoon_asr", device="auto")
turns = asr_diarize(
test_audio,
asr_model="typhoon_asr",
device="auto",
)

if not turns:
print("No speech turns found.")
Expand All @@ -54,3 +61,4 @@ def main():
if __name__ == "__main__":
main()


10 changes: 10 additions & 0 deletions pythaiasr/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
get_pythaiasr_path,
get_typhoon_model_files,
get_diarization_model_files,
get_nemotron_diarization_model_files,
download_file,
)
from pythaiasr.typhoon import (
Expand All @@ -40,9 +41,13 @@
)
from pythaiasr.diarization import (
Diarization,
NemotronDiarization,
Nemotron3Diarization,
diarize,
asr_diarize,
merge_same_speaker_segments,
segments_to_rttm,
extract_speaker_dict,
)

# Friendly alias
Expand Down Expand Up @@ -495,9 +500,13 @@ def stream_asr(
"asr",
"stream_asr",
"Diarization",
"NemotronDiarization",
"Nemotron3Diarization",
"diarize",
"asr_diarize",
"merge_same_speaker_segments",
"segments_to_rttm",
"extract_speaker_dict",
"FastConformerRNNT",
"TyphoonASR",
"RealtimeStreamASR",
Expand All @@ -510,5 +519,6 @@ def stream_asr(
"get_pythaiasr_path",
"get_typhoon_model_files",
"get_diarization_model_files",
"get_nemotron_diarization_model_files",
"download_file",
]
Loading
Loading