# 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 { // 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인+)에서 정확도 하락