open-dictate
GitHub

open-dictate 按住、說話、放開,字就出現在游標。

Apple Silicon Mac 上的繁體中文語音輸入,本地優先。MLX Whisper 在你自己的電腦上把聲音轉成字,你自己的詞庫校正它認得的詞,整句話直接打進游標所在的位置。系統可以標記、可以建議,但不能靜默改寫你的意思。

早期公開版 · daemon 0.6.1 · MIT 授權 · macOS 14 以上 · Apple Silicon

01 · 示範 一次語音輸入,一步一步看

任何 app 的任何輸入框示範動畫

週會筆記

幫我把 TouchDesigner 的檔案放上 GitHub 的專案頁,明天下午三點前寄給大家。

Whisper 把兩個專有名詞聽成小寫、拆開的英文,你教過的兩組詞庫對照把它們改回來。「三點」照你說的留著。

3.4 s

按住 fn,輸入下一句。

用滑鼠、手指或空白鍵按住上面的 fn,再放開。句子是事先寫好的,這一頁不會聽你說話。

這是示範動畫。句子事先寫好,這一頁不會碰你的麥克風。真正在做事的,是你 Mac 上的選單列 app 和 daemon。 讀介面契約 →

02 · 運作方式

五個步驟,一條規則

你按住快捷鍵時,Swift 寫的選單列 app 開始錄音,放開後把 WAV 檔交給一直保持載入的 Python daemon。daemon 轉錄、校正、加標點,回傳文字,app 再把字打進游標所在的位置。轉錄之後,能改動你字詞的只有你放進詞庫的那些對照。

  1. 01 錄音

    按住鍵的時候

    按住 fn 或右側 Option,app 錄下 16 kHz 單聲道音訊。不到半秒的按壓直接丟掉,另有一道語音形狀檢查,擋下只錄到環境噪音的誤觸。

    rec = 3.4 s數值來自上面的示範

  2. 02 轉錄

    說了什麼

    MLX Whisper 在 Apple Silicon 上執行,模型一直載入在 daemon 裡。進下一步之前,先濾掉已知的字幕署名幻覺,截斷失控的重複迴圈。

    model = large-v3-turbo數值來自上面的示範

  3. 03 校正

    你的意思

    詞庫是一張誤聽 → 正確的對照表,只有命中的詞會被替換,整句話不會被改寫。寧可漏改,不可錯改。

    changes = 2數值來自上面的示範

  4. 04 標點

    讀起來的樣子

    預設的 smart_zh 規則只在中文語境把標點轉成全形,英文、數字、網址都不動。可以另外開本機 LLM 補完整標點,它只要動到標點以外的任何一個字,整段結果就作廢。

    punct = "smart_zh"數值來自上面的示範

  5. 05 插入

    字落在哪裡

    文字透過輔助使用(Accessibility)API 直接插入,不行再改用貼上。app 不會把焦點從你正在打字的輸入框搶走。

    insert → AX · paste數值來自上面的示範

快捷鍵之外

會議模式 Meeting Mode
本機錄音檔,或已經轉好的 JSON/JSONL 分段,整理成可審核的逐字稿包:Markdown、JSONL、SRT、VTT。用的是同一個 Whisper、同一份詞庫。
審核佇列 Review queue
掃描器把疑似誤聽和數字標成候選。你決定接受、修改或拒絕,只有接受的才進詞庫,每個決定都能復原。
說話者標籤 Speaker labels
逐字稿使用匿名標籤 SPEAKER_00、SPEAKER_01。辨識是誰在說話,規劃成另一層選用、只在本機的功能。
選單列 Menu bar
從上一句或選取的文字教詞庫、回報誤聽,也在這裡切換快捷鍵、麥克風和標點模式。

七條規則

  1. 只換詞,不改句。 詞庫對照可以修正一個詞,系統不能改寫整句話。
  2. 寧可漏改,不可錯改。 一組錯的對照會污染之後每一次轉錄,所以有風險的對照要等上下文或你的審核。
  3. 可以統一字形,不能改變意思。 繁體中文字形可以正規化,數字不會被自動改動。
  4. 音檔留在本機。 錄音、轉錄、校正,全部在你的 Mac 上執行。
  5. 不搶焦點。 介面不會把焦點從你正在使用的輸入框拿走。
  6. 聲音屬於生物特徵資料。 說話者資料和聲紋 embedding 只放本機,永遠不進 Git。
  7. 靠審核成長。 詞庫從你接受的建議慢慢變好,不會自己偷偷改。

這一頁用到的詞

