ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 녹음 파일을 넣으면 요약이 나온다 — 음성 자동 전사 & 요약 파이프라인 구축기
    IT 2026. 3. 19. 22:00

    세미나를 듣고 왔다. 핵심 내용을 정리해야 하는데, 1시간짜리 녹음 파일을 다시 듣기가 귀찮다. 녹음은 성실하게 했는데 정리는 영원히 밀린다.

    이 문제를 해결하기 위해 "녹음 파일을 폴더에 넣으면 요약 문서가 자동으로 나오는" 파이프라인을 만들었다. 폰에서 녹음하면 서버로 자동 동기화되고, AI가 전사하고, 화자를 구분하고, 요약하고, 저장하고, 알림까지 보내준다.

    이 글에서는 전체 아키텍처와 각 단계의 설계 의도를 정리한다.


    전체 아키텍처

    스마트폰 (음성 녹음)
        ↓ SFTP 자동 동기화 (FolderSync)
    서버 ~/voice-inbox/
        ↓ watchdog (파일 감지 즉시 트리거)
        ↓ Python 파이프라인
        ├─ 1. 필터링 (통화 녹음 제외)
        ├─ 2. 포맷 변환 (m4a → wav)
        ├─ 3. STT (Whisper large-v3, GPU)
        ├─ 4. 화자 분리 (pyannote-audio)
        ├─ 5. LLM 요약 (Claude CLI)
        ├─ 6. Markdown 저장 (knowledge vault)
        ├─ 7. Git commit & push
        └─ 8. 텔레그램 알림
    

    설계 원칙: 녹음 후 사람이 할 일은 없다. 폰에서 녹음 버튼을 누르고, 나중에 텔레그램에서 요약을 읽으면 된다.


    Step 1: 폰 → 서버 동기화

    스마트폰의 녹음 파일을 서버로 가져와야 한다. 클라우드를 경유하지 않고 SFTP(SSH)로 직접 전송한다.

    FolderSync 앱을 사용했다. 설정은 단순하다.

    • 프로토콜: SFTP (SSH 키 인증)
    • 방향: 단방향 (폰 → 서버)
    • 폰 폴더: Recordings/Voice Recorder/
    • 서버 폴더: ~/voice-inbox/
    • 주기: 15분마다 또는 Wi-Fi 연결 시

    보안상 SSH 키 인증을 사용한다. 비밀번호가 폰에 저장되지 않고, 클라우드를 경유하지 않으므로 녹음 파일이 외부에 노출되지 않는다.


    Step 2: 파일 감지 — watchdog

    서버에 파일이 도착하면 즉시 파이프라인이 시작되어야 한다. cron으로 주기적으로 체크하는 건 반응이 느리다.

    Python watchdog 라이브러리로 ~/voice-inbox/ 디렉토리를 실시간 감시한다. 파일이 생성되면 즉시 파이프라인을 호출한다.

    from watchdog.observers import Observer
    from watchdog.events import FileSystemEventHandler
    
    class VoiceFileHandler(FileSystemEventHandler):
        def on_closed(self, event):
            if is_audio_file(event.src_path):
                process(event.src_path)
    

    systemd 서비스로 등록하여 서버 부팅 시 자동으로 감시를 시작한다.

    중복 처리 방지

    동기화 앱이 같은 파일을 여러 번 전송하거나, 서비스 재시작 시 기존 파일을 다시 감지하는 문제가 있다. 처리 완료 기록(ledger) 파일로 해결했다.

    # processed.txt
    음성 250509_170658.m4a
    음성 260318_133858.m4a
    ...
    

    파이프라인 시작 시 ledger를 확인하고, 이미 처리된 파일이면 스킵한다. 완료 후 ledger에 기록한다.


    Step 3: 포맷 변환

    스마트폰 녹음 앱은 대부분 m4a(AAC) 포맷으로 저장한다. Whisper는 m4a를 직접 읽을 수 있지만, 화자 분리 모델이 사용하는 오디오 로더(soundfile)는 m4a를 지원하지 않는다.

    ffmpeg로 16kHz mono WAV로 변환한다.

    ffmpeg -y -i input.m4a -ar 16000 -ac 1 output.wav
    

    16kHz는 음성인식 모델의 표준 샘플링 레이트이고, mono로 변환하면 처리 속도가 빨라진다. 변환된 wav는 파이프라인 완료 후 자동 삭제한다.


    Step 4: 전사 — Whisper large-v3

    OpenAI Whisper large-v3 모델로 음성을 텍스트로 변환한다.

    import whisper
    
    model = whisper.load_model("large-v3", device="cuda")
    result = model.transcribe(audio_path, language="ko")
    

    왜 large-v3인가

    • 정확도 최우선: 세미나, 강의 등 전문 용어가 많은 녹음에서 작은 모델은 오인식이 잦다
    • 한국어 + 영어 혼용: "attention mechanism"이나 "fine-tuning" 같은 영어 용어가 섞여도 자연스럽게 전사
    • GPU 메모리: ~10GB 사용. 128GB 통합 메모리 서버에서 충분

    성능

    • 45분 녹음: 약 6분 (GPU)
    • 30분 녹음: 약 4분 (GPU)

    language="ko"를 명시하면 언어 감지 단계를 건너뛰어 더 빠르고 정확하다.


    Step 5: 화자 분리 — pyannote-audio

    Whisper 전사 결과에는 "누가 말했는지"가 없다. pyannote-audio 파이프라인으로 화자를 구분한다.

    from pyannote.audio import Pipeline
    
    pipeline = Pipeline.from_pretrained("pyannote/speaker-diarization-3.1")
    diarization = pipeline(audio_path)
    

    pyannote는 내부적으로 두 단계를 거친다.

    1. 세그멘테이션: 음성 구간 감지 + 화자 전환 시점 감지
    2. 임베딩 + 클러스터링: 각 구간의 "목소리 지문"을 추출하고, 같은 목소리끼리 묶어서 Speaker 1, 2, 3... 라벨 부여

    Whisper + pyannote 결합

    두 결과를 타임스탬프 기준으로 매핑한다. Whisper가 만든 각 텍스트 세그먼트의 중간 시점이 pyannote의 어떤 화자 구간에 속하는지 매칭한다.

    for segment in whisper_segments:
        mid = (segment["start"] + segment["end"]) / 2
        for turn, _, speaker in diarization.itertracks(yield_label=True):
            if turn.start <= mid <= turn.end:
                segment["speaker"] = speaker
                break
    

    결과:

    [00:12] Speaker 1: 오늘 주제는 AI 활용 사례입니다.
    [00:45] Speaker 2: 네, 그 부분에 대해서 질문이 있는데요.
    [01:23] Speaker 1: 추가로 말씀드리면...
    

    Step 6: LLM 요약 — Claude CLI

    화자별로 분리된 전사 텍스트를 Claude CLI에 넘겨서 구조화된 요약을 생성한다.

    claude -p --output-format text <<< "$transcript"
    

    프롬프트에서 요청하는 항목:

    • 녹음 유형 자동 분류 (세미나, 수업, 메모, 인터뷰 등)
    • 핵심 내용 요약 (3~5문장)
    • 화자별 주요 발언 정리
    • 주요 결정사항 목록
    • 액션 아이템 (후속 조치 필요 항목)
    • 적절한 한국어 제목 생성

    Claude가 JSON으로 응답하면 파싱하여 Markdown 노트로 변환한다.


    Step 7: 저장 구조

    생성된 요약 노트와 전사 원문을 knowledge vault에 저장한다.

    voice-notes/
    ├── 2026-03-18-세미나-주제.md        ← 요약 노트
    ├── raw/
    │   └── 2026-03-18-세미나-주제.txt   ← 화자별 전사 원문
    └── weekly/
        └── 2026-W12-digest.md           ← 주간 다이제스트
    

    요약 노트의 frontmatter:

    ---
    source: 음성녹음
    type: 세미나
    date: 2026-03-18
    duration: 45분 32초
    speakers: 3
    tags: [voice-note, 세미나]
    ---
    

    파일명에 날짜를 포함하고, Claude가 생성한 제목을 slug로 변환하여 사용한다. 동일 날짜에 같은 제목이면 번호를 붙여 중복을 방지한다.


    Step 8: Git push & 텔레그램 알림

    저장이 끝나면 자동으로 git commit & push한다. knowledge vault이 GitHub에 호스팅되어 있으므로, 어디서든 웹으로 접근할 수 있다.

    텔레그램 Bot API로 알림을 전송한다. 요약 내용과 GitHub 링크가 포함된다.

    🎙 음성 노트 처리 완료
    
    세미나 — AI 활용 사례
    유형: 세미나 | 길이: 45분 32초
    
    핵심 내용 요약...
    
    액션 아이템:
      • 관련 논문 리뷰
      • 다음 세미나 준비
    
    [요약 노트] | [전사 원문]
    

    녹음 후 약 10분이면 텔레그램에 알림이 온다.


    주간 다이제스트

    매주 일요일 아침, 지난 7일간 처리된 음성 노트를 종합하는 다이제스트를 자동 생성한다.

    • 총 녹음 수와 시간
    • 유형별 분류 (세미나 3건, 수업 2건...)
    • 주요 결정사항 통합
    • 미완료 액션 아이템 하이라이트

    cron으로 스케줄링하고, 결과를 텔레그램으로 전송한다.


    실전에서 마주친 문제들

    1. m4a 포맷 호환

    처음에는 Whisper에 m4a를 직접 넣었는데, 화자 분리 단계에서 soundfile이 m4a를 못 읽어서 실패했다. ffmpeg 변환 단계를 추가로 해결.

    2. SFTP 업로드 중 트리거

    FolderSync가 파일을 업로드하는 도중에 watchdog이 트리거되면 불완전한 파일을 처리하게 된다. 파일 안정화 대기 로직으로 해결했다 — 파일 크기가 일정 시간 동안 변하지 않으면 업로드 완료로 판단.

    3. 중복 처리

    같은 파일이 여러 번 처리되는 문제가 생각보다 다양한 경로로 발생했다.

    문제 상황:

    • systemd 서비스가 재시작되면 watchdog이 기존 파일을 "새 파일"로 다시 감지
    • 동기화 앱이 같은 파일을 재전송하면 또 트리거
    • ffmpeg가 m4a를 wav로 변환할 때 생성된 임시 파일을 watchdog이 "새 음성 파일"로 감지

    3중 방어로 해결했다:

    ① Ledger 파일 (processed.txt) — 처리 완료된 파일명을 한 줄씩 기록하는 텍스트 파일이다. 파이프라인 시작 시 이 파일을 확인하고, 이미 기록된 파일이면 즉시 스킵한다. 파이프라인이 정상 완료되면 파일명을 추가한다. 서비스가 재시작되어도 이 기록은 남아 있으므로 재처리를 방지한다.

    ② ffmpeg 임시 파일 필터링 — watchdog이 새 wav 파일을 감지했을 때, 같은 이름의 m4a 원본이 존재하면 "ffmpeg가 만든 임시 변환 파일"이므로 무시한다. 예를 들어 recording.wav가 감지됐는데 recording.m4a가 같은 폴더에 있으면 스킵.

    ③ In-memory Set — watchdog 프로세스가 살아 있는 동안 메모리에 "현재 처리 중인 파일 경로"를 Set 자료구조로 관리한다. 같은 파일에 대해 watchdog 이벤트가 여러 번 발생해도 (on_created + on_closed 등) 이미 Set에 있으면 무시한다. 처리 완료 후 Set에서 제거한다.

    이 세 가지가 각각 다른 시점과 다른 원인의 중복을 막는다. Ledger는 "과거에 처리한 적 있는지", ffmpeg 필터는 "변환 부산물인지", In-memory Set은 "지금 이 순간 처리 중인지"를 담당한다.


    결과

    현재 이 파이프라인으로 13개 녹음 파일을 처리했다. 파일을 폴더에 넣으면 10분 내에 요약이 텔레그램으로 온다.

    투입한 시간: 파이프라인 구축에 반나절.
    절약하는 시간: 녹음 1건당 30분~1시간의 다시 듣기 & 수동 정리.

    녹음은 계속 하는데 정리는 안 하고 있었다면 — 이 파이프라인이 "녹음의 가치"를 실현해준다. 녹음 버튼을 누르는 것만으로 지식이 축적된다.


    이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.

Designed by Tistory.