-
녹음 파일을 넣으면 요약이 나온다 — 음성 자동 전사 & 요약 파이프라인 구축기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.wav16kHz는 음성인식 모델의 표준 샘플링 레이트이고, 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는 내부적으로 두 단계를 거친다.
- 세그멘테이션: 음성 구간 감지 + 화자 전환 시점 감지
- 임베딩 + 클러스터링: 각 구간의 "목소리 지문"을 추출하고, 같은 목소리끼리 묶어서 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가 초안을 생성하고, 작성자가 검토·편집하였습니다.
'IT' 카테고리의 다른 글
AI 검색시스템에 '무엇을' 넣을지 정하는 법 (0) 2026.03.23 OpenClaw를 250줄 게이트웨이로 교체한 이유 (0) 2026.03.22 GPU 하나로 AI 작업 두 개 돌리기 — 우선순위 스케줄러 만들기 (1) 2026.03.21 OAuth 2.0: 비밀번호를 넘기지 않고 권한만 빌려주는 방법 (3) 2026.03.20 pyannote 화자 분리가 GPU에서 안 돌아갈 때 — Blackwell nvrtc 패치 1줄의 힘 (0) 2026.03.19 음성을 텍스트로, 목소리를 사람으로 — Whisper와 pyannote가 풀어낸 두 가지 문제 (0) 2026.03.19 수만 장 가족사진에 AI가 메타데이터를 입히는 과정 — Immich + VLM 파이프라인 해부 (1) 2026.03.18 gogcli에서 gws로: REST API → CLI → AI Agent, 도구의 진화를 따라가다 (0) 2026.03.17 DGX Spark에서 Immich로 가족앨범 GPU 가속 관리하기 (1) 2026.03.16 DGX Spark에서 ONNX Runtime GPU 빌드 성공기 — 8번의 실패와 1번의 성공 (0) 2026.03.16