按住說話 Push-to-talk
說話時按住一個鍵,放開就結束。Open Dictate 用 fn 或右側 Option。
常駐程式 Daemon
dictated.py,在背景一直把模型載入著的程式,短句不用等冷啟動。app 透過 Unix socket 跟它溝通。
MLX Whisper
在 Apple 的 MLX 框架上執行的 Whisper 語音辨識模型。預設是 whisper-large-v3-turbo。
詞庫對照 Glossary pair
一筆 誤聽 → 正確,例如 git hub → GitHub。只要誤聽那一邊出現,就照寫好的字替換。
語境對照 Contextual pair
只在某些句子裡才安全的對照。它不會被盲目套用,要等上下文或你來判斷。
審核佇列 Review queue
等你接受、修改或拒絕的候選對照。每個決定都留在歷史紀錄裡,可以復原。
smart_zh
預設的規則式標點:中文後面的半形標點轉成全形。結果固定,重跑幾次都一樣。
不改寫閘門 No-rewrite gate
選用 LLM 的輸出必須通過的檢查:拿掉標點之後,除了授權過的詞庫對照,必須和輸入一字不差。
SPEAKER_00
會議逐字稿裡的匿名說話者標籤。系統不確定是誰時寫 unknown,不硬猜人名。

03 · 現況

現在能用的

Open Dictate 是早期公開版(public seed),從一套每天在用的私人工具抽出來。語音輸入這條路已經能在 Apple Silicon Mac 上使用,會議、審核、說話者這幾層刻意保守。下面每一項的狀態都照抄 README。

功能

  • 按住說話輸入公開版
  • 本地 MLX Whisper daemon公開版
  • 確定性詞庫校正公開版
  • 繁中全形標點公開版
  • 選單列教詞庫公開版
  • 會議逐字稿包公開版音檔,或 JSON/JSONL 分段
  • 會議模式的本機音檔轉錄公開版MLX Whisper adapter
  • 轉錄後誤聽偵測MVP審核標記
  • 審核優先的詞庫成長MVP命令列佇列
  • 匿名說話者標籤MVP
  • 本機說話者身分規劃中敏感、選用的一層

安裝前先知道

  • 只支援 Apple Silicon、macOS 14 以上。Intel Mac 不支援。
  • 目前從原始碼安裝。需要 Git、Xcode Command Line Tools 和 Python 3.11 以上,還沒有 DMG。
  • 裝好的 app 依賴你 clone 下來的資料夾。搬動資料夾之後,跑一次 doctor,再重新安裝。
  • app 目前是 ad-hoc 簽章。重新建置後,macOS 可能要你把權限關掉再打開。
  • 第一次執行會下載模型,載入可能要兩分鐘左右。

變成一般 Mac app 的路

  1. 階段 0發布架構。 產品設定集中成一份,資料搬到 Application Support,執行時不再依賴 clone。
  2. 階段 1自帶執行環境的 beta。 Python 包進 app,首次啟動引導權限與模型下載,產出測試用 DMG。目標:在乾淨的 Mac 上只靠 Finder,十分鐘內完成第一次語音輸入。
  3. 階段 2簽章與公證。 Developer ID 簽章、Apple 公證,打開時不跳 Gatekeeper 警告,附 checksum。
  4. 階段 3更新與回滾。 簽章過的 app 內更新,Stable 與 Beta 兩個頻道,更新失敗自動退回,不碰你的資料。
  5. 階段 4更多管道。 Homebrew Cask。Mac App Store 要等 sandbox 的問題確認之後再評估。

macOS 發布路線圖 →

04 · 隱私

聲音留在你的 Mac

音檔、逐字稿、日誌、審核佇列和說話者資料都留在你的電腦,除非你自己匯出。語音輸入不需要伺服器,也不需要帳號。

資料放在哪裡

資料位置可以進公開 repo 嗎
語音輸入日誌~/.open-dictate/dictation-log/不行
會議逐字稿~/.open-dictate/meetings/不行
審核佇列~/.open-dictate/review-queue/不行
個人詞庫~/.open-dictate/glossaries/預設不行
說話者資料~/.open-dictate/speakers/永遠不行
公開測試資料fixtures/ · examples/可以,只限虛構內容

什麼時候會用到網路

  1. 安裝時。Python 套件從 PyPI 下載。
  2. 模型。第一次執行會從 Hugging Face 下載 mlx-community/whisper-large-v3-turbo。daemon 載入模型時,下載用的函式庫可能會檢查有沒有新版。這個請求只帶模型名稱,不含你的音檔或文字。
  3. LLM 標點,如果你打開它。它連到 127.0.0.1 上的 Ollama,也就是你自己 Mac 上的伺服器。如果你把 OPEN_DICTATE_PUNCT_LLM_URL 指到別台機器,文字就會送到那裡。
  4. 會議模式加上 --model。你指定的模型第一次使用時會下載。

就這些。模型下載到本機之後,沒有網路也能繼續語音輸入。

說話者 embedding 和聲紋屬於生物特徵資料。

只放本機、不進 Git、不貼到 issue,取得同意才建檔。要分享逐字稿時,用匿名標籤。

