- Sidecar: pyannote /diarize 엔드포인트 + requirements.txt 업데이트 - LLM 기반 화자 추정 (Phase 1 — 오디오 보존 없이 전사 텍스트 분석) - CaptionSegment에 speaker 필드 추가 - EditableSegment: 화자별 색상 바 + 화자 Chip 표시 - TranscriptTab: 화자 구분 버튼 + 진행률 - 설정: HuggingFace 토큰 입력 + Diarization 토글 - IPC: DIARIZE + DIARIZATION_PROGRESS 채널 - 에러코드: 895-897 - 12개 locale i18n
228 lines
6.5 KiB
Markdown
228 lines
6.5 KiB
Markdown
# Phase 15.5: 화자 구분 (Speaker Diarization)
|
|
|
|
> pyannote-audio community-1 기반 배치 모드 화자 구분
|
|
> 녹음 종료 후 전체 오디오에 diarization 적용 → 화자별 세그먼트 태깅
|
|
|
|
---
|
|
|
|
## 1. 개요
|
|
|
|
### 1.1 동작 방식
|
|
|
|
```
|
|
[녹음 종료] → 전사 완료
|
|
│
|
|
├── CaptionService가 세그먼트 생성 (기존)
|
|
│
|
|
└── [후처리] Diarization 파이프라인
|
|
├── 1. 녹음된 오디오 파일을 sidecar에 전송
|
|
├── 2. pyannote가 화자 세그먼트 분석
|
|
├── 3. 전사 세그먼트와 화자 세그먼트 매칭
|
|
├── 4. 각 전사 세그먼트에 speaker 라벨 부여
|
|
└── 5. UI에 화자별 색상 구분 표시
|
|
```
|
|
|
|
### 1.2 핵심 결정
|
|
- **배치 모드**: 실시간이 아닌, 녹음 종료 후 전체 오디오 분석
|
|
- **pyannote community-1**: 오픈소스(MIT), 오프라인 가능
|
|
- **HF 토큰**: 최초 모델 다운로드 시 1회 필요 → 설정에서 입력
|
|
- **선택적**: diarization ON/OFF 토글 (기본 OFF, 성능 부담)
|
|
|
|
---
|
|
|
|
## 2. Sidecar 수정
|
|
|
|
### 2.1 requirements.txt 추가
|
|
```
|
|
pyannote.audio>=3.3.0
|
|
torch>=2.0.0
|
|
```
|
|
|
|
### 2.2 새 엔드포인트: POST /diarize
|
|
|
|
```python
|
|
@app.post("/diarize")
|
|
async def diarize(
|
|
file: UploadFile = File(...),
|
|
hf_token: str = Form(default=""),
|
|
num_speakers: int = Form(default=0), # 0 = 자동 감지
|
|
) -> JSONResponse:
|
|
"""오디오 파일의 화자 구분 수행."""
|
|
# 1. 오디오 파일 임시 저장
|
|
# 2. pyannote Pipeline 로드 (캐시)
|
|
# 3. pipeline(audio_file) 실행
|
|
# 4. 결과: [{ speaker: "SPEAKER_00", start: 0.5, end: 3.2 }, ...]
|
|
return JSONResponse(content={"segments": segments})
|
|
```
|
|
|
|
### 2.3 Pipeline 캐시
|
|
```python
|
|
_diarization_pipeline = None
|
|
|
|
def _get_diarization_pipeline(hf_token: str):
|
|
global _diarization_pipeline
|
|
if _diarization_pipeline is None:
|
|
from pyannote.audio import Pipeline
|
|
_diarization_pipeline = Pipeline.from_pretrained(
|
|
"pyannote/speaker-diarization-community-1",
|
|
use_auth_token=hf_token,
|
|
)
|
|
# CPU/GPU 자동 선택
|
|
if _gpu_available:
|
|
import torch
|
|
_diarization_pipeline.to(torch.device("cuda"))
|
|
return _diarization_pipeline
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Electron 앱 수정
|
|
|
|
### 3.1 shared/types.ts
|
|
|
|
```typescript
|
|
// CaptionSegment 확장
|
|
export interface CaptionSegment {
|
|
id: string
|
|
text: string
|
|
timestamp: number
|
|
isFinal: boolean
|
|
speaker?: string // 추가: "SPEAKER_00", "SPEAKER_01" 등
|
|
}
|
|
|
|
// Diarization 관련 타입
|
|
export interface DiarizationSegment {
|
|
speaker: string
|
|
start: number // seconds
|
|
end: number // seconds
|
|
}
|
|
|
|
export interface DiarizationResult {
|
|
segments: DiarizationSegment[]
|
|
numSpeakers: number
|
|
}
|
|
```
|
|
|
|
### 3.2 shared/ipc-channels.ts
|
|
|
|
```typescript
|
|
MEETING_MODE: {
|
|
// 기존 + 추가
|
|
DIARIZE: 'meetingMode:diarize',
|
|
DIARIZATION_PROGRESS: 'meetingMode:diarizationProgress',
|
|
}
|
|
```
|
|
|
|
### 3.3 MeetingModeService 확장
|
|
|
|
```typescript
|
|
async diarizeSession(sessionId: string): Promise<DiarizationResult> {
|
|
// 1. 녹음된 오디오 파일 경로 확인
|
|
// 2. sidecar /diarize 엔드포인트 호출
|
|
// 3. 결과의 화자 세그먼트와 전사 세그먼트 매칭
|
|
// 4. 각 전사 세그먼트에 speaker 라벨 부여
|
|
// 5. DB 저장 (rawTranscript에 화자 정보 포함)
|
|
}
|
|
```
|
|
|
|
### 3.4 오디오 파일 보존
|
|
현재 CaptionService는 오디오를 STT 후 폐기합니다.
|
|
회의 모드에서는 오디오를 임시 파일로 보존해야 합니다.
|
|
|
|
```typescript
|
|
// MeetingModeService.startRecording()에서
|
|
// AudioCaptureService의 raw PCM 데이터를 WAV 파일로 저장
|
|
private _audioFilePath: string | null = null
|
|
```
|
|
|
|
### 3.5 설정 UI
|
|
- HuggingFace 토큰 입력 (설정 > STT 탭)
|
|
- Diarization 활성화 토글
|
|
- 화자 수 설정 (0=자동, 2~10)
|
|
|
|
### 3.6 UI — 화자별 색상 구분
|
|
```typescript
|
|
const SPEAKER_COLORS = [
|
|
d3roPalette.tag.purple, // Speaker 1
|
|
d3roPalette.tag.green, // Speaker 2
|
|
d3roPalette.tag.orange, // Speaker 3
|
|
d3roPalette.accent.amber, // Speaker 4
|
|
d3roPalette.tag.red, // Speaker 5
|
|
]
|
|
```
|
|
|
|
TranscriptTab + EditableSegment에서 화자별 좌측 색상 바 표시.
|
|
|
|
---
|
|
|
|
## 4. 에러 코드
|
|
|
|
```typescript
|
|
DiarizationFailed = 895,
|
|
DiarizationModelNotLoaded = 896,
|
|
DiarizationTokenRequired = 897,
|
|
```
|
|
|
|
---
|
|
|
|
## 5. 구현 순서
|
|
|
|
### Step A: Sidecar (Python)
|
|
1. requirements.txt 업데이트
|
|
2. /diarize 엔드포인트 구현
|
|
3. Pipeline 캐시 + GPU/CPU 자동 선택
|
|
|
|
### Step B: 오디오 파일 보존
|
|
4. MeetingModeService에서 녹음 중 WAV 파일 저장
|
|
5. 녹음 종료 후 파일 경로를 DB에 저장
|
|
|
|
### Step C: 기반 레이어
|
|
6. types.ts — CaptionSegment.speaker, DiarizationSegment 등
|
|
7. ipc-channels.ts — DIARIZE, DIARIZATION_PROGRESS
|
|
8. errors.ts — 895-897
|
|
9. AppConfig — hfToken, diarizationEnabled, diarizationNumSpeakers
|
|
|
|
### Step D: 서비스
|
|
10. MeetingModeService.diarizeSession() — sidecar 호출 + 세그먼트 매칭
|
|
11. LocalSTTService — /diarize 호출 래퍼
|
|
|
|
### Step E: IPC + Preload
|
|
12. 핸들러 + preload API
|
|
|
|
### Step F: UI
|
|
13. 설정 — HF 토큰 + diarization 토글
|
|
14. TranscriptTab — 화자별 색상 바
|
|
15. EditableSegment — speaker 라벨 표시
|
|
16. 상세 페이지 — "화자 구분 실행" 버튼
|
|
|
|
### Step G: i18n + 검증
|
|
|
|
---
|
|
|
|
## 6. 파일 목록
|
|
|
|
### 수정 (Python)
|
|
- sidecar/main.py — /diarize 엔드포인트
|
|
- sidecar/requirements.txt — pyannote.audio, torch
|
|
|
|
### 수정 (TypeScript)
|
|
- src/shared/types.ts — CaptionSegment.speaker, DiarizationResult
|
|
- src/shared/ipc-channels.ts — DIARIZE, DIARIZATION_PROGRESS
|
|
- src/shared/errors.ts — 895-897
|
|
- src/main/services/MeetingModeService.ts — diarizeSession, 오디오 보존
|
|
- src/main/ipc/meeting-mode-handlers.ts — DIARIZE 핸들러
|
|
- src/preload/index.ts — diarize API
|
|
- src/renderer/components/meeting/TranscriptTab.tsx — 화자 색상
|
|
- src/renderer/components/meeting/EditableSegment.tsx — speaker 표시
|
|
- src/renderer/components/meeting/MeetingDetailTabs.tsx — diarize 버튼
|
|
- src/renderer/components/SettingsModal.tsx — HF 토큰 + 토글
|
|
- src/renderer/i18n/*.json — 12개 locale
|
|
|
|
---
|
|
|
|
## 7. 리스크
|
|
|
|
1. **PyInstaller 번들**: torch 추가 시 ~1GB 증가 → lazy import로 완화
|
|
2. **CPU 성능**: diarization은 GPU 없으면 느림 (30초 오디오 ~10초) → 프로그레스 바 필수
|
|
3. **HF 토큰 UX**: 사용자에게 HuggingFace 가입 + 토큰 생성을 요구 → 가이드 UI
|
|
4. **정확도**: 소규모 회의(2~3인)에서 최적, 대규모(5인+)에서 정확도 하락
|