Web STT: speaker diarization via pyannote; whisper_stt snapshot validation

- Add app/diarize.py: merge faster-whisper segments with pyannote (A/B/C)
- Wire /api/jobs and /api/transcribe; job API returns speaker_diarization, diarize_skip_reason
- UI: meta line shows diarization applied/skipped; hint for models path
- requirements.txt: pyannote.audio; README APP_DIARIZE / APP_PYANNOTE_MODEL_DIR
- whisper_stt.py: validate config.yaml before loading pipeline
- requirements-whisper-stt.txt: minor doc updates if any

Made-with: Cursor
This commit is contained in:
dosangyoon
2026-03-23 13:09:31 +09:00
parent c90230053a
commit 2e503d1a56
7 changed files with 285 additions and 8 deletions

View File

@@ -16,6 +16,31 @@ import whisper.audio as whisper_audio
DEFAULT_DIARIZE_MODEL_DIR = "./models/pyannote-diarization-3.1"
def _validate_pyannote_snapshot(model_dir: str) -> None:
"""README만 있거나 중간에 끊긴 다운로드면 config.yaml 이 없다."""
cfg = os.path.join(model_dir, "config.yaml")
if os.path.isfile(cfg):
return
abs_dir = os.path.abspath(model_dir)
print(
f"오류: pyannote 모델 폴더가 불완전합니다 (config.yaml 없음): {abs_dir}\n"
"Hugging Face에서 README만 받아졌거나, 다운로드가 중간에 끊긴 상태일 수 있습니다.\n\n"
"프로젝트 루트에서 로그인 후 전체 스냅샷을 다시 받으세요:\n"
" hf auth login\n"
" hf download pyannote/speaker-diarization-3.1 \\\n"
f" --local-dir {DEFAULT_DIARIZE_MODEL_DIR}\n\n"
"기존 폴더를 비우고 받으려면(주의: 폴더 안 파일 전부 삭제):\n"
f" rm -rf \"{abs_dir}\"/*\n"
" hf download pyannote/speaker-diarization-3.1 \\\n"
f" --local-dir {DEFAULT_DIARIZE_MODEL_DIR}\n\n"
"https://hf.co/pyannote/speaker-diarization-3.1 에서 모델 약관 동의가 되어 있어야 합니다.\n"
"화자 구분 없이 Whisper만 쓰려면:\n"
" python whisper_stt.py 입력.m4a 출력.txt --no-diarize\n",
file=sys.stderr,
)
sys.exit(1)
def _resolve_ffmpeg_exe() -> str:
"""PATH의 ffmpeg 또는 imageio-ffmpeg 번들 바이너리."""
path = shutil.which("ffmpeg")
@@ -177,6 +202,7 @@ def _run_diarization(audio_path: str, *, diarize_model_dir: str | None) -> list[
from pyannote.audio import Pipeline
model_dir = _resolve_local_diarize_dir(diarize_model_dir)
_validate_pyannote_snapshot(model_dir)
print(f"[4/4] 화자 분리(pyannote) — 로컬 모델: {model_dir}", flush=True)
print("[4/4] 화자 분리 실행 중... (수 분 걸릴 수 있음)", flush=True)
@@ -186,8 +212,8 @@ def _run_diarization(audio_path: str, *, diarize_model_dir: str | None) -> list[
except Exception as e:
print(
f"오류: pyannote 파이프라인을 불러오지 못했습니다: {e}\n\n"
"모델 파일이 손상되었거나 하위 가중치가 빠졌을 수 있습니다.\n"
"다시 받기:\n"
"config.yaml 은 있는데도 실패하면 하위 체크포인트(segmentation·embedding 등)가 빠졌을 수 있습니다.\n"
"폴더를 비운 뒤 전체 재다운로드:\n"
" hf download pyannote/speaker-diarization-3.1 \\\n"
f" --local-dir {DEFAULT_DIARIZE_MODEL_DIR}\n",
file=sys.stderr,