完整隱私模型 →

05 · 安裝

裝起來

Open Dictate 目前用一支腳本從原始碼安裝。需要 macOS 14 以上的 Apple Silicon Mac、Xcode Command Line Tools,以及 Python 3.11 以上。

terminal
git clone https://github.com/frank890417/open-dictate.git
cd open-dictate
./install.sh        # venv, app, LaunchAgents, model warm-up

安裝程式會建立虛擬環境、把 OpenDictate.app 建置到「應用程式」資料夾、依你的 clone 路徑寫好兩個 LaunchAgent,然後載入模型。要等 daemon 真的回應 ping 才算成功,只有 socket 檔案不算。

第一次會下載模型,可能要兩分鐘左右。網路慢的話:OPEN_DICTATE_WARM_TIMEOUT=300 ./install.sh。

接著開三個權限,設定一個鍵

  1. 系統設定 → 隱私權與安全性:在麥克風、輔助使用、輸入監控裡允許 OpenDictate。
  2. 系統設定 → 鍵盤:把按下 fn(地球)鍵的動作設成不執行任何動作。關掉 Apple 聽寫的 fn 快捷鍵,也關掉其他會搶同一個鍵的語音輸入 app。
  3. 點進任何輸入框,按住 fn,說話,放開。想改用右側 Option,從選單列切換快捷鍵。

出狀況的時候

terminal
./scripts/doctor.sh            # read-only checks, then a real ping
./uninstall.sh                 # removes the app and LaunchAgents, keeps ~/.open-dictate
./uninstall.sh --purge-data    # shows where your data is; never deletes it

doctor 會檢查 app、簽章、LaunchAgent、clone 路徑、socket,最後實際 ping 一次,什麼都不改。權限的部分它只能提醒,因為它不讀 macOS 受保護的權限資料庫。

會議和詞庫,從終端機操作

terminal
# Meeting Mode: a local recording in, a reviewable package out
python3 daemon/meeting_cli.py transcribe ~/Desktop/meeting.m4a --out /tmp/od-meeting --language zh

# the review queue: look, then decide
python3 daemon/glossary/cli.py candidates
python3 daemon/glossary/cli.py accept cand_xxxxx

中英混雜的會議用 --language auto。輸出 Markdown、JSONL、SRT、VTT,外加一份 JSON 摘要。

完整安裝說明 →

06 · 開發

一起開發

兩支程式,一份契約。Swift app 負責快捷鍵、麥克風和插入文字,Python daemon 負責轉錄和校正。兩邊透過 Unix socket 傳換行分隔的 JSON,IO-CONTRACT.md 是雙方共同的依據。

兩條路線

語音輸入
hold fn
  |
OpenDictate.app (Swift)       records 16 kHz mono PCM16 WAV
  |  {"cmd": "transcribe", "wav": "/tmp/...wav", "punct": "smart_zh"}
  v
/tmp/open-dictate.sock        newline-delimited JSON
  |
dictated.py (Python, warm)
  |-- MLX Whisper             audio -> raw text
  |-- glossary pairs          raw -> corrected   (muse_lexicon)
  |-- punctuation             smart_zh | llm_zh (gated) | raw
  |-- log                     ~/.open-dictate/dictation-log/
  v
{"ok": true, "text": "...", "raw": "...", "changes": [...]}
  |
OpenDictate.app  ->  Accessibility insert (paste fallback)  ->  cursor
會議模式
meeting.m4a   or   segments.json / .jsonl
  |
meeting_cli.py transcribe
  |-- MLX Whisper             audio files only
  |-- glossary pairs          the same deterministic table
  |-- speaker labels          SPEAKER_00, SPEAKER_01 (anonymous)
  |-- QA flags                possible mishearings, numbers
  v
transcript.md  .jsonl  .srt  .vtt  meeting-result.json

socket 協定

五個指令。讀取端會忽略不認得的欄位,所以 daemon 加診斷資訊不會弄壞 app。no_speech 是正常結果,不算錯誤。

IO-CONTRACT.md
# requests: one JSON object per line on /tmp/open-dictate.sock
{"cmd": "transcribe", "wav": "/tmp/open-dictate-rec-....wav", "punct": "smart_zh"}
{"cmd": "ping"}
{"cmd": "reload_lexicon"}
{"cmd": "add_pair", "wrong": "誤聽", "right": "正確", "source": "dictate-ui"}
{"cmd": "stats"}

# responses
{"ok": true, "text": "校正後文字", "raw": "whisper 原始輸出", "changes": [["誤聽", "正確"]], "punct": "smart_zh"}
{"ok": false, "error": "no_speech"}

app 的轉錄逾時和 daemon 的標點預算,這兩個常數本身就是一組契約。只調其中一個,app 會以為 daemon 離線,daemon 其實在背景把事情做完。公式寫在契約裡。

