<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Python on 42Class</title><link>https://42class.com/tags/python/</link><description>Recent content in Python on 42Class</description><generator>Hugo -- gohugo.io</generator><language>ko</language><lastBuildDate>Sun, 05 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://42class.com/tags/python/index.xml" rel="self" type="application/rss+xml"/><item><title>Meetnote 개발 회고 — 로컬 회의록 자동화 Obsidian 플러그인</title><link>https://42class.com/posts/meetnote-retrospective/</link><pubDate>Sun, 05 Apr 2026 00:00:00 +0000</pubDate><guid>https://42class.com/posts/meetnote-retrospective/</guid><description>&lt;h2 id="왜-만들었나"&gt;왜 만들었나
&lt;/h2&gt;&lt;p&gt;회의가 끝나고 나면 항상 같은 고민이 반복됐다. &amp;ldquo;누가 뭐라고 했더라?&amp;rdquo; 회의록을 작성하자니 회의에 집중이 안 되고, 녹음만 해두면 다시 듣기가 귀찮다. 기존 SaaS 서비스들은 비용도 문제지만, 회의 내용이 외부 서버로 나가는 것 자체가 부담스러웠다.&lt;/p&gt;
&lt;p&gt;그리고 결정적인 이유가 하나 더 있었다. 지금 모든 업무 문서를 Obsidian에서 마크다운으로 관리하고 있는데, 이건 AX(AI Transformation) 관점에서 매우 중요한 선택이었다. &lt;strong&gt;마크다운은 AI 에이전트에게 전달하기 가장 좋은 포맷이다.&lt;/strong&gt; 회의록도 마크다운으로 생성되면 별도 서비스에서 옮기는 번거로움 없이, 다른 업무 문서들과 함께 한 곳에서 관리할 수 있다. 에이전트가 과거 회의 맥락을 참조하고, 액션 아이템을 추적하고, 관련 문서를 연결하는 것까지 자연스럽게 이어진다.&lt;/p&gt;
&lt;p&gt;그래서 만들었다. &lt;strong&gt;완전히 로컬에서 동작하는 회의록 자동화 도구.&lt;/strong&gt; 비용 0원, 오프라인 가능, GPU 가속. 그리고 결과물은 Obsidian Vault 안의 마크다운 파일.&lt;/p&gt;
&lt;h2 id="무엇을-만들었나"&gt;무엇을 만들었나
&lt;/h2&gt;&lt;p&gt;&lt;a class="link" href="https://github.com/changsu-jin/meetnote" target="_blank" rel="noopener"
 &gt;Meetnote&lt;/a&gt;는 Obsidian 플러그인 + Python 백엔드로 구성된 로컬 회의록 자동화 도구다.&lt;/p&gt;
&lt;p&gt;핵심 기능:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;실시간 음성 인식&lt;/strong&gt; — Whisper large-v3-turbo, 5초 단위 스트리밍&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;화자 분리&lt;/strong&gt; — pyannote 3.1, GPU 자동 감지&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;화자 식별&lt;/strong&gt; — 임베딩 DB 기반, 회의할수록 정확도 향상&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI 요약&lt;/strong&gt; — Claude CLI / Ollama, 액션 아이템 자동 생성&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;이메일 발송&lt;/strong&gt; — 참석자별 회의록 HTML 메일&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;암호화&lt;/strong&gt; — AES 암호화, 자동 삭제, 감사 로그&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Apple Silicon 기준 60분 회의를 약 5분 만에 처리한다.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://42class.com/img/meetnote-screenshot.png" alt="Meetnote 실제 사용 화면" loading="lazy" class="gallery-image"&gt;
&lt;/p&gt;
&lt;h2 id="기술-스택"&gt;기술 스택
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;영역&lt;/th&gt;
 &lt;th&gt;기술&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Plugin&lt;/td&gt;
 &lt;td&gt;TypeScript, Obsidian API, Web Audio API&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Backend&lt;/td&gt;
 &lt;td&gt;Python, FastAPI, WebSocket&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;STT&lt;/td&gt;
 &lt;td&gt;mlx-whisper (Apple Silicon), faster-whisper (CPU)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;화자 분리&lt;/td&gt;
 &lt;td&gt;pyannote.audio 3.1&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;화자 임베딩&lt;/td&gt;
 &lt;td&gt;wespeaker-voxceleb-resnet34-LM&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;AI 요약&lt;/td&gt;
 &lt;td&gt;Claude CLI, Ollama&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;배포&lt;/td&gt;
 &lt;td&gt;Docker, GitHub Actions, GHCR&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="아키텍처"&gt;아키텍처
