by Joseph
긴 음성 파일을 전사하면 전체 내용을 텍스트로 확인할 수 있지만, 사용자가 원하는 구간을 빠르게 찾기는 어렵습니다. 회의 녹음, 강의, 인터뷰처럼 길이가 긴 음성에서는 전사문과 함께 어느 시점에 어떤 내용이 시작되는지 보여주는 목차가 있으면 훨씬 읽기 편해집니다.
이번 튜토리얼에서는 RTZR STT API로 음성 파일을 전사한 뒤, 전사 결과를 기반으로 자동 챕터와 타임스탬프를 생성하는 Python 예제를 만들어보겠습니다.
예제에서는 국립민속박물관의 Creative Commons 라이선스 영상인 [전시라이브러리] '그 겨울의 행복' 길상 특별전을 전사한 예제용 JSON을 기준으로 설명합니다.
예제 영상 출처
제목: [전시라이브러리] '그 겨울의 행복' 길상 특별전
채널: 국립민속박물관
길이: 7분 28초
라이선스: YouTube Creative Commons Attribution license (reuse allowed)
1. 목표와 결과
만들어볼 것
이번 예제의 목표는 긴 음성 파일을 입력하면 다음과 같은 결과를 자동으로 만드는 것입니다.
- 음성 파일을 RTZR STT API로 전사합니다.
- 전사 결과를 문단 단위로 나눕니다.
- 각 문단을 임베딩 벡터로 변환합니다.
- 문맥이 크게 바뀌는 지점을 찾아 챕터 경계로 사용합니다.
- 각 챕터의 시작 시간을 타임스탬프로 표시합니다.
- 각 챕터를 대표하는 실제 발화를 하나씩 보여줍니다.
전체 흐름은 다음과 같습니다.
음성 파일
↓
RTZR STT API 전사
↓
전사 결과 JSON 저장
↓
문단 임베딩 생성
↓
문맥 변화 지점 탐색
↓
챕터 타임스탬프와 대표 발화 생성결과 미리보기
최종 결과는 Markdown과 JSON 파일로 저장됩니다. 이 예제에서는 7분 28초 길이의 음성이 5개 챕터로 나뉩니다. 코드가 생성하는 값은 챕터 시작 시간과 대표 발화이며, 아래의 구간 제목은 독자의 이해를 돕기 위해 본문에서 덧붙인 것입니다.
00:00:12 | 전시 소개
대표 발화: ...겨울을 맞이하여 길상 특별전, 그 겨울의 행복을 개최합니다.
00:00:12 | 국립민속박물관은 다가오는 겨울을 맞이하여 길상 특별전, 그 겨울의 행복을 개최합니다.
00:00:18 | 이번 전시는 코로나 19팬데믹이 장기화되고 있는 시점에서 관람객 여러분들에게 길상이라는 소재를 통해 긍정과 희망의 메세지를 드리기 위해 기획되었습니다.
00:00:30 | 길상은 좋은 일이 일어날 조짐을 뜻하는 한자어인데요.
00:00:34 | 쉽게 말하면 행복을 바라는 마음을 상징적으로 표현하는 것을 뜻합니다.
00:00:39 | 이번 전시를 통해 행복과 행운이 오기를 바라는 염원과 관련된 우리 문화 요소를 살펴보고, 자신의 행복을 돌아보는 기회를 가지실 수 있기를 바랍니다.
00:00:52 | 전시 구성과 길상 소개
대표 발화: ...행복한 순간을 담은 그림과 사진자료들을 영상으로 담아냈고, 복과 운을 바라는 여러 요소들을 전시하였습니다.
00:00:52 | 전시회 구성은 1부에서 길상과 행복의 의미를 환기시킨 후에 2부와 3부에서 본격적으로 길상의 모습을 살펴볼 수 있도록 했습니다.
00:01:01 | 공간적으로 2부와 3부는 일종의 중전과도 같은 행복의 정원을 두고 연결됩니다.
00:01:07 | 2부에서는 전통적 오복 개념에 기초하여 네 부분으로 나누어 길상을 살펴보고, 3부에서는 근현대 시기의 길상의 변화와 지속을 살펴봅니다.
00:01:17 | 또한 2부의 끝부분에서는 직물과 자수, 나전칠기, 도자와 나무 등 재료에 따른 길상 무늬를 감상할 수 있는 공간도 있고.
00:01:26 | 곳곳에 탑 쌓기와 새점치기, 소원 쓰기 등 다양한 체험 콘텐츠도 마련되어 있습니다.
00:01:42 | 전시의 1부는 길상과 행복의 의미를 일깨워보는 취지로 구성하였습니다.
00:01:47 | 복을 바라는 것은 예나 지금이나 매우 자연스러우면서도 중요한 일이었습니다.
00:01:53 | 마치 쌍한경으로 행복한 순간을 들여다보듯이 행복한 순간을 담은 그림과 사진자료들을 영상으로 담아냈고, 복과 운을 바라는 여러 요소들을 전시하였습니다.
00:02:16 | 이분는 다양한 길상 무늬를 담은 별전으로 시작됩니다.
00:02:20 | 길상은 상징적, 함축적으로 의미를 드러내는 방식이기 때문에 다양한 무늬로서 표현되었는데, 이를 잘 보여주는 것이 바로 동전 모양의 장식품인 별전입니다.
00:02:31 | 시각장애인을 위해 별전의 형태를 손으로 만져볼 수 있도록 확대하여 촉각물로 제작하였습니다.
00:02:39 | 전통적 길상 요소
대표 발화: 또한 대표적인 장수의 상징입니다. 출세, 즉 과거에 합격하여 입신 양명하고 부귀해지는 것 또한 옛 사람들이 꼽은 중요한 요소였습니다.
00:02:39 | 장수는 옛 사람들의 가장 큰 관심사였습니다.
00:02:42 | 아이가 태어나면 장수를 기원하며 베네쩌고리를 만들어 입혔고, 사주를 따져 이름을 지었습니다.
00:02:49 | 어르신들에게는 회갑 때 장수를 기원하는 시를 써서 추원하는 모습을 볼 수 있습니다.
00:02:55 | 고양이 역시 70세 노인을 뜻하는 한자와 발음이 같아 장수를 상징하며 올해 사는 10가지인 10장 생.
00:03:02 | 또한 대표적인 장수의 상징입니다.
00:03:07 | 출세, 즉 과거에 합격하여 입신 양명하고 부귀해지는 것 또한 옛 사람들이 꼽은 중요한 요소였습니다.
00:03:15 | 부귀를 상징하는 대표적인 것은 바로 모란입니다.
00:03:18 | 꽃 중에 왕이라 불리는 모란은 부귀영화를 상징하여 생활용품에 장식되었습니다.
00:03:24 | 개와 같은 갑각류는 등갑을 뒤집은 말인 갑등이 1등이라는 발음과 같아서 출세의 상징으로 여겨졌습니다.
00:03:33 | 잉어 역시 등용문 고사에서 유래하여 합격을 의미하는 의미로 문방구 등의 문늬로 많이 활용되었습니다.
00:03:42 | 화목하고 평안함 역시 많은 이들이 꿈꾸는 행복의 조건입니다.
00:03:46 | 가와 만 사성이라 하여 집안이 화목해야 모든 일이 잘된다는 문구는 널리 알려져 있습니다.
00:03:53 | 평안을 상징하는 길상무늬는 쌍쌍이 어우러진 새들 꽃과 나비 등이 대표적입니다.
00:04:01 | 저출산 시대이자 난임과 불임이 많아진 요즘에도 다시 중요해진 행복의 요소로는 자녀를 낳는 곳도 있습니다.
00:04:09 | 옛 사람들이 다산을 상징하는 의미로 사용한 길상무늬들의 특징은 빽빽하게 많은 것을 다산의 의미로 여겼다는 것입니다.
00:04:17 | 예를 들어 석류, 수박, 포도, 오이 가지 등 열매나 씨앗이 많은 것들, 또 물고기처럼 알을 많이 낳는 것 등 모두 다산을 의미했습니다.
00:04:30 | 다양한 재료에 나타나는 길상 무늬들을 감상하실 수 있는 코너도 마련되어 있습니다.
00:04:37 | 한 땀 한 땀 시를 꿰매고 자수로 새긴 꽃과 동물, 조은 문구 등 여러 길상 무늬는 돌이나 혼렛 때 입는 으레복 등에 표현되었습니다.
00:04:47 | 나전칠기와 도자 나무에도 역시 다양한 동식물과 문자, 무늬, 기하학적인 무늬 등 여러 가지의 좋은 뜻을 담아 장식하였습니다.
00:05:11 | 현대적 행복과 행운
대표 발화: ...가치에 대한 측면이었지만, 행복에는 즐거움, 만족감 같은 정서적인 측면도 있습니다.
00:05:11 | 앞서 1부와 2부가 전통적인 행복에 대해 다루었다면, 3부는 현대의 행복에 대해 생각해 보는 취지로 마련된 공간입니다.
00:05:20 | 앞서 살펴본 오복은 성취하는 어떤 가치에 대한 측면이었지만, 행복에는 즐거움, 만족감 같은 정서적인 측면도 있습니다.
00:05:30 | 물론 현대에도 같이 해서 행복을 찾는 경향도 여전히 있지만, 마음에서 찾는 작은 행복도 있다는 점을 보여드리고 싶었습니다.
00:05:39 | 또한 3부에서는 현대에 오면서 더욱 두드러진 개념인 행운에 대해서도 다루고 있습니다.
00:05:45 | 우리나라뿐만 아니라 각기 다른 다양한 외국의 행운의 상징들을 모아보았습니다.
00:05:52 | 작은 것에서 행복을 찾고 있는 다양한 행복의 변화상을 느끼셨으면 좋겠습니다.
00:06:01 | 전시 공간과 마무리
대표 발화: 전시 관람이라는 행위 자체가 행복한 경험이 되기를 바라는 취지에서 공간을 조성했고, 휴식과 관람이 조화를 이룰 수 있도록 구성하였습니다.
00:06:01 | 이번 전시 공간은 행복의 정원이라는 콘셉트로 구성되었습니다.
00:06:05 | 여러 개의 방들과 마당처럼 조성된 공간에서 전시를 보다가 마당으로 나와서 잠시 휴식을 취할 수도 있고, 여러 가지 체험 요소들도 즐길 수 있습니다.
00:06:17 | 전시 관람이라는 행위 자체가 행복한 경험이 되기를 바라는 취지에서 공간을 조성했고, 휴식과 관람이 조화를 이룰 수 있도록 구성하였습니다.
00:06:27 | 큰 벽에 비춰지는 겨울 풍경을 담은 메인 영상을 보면서 휴식을 취해 보시면 좋겠습니다.
00:06:34 | 또한 이번 전시에는 장애인을 위한 콘텐츠들도 마련했습니다.
00:06:38 | 저시력자와 시각장애인을 위한 점자 리플렛 큰 글씨로 주요 유물을 설명하는 대활자 책자를 각 부마다 비치했습니다.
00:06:48 | 별전은 촉각물로 제작하여 그 무늬를 직접 만져보실 수도 있고, 청각장애인을 위해 전시 영상에 자막과 함께 수어 해설 영상도 덧붙였습니다.
00:06:59 | 행복은 어떤 가치를 이루는 것에서 올 수도 있지만, 작고 구체적인 경험에서도 온다고 합니다.
00:07:05 | 이 전시가 다가오는 겨울에 행복한 경험이 되기를 바랍니다.
00:07:09 | 또한 전시를 관람하시면서 자신이 어떤 행복을 바라는지에 대해 생각해보고, 그 답을 찾을 수 있다면 더욱더 좋겠습니다.
00:07:17 | 감사합니다.
각 챕터의 시간은 전사 결과에 포함된 발화 시작 시간인 start_at 값을 사용합니다. 별도의 오디오 정렬 과정을 추가하지 않아도, STT 결과에 포함된 시간 정보를 그대로 활용할 수 있습니다.
2. 프로젝트 준비
프로젝트 구조
프로젝트는 크게 전사 단계와 챕터 생성 단계로 나뉩니다.
stt-chapter-generator/
├── transcribe.py
├── rtzr_openapi_client.py
├── chapterize.py
├── pyproject.toml
├── uv.lock
├── .env.example
├── data/
│ ├── audio/
│ ├── transcripts/
│ └── outputs/각 파일의 역할은 다음과 같습니다.
transcribe.py: 실행 옵션을 읽고, RTZR STT API 전사 결과를 저장합니다.rtzr_openapi_client.py: RTZR 공식 문서의RTZROpenAPIClient흐름을 분리한 클라이언트입니다.pyproject.toml: 프로젝트 정보와 실행에 필요한 Python 의존성을 정의합니다.uv.lock: 같은 의존성 버전으로 재현 가능하게 실행하기 위한 잠금 파일입니다..env.example: API 인증 정보를 저장하기 위한 환경변수 예시입니다.data/audio: 입력 음성 파일을 둡니다.data/transcripts: STT API 전사 결과 JSON을 저장합니다.data/outputs: 챕터 생성 결과를 저장합니다.
사용 라이브러리
이번 예제에서는 다음 라이브러리를 사용합니다.
requests:RTZROpenAPIClient내부에서 RTZR STT API에 HTTP 요청을 보내기 위해 사용합니다.sentence-transformers: 문단을 임베딩 벡터로 변환하기 위해 사용합니다.kiwipiepy: 한국어 발화에서 명사를 추출하기 위해 사용합니다.numpy: 벡터 연산과 유사도 계산에 사용합니다.
텍스트 임베딩 모델로는 Hugging Face의 google/embeddinggemma-300m을 사용합니다. sentence-transformers 라이브러리의 SentenceTransformer로 모델을 불러와 각 문단을 벡터로 변환합니다.
텍스트 임베딩 모델을 Hugging Face에서 내려받는 경우, 모델에 따라 라이선스 승인이 필요할 수 있습니다. gated repository로 표시되는 모델은 Hugging Face 계정으로 로그인한 뒤 모델 페이지에서 라이선스 조건을 승인해야 사용할 수 있습니다.
환경 설정
이 예제는 Python 3.12 기준으로 작성했으며, .python-version과 pyproject.toml에 해당 버전을 명시했습니다.
uv sync를 실행하면 uv.lock을 기준으로 .venv가 생성되고 필요한 라이브러리가 설치됩니다.
uv syncRTZR API를 호출하기 위해 .env 파일에 RTZR_CLIENT_ID와 RTZR_CLIENT_SECRET을 저장합니다.
RTZR_CLIENT_ID=...
RTZR_CLIENT_SECRET=...이후 실행할 때는 .env 파일을 함께 불러옵니다.
3. RTZR STT API로 전사하기
API 요청 흐름
transcribe.py는 RTZR STT API를 다음 순서로 호출합니다.
RTZR_CLIENT_ID / RTZR_CLIENT_SECRET
↓
access_token 발급
↓
음성 파일 전사 요청
↓
transcribe_id 수신
↓
전사 완료 여부 polling
↓
전사 결과 JSON 저장먼저 인증 API를 호출해 access_token을 발급받습니다. 이후 음성 파일을 업로드하면서 전사 요청을 보내면, API는 바로 전사문 전체를 반환하지 않고 transcribe_id를 반환합니다.
전사는 서버에서 비동기로 처리되므로, 클라이언트는 이 transcribe_id를 사용해 주기적으로 상태를 확인합니다. 전사가 완료되면 최종 전사 결과를 받아 data/transcripts 폴더에 저장합니다.
이번 예제에서는 RTZR API 호출 로직을 RTZROpenAPIClient 클래스로 분리했습니다. transcribe.py는 실행 옵션을 읽고 설정을 만든 뒤, 클라이언트를 호출해 전사 작업을 생성하고 결과를 저장합니다.
RTZROpenAPIClient.token:RTZR_CLIENT_ID,RTZR_CLIENT_SECRET으로access_token을 발급받습니다.RTZROpenAPIClient.transcribe_file(): 음성 파일을 업로드하고 전사 작업을 생성합니다.RTZROpenAPIClient.get_transcription():transcribe_id로 전사 상태를 조회합니다.RTZROpenAPIClient.wait_for_result(): 전사가 끝날 때까지 polling하고 최종 결과를 반환합니다.
음성 파일 전사하기
전사할 음성 파일을 data/audio 폴더에 넣은 뒤, transcribe.py로 음성 파일을 전사합니다.
uv run --env-file .env -- python transcribe.py \
data/audio/gilsang_winter_happiness.wav \
--model-name sommers \
--language ko \
--use-paragraph-splitter \
--paragraph-max 40 \
--use-disfluency-filter전사 결과는 기본적으로 아래 경로에 저장됩니다. 이후 단계에서는 이와 같은 형태의 예제용 전사 JSON이 준비되어 있다고 보고 진행합니다.
data/transcripts/gilsang_winter_happiness.transcript.json이번 예제에서는 paragraph_splitter와 disfluency_filter를 함께 사용합니다.
paragraph_splitter는 전사 결과를 후처리하기 좋은 문단 단위로 나누는 데 사용합니다. 자동 챕터 생성을 하려면 너무 긴 전사문 하나보다, 어느 정도 잘게 나뉜 문단들이 있는 편이 다루기 쉽습니다.
disfluency_filter는 "음", "어", 반복 표현처럼 의미가 약한 구어체 표현을 줄이는 데 사용합니다. 구어체 표현이 많이 남아 있으면 문단 임베딩이나 키워드 추출 결과에도 영향을 줄 수 있기 때문에, 후처리 단계에서는 불필요한 표현을 줄이는 것이 도움이 됩니다.
전사 결과에서 사용하는 값
STT API의 전사 결과에는 여러 정보가 포함됩니다. 이번 예제에서는 그중 챕터 생성에 필요한 값만 사용합니다.
msg: 전사된 발화 텍스트입니다.start_at: 발화가 시작된 시간입니다.utterances: 발화 단위 결과 목록입니다.
전사 결과는 대략 다음과 같은 형태입니다.
{
"id": "transcribe-id",
"status": "completed",
"results": {
"utterances": [
{
"start_at": 52411,
"duration": 8780,
"msg": "전시회 구성은 1부에서 길상과 행복의 의미를 환기시킨 후에 2부와 3부에서 본격적으로 길상의 모습을 살펴볼 수 있도록 했습니다.",
"spk": 0
},
{
"start_at": 61711,
"duration": 5440,
"msg": "공간적으로 2부와 3부는 일종의 중전과도 같은 행복의 정원을 두고 연결됩니다.",
"spk": 0
}
]
}
}자동 챕터 생성에서는 msg를 문맥 분석에 사용하고, start_at을 챕터 타임스탬프로 사용합니다.
예를 들어 어떤 문단이 새로운 챕터의 시작점으로 선택되면, 그 문단에 포함된 첫 발화의 start_at 값을 가져와 00:02:39 같은 형식으로 변환합니다.
4. 전사 결과로 챕터 생성하기
챕터 생성하기
전사 결과가 준비되면 chapterize.py를 실행합니다.
uv run python chapterize.py data/transcripts/gilsang_winter_happiness.transcript.json결과 파일은 아래 경로에 저장됩니다.
data/outputs/gilsang_winter_happiness.chapters.json
data/outputs/gilsang_winter_happiness.chapters.mdMarkdown 결과는 다음 명령어로 확인할 수 있습니다.
cat data/outputs/gilsang_winter_happiness.chapters.mdchapterize.py는 크게 세 단계를 수행합니다.
- 전사 결과를 문단 단위로 읽습니다.
- 문단 임베딩을 이용해 챕터 경계를 찾습니다.
- 각 챕터에서 대표 발화를 선택합니다.
chapterize.py에서는 챕터 생성 흐름을 다음 함수들로 나누었습니다.
load_segments(): 전사 결과의utterances를 읽어 필요한 텍스트와 시작 시간을 추출합니다.encode_segments(): 각 문단을 임베딩 벡터로 변환합니다.calculate_c99_boundary_scores(): 문단 사이의 경계 점수를 계산합니다.select_ranked_boundaries(): 경계 점수의 상대 순위를 기준으로 최종 챕터 경계를 선택합니다.select_representative_segment(): 각 챕터를 대표할 발화를 고릅니다.
챕터 경계 찾기
chapterize.py는 전사 결과의 각 문단을 텍스트 임베딩 벡터로 변환합니다. 그 다음 인접한 문단 묶음 사이의 유사도를 비교해, 앞뒤 문맥이 크게 달라지는 지점을 챕터 경계 후보로 봅니다.
참고
이 예제에서는 C99 알고리즘의 아이디어를 간략화해 사용했습니다. C99는 Freddy Y. Y. Choi의 논문 Advances in domain independent linear text segmentation에서 제안된 텍스트 분할 알고리즘입니다.
처리 과정은 다음과 같습니다.
- 전사 문단을 임베딩 벡터로 변환합니다.
- 각 문단 벡터 사이의 코사인 유사도를 계산해 유사도 행렬을 만듭니다.
- C99 방식처럼 주변 값과 비교해 주변 순위 점수인 local rank 행렬로 변환합니다. local rank는 주변 값들과 비교했을 때의 상대적인 순위를
0부터1사이의 점수로 나타낸 값입니다. - 문단 사이마다 앞뒤 문단 5개씩을 묶어서 비교해 문맥 변화 정도를 계산합니다.
- 왼쪽 묶음 내부의 평균 rank, 오른쪽 묶음 내부의 평균 rank, 양쪽 묶음 사이의 평균 rank를 비교합니다.
- 왼쪽과 오른쪽은 각각 내부적으로 비슷하지만, 양쪽 사이의 값이 낮을수록 문맥이 바뀐 지점으로 봅니다.
- 이 차이를 경계 점수로 사용하고, 점수가 높은 지점을 챕터 경계 후보로 선택합니다.
경계 점수는 다음과 같은 직관으로 볼 수 있습니다.
경계 점수 = 좌우 내부 평균 rank - 좌우 사이 평균 rank예를 들어 어떤 위치를 기준으로 계산한 값이 다음과 같다고 가정해보겠습니다.
왼쪽 문단 묶음 내부 평균 rank: 0.78
오른쪽 문단 묶음 내부 평균 rank: 0.74
좌우 문단 묶음 사이 평균 rank: 0.32이 경우 좌우 내부 평균 rank는 0.76이고, 좌우 사이 평균 rank는 0.32입니다.
경계 점수 = 0.76 - 0.32 = 0.44왼쪽끼리도 비슷하고 오른쪽끼리도 비슷하지만, 왼쪽과 오른쪽 사이의 값은 낮기 때문에 이 지점은 문맥이 바뀌었을 가능성이 높은 후보가 됩니다.
즉, 경계 점수는 단순히 한 문단과 다음 문단만 비교한 값이 아니라, 앞뒤 문단 묶음이 서로 얼마나 다른지를 나타내는 값입니다. 그래서 짧은 말 한두 개에 흔들리기보다, 주변 흐름까지 함께 보고 챕터를 나누도록 했습니다.
챕터 개수 정하기
챕터 경계 후보가 너무 많으면 결과가 지나치게 잘게 나뉩니다. 반대로 후보를 너무 적게 선택하면 긴 음성의 구조가 잘 드러나지 않습니다.
이번 예제에서는 모든 경계 후보를 무조건 사용하지 않고, 경계 점수가 높은 후보를 우선적으로 선택합니다. 먼저 경계 점수를 높은 순서로 정렬하고, 전체 후보 안에서 상대적으로 높은 위치에 있는 후보만 남깁니다. 코드에서는 이 상대 순위 기준을 `0.385`로 고정했습니다. 결과가 너무 잘게 나뉘지 않도록 전체 전사 글자 수로 선택 가능한 경계 수의 상한을 정합니다. 마지막으로 너무 가까운 위치가 반복해서 선택되지 않도록 경계 사이에는 최소 5개 문단의 간격을 둡니다.
이 방식은 사용자가 직접 챕터 개수를 정하지 않아도 동작합니다. 다만 영상이나 강의처럼 원하는 목차 개수가 어느 정도 정해져 있다면, 이후 확장 단계에서 챕터 개수를 직접 지정하는 옵션을 추가해볼 수도 있습니다.
대표 발화 선택하기
각 챕터의 전사문에서 다음 기준으로 대표 발화를 하나 선택합니다.
- 챕터 안의 발화들을 형태소 분석기에 넣고 명사만 추출합니다.
- 여러 번 등장하거나 챕터 안에서 상대적으로 중요한 명사를 최대
8개까지 내부 키워드 후보로 둡니다. - 각 발화 후보에 대해 이 키워드들이 실제 문자열 안에 포함되어 있는지 확인합니다.
- 키워드가 더 많이 포함된 발화일수록 챕터 내용을 잘 대표한다고 보고 높은 점수를 줍니다.
- 너무 긴 발화는 핵심 키워드가 들어간 부분을 중심으로 짧게 발췌합니다.
- 최종 출력에는 키워드 목록을 따로 노출하지 않고, 사용자가 읽기 쉬운 대표 발화만 보여줍니다.
키워드 포함 여부는 생성 모델의 추정이 아니라 원문 문자열 기준으로 확인합니다. 예를 들어 챕터의 주요 키워드가 길상, 행복, 장수라면, 각 발화 안에 이 단어들이 실제로 포함되어 있는지 세고, 더 많은 핵심 단어를 포함한 발화를 우선적으로 선택합니다.
이 방식은 생성 모델을 사용하지 않기 때문에 실행 구조가 단순합니다. 또한 실제 전사문에서 발췌하므로, 원문에 없는 내용을 새로 만들어내지 않습니다.
5. 실행 결과
Markdown 결과는 사람이 바로 읽기 위한 출력입니다.
# Chapters: gilsang_winter_happiness
- **00:00:12**
- 대표 발화: ...겨울을 맞이하여 길상 특별전, 그 겨울의 행복을 개최합니다.
- **00:00:52**
- 대표 발화: ...행복한 순간을 담은 그림과 사진자료들을 영상으로 담아냈고, 복과 운을 바라는 여러 요소들을 전시하였습니다.
- **00:02:39**
- 대표 발화: 또한 대표적인 장수의 상징입니다. 출세, 즉 과거에 합격하여 입신 양명하고 부귀해지는 것 또한 옛 사람들이 꼽은 중요한 요소였습니다.
- **00:05:11**
- 대표 발화: ...가치에 대한 측면이었지만, 행복에는 즐거움, 만족감 같은 정서적인 측면도 있습니다.
- **00:06:01**
- 대표 발화: 전시 관람이라는 행위 자체가 행복한 경험이 되기를 바라는 취지에서 공간을 조성했고, 휴식과 관람이 조화를 이룰 수 있도록 구성하였습니다.JSON 결과는 후속 처리에 사용하기 위한 챕터 객체들의 배열입니다. 아래는 챕터 하나만 나타낸 예시입니다.
[
{
"number": 1,
"start_at": 12843,
"start": "00:00:12",
"representative_text": "...겨울을 맞이하여 길상 특별전, 그 겨울의 행복을 개최합니다.",
"segment_count": 5
}
]6. 마무리
이번 튜토리얼에서는 RTZR STT API로 음성 파일을 전사하고, 전사 결과를 이용해 자동 챕터와 타임스탬프를 생성해보았습니다.
긴 음성 콘텐츠에서는 전체 전사문만 제공하는 것보다, 사용자가 원하는 구간으로 바로 이동할 수 있는 구조가 중요합니다. 이때 RTZR STT API가 제공하는 발화 시작 시간, 문단 단위 전사 결과, Disfluency Filter는 후처리 파이프라인을 단순하게 만드는 데 도움이 됩니다.
별도의 오디오 정렬 과정 없이 타임스탬프를 만들 수 있고, 구어체 노이즈가 줄어든 문단을 입력으로 사용할 수 있기 때문에 자동 챕터 생성 같은 후처리 작업을 더 간단하게 구성할 수 있습니다.
이 예제는 별도의 LLM 없이 동작하도록 구성했지만, 필요하다면 이후 단계에서 요약 모델이나 LLM을 연결해 더 자연스러운 챕터 제목을 생성하도록 확장할 수 있습니다.
전체 예제 코드는 아래 GitHub 저장소에서 확인할 수 있습니다.