Skip to content

Commit 814bb13

Browse files
authored
Add STT chapter generator (#3)
* Add STT chapter generator * Use uv for STT chapter generator * Refine chapter boundary selection * Address review comments
1 parent 16ef85c commit 814bb13

9 files changed

Lines changed: 2108 additions & 0 deletions

File tree

stt-chapter-generator/.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
RTZR_CLIENT_ID=your_client_id
2+
RTZR_CLIENT_SECRET=your_client_secret

stt-chapter-generator/.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.env
2+
.venv/
3+
__pycache__/
4+
*.pyc
5+
6+
data/
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.12

stt-chapter-generator/README.md

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# RTZR STT Chapter Generator
2+
3+
RTZR STT API로 음성 파일을 전사한 뒤, 오픈소스 문장 임베딩 모델로 챕터 경계를 자동 생성하는 Python 예제입니다.
4+
챕터 표시는 별도 LLM 없이, 실제 전사문에서 고른 대표 발화를 사용합니다.
5+
6+
역할은 단순하게 나눕니다.
7+
8+
- `transcribe.py`: RTZR STT API에 오디오 파일을 보내고 transcript JSON을 저장합니다.
9+
- `rtzr_openapi_client.py`: RTZR 공식 문서의 `RTZROpenAPIClient` 흐름을 분리한 클라이언트입니다.
10+
- `chapterize.py`: transcript의 문단을 문장 임베딩 모델로 비교해 C99-rank 방식으로 챕터 경계를 찾습니다. 챕터 표시는 Kiwi 형태소 분석기로 추출한 내부 키워드를 이용해 대표 발화를 고릅니다.
11+
12+
## 1. Setup
13+
14+
```bash
15+
cd stt-chapter-generator
16+
uv sync
17+
```
18+
19+
이 예제는 `pyproject.toml`에 의존성을 정의하고 `uv`로 실행 환경을 관리합니다.
20+
`.python-version`에는 기본 Python 버전으로 `3.12`를 명시했습니다.
21+
`uv sync`를 실행하면 `.venv``uv.lock`을 기준으로 필요한 패키지가 설치됩니다.
22+
23+
프로젝트 폴더의 `.env.example`을 복사해 `.env` 파일을 만들고 RTZR 키를 저장합니다.
24+
25+
```bash
26+
cp .env.example .env
27+
```
28+
29+
```env
30+
RTZR_CLIENT_ID=...
31+
RTZR_CLIENT_SECRET=...
32+
```
33+
34+
실행할 때는 `uv run --env-file .env`를 사용해 `.env` 값을 함께 불러옵니다.
35+
36+
```bash
37+
uv run --env-file .env -- python transcribe.py --help
38+
```
39+
40+
## 2. Transcribe
41+
42+
분석할 음성 파일을 준비한 뒤 경로를 인자로 넘깁니다.
43+
44+
```bash
45+
uv run --env-file .env -- python transcribe.py path/to/audio.wav \
46+
--model-name whisper \
47+
--language ko \
48+
--use-paragraph-splitter \
49+
--paragraph-max 40 \
50+
--use-disfluency-filter
51+
```
52+
53+
결과는 기본적으로 아래 경로에 저장됩니다.
54+
55+
```text
56+
data/transcripts/audio.transcript.json
57+
```
58+
59+
`--use-disfluency-filter```, ``, 반복 발화처럼 의미가 약한 구어체 표현을 줄이는 RTZR 옵션입니다. RTZR의 기본값은 켜짐입니다. 원본 구어체에 가까운 전사 결과와 비교하고 싶다면 아래처럼 끌 수 있습니다.
60+
61+
```bash
62+
uv run --env-file .env -- python transcribe.py path/to/audio.wav \
63+
--model-name whisper \
64+
--language ko \
65+
--use-paragraph-splitter \
66+
--paragraph-max 40 \
67+
--no-disfluency-filter \
68+
--output data/transcripts/audio.raw.transcript.json
69+
```
70+
71+
## 3. Generate Chapters
72+
73+
기본 실행은 문장 임베딩 모델과 C99-rank 경계 점수로 챕터 경계를 생성합니다. 이후 각 챕터 안에서 자주 등장하면서 전체 전사에서는 상대적으로 덜 흔한 명사 키워드를 내부적으로 고르고, 키워드와 가까운 실제 발화를 대표 발화로 표시합니다.
74+
75+
챕터 수 상한은 전체 전사 글자 수를 기준으로 자동 계산됩니다. 기본값은 약 `1000`자마다 하나의 경계를 허용하되, 최소 `2`개에서 최대 `10`개의 경계까지만 선택합니다. 이후 경계 점수가 높은 지점을 우선적으로 고르고, 너무 가까운 위치가 반복해서 선택되지 않도록 최소 간격을 둡니다.
76+
77+
```bash
78+
uv run python chapterize.py data/transcripts/audio.transcript.json
79+
```
80+
81+
결과:
82+
83+
```text
84+
data/outputs/audio.chapters.json
85+
data/outputs/audio.chapters.md
86+
```
87+
88+
Markdown 결과를 바로 확인하려면:
89+
90+
```bash
91+
cat data/outputs/audio.chapters.md
92+
```
93+
94+
## Fixed Defaults
95+
96+
튜토리얼에서는 사용자가 경계 탐지 값을 직접 튜닝하지 않도록 주요 값을 코드 내부 기본값으로 고정했습니다.
97+
98+
- 경계 간 최소 간격: `5`개 문단
99+
- 경계 계산 window: `5`
100+
- 최대 경계 수: 전체 전사 글자 수 기준, `min(10, max(2, round(total_chars / 1000)))`
101+
- 경계 선택 기준: 경계 점수의 상대 순위가 `0.385` 이상인 후보 우선 선택
102+
- C99-rank 반경: `3`
103+
- 챕터 표시 방식: 실제 전사문에서 고른 대표 발화
104+
- 내부 키워드 개수: `8`
105+
106+
사용자가 실행 시 바꿀 수 있는 옵션은 출력 위치를 정하는 `--output-dir`입니다.
107+
108+
## Speech-Like Transcripts
109+
110+
실제 음성 전사는 글보다 구어체 표현, 반복, 머뭇거림이 많습니다. 이 예제에서는 전사 단계에서 `--use-disfluency-filter`를 사용해 간투어를 줄이고, 챕터 경계는 정리된 전사 문단을 기준으로 계산합니다.
111+
112+
다만 필터가 모든 구어체 문제를 해결하는 것은 아닙니다. 그래서 튜토리얼이나 실험에서는 같은 음성을 `--use-disfluency-filter``--no-disfluency-filter`로 각각 전사한 뒤 챕터 결과를 비교해보는 것이 좋습니다.
113+
114+
## Representative Text
115+
116+
챕터 표시는 LLM으로 새 문장을 생성하지 않습니다. Kiwi 형태소 분석기로 일반 명사, 고유 명사, 외국어 토큰을 추출한 뒤 TF-IDF 점수로 챕터별 내부 키워드를 고릅니다.
117+
118+
이 키워드는 출력에 직접 노출하지 않고, 챕터를 잘 대표하는 발화를 고르는 데만 사용합니다. 이 방식은 별도 로컬 LLM을 설치하지 않아도 되고 실행이 빠릅니다. 대신 사람이 쓴 제목처럼 자연스러운 문장을 만드는 방식이 아니라, 실제 전사에서 고른 대표 발화를 보여주는 방식입니다.
119+
120+
## Example Output
121+
122+
저장소에는 음성 파일과 전사 결과를 포함하지 않습니다. 아래는 국립민속박물관의 Creative Commons 라이선스 영상인 [\[전시라이브러리\] '그 겨울의 행복' 길상 특별전](https://www.youtube.com/watch?v=-VD6kS4d_kE)을 기준으로 한 실행 결과 형식 예시입니다.
123+
124+
> 예제 영상 출처
125+
> - 제목: [\[전시라이브러리\] '그 겨울의 행복' 길상 특별전](https://www.youtube.com/watch?v=-VD6kS4d_kE)
126+
> - 채널: 국립민속박물관
127+
> - 라이선스: YouTube Creative Commons Attribution license (reuse allowed)
128+
129+
```md
130+
# Chapters: gilsang_winter_happiness
131+
132+
- **00:00:12**
133+
- 대표 발화: 전시회 구성은 1부에서 길상과 행복의 의미를 환기시킨 후에 2부와 3부에서 본격적으로 길상의 모습을 살펴볼 수 있도록 했습니다.
134+
- **00:02:39**
135+
- 대표 발화: ...사는 열 가지인 십장생 또한 대표적인 장수의 상징입니다. 출세, 즉 과거에 합격하여 입신양명하고 부귀해지는 것 또한 옛 사람들이 꼽은 중요한 요소였습니다.
136+
- **00:05:11**
137+
- 대표 발화: ...가치에 대한 측면이었지만, 행복에는 즐거움, 만족감 같은 정서적인 측면도 있습니다.
138+
- **00:06:01**
139+
- 대표 발화: 전시 관람이라는 행위 자체가 행복한 경험이 되기를 바라는 취지에서 공간을 조성했고, 휴식과 관람이 조화를 이룰 수 있도록 구성하였습니다.
140+
```
141+
142+
## Models
143+
144+
기본 모델:
145+
146+
- Embedding: `google/embeddinggemma-300m`
147+
148+
처음 실행할 때 Hugging Face에서 모델 파일을 내려받기 때문에 시간이 걸릴 수 있습니다. 한 번 받은 뒤에는 로컬 캐시를 사용합니다.
149+
150+
`google/embeddinggemma-300m`은 Hugging Face에서 Google Gemma 사용 조건 동의가 필요할 수 있습니다. 처음 실행할 때 접근 권한 오류가 나면 Hugging Face에 로그인한 뒤 모델 페이지에서 라이선스 조건에 동의하고 다시 실행합니다.

0 commit comments

Comments
 (0)