&lt;/h2&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;┌─────────────┐ WebSocket (PCM 16kHz) ┌──────────────┐
│ Obsidian │ ──────────────────────────────▶│ FastAPI │
│ Plugin │◀────────────── JSON ──────────│ Backend │
│ (TS) │ │ (Python) │
└─────────────┘ └──────┬───────┘
 │
 ┌─────────────────┼─────────────────┐
 │ │ │
 ┌─────▼─────┐ ┌──────▼──────┐ ┌──────▼──────┐
 │ Whisper │ │ pyannote │ │ Speaker │
 │ STT │ │ Diarizer │ │ Embedding │
 └───────────┘ └─────────────┘ └─────────────┘
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Plugin이 Web Audio API로 마이크 음성을 캡처해서 5초 단위로 WebSocket을 통해 바이너리 전송한다. 백엔드는 Whisper로 실시간 전사하고, 녹음 종료 후 pyannote로 화자 분리를 수행한다. 전사 결과와 화자 분리 결과를 시간 기반으로 병합해서 &amp;ldquo;누가 무슨 말을 했는지&amp;rdquo; 최종 결과를 만든다.&lt;/p&gt;
&lt;h2 id="개발-과정에서-마주친-문제들"&gt;개발 과정에서 마주친 문제들
&lt;/h2&gt;&lt;h3 id="1-mlx-vs-faster-whisper--플랫폼별-분기"&gt;1. MLX vs faster-whisper — 플랫폼별 분기
&lt;/h3&gt;&lt;p&gt;Apple Silicon에서는 MLX가 압도적으로 빠르지만, Linux/CUDA 환경에서는 faster-whisper가 낫다. 처음에는 하나로 통일하려 했지만, 성능 차이가 너무 커서 런타임에 플랫폼을 감지하고 분기하는 방식을 택했다.&lt;/p&gt;
&lt;h3 id="2-화자-분리와-전사-결과-병합"&gt;2. 화자 분리와 전사 결과 병합
&lt;/h3&gt;&lt;p&gt;Whisper와 pyannote는 독립적으로 동작한다. 둘의 타임스탬프가 정확히 일치하지 않기 때문에, 시간 구간의 최대 겹침(temporal overlap)을 기준으로 화자를 배정했다. 같은 화자의 연속 발언은 5초 이내 간격이면 하나로 합친다.&lt;/p&gt;
&lt;h3 id="3-환각hallucination-필터링"&gt;3. 환각(Hallucination) 필터링
&lt;/h3&gt;&lt;p&gt;Whisper는 가끔 무음 구간에서 환각을 일으킨다. &lt;code&gt;no_speech_prob&lt;/code&gt;과 &lt;code&gt;compression_ratio&lt;/code&gt;를 기준으로 품질 필터를 넣어 해결했다. LLM 기반 전사 보정도 추가했는데, 고유명사나 전문 용어의 정확도가 체감될 정도로 올라갔다.&lt;/p&gt;
&lt;h3 id="4-동시-녹음-세션-처리"&gt;4. 동시 녹음 세션 처리
&lt;/h3&gt;&lt;p&gt;Transcriber와 Diarizer는 GPU 메모리를 많이 쓰기 때문에 싱글톤으로 관리한다. 하지만 여러 사용자가 동시에 녹음할 수 있어야 하므로, WebSocket 세션별로 격리하되 공유 리소스는 Lock으로 보호하는 구조로 만들었다.&lt;/p&gt;
&lt;h3 id="5-docker-빌드-최적화"&gt;5. Docker 빌드 최적화
&lt;/h3&gt;&lt;p&gt;처음 Docker 빌드는 46분이 걸렸다. PyTorch + pyannote 의존성이 무거웠기 때문이다. base 이미지와 app 이미지를 분리하고, GitHub Actions에서 base 이미지를 캐싱하는 방식으로 빌드 시간을 3분까지 줄였다. 이후 추가 최적화로 1분대까지 단축했다.&lt;/p&gt;
&lt;h2 id="누적-학습--화자-임베딩-db"&gt;누적 학습 — 화자 임베딩 DB
&lt;/h2&gt;&lt;p&gt;가장 만족스러운 기능이다. 회의에서 새로운 화자가 감지되면 임베딩 벡터를 자동 저장한다. 다음 회의에서 코사인 유사도(임계값 0.70)로 기존 화자와 매칭한다. 회의를 할수록 화자 인식 정확도가 올라가는 구조다.&lt;/p&gt;
&lt;h2 id="타임라인"&gt;타임라인
&lt;/h2&gt;&lt;p&gt;집중적으로 작업한 건 약 일주일. 115개 커밋, 혼자서 기획부터 배포까지.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Phase 0&lt;/strong&gt; — MVP: 실시간 전사, 화자 분리, 요약, 암호화, RAG 검색&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Phase 1&lt;/strong&gt; — 안정화: 버그 수정, 환각 필터, 품질 최적화&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Phase 2&lt;/strong&gt; — UX: 사이드 패널, 화자 관리 UI, 이메일, 원클릭 설치&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Phase 3&lt;/strong&gt; — 배포: Docker, GHA 캐싱, 온보딩 모달, 보안 강화&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="돌아보며"&gt;돌아보며
&lt;/h2&gt;&lt;p&gt;일주일이라는 짧은 기간이었지만, &amp;ldquo;내가 직접 쓸 도구&amp;quot;를 만드는 과정이라 몰입도가 높았다. 특히 화자 임베딩 DB의 누적 학습은 쓸수록 똑똑해지는 느낌이 있어서 뿌듯하다.&lt;/p&gt;
&lt;p&gt;아쉬운 점은 Windows 네이티브 지원이 아직 Docker(WSL2) 의존이라는 것. 그리고 CPU 환경에서는 60분 회의 처리에 40분이 걸려서 실용성이 떨어진다.&lt;/p&gt;
&lt;p&gt;다음 목표는 웹 기반 대시보드와 팀 단위 화자 DB 공유 기능이다.&lt;/p&gt;
&lt;hr&gt;
&lt;div style="background: linear-gradient(135deg, #24292e 0%, #40464d 100%); border-radius: 12px; padding: 24px 32px; text-align: center; margin-top: 32px;"&gt;
 &lt;p style="color: #f0f0f0; font-size: 1.2em; margin-bottom: 12px; font-weight: bold;"&gt;Meetnote — 로컬 회의록 자동화&lt;/p&gt;
 &lt;p style="color: #8b949e; font-size: 0.9em; margin-bottom: 16px;"&gt;Obsidian plugin for local meeting transcription, speaker diarization, and AI summarization&lt;/p&gt;
 &lt;a href="https://github.com/changsu-jin/meetnote" style="display: inline-block; background: #238636; color: white; padding: 10px 24px; border-radius: 8px; text-decoration: none; font-weight: bold; font-size: 1em;"&gt;GitHub에서 보기 →&lt;/a&gt;
&lt;/div&gt;</description></item></channel></rss>