
Dhwani는 Whistle을 사용해 브라우저에서 말한 내용을 사용자의 컴퓨터에서 텍스트로 변환하고, 원하는 폴더에 .txt 파일로 저장하는 자체 호스팅 음성 메모 앱이다. 음성과 메모 내용은 컴퓨터 밖으로 전송되지 않지만, 페이지의 Tailwind CSS 스타일을 불러오려면 인터넷 연결이 필요하다.
Dhwani(ध्वनि)는 힌디어와 산스크리트어로 ‘소리’를 뜻한다. 음성 인식은 Whistle을 이용해 로컬에서 실행한다. 메모는 데이터베이스가 아닌 일반 텍스트 파일로 저장하며, 앱은 Python 3.12, FastAPI, 바닐라 JavaScript, Tailwind CSS로 구성됐다.
작동 방식과 데이터 처리
서버는 사용자의 컴퓨터에서 실행되며 기본 주소 127.0.0.1에 연결된다. 브라우저는 마이크 음성을 16kHz로 녹음하고 16비트 모노 WAV로 인코딩해 서버에 보낸다. 서버는 Host와 Origin, 파일 크기, WAV 형식을 확인한 뒤 Whistle에 음성 인식을 요청한다. 변환된 텍스트는 메모 입력란에 추가되며, 사용자가 저장을 누르면 선택한 폴더에 새 .txt 파일을 만든다.
녹음은 한 번에 하나의 클립씩 처리한다. 브라우저에서 녹음은 최대 25초 후 자동으로 멈추며, 서버의 클립 제한은 30초다. 클립을 자동으로 여러 조각으로 나누지 않으므로 더 긴 메모는 여러 번 녹음해야 한다. 텍스트는 각 클립의 변환이 끝난 뒤 표시되고, 실시간 텍스트 변환은 지원하지 않는다.
오디오는 기본 설정에서 메모리에만 머문다. 브라우저가 클립을 전송하면 서버가 변환하고 오디오를 버린다. 예외는 선택적으로 켤 수 있는 디버그 모드다. VOICENOTES_DEBUG_CLIPS_DIR를 설정하면 클립과 변환된 텍스트를 지정한 폴더에 저장한다. 일반 사용에서는 이 설정을 비워 둔다.
앱은 CDN에서 Tailwind CSS 스타일시트만 불러온다. 따라서 메모와 음성 인식 데이터는 로컬에 남지만, 페이지를 꾸미려면 cdn.jsdelivr.net에 접속할 수 있어야 한다. 인터넷 연결이 없으면 페이지는 열리지만 스타일이 적용되지 않는다. 자세한 배경은 ADR-006에 설명돼 있다.
설치와 실행
저장소를 복제한 뒤 프로젝트 루트에서 초기 설정 스크립트를 한 번 실행한다.
./scripts/setup.sh
이 스크립트는 uv sync로 Python 의존성을 설치하고 테스트를 실행한 뒤 Whistle 엔진과 모델을 내려받는다. 최초 다운로드에는 네트워크가 필요하며, 이후에는 캐시를 재사용한다. 설정에는 uv가 필요하다. macOS와 Linux에서 사용할 수 있고, Windows에서는 WSL을 이용한다. 스크립트는 필요할 때 다시 실행해도 된다.
앱은 다음 명령으로 실행한다.
uv run python -m voicenotes
브라우저에서 http://127.0.0.1:8000을 연다. 홈페이지는 /, 메모 앱은 /app에 있다.
초기 설정을 건너뛰었다면 첫 사용 전에 모델을 직접 준비해야 한다.
HF_HUB_OFFLINE=0 uv run python -m voicenotes.warmup
초기 설정 스크립트도 Whistle 엔진과 모델을 Hugging Face에서 ~/.cache/cactus-needle/로 내려받을 때 한 차례 HF_HUB_OFFLINE=0을 설정한다. 준비가 끝난 뒤에는 음성 인식에 네트워크 연결이 필요 없다. 앱은 Whistle 사용 이벤트도 끈다. 이때 NEEDLE_TELEMETRY=0과 DO_NOT_TRACK=1 설정을 사용하며, 관련 내용은 ADR-007에서 확인할 수 있다.
메모 작성과 저장
먼저 절대 경로로 저장 폴더를 추가한다. 예를 들면 /Users/you/Documents/Notes다. 녹음 버튼을 누르고 말한 뒤 정지 버튼을 누르면 변환된 텍스트가 메모에 추가된다. 클립은 25초가 지나면 자동으로 멈춘다. 음성 대신 직접 입력할 수도 있다. 텍스트를 확인한 다음 폴더를 선택하고 ‘Save as .txt’를 누르면 저장된다.
메모 제목이 파일 이름이 된다. 같은 이름의 파일이 이미 있으면 (2), (3)을 붙여 새 파일을 만들며 기존 파일을 덮어쓰지 않는다. 앱에서 폴더 등록을 해제해도 디스크에 있는 파일은 삭제되지 않는다.
설정에서 VOICENOTES_HOST는 기본값 127.0.0.1인 수신 주소다. 앱에 로그인 기능이 없으므로 기본값을 유지해야 한다. VOICENOTES_PORT의 기본값은 8000이다. VOICENOTES_FOLDERS_PATH는 등록된 폴더 목록을 저장하는 파일 경로이며 기본값은 data/folders.json이다. VOICENOTES_KEYWORDS에는 음성 인식에서 우선 고려할 이름을 쉼표로 구분해 입력할 수 있다. 예를 들어 Priya,Tom을 설정하면 일부 이름의 인식에 도움이 되지만 모든 이름을 정확히 인식하지는 않는다. VOICENOTES_DEBUG_CLIPS_DIR는 디버그 전용으로, 설정하면 녹음 클립과 변환 텍스트를 저장한다.
접근 범위와 구현 구조
앱은 한 명의 사용자가 하나의 프로세스에서 쓰는 구성을 전제로 한다. Whistle은 CPU에서 한 번에 한 클립을 처리하며, 이 방식이 첫 번째 병목이다. 다음 확장 단계로는 작업자 풀을 두고 그 뒤에 작업 큐를 추가하는 방안이 제시돼 있다. 폴더 전체를 검색하는 기능은 없으며, 파일을 대상으로 검색하려면 별도 색인이 필요하다. 관련 설계 결정은 ADR-002와 ADR-004에 정리돼 있다.
로그인은 제공하지 않는다. 요청 가드는 로컬 Host와 Origin 헤더만 허용하며, 127.0.0.1 또는 localhost에서 오는 요청만 받아들인다. 로컬 전용 접근 방식은 ADR-008에 설명돼 있다.
앱의 구성은 다음과 같다. voicenotes/app.py는 라우트와 요청 가드, 오류 처리를 맡는다. voicenotes/transcription.py는 WAV 파싱, Whistle 연결, 개인정보 보호 관련 환경 설정을 담당한다. voicenotes/folders.py는 JSON 파일 하나에 폴더 목록을 저장하고, voicenotes/notes.py는 안전한 파일 이름과 덮어쓰지 않는 저장을 처리한다. voicenotes/config.py는 환경 설정을 읽는다. voicenotes/static/에는 home.html, app.html, app.js, recorder.js, recorder-worklet.js가 들어 있다. scripts/setup.sh는 최초 설정에 사용하며, adr/에는 아키텍처 결정 기록이 있다. 실행 진입점은 __main__.py이고, warmup.py는 설정 중 모델을 불러온다.
앱이 제공하는 API는 다음과 같다.
GET /: 홈페이지를 불러온다.GET /app: 메모 앱을 불러온다.GET /healthz: 상태를 확인한다.GET /api/folders: 등록된 폴더 목록을 반환한다.POST /api/folders: 절대 경로로 폴더를 등록한다.DELETE /api/folders/{id}: 폴더 등록을 해제한다. 파일은 디스크에 남는다.POST /api/transcriptions: WAV 클립 하나를 보내고 텍스트를 받는다.POST /api/notes: 메모를.txt파일로 저장한다.
아키텍처 결정 기록(ADR)은 선택의 배경과 결과, 재검토 조건을 설명한다. ADR-001은 서버에서 Whistle을 실행하고 FastAPI와 자체 호스팅을 사용하며 음성 인식에는 외부 네트워크를 쓰지 않는 계획을 다룬다. ADR-002는 사용자 지정 폴더의 .txt 파일 저장을, ADR-003은 브라우저에서 16kHz로 음성을 녹음하는 방식을 다룬다. ADR-004는 브라우저의 25초 제한과 서버의 30초 제한, 자동 분할을 하지 않는 결정을 기록한다. ADR-005는 디버그 모드에서만 오디오를 보관하는 설정을 설명한다. ADR-006은 CDN에서 Tailwind CSS를 가져오고 홈페이지와 앱을 각각 /와 /app에 두는 구성을 다룬다. ADR-007은 Whistle 텔레메트리 비활성화, 기본 오프라인 실행, 영어 전용 인식과 키워드 편향 설정을 기록한다. ADR-008은 로컬 Host와 Origin 헤더만 받는 요청 가드에 관한 기록이다.
테스트와 한계
테스트는 다음 명령으로 실행한다.
uv run pytest
테스트 모음은 폴더 규칙, 파일 이름과 저장, API, WAV 파싱을 다루며 실제 음성 변환기 대신 가짜 변환기를 사용한다. 실제 Whistle 경로는 수동으로 확인했다.
Dhwani는 영어만 지원한다. 이름은 VOICENOTES_KEYWORDS를 설정해도 Whistle이 놓칠 수 있으며, 이 경우 텍스트를 직접 고쳐야 한다. 파일 저장은 원자적으로 이뤄지지 않아 저장 중 앱이 충돌하면 파일 일부만 남을 수 있다. 폴더 간 검색도 지원하지 않는다. 마지막으로 Tailwind CDN에 연결하지 못하면 페이지에 스타일이 적용되지 않는다.
프로젝트는 Snehal Dutta가 만들었다.
댓글 (0)
로그인하면 이 기사에 내 생각을 남길 수 있어요