東西在哪裡

元件路徑
Swift 選單列 appOpenDictate/
語音輸入 daemondaemon/dictated.py
會議 CLIdaemon/meeting_cli.py
誤聽掃描器daemon/qa/mishear_detector.py
審核佇列daemon/glossary/
說話者層daemon/speaker/
內建詞庫vendor/tools/td-subtitle/glossaries/
詞庫引擎vendor/tools/muse-lexicon/muse_lexicon.py
語音輸入日誌(本機)~/.open-dictate/dictation-log/

建置與測試

terminal
./build.sh
python3 -m unittest discover tests
python3 scripts/golden-bench.py --skip-daemon
python3 scripts/public-safety-scan.py
./scripts/smoke-test.sh        # builds, tests, exports a meeting demo, checks the signed app

發 pull request 之前

  • 範例只用虛構或公有領域的內容。
  • 不要 commit 真實音檔、逐字稿、語音輸入日誌、私人路徑、個人詞庫或說話者資料。
  • 優先用確定性校正和審核佇列,不要靜默改寫。
  • 跑安全掃描和測試,有動到 Swift 就再跑 ./build.sh。

文件

文件中英混合,有幾份只有單一語言。

07 · 給 AI 代理

給 AI 代理

程式代理(coding agent)可以在終端機裡安裝、診斷、擴充 Open Dictate,需要的東西都是純文字:這一頁、/llms.txt 和介面契約。它做不到的是開 macOS 權限,這一步留給你。

在這台 Mac 安裝:貼給 Claude Code

prompt
Clone https://github.com/frank890417/open-dictate,先讀 README.md、docs/SETUP.md 和 docs/PRIVACY.md。
確認這台 Mac 是 Apple Silicon、macOS 14 以上(uname -m、sw_vers)。不符合就停下來告訴我。
執行 ./install.sh。完成後跑 ./scripts/doctor.sh 和 python3 daemon/dictate_cli.py ping,把結果告訴我。
告訴我要在系統設定開哪三個權限、fn 鍵要怎麼設。這些你沒辦法幫我開。

不要刪除 ~/.open-dictate 底下任何東西,裡面是我的詞庫和日誌。

改程式:貼給 Claude Code

prompt
Clone https://github.com/frank890417/open-dictate。動 daemon/ 或 OpenDictate/ 之前先讀 IO-CONTRACT.md,那是兩邊之間的契約。

任務:<描述要改的東西,例如「在 stats 回應裡加一個欄位」>

repo 的規則:校正只用詞庫對照,不改寫句子,寧可漏改,不可錯改。socket 協定要向下相容。fixture 和測試只用虛構資料。
完成的條件:python3 -m unittest discover tests、python3 scripts/golden-bench.py --skip-daemon、python3 scripts/public-safety-scan.py 都通過,有改 Swift 的話 ./build.sh 也要成功。

給機器讀的

  • /llms.txt這是什麼、處理流程、規則、所有文件,llmstxt.org 格式
  • /llms-full.txtREADME、IO-CONTRACT.md 和所有文件合成一個檔
  • IO-CONTRACT.mdapp、daemon、詞庫引擎之間的共同依據
  • application/ld+json這一頁帶有 SoftwareApplication 的 JSON-LD
  • /這一頁的英文版

MCP:規劃中,還沒做

目前還沒有 MCP 伺服器。路線圖的第一步是本機、唯讀、走 stdio 的伺服器,提供狀態、功能清單和詞庫數量。之後每個會動手的工具,都要在使用當下取得你的同意。用麥克風錄音仍然只能在 app 裡進行,沒有背景錄音,也沒有一次給到底的同意。

MCP 路線圖 →

08 · 來歷

從哪裡來

Open Dictate 從一套每天在用的私人語音輸入工具抽出來,再重新整理成公開安全、沒有歷史包袱的 repo:沒有錄音、沒有逐字稿、沒有個人詞庫,只留下引擎、介面契約和虛構的範例。

現在公開 repo 是唯一的依據。作者自己的私人版本,只在同一套核心與打包流程上保留個人設定和 adapter。姊妹專案 open-audiovisual 也是這樣公開的:先在真實的工作裡用過,再拿出來。

系統可以標記、可以建議,但不能靜默改寫意思。

設計原則 · README

接下來,摘自路線圖

  1. 本機的說話者資料,取得同意才建檔,只存在你的 Mac
  2. 在選單列審核詞庫候選
  3. 匯入、匯出屬於你自己的詞庫包
  4. 不依賴 clone、開發用 Python 或 Xcode 工具就能執行的 app
  5. Developer ID 簽章、經過公證、在乾淨 Mac 上測過的 DMG
  6. 簽章過的 app 內更新、回滾、Stable 與 Beta 頻道

README 裡的路線圖 →