AI 탐구노트

WhisperX를 이용한 간이 회의 녹취 프로그램 만들어보기 본문

DIY 테스트

WhisperX를 이용한 간이 회의 녹취 프로그램 만들어보기

42morrow 2025. 5. 23. 12:00

 

전화통화 내용이나 회의 중 녹음한 내용을 다시 텍스트로 전환하는 것을 해 보겠습니다. 복수의 사람들이 말하는 것을 각각 구분할 수 있어야 하므로 '화자 분리'라는 기술이 사용됩니다. 

 

 

대략적으로 구현하려는 기능은 다음과 같습니다. 

 

1️⃣ 목표 기능 

 

1.입력

  • 오디오 파일 (mp3 또는 wav 파일)

2.처리 과정

  • 화자 분리 (diarization) : 허깅페이스에 공개된 pyannote/speaker-diarization-3.1 를 이용
  • 음성 텍스트 변환 (STT) : WhisperX 이용

3.추후 확장 (언제가 될까? -_-;)

  • 화자 별 오디오 시각화
  • 채팅창 형식의 Gradio 앱에 내용 표시
  • 다국어 지원 (언어 자동 감지)
  • 자동 번역 (EasyNMT, m2m100, opus-mt 또는 경량 LLM 이용)

 

2️⃣ 개발 환경 구성

 

화자 분리를 위한 필요 라이브러리 설치는 다음과 같이 진행합니다. 

# 기본 환경 설정 (conda 추천)
$ conda create -n audio_chat python=3.10
$ conda activate audio_chat

# 필요 라이브러리 설치
$ pip install pyannote.audio
$ pip install whisperx
$ pip install torchaudio
$ pip install librosa
$ pip install pydub
$ pip install matplotlib seaborn pandas
$ pip install gradio  # GUI용 (선택적)

 

허깅페이스 로그인 진행 (위 언급한 모델을 사용하기 위해서 API Key 도 필요)

$ huggingface-cli login

 

 

3️⃣ 기본적인 워크플로우

  • 오디오 로딩 및 준비
  • 화자 분리 진행
  • STT 처리 (WhisperX 이용)
  • 결과물 병합 (화자분리 + STT 텍스트)
  • 화자 별 오디오 신호 시각화 (matplot 이용한 파형 표시)
  • 채팅창 GUI 구현 (Gradio 이용)

 

4️⃣ 실제 음성으로 진행한 테스트

 

이런 과정을 거쳐 나온 것은 다음과 같습니다.

 

우선 테스트는 한국어 영상과 영어 영상 2가지로 해 봤습니다.

 

1) 한국어 영상 테스트 

 

한국어 영상의 경우, 유튜브에 공개된 방송 영상 중에 음원만 mp3로 일부(약 36초 정도) 가져와서 테스트를 진행해 봅니다. 총 4명이 예능 프로에서 두서없이 대화를 하는 방식으로 되어 있어 겹치는 구간이 다수 있으며 어떤 경우는 방송에서 음소거를 하는 경우도 있습니다. 

그림 : 예능 방송 중의 한 장면

 

한국어 영상의 경우, 대략 화자 구분과 각자의 대사는 다음과 같습니다. 

  • Speaker_00 : 홍진경
  • Speaker_01 : 지석진
  • Speaker_02 : 유재석
  • Speaker_03 : 조세호

그림 : 한국어 영상을 이용한 화자 분리 및 대사 확인

 

 

2) 영어 영상

 

영어 영상의 경우는 일론 머스크와 기자 간의 인터뷰 내용입니다. 총 2명이 등장하고 질문과 답변 형태로 대화를 이어나가는 방식으로 진행됐습니다. 겹치는 구간이 별로 없다는 얘기가 되겠죠.

 

그림 : TED 영상 (테슬라 기가 텍사스 공장에서 진행된 인터뷰 내용)

 

이 영상의 경우, 대략 화자 분리와 대사는 다음과 같습니다. 아무래도 겹치는 부분이 별로 없다보니 그런지 깔끔합니다. 

  • Speaker_01 : Chris Anderson (TED)
  • Speaker_02 : Elon Musk

그림 : 영어 영상을 이용한 화자 분리 및 대사 확인

 

 

5️⃣ 동시 발화 (Overlapping Speech) 문제

 

실제 방송 등에서는 여러 사람이 동시에 말하는 경우(overlap)이 자주 발생합니다. 화자 분리를 위해 사용한 pyannote.audio의 경우, 중첩(동시) 발화를 감지할 수 있으나 Whisper는 오디오의 일정 구간에 대해 하나의 텍스트만 출력할 수 있습니다. 그래서 위의 한국어 영상의 음성 분석과 같은 현상이 발생합니다. 

 

해결할 수 있는 방안으로는 다음과 같은 것이 있습니다.

  1. WhisperX의 diarization 모드 이용 : WhisperX 사용 시 diarization_speaker_turns 옵션을 활용해 정확히 speaker turn 기반으로 구간을 나눕니다. 
  2. 동시 발화 필터링 또는 정리 : 동일 구간에 여러 화자에게 같은 문장이 반복될 경우, 첫번째 화자만 남기고 나머지는 제겋하도록 합니다. 단 이 경우는 후처리로 진행됩니다. 

 

