- Sidecar: pyannote /diarize 엔드포인트 + requirements.txt 업데이트 - LLM 기반 화자 추정 (Phase 1 — 오디오 보존 없이 전사 텍스트 분석) - CaptionSegment에 speaker 필드 추가 - EditableSegment: 화자별 색상 바 + 화자 Chip 표시 - TranscriptTab: 화자 구분 버튼 + 진행률 - 설정: HuggingFace 토큰 입력 + Diarization 토글 - IPC: DIARIZE + DIARIZATION_PROGRESS 채널 - 에러코드: 895-897 - 12개 locale i18n
6.5 KiB
6.5 KiB
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
@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 캐시
_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
// 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
MEETING_MODE: {
// 기존 + 추가
DIARIZE: 'meetingMode:diarize',
DIARIZATION_PROGRESS: 'meetingMode:diarizationProgress',
}
3.3 MeetingModeService 확장
async diarizeSession(sessionId: string): Promise<DiarizationResult> {
// 1. 녹음된 오디오 파일 경로 확인
// 2. sidecar /diarize 엔드포인트 호출
// 3. 결과의 화자 세그먼트와 전사 세그먼트 매칭
// 4. 각 전사 세그먼트에 speaker 라벨 부여
// 5. DB 저장 (rawTranscript에 화자 정보 포함)
}
3.4 오디오 파일 보존
현재 CaptionService는 오디오를 STT 후 폐기합니다. 회의 모드에서는 오디오를 임시 파일로 보존해야 합니다.
// MeetingModeService.startRecording()에서
// AudioCaptureService의 raw PCM 데이터를 WAV 파일로 저장
private _audioFilePath: string | null = null
3.5 설정 UI
- HuggingFace 토큰 입력 (설정 > STT 탭)
- Diarization 활성화 토글
- 화자 수 설정 (0=자동, 2~10)
3.6 UI — 화자별 색상 구분
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. 에러 코드
DiarizationFailed = 895,
DiarizationModelNotLoaded = 896,
DiarizationTokenRequired = 897,
5. 구현 순서
Step A: Sidecar (Python)
- requirements.txt 업데이트
- /diarize 엔드포인트 구현
- Pipeline 캐시 + GPU/CPU 자동 선택
Step B: 오디오 파일 보존
- MeetingModeService에서 녹음 중 WAV 파일 저장
- 녹음 종료 후 파일 경로를 DB에 저장
Step C: 기반 레이어
- types.ts — CaptionSegment.speaker, DiarizationSegment 등
- ipc-channels.ts — DIARIZE, DIARIZATION_PROGRESS
- errors.ts — 895-897
- AppConfig — hfToken, diarizationEnabled, diarizationNumSpeakers
Step D: 서비스
- MeetingModeService.diarizeSession() — sidecar 호출 + 세그먼트 매칭
- LocalSTTService — /diarize 호출 래퍼
Step E: IPC + Preload
- 핸들러 + preload API
Step F: UI
- 설정 — HF 토큰 + diarization 토글
- TranscriptTab — 화자별 색상 바
- EditableSegment — speaker 라벨 표시
- 상세 페이지 — "화자 구분 실행" 버튼
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. 리스크
- PyInstaller 번들: torch 추가 시 ~1GB 증가 → lazy import로 완화
- CPU 성능: diarization은 GPU 없으면 느림 (30초 오디오 ~10초) → 프로그레스 바 필수
- HF 토큰 UX: 사용자에게 HuggingFace 가입 + 토큰 생성을 요구 → 가이드 UI
- 정확도: 소규모 회의(2~3인)에서 최적, 대규모(5인+)에서 정확도 하락