Files
stt/README.md
dosangyoon 14df6d8805 feat(ui): resume STT job after refresh via ?job= and localStorage
- Sync job_id to URL and localStorage; tryResumeJob on load
- Clear sync on terminal state, clear button, new upload, poll errors
- README: note server thread vs UI reattach and JOB_TTL

Made-with: Cursor
2026-03-23 17:46:38 +09:00

322 lines
16 KiB
Markdown

# Web STT (mp3/m4a 업로드 → 텍스트 변환)
## 구성
- **백엔드**: FastAPI (업로드/검증/STT 수행)
- **STT 엔진**: `faster-whisper` (Whisper 모델)
- **화자 분리(웹 기본 켜짐)**: `pyannote``whisper_stt.py`와 동일하게 로컬 `./models/pyannote-diarization-3.1` + HF 토큰·게이트 동의 필요. UI/API에서 끌 수 있음(`diarize=false`).
- **프론트**: 단일 HTML (파일 선택 → 전사 → 결과 표시/다운로드)
- **선택 CLI**: `whisper_stt.py` — OpenAI Whisper 기반 로컬 전사(**기본: 화자 구분**, 동일 pyannote 스냅샷)
## 동작 개요 (pseudocode)
```text
UI:
onSelect(file):
validate client-side (extension)
enable "전사" 버튼
onClickTranscribe():
POST /api/transcribe (multipart/form-data, file, options)
show progress (업로드 중 / 처리 중)
render returned text + segments
allow download as .txt
API:
POST /api/transcribe:
if no file -> 400
validate mime/ext in allowed audio types -> 415 if not
save to temp file
run STT(model, language, vad_filter, beam_size, ...)
return { text, segments[], detected_language, duration_sec }
cleanup temp file
```
---
## 사전 요구 사항
### Ubuntu (22.04 / 24.04 등)
오디오 디코딩과 일부 Python 패키지 빌드에 쓰입니다.
```bash
sudo apt update
sudo apt install -y ffmpeg build-essential
```
- **`ffmpeg`**: `faster-whisper`·Whisper가 mp3/m4a 등을 읽을 때 필요합니다. (`apt`로 설치하는 편이 가장 단순합니다.)
- **`build-essential`**: 소스/휠 빌드가 필요한 의존성이 있을 때 도움이 됩니다.
선택(GPU로 faster-whisper 등을 쓸 때):
- NVIDIA 드라이버 및 CUDA는 [NVIDIA 문서](https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html)에 맞게 설치합니다.
- 이 저장소 기본값은 **CPU**입니다. GPU 사용 시 `APP_WHISPER_DEVICE`·`APP_WHISPER_COMPUTE_TYPE` 등을 환경에 맞게 조정하세요.
### macOS
```bash
brew install ffmpeg
```
`pip``imageio-ffmpeg`만으로도 CLI 쪽 보조는 가능하지만, 서버·도구 공통으로 **시스템 `ffmpeg` 설치를 권장**합니다.
---
## Python 환경 (Conda 권장)
이 프로젝트는 **conda 환경 `stt`** (Python 3.11) 사용을 권장합니다. (Cursor/VS Code는 `.vscode/settings.json`에 인터프리터 경로가 있습니다.)
### 1) `stt` 생성 및 웹 서버 의존성
```bash
conda create -n stt python=3.11 -y
conda activate stt
pip install -U pip setuptools wheel
pip install -r requirements.txt
pip install -r requirements-diarize.txt
```
**Linux에서 `pip install -r requirements.txt` 만으로 `resolution-too-deep` 이 난 경우**
예전에는 `pyannote.audio>=3.1.0` 이 한 파일에 있어 최신 버전까지 풀며 그래프가 깊어질 수 있었습니다. 지금은 **베이스**(`requirements.txt`)와 **화자 분리**(`requirements-diarize.txt`, `torch`·`pyannote.audio==3.3.2` 고정)로 나뉘어 있습니다. 위처럼 **두 파일을 순서대로** 설치하고, 그래도 실패하면 **conda로 PyTorch 먼저** 한 뒤 `pip install -r requirements-diarize.txt` 만 다시 시도해 보세요 (`requirements-whisper-stt.txt` 절의 torch 복구 절차 참고).
### 2) (선택) 로컬 전사 CLI — `whisper_stt.py`
```bash
conda activate stt
pip install -r requirements-whisper-stt.txt
```
**Ubuntu에서 `pip install -r requirements-whisper-stt.txt` 가 torch 관련 `OSError` / `ATen.h` 없음 등으로 실패할 때**
기존 `torch` 설치가 깨졌거나 pip가 교체 도중 멈춘 경우가 많습니다.
```bash
conda activate stt # 또는 사용 중인 env (예: ncue)
pip uninstall -y torch torchvision torchaudio functorch
pip uninstall -y torch torchvision torchaudio functorch # Skipping만 나올 때까지 반복
pip cache purge
pip install torch torchaudio --index-url https://download.pytorch.org/whl/cpu
pip install -r requirements-whisper-stt.txt
```
**같은 오류(`ATen.h` 없음 등)가 `torch` 재설치 시에도 반복되면**
`pip uninstall`만으로는 깨진 `site-packages/torch` 폴더가 남는 경우가 있습니다. 아래로 **잔여 디렉터리를 직접 삭제**한 뒤 다시 설치하세요. (`python3.11``python -c "import sys; print(sys.version_info[:2])"`로 맞춤.)
```bash
conda activate ncue # 문제 나는 env
pip uninstall -y torch torchvision torchaudio functorch 2>/dev/null || true
rm -rf "$CONDA_PREFIX/lib/python3.11/site-packages/torch" \
"$CONDA_PREFIX/lib/python3.11/site-packages/torch-"*.dist-info \
"$CONDA_PREFIX/lib/python3.11/site-packages/torchaudio" \
"$CONDA_PREFIX/lib/python3.11/site-packages/torchaudio-"*.dist-info \
"$CONDA_PREFIX/lib/python3.11/site-packages/torchgen" \
"$CONDA_PREFIX/lib/python3.11/site-packages/functorch"
pip cache purge
pip install torch torchaudio --index-url https://download.pytorch.org/whl/cpu
pip install -r requirements-whisper-stt.txt
```
그래도 실패하면 **새 conda 환경**(`conda create -n stt-whisper python=3.11 -y`)을 만들고, 위 README의 **conda로 PyTorch 먼저** 절차만 그 env에서 진행하는 것이 가장 확실합니다.
애초에 꼬이지 않게 하려면 **PyTorch를 conda로 먼저** 깐 뒤 위 requirements만 pip로 설치하는 것을 권장합니다.
```bash
conda activate stt
conda install pytorch torchaudio cpuonly -c pytorch -y
pip install -r requirements-whisper-stt.txt
```
- **Hugging Face `hf` CLI**: `pip install huggingface_hub``hf auth login`, `hf download …` (화자 구분용 pyannote 모델 등).
- $ hf auth login
- $ hf download pyannote/speaker-diarization-3.1 --local-dir ./models/pyannote-diarization-3.1
- **화자 구분(기본 켜짐)**: `./models/pyannote-diarization-3.1` 에 pyannote 스냅샷이 있어야 합니다. 없으면 스크립트가 `hf download` 안내 후 종료합니다. 모델 받기: [pyannote/speaker-diarization-3.1](https://huggingface.co/pyannote/speaker-diarization-3.1) 약관 동의 후 `hf auth login`, `hf download … --local-dir ./models/pyannote-diarization-3.1`. 다른 경로는 `--diarize-model-dir` 또는 `WHISPER_DIARIZE_MODEL_DIR` 로 지정.
- **하위 gated 모델(403 시)**: `speaker-diarization-3.1`만 동의했다고 끝나지 않습니다. 실행 시 최소한 아래 **각각** Hugging Face에서 로그인 후 약관·양식을 제출해야 합니다: [speaker-diarization-3.1](https://huggingface.co/pyannote/speaker-diarization-3.1), [segmentation-3.0](https://huggingface.co/pyannote/segmentation-3.0), [speaker-diarization-community-1](https://huggingface.co/pyannote/speaker-diarization-community-1) (`xvec_transform.npz` 등). 오류에 다른 `pyannote/…` 가 나오면 그 저장소도 동일합니다. **403 / «not in the authorized list»** 이면 (1) 빠진 저장소 동의 (2) 토큰이 동의한 **같은 계정**의 것인지 (`hf auth whoami`) (3) 동의 후 새로 발급한 Read 토큰 사용을 확인하세요. 실행 전 `hf auth login` 또는 `HF_TOKEN`. `whisper_stt.py`는 로컬 `config.yaml` 로드 후에도 이 토큰으로 허브에서 하위 파일을 받습니다.
- **화자 구분 끄기**: `python whisper_stt.py 입력.m4a 출력.txt --no-diarize` (Whisper 통문만 저장)
```bash
python whisper_stt.py 입력.m4a 출력.txt
python whisper_stt.py 입력.m4a 출력.txt --no-diarize
python whisper_stt.py 입력.m4a 출력.txt --diarize-model-dir /다른/경로/pyannote-diarization-3.1
```
### 대안: `environment.yml` (환경 이름 `ncue`, conda에 `ffmpeg` 포함)
```bash
conda env create -f environment.yml
conda activate ncue
```
`pip` 의존성은 `requirements.txt``requirements-diarize.txt`를 통해 설치됩니다. 팀에서 이미 `ncue`를 쓰는 경우에만 사용해도 됩니다.
---
## 서버 실행
```bash
conda activate stt # 또는 ncue
uvicorn app.main:app --reload --host 127.0.0.1 --port 8025
```
또는 저장소 루트에서 **`./run.sh`** (백그라운드 + `server.log`, 기본 포트 `8025`, `PORT`·`CONDA_ENV` 등으로 조정).
브라우저에서 `http://127.0.0.1:8025` 접속.
웹 UI는 **faster-whisper** 전사와(옵션) **pyannote 화자 분리**를 지원합니다. OpenAI Whisper 기반 CLI는 **`whisper_stt.py`** 를 사용하세요. 비동기 작업(`POST /api/jobs`)은 **서버 스레드에서 계속** 돌아가며, UI는 **`?job=<uuid>`·`localStorage`** 로 새로고침 후에도 같은 작업을 다시 조회·폴링합니다(메모리 TTL `APP_JOB_TTL_SEC` 초과 시 job 은 사라짐).
### HTTPS 도메인(`https://ncue.net/stt/…`)에서 503 (Service Unavailable)
브라우저가 **503**을 보여 줄 때, 대부분 **리버스 프록시(nginx·Apache 등)가 백엔드(uvicorn)에 붙지 못했다**는 뜻입니다. 이 저장소의 앱은 일반적으로 **503을 직접 내지 않습니다.**
**흔한 원인**
1. **`run.sh`를 다른 머신에서만 실행한 경우**
`run.sh`는 uvicorn을 **`127.0.0.1:8025`**에만 띄웁니다. **웹 서버(프록시)가 돌아가는 서버와 같은 호스트**에서 프로세스가 떠 있어야 `https://도메인/stt/` 프록시가 연결됩니다. 노트북에서만 `./run.sh`를 켜 두면 공개 도메인 쪽 업스트림은 비어 있어 **503**이 납니다.
2. **프로세스가 곧바로 종료된 경우**
DB 설정 오류, **import 실패(NumPy·torch 등)** 로 uvicorn이 뜨자마자 죽으면 프록시도 503을 냅니다. **서버에서** `tail -n 100 server.log` 로 스택 트레이스를 확인하세요.
3. **포트·프록시 설정 불일치**
프록시가 가리키는 포트가 실제 uvicorn 포트(`PORT`, 기본 8025)와 같아야 합니다.
#### 구형 CPU / NumPy `X86_V2` 오류 (`server.log`)
다음처럼 **앱이 시작조차 못 하고** 죽는 경우가 있습니다.
`RuntimeError: NumPy was built with baseline optimizations: (X86_V2) but your machine doesn't support: (X86_V2).`
의미: 설치된 NumPy **바이너리**가 x86_64_v2(AVX2 등)를 요구하는데, 서버 CPU가 이를 지원하지 않습니다. (가상머신·오래된 Xeon 등에서 흔합니다.)
**조치** (웹 서버에서, 사용 중인 conda env — 예: `ncue`):
```bash
conda activate ncue
pip install "numpy>=1.26,<2.2" --force-reinstall
pip install -r requirements.txt
pip install -r requirements-diarize.txt
./run.sh
```
pip만으로 안 되면 **conda-forge** NumPy가 해당 CPU에 맞는 빌드를 주는 경우가 많습니다.
```bash
conda install -n ncue "numpy<2.2" -c conda-forge
```
이 저장소 `requirements.txt`에는 위와 같이 **`numpy<2.2`** 를 명시해 두었습니다. 이미 깨진 환경은 위처럼 한 번 재설치하면 됩니다.
**`pyannote-metrics 4.x` 와 NumPy 버전 경고**
`pyannote-metrics` 4.0.0 이상은 **`numpy>=2.2.2`** 를 요구해, 위 `numpy<2.2` 정책과 맞지 않습니다. `requirements-diarize.txt` 에서 **`pyannote-metrics>=3.2,<4`** 로 상한을 두고, **`pyannote-core` / `pyannote-database` / `pyannote-pipeline` 도 6대 미만 등 3.x 스택**에 맞게 캡해 두었습니다. (CPU가 **X86_V2를 지원**하고 NumPy 2.2+ 휠을 쓸 수 있다면, 팀 정책에 따라 `numpy` 상한을 올리고 metrics 4.x + `pyannote-audio` 4.x를 쓰는 선택도 가능합니다.)
**`pyannote-audio 4.x``pyannote-metrics 3.x` 충돌**
`pyannote-audio` 4.x 는 **`pyannote-metrics>=4`** 를 요구합니다. metrics 를 3.x로 두려면 **audio 도 3.3.2** 여야 합니다. 경고가 나오면:
```bash
pip uninstall -y pyannote-audio pyannote.audio
pip install -r requirements-diarize.txt
```
구형 CPU용으로 **`numpy<2.2`** 를 쓰는 경우, 위 설치 후 NumPy가 올라갔다면 `pip install "numpy>=1.26,<2.2" --force-reinstall` 로 다시 맞춥니다.
**같은 서버에서 빠른 점검**
```bash
curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8025/healthz
# 200 이어야 정상
ss -lntp | grep 8025
# 또는: lsof -iTCP:8025 -sTCP:LISTEN
```
**nginx 예시** (`/stt/` 아래로 서비스할 때, 접두사를 벗겨 uvicorn의 `/`·`/api/…`에 넘깁니다)
```nginx
location /stt/ {
proxy_pass http://127.0.0.1:8025/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
```
`proxy_pass`**끝 슬래시(`/`)**가 있어야 `location /stt/`와 짝이 맞아 경로가 올바르게 넘어갑니다. 설정을 고친 뒤 **`nginx -t` 후 reload** 하세요.
**Apache (`httpd`) 예시** (`mod_proxy`, `mod_proxy_http` 필요)
```apache
ProxyPass "/stt/" "http://127.0.0.1:8025/"
ProxyPassReverse "/stt/" "http://127.0.0.1:8025/"
```
설정 반영 후 `apachectl configtest` 및 재시작(또는 reload) 하세요.
공개 URL은 가능하면 **`https://예시/stt/`** 처럼 **슬래시까지 포함**해 두면, UI의 상대 경로(`healthz`, `api/…`)가 같은 접두사 아래로 잘 붙습니다.
---
## 옵션·환경 변수
- **모델**: 기본 `small` (정확도/속도 균형). `APP_WHISPER_MODEL=base|small|medium|large-v3` 등으로 변경 가능.
- **디바이스**: 기본 CPU. Apple Silicon에서 Metal은 `faster-whisper` 단독으로는 제한이 있어 CPU 기본값을 권장.
- **화자 분리**: `pip install -r requirements-diarize.txt``pyannote.audio`·`torch` 등이 포함됩니다(베이스 `requirements.txt` 설치 후). 모델 폴더는 `WHISPER_DIARIZE_MODEL_DIR` / `PYANNOTE_MODEL_DIR` 또는 기본 `./models/pyannote-diarization-3.1`. 다른 경로는 `APP_PYANNOTE_MODEL_DIR`로 지정 가능. HF 토큰(`HF_TOKEN` 등)과 gated 저장소 동의는 `whisper_stt.py` 절·`requirements-whisper-stt.txt` 주석과 동일합니다.
- **기타**: `APP_WHISPER_DEVICE`, `APP_WHISPER_COMPUTE_TYPE`, 업로드 크기 등은 `app/main.py``.env` 예시를 참고.
---
## Linux + Conda: `libtinfo.so.6` / `vi`·`bash` 경고
`(ncue)` 등 conda 환경을 켠 뒤 **`LD_LIBRARY_PATH``${CONDA_PREFIX}/lib`가 들어가면**, 터미널에서 돌아가는 **시스템 프로그램(`vi`, `bash`, `less` 등)** 이 conda에 들어 있는 `libtinfo.so.6`을 먼저 물 수 있습니다. 그 라이브러리에 ELF 버전 태그가 맞지 않을 때 **«no version information available»** 가 납니다. (`run.sh`는 스크립트 안에서 이를 피하도록 처리해 두었습니다.)
**`conda deactivate` 해도 경고가 남는 경우**
- `deactivate`만으로 **`LD_LIBRARY_PATH`가 비워지지 않을 수 있습니다** (이전 셸 값 복원 실패, `.bashrc`에서 직접 export, 다른 툴이 덧붙임).
- **`vi`가 conda·vim 빌드**일 수 있습니다. `type -a vi`, `which -a vi` 로 확인하고, 가능하면 **`/usr/bin/vi`** 또는 **`/usr/bin/vim`** 을 쓰세요.
**당장 편집·실행**
```bash
unset LD_LIBRARY_PATH
vi run.sh
```
또는 한 줄로:
```bash
env -u LD_LIBRARY_PATH /usr/bin/vi run.sh
LD_LIBRARY_PATH= /usr/bin/vi run.sh
```
저장소 헬퍼 (`chmod +x scripts/env-no-ld.sh` 후):
```bash
./scripts/env-no-ld.sh vi run.sh
./scripts/env-no-ld.sh /usr/bin/vi run.sh
```
**원인 확인**
```bash
echo "LD_LIBRARY_PATH=${LD_LIBRARY_PATH-<비어 있음>}"
type -a vi
conda deactivate
echo "LD_LIBRARY_PATH=${LD_LIBRARY_PATH-<비어 있음>}" # 여전히 miniconda 경로면 .bashrc 등 확인
grep -n LD_LIBRARY_PATH ~/.bashrc ~/.profile ~/.bash_profile 2>/dev/null
```
**장기적으로**
1. `grep -r LD_LIBRARY_PATH "$HOME/workspace/miniconda3/envs/ncue/etc/conda/activate.d/"` 등으로 어떤 패키지가 넣는지 확인합니다.
2. GPU/torch가 꼭 필요한 터미널과 **편집·git용 터미널**을 나누거나, 편집 전에 `unset LD_LIBRARY_PATH` 를 습관화합니다.
3. `conda install -c conda-forge ncurses` 로 env 안 ncurses를 맞추면 완화되는 경우가 있습니다(환경마다 다름).
---
## 플랫폼 요약
| 항목 | Ubuntu | macOS |
|------|--------|--------|
| `ffmpeg` | `sudo apt install ffmpeg` | `brew install ffmpeg` |
| Python | Conda `stt` 권장 | 동일 |
| 웹 STT | `pip install -r requirements.txt``pip install -r requirements-diarize.txt` | 동일 |
| `whisper_stt.py` | `pip install -r requirements-whisper-stt.txt` | 동일 |