사용한 코드는 다음과 같습니다. 

import whisperx
import torch
import pandas as pd

device = "cuda" if torch.cuda.is_available() else "cpu"
audio_file = "test_ko.mp3"

# 1. Whisper로 ASR
model = whisperx.load_model("large-v2", device)
asr_result = model.transcribe(audio_file)

# 2. WhisperX로 alignment 모델 불러오기 (한국어 지원)
align_model, metadata = whisperx.load_align_model(language_code="ko", device=device)
aligned_result = whisperx.align(asr_result["segments"], align_model, metadata, audio_file, device)

# 3. WhispherX diarization 실행 (Annotation 객체 반환)
diarize_pipeline = whisperx.diarize.DiarizationPipeline(use_auth_token="hf_key", device=device)
annotation = diarize_pipeline(audio_file)
diarize_df = annotation  # 이미 DataFrame이므로 변환 불필요

# 4. 단어별로 화자 정보 매핑 (인자 순서 주의!)
final_result = whisperx.assign_word_speakers(diarize_df, aligned_result)

# 5. 중복 제거 함수 (동일 구간 내 다수 화자 발화 시 처리)
def remove_duplicate_speaker_segments(segments):
    seen = set()
    filtered = []
    for s in segments:
        key = (round(s['start'], 2), round(s['end'], 2), s['text'].strip())
        if key not in seen:
            filtered.append(s)
            seen.add(key)
    return filtered

filtered_segments = remove_duplicate_speaker_segments(final_result["segments"])

# 6. 결과 출력
for segment in filtered_segments:
    speaker = segment.get("speaker", "UNKNOWN")
    start = float(segment["start"])
    end = float(segment["end"])
    print(f"[{start:3.1f} ~ {end:3.1f}s] {speaker}: {segment['text']}")

 

적용한 결과는 다음과 같습니다. 

[0.0 ~ 6.8s] SPEAKER_01:  여기 뒤에 가짜의 삶.
[6.8 ~ 18.1s] SPEAKER_01: 일단 우리 가짜의 삶은 우리 조세호 씨, 우리 홍진경 씨, 우리 석삼이 형까지 해서 저에게 큰 웃음을 주시고 제가 항상 또 많이 또 좋아.
[18.2 ~ 22.6s] SPEAKER_02: 내가 뭐 두 분 기자회견 같은데.
[22.8 ~ 23.4s] SPEAKER_02: 우리 마주친 표정은.
[25.3 ~ 30.4s] SPEAKER_02:  존경하는 국민 여러분 안녕하십니까.
[31.9 ~ 33.4s] SPEAKER_02: 네 여러분들은 오해십니다.
[33.4 ~ 35.2s] SPEAKER_02: 오늘 가짜의 삶을 맞아요.
[35.2 ~ 37.1s] SPEAKER_02: 저희가 다 밝혀드리겠습니다.
[37.5 ~ 39.0s] SPEAKER_02: 모든 걸 밝히도록 하겠습니다.
[39.0 ~ 40.0s] SPEAKER_02: 오늘 왜 옷을 또 거부의 옷을 입고 왔죠.

 

대략 비슷하게 나온 것 같습니다. 하지만, 중복 제거를 실행할 경우, 같은 구간 내에 다수의 사람이 말하는 것 가운데 놓치는 것이 많이 생길 수 있다는 문제가 발생할 수 있습니다. 용도에 따라 해당 부분들을 선택적으로 적용되어야 할 것 같습니다.

 

그리고, 내용에서 Speaker_02가 너무 많이 나오는 부분은 발화자의 발화 내용이 너무 짧기도 했고 구간이 겹치는데 먼저 발화를 한 사람 기준으로 매핑이 되었기 때문으로 보입니다. 흠... 이것도 문제네요... 좀 더 섬세한 분리가 필요할 것 같은데... 이 부분은 난이도가 좀 있어 보이므로 향후 숙제로 넘겨야 할 듯 싶습니다. 

그림: 타임라인 분리 예시 (흠... 화자 별로 레인이 달라야 하는데 흠... 막 섞여 나오네요, 다시 만들어봐야할 듯...)


 

이번 글에서는 WhisperX를 이용해 오디오 파일에서 화자분리와 STT를 함께 적용해 보는 것을 해 봤습니다. 

 

인터뷰처럼 화자가 확실히 분리된 경우는 잘 되었지만 그렇지 않은 경우는 어려움이 있는 것 같습니다. 특히나 순서 가리지 않고 동일 구간에 아무말이나 막 하는 예능 프로그램의 경우는 더 그런 것 같습니다. 

 

간단한 회의록이 목적이었기 때문에 일반적인 회사 내 회의 속성에는 충분히 활용할 수 있을 것 같긴 합니다. 매번 말싸움만 하는 곳이 아니라면 말이죠. ^^;