‹ 首页

opencode-groq-api

@mathruffian-dot · 收录于 昨天 · 上游提交 昨天

用 Groq 的免費 API 做「語音轉字幕」與「逐字稿清洗」,並把整條流程包成可重複使用的技能。說「語音轉字幕」「影片上字幕」「做逐字稿」「Whisper」「Groq」「把錄音整理成講義」「把 API 變成工具」時載入。

适合你,如果经常需要将录音或视频转成可编辑的文字稿

/ 通过 npx 安装 校验哈希
npx oh-my-skill add mathruffian-dot/opencode-lazy-packs/opencode-groq-api
/ 通过 bash 安装
curl -fsSL https://oh-my-skill.com/install.sh | bash -s -- mathruffian-dot/opencode-lazy-packs/opencode-groq-api
/ 已经装过?验证本机副本,不用重装
npx oh-my-skill verify mathruffian-dot/opencode-lazy-packs/opencode-groq-api
安装目标可用 --agent / --scope 或 --to 明确指定;省略时只会在唯一已存在的 agent 目录上自动选择,零命中或多命中会停止并提示。content_hash 缺失或不一致均拒装。
53GitHub stars
~18.4K上下文体积 · 单文件
索引托管

怎么用

商店整理自技能原文 · 版本 1d61b46 · 表述以原文为准
它做什么

装上此技能后,当你提供录音或影片文件,Claude会先调用Groq的Whisper模型转成字幕(.srt),再用文字模型清洗成讲义(.md)。

什么时候触发

当你说出“语音转字幕”、“做逐字稿”、“把录音整理成讲义”等关键词,或提到“Whisper”、“Groq”时触发。

装好后可以这样说
自动生成.srt和.md文件。
使用Groq文字模型清洗。
技能原文 SKILL.md作者撰写 · MIT · 1d61b46

把 API 變成自己的工具(Groq)

官方文件查證日期:2026-07-27 依據:<https://console.groq.com/docs/speech-to-text>、<https://console.groq.com/docs/api-reference>、<https://console.groq.com/docs/rate-limits>、<https://console.groq.com/docs/models>、<https://console.groq.com/docs/your-data>、<https://console.groq.com/docs/billing-faqs>、<https://console.groq.com/docs/quickstart>

🔴 先看這裡:隱私紅線(這一段是本包最重要的內容)

這一包做的每一件事,都會把你的聲音檔送到別人的伺服器上。

不是「處理完就消失」那種送,是整個音檔上傳、在美國的機器上跑完、再把文字傳回來。你電腦上的麥克風錄到什麼,就送出去什麼。

這三類錄音,一律不要上傳

| ❌ 不要上傳 | 為什麼 | |------------|--------| | 含學生聲音的課堂錄音 | 學生是未成年人。要不要讓孩子的聲音上傳到境外雲端,這件事你不能代替家長同意。 | | 家長會談、親師溝通、電話錄音 | 對方通常不知道有錄音,更不知道會被上傳 | | 輔導紀錄、個案會議、學生私下談話 | 這類內容一旦外流,受傷的是孩子,而且你會有法律責任 |

順便三條也不要:唸到學生姓名的點名錄音學生口說測驗的作答錄音任何你不確定裡面有誰在講話的檔案

這些可以上傳
  • 你自己一個人講課、備課、口述的錄音(只有你的聲音
  • 你自己的公開演講、研習分享
  • 你自己錄的旁白、口播稿
  • 公開影片(新聞、公播教材、你自己頻道的影片)
一句話的判斷標準
## 🔴 「這個音檔如果明天出現在網路上,會不會有人受傷?會不會需要通報?」 只要答案是「會」或「不確定」——不上傳。沒有例外,不看時間趕不趕。
⚠️ 最容易出事的地方:檔案結尾

課堂錄音最常見的意外不是內容,是「忘記按停止」。

你以為錄的是自己講課的 40 分鐘,實際上錄音一直開到下課、開到學生跑上來問問題、開到你跟隔壁老師聊完。

🔴 規矩:上傳前先跳到檔案的最後 30 秒聽一遍。 這一步花 30 秒,可以擋掉這一包 90% 的真實風險。
Groq 官方的資料政策(查證 2026-07-27)

講完紅線再講政策,順序不能反——政策再好,也不會讓紅線消失。

| 項目 | 官方說法 | 出處 | |------|---------|------| | 推論請求的資料會保留嗎 | 預設不保留("By default, Groq does not retain customer data for inference requests.") | Your Data in GroqCloud | | 會拿去訓練模型嗎 | 不會。服務協議明訂:未經客戶明示同意,不得將輸入/輸出用於訓練或微調模型 | Groq Services Agreement | | 有例外嗎 | 。為了排除故障、調查濫用,可能暫時記錄輸入與輸出,最長保留 30 天(法律要求時可能更久) | Your Data in GroqCloud | | 可以完全關掉嗎 | 可以。所有客戶都能在 Console 的 Data Controls 開啟 Zero Data Retention(ZDR) | Your Data in GroqCloud | | 免費方案有比較差嗎 | 官方文件沒有為免費方案另訂較寬鬆的規定,寫的是同一套 | 同上 |

建議研習前先做一件事:登入 Console → SettingsData Controls → 開啟 Zero Data Retention。花 10 秒,之後所有請求都適用。

🔴 但是——開了 ZDR,紅線一條都不會消失。 政策是廠商的承諾,不是「你的檔案沒有離開過你的電腦」的技術保證。 中間還有網路、還有你的電腦、還有你等一下會不會把 srt 檔傳到 LINE 群組。 真正安全的做法只有一個:那個檔案根本不要上傳。
真的非處理不可怎麼辦

有些內容就是不能上雲端,但你又真的需要逐字稿。三條路:

  1. 請當事人自己來:讓學生/家長自己用手機的語音輸入打字,你只收文字
  2. 本機跑:在自己電腦上跑離線的語音辨識模型(不連網、不上傳)。這條不在本研習範圍,也不在本包
  3. 不做:這是完全合理的選項。省下的 20 分鐘,遠遠比不上一次個資事件

這一包在研習裡的位置:作品⑤「把 API 變成自己的工具」

第三天上午。這一包做的是「內部工具」——只有你自己在電腦上用,還不碰網頁。

前面幾包是「把別人做好的東西接進來」(NotebookLM、Obsidian、Supabase)。這一包不一樣:

你會拿一個原始的 API,做出一個以後可以一直用的東西。

這是整套研習裡第一次「呼叫外部服務、拿回結果、包成工具」。學會這個模式,之後接任何服務都是同一套邏輯。

🔑 這一包成不成功,判斷標準只有一個

| | 跑一次 | 變成工具 | |---|-------|-------------| | 你做的事 | 貼一串指令,看到 srt 跑出來 | 對 OpenCode 說一句人話,它自己做完 | | 換一份新素材 | 要回來翻講義、重貼指令 | 同一句話再說一次就好 | | 下個月還會用嗎 | 不會,因為忘了怎麼跑 | 會,因為只要記得那句話 | | 這一包的驗收 | ❌ 不算完成 | ✅ 這才算完成 |

所以步驟六(包成 Skill)不是加分題,是這一包的正題。 前面五個步驟都只是為了搞清楚「那句人話背後要做什麼」。

📌 下午的 10-netlify 會把這個工具再往外推一層:變成一個網頁,讓別人也能用。 今天上午先把「自己能用」做穩,下午才有東西可以搬上網。

這一包會幫你做什麼

兩條主線,都用同一把金鑰:

| 線 | 做什麼 | 輸入 → 輸出 | 用到的服務 | |----|--------|-------------|-----------| | A 轉字幕 | 把聲音變成有時間碼的字幕檔 | .mp4 / .mp3 / .m4a.srt | Groq 的 Whisper | | B 文字清洗 | 把逐字稿整理成能發給學生看的講義 | .srt / .txt.md | Groq 的文字模型 |

A 線的產物剛好是 B 線的原料。合起來就是:一段錄音 → 一份講義


🚨 這一包的四個陷阱(先看完再動手)

| # | 陷阱 | 症狀 | 一句話解法 | |---|------|------|-----------| | ① | Groq 沒有 response_format=srt | 照 OpenAI 的教學打,回 400 | 拿 verbose_json自己組 srt(步驟三附小程式) | | ② | 免費方案單檔上限 25 MB | 一支 40 分鐘影片直接被打回 | 先用 ffmpeg 壓成 16k 單聲道,超過 25 MB 就切段 | | ③ | Whisper 的中文不分繁簡 | 辛苦轉完,字幕是簡體 | language=zh 只能到「中文」;繁體要另外處理(步驟三 A5) | | ④ | 切段後時間碼要自己加偏移 | 第二段字幕全部從 00:00 開始,整份對不上畫面 | 用每一段的實際長度累加,不要用名目的「10 分鐘」 |

陷阱 ① 是最花時間的一個——網路上 95% 的 Whisper 教學都是寫給 OpenAI 的,那邊有 srt,Groq 沒有。 你會以為自己打錯字。


免費方案(不用綁信用卡)
⚠️ 以下為 2026-07-27 查到的數字。服務商隨時會調整,以官方頁面為準(本節最後教你怎麼自己查)。
要不要綁卡

| 問題 | 答案(查證 2026-07-27) | |------|------------------------| | 註冊要不要信用卡 | 不用。 用 Google/GitHub/Email 註冊,直接就能產金鑰 | | 免費用量要不要信用卡 | 不用。 免費方案是「限速」不是「限時」,沒有試用到期這回事 | | 什麼時候才需要卡 | 只有升級 Developer 方案時。官方文件寫得很清楚:升級需要「a valid payment method(credit card, US bank account, or SEPA debit account)」 |

🔴 研習現場的規矩:整個過程不會有任何畫面要你輸入卡號。 只要看到要求付款方式的畫面,代表你走進了升級流程——按上一頁退出來,不要輸入。 這一包主線完全不需要 Developer 方案。
免費額度(Whisper 語音)

| 項目 | 免費方案 | 換算成老師聽得懂的話 | |------|---------|---------------------| | 單檔大小上限 | 25 MB | 壓成 16k 單聲道 FLAC 後,約 25~30 分鐘的音訊 | | 每分鐘請求數 | 20 次 | 一次跑 20 個檔沒問題 | | 每日請求數 | 2,000 次 | 用不完 | | 每小時音訊秒數 | 7,200 秒 | 每小時可以轉 2 小時的音訊 | | 每日音訊秒數 | 28,800 秒 | 每天可以轉 8 小時的音訊 | | 最短音訊 | 0.01 秒 | — | | 最低計費長度 | 10 秒 | ⚠️ 見下方 |

⚠️ 「最低計費 10 秒」這條會咬人。 意思是:再短的檔,也至少吃掉 10 秒額度。 所以「把一堂課切成 200 個小片段各自送一次」是最浪費的做法——200 個 3 秒的檔會被算成 2,000 秒。 正確做法:切大段(10 分鐘一段),不要切小段。

(付費的 Developer 方案單檔上限是 100 MB。本包不走那條,寫在這裡只是讓你看到別人的教學寫 100 MB 時不會困惑。)

免費額度(文字模型,B 線用)

| 模型 | 每分鐘請求 | 每日請求 | 每分鐘 token | 每日 token | |------|-----------|---------|-----------------|-----------| | llama-3.3-70b-versatile | 30 | 1,000 | 12,000 | 100,000 | | openai/gpt-oss-120b | 30 | 1,000 | 8,000 | 200,000 | | llama-3.1-8b-instant | 30 | 14,400 | 6,000 | 500,000 |

🚨 這裡有一個沒人講、但一定會撞到的坑: 「每分鐘 token」小得驚人。 一小時的中文演講逐字稿大約 9,000~12,000 字,換算下來遠遠超過 12,000 token 的每分鐘上限。 也就是說:你不可能把一整份逐字稿一次丟給它清洗。 一定會拿到 429解法:分段送。 步驟五的小程式已經內建分段(預設一段 1,200 字)和自動重試,照著跑就好。
怎麼自己查最新的(模型會改名、額度會變)

| 想查什麼 | 去哪查 | |---------|--------| | 現在有哪些模型(最可靠) | 在終端機跑:curl.exe https://api.groq.com/openai/v1/models -H "Authorization: Bearer $env:GROQ_API_KEY" | | 模型說明頁 | <https://console.groq.com/docs/models> | | 我這個帳號的實際限額 | Console → SettingsLimits(這裡看到的才是你的,文件是通則) | | 語音的檔案大小上限 | <https://console.groq.com/docs/speech-to-text> | | 資料政策 | <https://console.groq.com/docs/your-data> |

📌 本包刻意不寫任何價格。 免費方案用不到,而寫死的價格只會在半年後變成錯誤資訊。 模型名稱也一樣:如果下面哪個模型名在你跑的時候不存在了,先跑上面那行 /v1/models 看它改叫什麼,不要硬套本文件。

選哪個 Whisper 模型:研習主線用 whisper-large-v3

Groq 上目前有兩個(查證 2026-07-27):

| 模型 | 官方標示的準確度 | 速度 | 語言 | |------|-----------------|------|------| | whisper-large-v3 | 錯字率 10.3% | 189 倍即時 | 多語(含中文) | | whisper-large-v3-turbo | 錯字率 12% | 216 倍即時 | 多語(含中文) |

🔴 推薦:whisper-large-v3。理由是一個很多人沒注意到的事實——
在免費方案下,這兩個模型的額度完全一樣(都是 20 次/分、7,200 音訊秒/小時、28,800 音訊秒/日)。 turbo 的賣點是「比較便宜」。但免費方案本來就不用錢,所以那個賣點在這裡等於零。 而速度差多少?189 倍 vs 216 倍。一支 30 分鐘的錄音,一個要 9.5 秒、一個要 8.3 秒。你根本感覺不到。 結論:免費用戶沒有任何理由選 turbo。用準的那個。

什麼時候才換 turbo:你要一次跑幾十個檔、而且真的坐在旁邊等;或者以後升級成付費、開始在意費用。

📌 關於繁體中文的表現Groq 官方沒有公布任何中文專屬的準確度數字,Whisper 官方也只有整體多語數字。 本包推薦 whisper-large-v3 的依據是「整體錯字率較低」+「免費方案額度相同」,不是任何中文的實測比較。 實際上兩個模型對台灣國語的表現都算堪用,主要問題不是聽錯字,是專有名詞(人名、地名、課本術語)和繁簡——這兩件事都靠步驟五的清洗解決,換模型幫助不大。

選哪個文字模型:主線用 llama-3.3-70b-versatile

| 模型 | 為什麼選/不選 | |------|---------------| | llama-3.3-70b-versatile | ✅ 主線。中文品質在免費模型裡較穩,而且每分鐘 token 上限 12,000 是免費方案裡最大的——清洗逐字稿吃的就是這個 | | openai/gpt-oss-120b | 🟡 備援。主線那個掛掉或改名時用這個 | | llama-3.1-8b-instant | 🟡 量大時用。每日 500,000 token 最寬鬆,但中文品質明顯較弱,適合「整批粗清、之後自己再看過」 |

⚠️ 模型名稱會改。跑之前先用 /v1/models 確認一次(見上方「怎麼自己查最新的」)。

🤔 等一下——文字清洗為什麼不直接叫 OpenCode 做就好?

這是研習現場一定會有人問的問題,而且問得很對

你已經在 01-connect-model 幫 OpenCode 接了模型。要清洗逐字稿,把檔案丟給它說「幫我整理成講義」,它就做完了。幹嘛還要多申請一把 Groq 金鑰、多寫一支程式?

答案是三件事:

| 理由 | 說明 | |------|------| | ① 這一包在教「模式」,不是在教「清洗」 | 呼叫 API 拿結果,這個模式之後接任何服務都通用。清洗只是拿來練手的題目 | | ② 語音那條沒有替代方案 | OpenCode 的模型聽不懂聲音檔。A 線非走 API 不可,而 B 線用同一把金鑰、同一套寫法,順著學最省力 | | ③ 下午要變成網頁 | 10-netlify 會把這個工具放到網路上給別人用。網頁不能呼叫你電腦裡的 OpenCode,只能呼叫 API。今天寫的這支程式,下午直接搬上去 |

💡 所以:日常自己用,直接叫 OpenCode 清洗完全 OK,不必每次都走 API。 但這一包要你走一次 API,是為了讓你手上有一個「離開 OpenCode 也能跑」的東西。

先備條件
  • [ ] 已完成 00-env-setup01-connect-model
  • [ ] 已完成 02-file-toolkit 的 B 段(要用 ffmpegffprobe 壓縮、切檔、量長度)
  • [ ] 建議完成 07-github(金鑰不外洩這件事在那一包有完整說明)
  • [ ] 有網路
  • [ ] 手上有一份符合上面隱私紅線的音訊或影片檔

驗證:

ffmpeg -version; ffprobe -version

兩個都印出版本 = 過。

⚠️ ffprobe 常常缺席。 如果只裝了 yt-dlp 自帶的那套 FFmpeg,ffprobe 可能不在 PATH 上。 補裝系統版:winget install --id Gyan.FFmpeg -e(沒有管理員權限就加 --scope user)。 mac:brew install ffmpeg/Linux:sudo apt install -y ffmpeg。 真的裝不起來也還有救——步驟四的小程式有備援做法,看那一節。
📁 先建一個工作資料夾,名字用英數字
New-Item -ItemType Directory -Force "$HOME\Documents\groq-subtitle"
🚨 資料夾和檔名先不要用中文。 Windows 的命令列(PowerShell 5.1)傳中文檔名給 ffmpegcurl 時偶爾會變亂碼,你會看到「找不到檔案」但檔案明明就在。 做法:轉檔前先把來源改成 lesson01.mp4 這種名字,全部跑完再改回中文。 這一條看起來很瑣碎,但研習現場每一梯都有人卡在這裡半小時。

步驟一:申請 Groq 金鑰

🖐️ 全程在瀏覽器手動做。AI agent 不要代為註冊、不要代填任何欄位。
1-1 註冊
  1. 開 <https://console.groq.com>
  2. Continue with GoogleContinue with GitHub(建議用 07-github 那個 GitHub 帳號,之後好管理),也可以用 Email
  3. 沒有等待清單、不用審核、不會問你要信用卡
1-2 建立金鑰

左側選單 API KeysCreate API Key → 取一個看得懂的名字(例如 研習-字幕工具)→ 建立。

🔴 金鑰只會完整顯示這一次。 關掉視窗之後看不到第二次,只能刪掉重產一把。 現在就複製起來,貼到你的密碼管理器(不是貼到記事本、不是貼到 LINE 給自己)。

金鑰長得像 gsk_ 開頭的一長串。

1-3 順手開啟 Zero Data Retention

SettingsData Controls → 開啟 Zero Data Retention

10 秒的事,之後所有請求都適用。(再說一次:開了它,前面那些紅線一條都不會消失。

1-4 驗證:金鑰是活的嗎

先做完步驟二設定好環境變數,再回來跑這一行(步驟二的驗證裡有)。


步驟二:金鑰要放哪裡(🚨 這一步做錯,金鑰會上 GitHub)

🔴 三條絕對規則
① 金鑰不寫進任何 .md 檔案裡 —— 包含你等一下要做的那個 Skill 檔。 ② 金鑰不寫進任何會被 git add 的檔案裡。 ③ 金鑰不貼進聊天視窗(包含貼給 AI agent 看)。

理由:研習的設定檔會投影在大螢幕上、會被拍照;而 GitHub 上有機器人專門在掃新推上去的金鑰,掃到的速度比你發現的速度快

主線做法:設成環境變數(推薦,整台電腦設一次)

環境變數的好處是:金鑰不存在於任何一個檔案裡,所以永遠不可能被 commit。

Windows(PowerShell)

[System.Environment]::SetEnvironmentVariable("GROQ_API_KEY", "貼上你的金鑰", "User")
設完一定要重開終端機(連 OpenCode 也要完全關掉重開),新的環境變數才會生效。

macOS / Linux(寫進 ~/.zshrc~/.bashrc,然後 source 一次)

echo 'export GROQ_API_KEY="貼上你的金鑰"' >> ~/.zshrc
source ~/.zshrc
驗證(🔴 這行刻意不印出金鑰本身,可以安心投影)

Windows

if ($env:GROQ_API_KEY) { "KEY_SET len=" + $env:GROQ_API_KEY.Length } else { "KEY_MISSING" }

macOS / Linux

if [ -n "$GROQ_API_KEY" ]; then echo "KEY_SET len=${#GROQ_API_KEY}"; else echo "KEY_MISSING"; fi

看到 KEY_SET len=<某個數字> = 過。看到 KEY_MISSING → 沒重開終端機,或變數名打錯。

驗證金鑰是活的
curl.exe https://api.groq.com/openai/v1/models -H "Authorization: Bearer $env:GROQ_API_KEY"
  • 回傳一大段 JSON(裡面看得到 whisper-large-v3)→ ✅ 金鑰可用,順便看到現在有哪些模型
  • invalid_api_key → 金鑰複製時少了一截,或前後有多餘的空白,重複製一次
Windows 一定要打 curl.exe(有 .exe),否則會叫到 PowerShell 內建的別名,行為完全不同。
專案做法:.env.gitignore(下午的 10-netlify 會用到)

如果你的工具要放進一個「會推上 GitHub 的專案資料夾」,用 .env

先建 .gitignore(🔴 先建這個,再建 .env,順序不能反)

.env
*.key
credentials.*
desktop.ini
*.tmp

再建 .env

GROQ_API_KEY=貼上你的金鑰

驗證 .env 真的被擋掉了:

git status --short
🔴 輸出裡出現 .env 就是失敗——代表 git 還看得到它,下一次 git add . 就會把金鑰送上 GitHub。 回頭檢查 .gitignore 是不是拼錯、是不是放在 repo 根目錄。

更嚴格的檢查(agent 請跑這一行):

git check-ignore -v .env

有輸出(會告訴你被 .gitignore 第幾行擋掉)= ✅ 過。沒有輸出 = ❌ 沒擋到。

📌 關於 Google 雲端硬碟.env 放在雲端硬碟資料夾裡會被同步到你的雲端帳號——這通常可以接受(那是你自己的帳號)。 但它跟 .gitignore 是兩回事,雲端同步不會擋 git。兩件事都要做。
如果你要在 opencode.json 裡引用金鑰

本包用不到這件事(我們是自己呼叫 API,不是把 Groq 接成 OpenCode 的模型供應商)。但萬一你之後要:

"environment": { "GROQ_API_KEY": "{env:GROQ_API_KEY}" }
🔴 {env:GROQ_API_KEY},不要寫實際那串字。 這是 00-env-setup 附錄「MCP 通用守則」第 5 條的規矩,本包完全沿用。
🚨 萬一金鑰不小心外流了

刪掉檔案、刪掉訊息、清掉 git 歷史——全部沒有用。 金鑰已經在別人手上了。

## 唯一有效的處置:回 Console → API Keys → 把那把金鑰刪掉,重新產一把。 這件事做完之前,其他都不用做。做完之後,記得把環境變數也換成新的。

步驟三:A 線 — 把聲音變成字幕(.srt

A1. 先把檔案壓小(🚨 這裡處理陷阱 ②)

免費方案單檔上限 25 MB。一支手機錄的 30 分鐘影片動輒好幾百 MB,直接送一定被打回。

官方建議的做法:降到 16kHz 單聲道、轉成 FLAC。 這樣做不會影響辨識準確度——Whisper 本來就只吃 16kHz,多出來的取樣率和第二個聲道,送上去也是被丟掉。

Groq 官方文件給的指令(原封不動):

ffmpeg -i "lesson01.mp4" -ar 16000 -ac 1 -map 0:a -c:a flac "clean.flac"

| 參數 | 意思 | |------|------| | -ar 16000 | 取樣率降到 16kHz(Whisper 的原生規格) | | -ac 1 | 併成單聲道 | | -map 0:a | 只要聲音,把影像整個丟掉(影片檔的體積 95% 是影像) | | -c:a flac | 轉成 FLAC(無損壓縮) |

壓完看大小:

"{0:N1} MB" -f ((Get-Item "clean.flac").Length / 1MB)
# macOS / Linux
du -h clean.flac

經驗值:16k 單聲道 FLAC 大約每分鐘 0.8~1 MB。 所以 25 MB ≈ 25~30 分鐘。超過就要切段 → 跳到步驟四

🟡 真的不想切檔的備案:改用 mp3 低位元率,一小時大約壓到 14 MB。 ``powershell ffmpeg -i "lesson01.mp4" -ar 16000 -ac 1 -map 0:a -c:a libmp3lame -b:a 32k "clean.mp3" `` 代價是有損壓縮,中文的準確度會略微下降。本包主線仍走 FLAC + 切段,這個備案只在時間真的不夠時用。
A2. 呼叫 API

Windows(PowerShell,整段是一行,長是正常的)

curl.exe https://api.groq.com/openai/v1/audio/transcriptions -H "Authorization: Bearer $env:GROQ_API_KEY" -F "file=@clean.flac" -F "model=whisper-large-v3" -F "language=zh" -F "temperature=0" -F "response_format=verbose_json" -o "clean.json"

macOS / Linux

curl https://api.groq.com/openai/v1/audio/transcriptions \
  -H "Authorization: Bearer $GROQ_API_KEY" \
  -F "file=@clean.flac" \
  -F "model=whisper-large-v3" \
  -F "language=zh" \
  -F "temperature=0" \
  -F "response_format=verbose_json" \
  -o "clean.json"

| 參數 | 為什麼要 | |------|---------| | model=whisper-large-v3 | 見前面「選哪個模型」 | | language=zh | 一定要加。官方明講:指定語言會同時提升準確度和速度。不加的話它要先花時間猜語言,中英夾雜的課堂錄音很容易猜錯 | | temperature=0 | 要它照聽到的寫,不要自由發揮 | | response_format=verbose_json | 時間碼在這裡面。用 jsontext 拿不到時間碼 | | -o clean.json | 存成檔案。不存的話一大堆 JSON 會直接噴在螢幕上 |

🚨 A3. 陷阱 ①:不要寫 response_format=srt

OpenAI 的 Whisper API 可以直接回 .srt。Groq 不行。

官方 API 文件寫得很明確——response_formatAllowed values 只有 jsontextverbose_json

srtvtt 會拿到 400 錯誤。而網路上 95% 的 Whisper 教學都是寫給 OpenAI 的,照抄過來就會撞這一關。

🔴 請直接記這句話Groq 只給你「時間碼 + 文字」,.srt 這個檔案要你自己組。 聽起來很麻煩,其實只是把 JSON 換個排版而已——下面那支小程式一次解決,而且以後長檔切段也是同一支。
A4. 把 verbose_json 組成 .srt

在工作資料夾建立 make_srt.py這支程式不需要安裝任何套件,全部用 Python 內建的):

# make_srt.py — 把 Groq 的 verbose_json 組成 .srt
# 用法:uv run python make_srt.py 輸出.srt 第1段.json 第2段.json ...
# 只有一段就只傳一個 json;多段會自動接上時間碼偏移(見步驟四)
import json, os, subprocess, sys

def fmt(t):
    """秒數 → SRT 的 00:00:00,000 格式"""
    ms = int(round(t * 1000))
    h, ms = divmod(ms, 3600000)
    m, ms = divmod(ms, 60000)
    s, ms = divmod(ms, 1000)
    return f"{h:02d}:{m:02d}:{s:02d},{ms:03d}"

def real_seconds(path):
    """用 ffprobe 量音訊檔的實際長度(秒)。量不到就回 None"""
    try:
        out = subprocess.run(
            ["ffprobe", "-v", "error", "-show_entries", "format=duration",
             "-of", "csv=p=0", path],
            capture_output=True, text=True, check=True).stdout.strip()
        return float(out)
    except Exception:
        return None

out_path, json_files = sys.argv[1], sys.argv[2:]
if not json_files:
    sys.exit("SRT_FAIL 沒有指定任何 json 檔")

offset, idx, blocks = 0.0, 1, []
for jf in json_files:
    with open(jf, encoding="utf-8") as f:
        data = json.load(f)
    for seg in data.get("segments", []):
        text = (seg.get("text") or "").strip()
        if not text:
            continue
        blocks.append(
            f"{idx}\n{fmt(seg['start'] + offset)} --> {fmt(seg['end'] + offset)}\n{text}\n")
        idx += 1
    # 🚨 陷阱 ④:偏移量用「這一段的實際長度」累加,不要用名目的 600 秒
    audio = os.path.splitext(jf)[0] + ".flac"
    dur = real_seconds(audio) if os.path.exists(audio) else None
    if dur is None:                       # ffprobe 不在?改用 API 回傳的長度
        dur = float(data.get("duration") or 0.0)
    offset += dur

with open(out_path, "w", encoding="utf-8") as f:
    f.write("\n".join(blocks))
print(f"SRT_OK segments={idx - 1} files={len(json_files)} out={out_path}")

跑它:

uv run python make_srt.py "lesson01.srt" "clean.json"

uv 來自 00-env-setup。沒有 uv 就用 python make_srt.py ...,這支程式只用內建模組,任何 Python 3 都跑得動。)

成功的樣子:印出 SRT_OK segments=<某個數字> files=1 out=lesson01.srt,而且 segments 不是 0。

A5. 🚨 陷阱 ③:字幕變成簡體怎麼辦

Whisper 的語言代碼只有「中文(zh)」,沒有「繁體中文」。 它自己決定要吐繁體還是簡體,同一份檔案跑兩次結果還可能不一樣。

兩條路,建議兩條都做

路線 A(一次搞定,但 Windows 有編碼坑):用 prompt 參數暗示它

Whisper 的 prompt 參數是用來「示範風格」的(上限 224 token)。你用繁體字寫這個提示,它輸出繁體的機率會大幅提高。

先用記事本建立 prompt.txt(存檔時編碼選 UTF-8),內容一行:

以下是繁體中文的課堂錄音逐字稿。

然後在 A2 的指令尾巴加一個參數

-F "prompt=<prompt.txt"
🚨 為什麼要用檔案,不直接把中文寫在指令裡? Windows PowerShell 5.1 把中文參數傳給 curl.exe會用系統的 cp950 編碼,送出去常常變亂碼——而且不會報錯,你只會覺得「這個 prompt 好像沒效」。 -F "欄位名=<檔名" 是 curl 的語法,意思是「這個欄位的值從檔案讀」,直接讀原始位元組,繞過整個編碼問題。 macOS / Linux 沒有這個問題,直接寫中文也可以。
📌 這是社群通用做法,不是官方保證。 有效但不是 100%。所以還要有路線 B。
路線 B(零風險保底):讓 OpenCode 只改字、不准動時間碼

把產出的 .srt 丟給 OpenCode,說:

幫我把這個 .srt 檔的字幕文字全部改成繁體中文(台灣用語),
順便修掉明顯的錯字。

🔴 硬規定:
- 時間碼那幾行(00:00:00,000 --> 00:00:03,120)一個字都不准改
- 字幕的行數與編號不准增減
- 不要合併或拆開任何一段
🔴 最後那三條一定要講,而且要講得像規定。 不講的話,AI 很常「順手」幫你合併太短的句子、重新分段——結果字幕跟畫面整份對不上,而且看起來完全正常,你要放完整支影片才會發現。

建議做法:路線 A 先做(省事),路線 B 當保底(順便修錯字)。

A6. ✅ A 線驗收:換一份新素材,同一句話能不能再跑一次

這才是這一包的驗收,不是「跑出一個 srt」。

| # | 做什麼 | 通過的樣子 | |---|--------|-----------| | 1 | 用播放器開啟原始影片,把 .srt 拖進去 | 字幕跟嘴巴對得上(不是「有字幕就好」) | | 2 | 跳到影片最後面看 | 最後一句字幕的時間接近影片總長(差很多 = 時間碼歪了,看步驟四) | | 3 | 換一支完全不同的錄音,從 A1 重跑一次 | 一樣跑得完 | | 4 | 🔴 步驟六做完之後:對 OpenCode 說「幫我把 <新檔名> 轉成字幕」 | 它自己做完全部,你沒有再貼任何指令這一項才是真的通過 |

第 4 項現在還做不到(Skill 還沒包)。先記著它,這是你等一下要達成的目標。


步驟四:長音檔怎麼切、時間碼怎麼接(🚨 這裡處理陷阱 ④)

只有超過 25 MB(約 25~30 分鐘)才需要這一步。 短檔請直接跳到步驟五。

官方建議 vs 本包做法

Groq 官方建議的是「切成有重疊的片段,各自轉,合併時處理重疊的部分」,並附了一份 Cookbook 範例。

本包不走重疊那條,理由很實際:重疊之後要判斷「這兩段重複的文字哪一份才對、接縫在哪」,這是整條流程裡最容易寫錯、也最難驗證的部分。對老師來說划不來。

本包做法:不重疊、切固定長度、用「每一段的實際長度」累加時間碼。 代價是接縫處可能斷在句子中間(少一兩個字或斷句怪怪的)。 但這件事步驟五的清洗會順手修掉,而且比起「整份時間碼歪掉」,這個代價便宜太多。
4-1 切檔(先壓再切,順序不能反)
# ① 先壓成 16k 單聲道 FLAC(跟 A1 一模一樣)
ffmpeg -i "lesson01.mp4" -ar 16000 -ac 1 -map 0:a -c:a flac "clean.flac"

# ② 再切成每段 10 分鐘(600 秒)
ffmpeg -i "clean.flac" -f segment -segment_time 600 -c:a flac "chunk_%03d.flac"

會產生 chunk_000.flacchunk_001.flacchunk_002.flac

為什麼是 10 分鐘? 10 分鐘的 16k 單聲道 FLAC 約 8~10 MB,離 25 MB 上限有很大的安全邊際。 切太小反而浪費(記得「最低計費 10 秒」那條),切太大會踩到上限。10 分鐘是甜蜜點。
為什麼要先壓再切? 這樣每一段的規格完全一致,量長度、接時間碼都不會出意外。 先切再壓的話,每段會各自被 ffmpeg 處理一次,長度可能出現零點幾秒的差異。
4-2 逐段呼叫 API

Windows(PowerShell)

Get-ChildItem "chunk_*.flac" | Sort-Object Name | ForEach-Object {
  $out = $_.BaseName + ".json"
  Write-Host "轉換中:$($_.Name)"
  curl.exe https://api.groq.com/openai/v1/audio/transcriptions -H "Authorization: Bearer $env:GROQ_API_KEY" -F "file=@$($_.Name)" -F "model=whisper-large-v3" -F "language=zh" -F "temperature=0" -F "response_format=verbose_json" -o $out
  Start-Sleep -Seconds 3
}

macOS / Linux

for f in chunk_*.flac; do
  echo "轉換中:$f"
  curl https://api.groq.com/openai/v1/audio/transcriptions \
    -H "Authorization: Bearer $GROQ_API_KEY" \
    -F "file=@$f" -F "model=whisper-large-v3" -F "language=zh" \
    -F "temperature=0" -F "response_format=verbose_json" \
    -o "${f%.flac}.json"
  sleep 3
done
💡 中間的 Start-Sleep 3 / sleep 3 是刻意的:免費方案每分鐘 20 次,這樣跑絕對不會撞到上限。 一支 1 小時的錄音會切成 6 段,全部跑完大約 1 分鐘。

檢查每一段都有回來:

Get-ChildItem "chunk_*.json" | Select-Object Name, Length
🔴 有哪個 .json 大小只有幾百 bytes → 那一段失敗了,裡面裝的是錯誤訊息不是字幕。 用記事本打開看它寫什麼,對照本文件最後的「常見卡關」。不要就這樣往下做,那一段的字幕會整段消失。
4-3 合併(🚨 這裡就是陷阱 ④)
為什麼不能直接接起來

每一段轉回來的時間碼,都是從 00:00:00 開始的。

也就是說第二段的第一句話,API 說它在 0 分 3 秒——但它實際上在整支影片的 10 分 3 秒。 直接把 6 段接起來,你會得到一份「前 10 分鐘有 6 份字幕、後面 50 分鐘全空」的檔案。

正確做法:加偏移量。但不能用「10 分鐘」硬算
🔴 這是本包最細、也最多人做錯的一條: ffmpeg-segment_time 600 是「大約 600 秒」,不是「剛好 600 秒」——實際切點會落在音訊的封包邊界上。 每段差個零點幾秒,六段累積起來就是好幾秒。 字幕一開始對得很好,越到後面越飄——而這種錯誤最難發現,因為前 10 分鐘完全正常。 正解:用 ffprobe 量每一段的實際長度,一段一段累加。 make_srt.py 已經內建這個做法(real_seconds() 那個函式),你不用自己算。

直接跑(把所有 json 按順序一次餵進去):

Windows

$files = (Get-ChildItem "chunk_*.json" | Sort-Object Name).Name
uv run python make_srt.py "lesson01.srt" $files

macOS / Linux

uv run python make_srt.py "lesson01.srt" $(ls chunk_*.json | sort)
🔴 順序絕對不能錯。 chunk_010.json 一定要排在 chunk_002.json 後面。 上面用了 Sort-Object Name / sort,而檔名是三位數補零的(%03d),所以字母排序就是正確順序。 如果你自己手動列檔名,請務必檢查順序——順序一錯,整份字幕會亂到看不出規律。
📌 ffprobe 真的裝不起來的備援make_srt.py 量不到長度時,會自動改用 API 回傳的 duration 欄位。 那個數字是 Groq 算的,通常也很準,只是不像 ffprobe 那樣是直接量檔案。能裝 ffprobe 還是裝。
4-4 驗證時間碼有沒有歪(🔴 這一步不要跳過)

這是唯一能抓到時間碼漂移的方法:

| # | 做什麼 | 通過的樣子 | |---|--------|-----------| | 1 | 看原始影片的總長度 | 記下來,例如 58:20 | | 2 | 用記事本打開 .srt捲到最後一行 | 最後一個時間碼很接近 58:20(差 5 秒內算正常) | | 3 | 把 .srt 拖進播放器,跳到影片最後 2 分鐘 | 字幕還對得上嘴巴 | | 4 | 跳到每一個接縫處(10 分、20 分、30 分…) | 那附近的字幕沒有明顯偏移 |

🔴 只驗開頭是抓不到這個 bug 的。 時間碼漂移的特徵就是「前面完全正常」。 一定要看最後面。

歪掉了怎麼辦

| 症狀 | 原因 | |------|------| | 最後一句只到 10:00 左右 | 只餵了一個 json 進去,或檔案順序被打亂 | | 越後面偏差越大 | ffprobe 沒裝,而且 API 的 duration 也不準 → 補裝系統版 FFmpeg 再跑一次 make_srt.py(不用重轉,json 都還在) | | 中間整段字幕消失 | 某一段的 .json 其實是錯誤訊息(回 4-2 檢查每個 json 的大小) |


步驟五:B 線 — 把逐字稿清洗成講義

5-1 先把 srt 變成純文字

字幕檔裡有編號和時間碼,那些不需要送去清洗(還會白白吃掉寶貴的 token 額度)。

uv run python -c "import re,sys;t=open('lesson01.srt',encoding='utf-8').read();lines=[l for l in t.splitlines() if l.strip() and not l.strip().isdigit() and '-->' not in l];open('lesson01.txt','w',encoding='utf-8').write('\n'.join(lines))"

或者更簡單——直接把 .srt 丟給 OpenCode 說「把時間碼和編號拿掉,只留文字,存成 lesson01.txt」

5-2 建立清洗程式

在同一個資料夾建立 clean.py(一樣不需要安裝任何套件):

# clean.py — 把逐字稿清洗成可讀講義(Groq Chat Completions)
# 用法:uv run python clean.py 逐字稿.txt 講義.md
import json, os, sys, time, urllib.error, urllib.request

API   = "https://api.groq.com/openai/v1/chat/completions"
MODEL = "llama-3.3-70b-versatile"   # 會改名!先跑 /v1/models 確認
LIMIT = 1200                        # 每段送幾個字。免費方案 TPM 很小,不要調大

KEY = os.environ.get("GROQ_API_KEY")
if not KEY:
    sys.exit("CLEAN_FAIL 環境變數 GROQ_API_KEY 沒設,回步驟二")

SYSTEM = (
    "你是台灣國中小老師的文字編輯助理。把語音辨識出來的逐字稿,"
    "整理成可以直接發給學生看的講義段落。規則:"
    "① 一律輸出繁體中文,用台灣的慣用詞;"
    "② 刪掉贅詞、口頭禪、結巴、重複、以及講者自己更正掉的前半句;"
    "③ 補上標點符號,依語意分段;"
    "④ 不要新增原文沒有的內容,不要加自己的解釋;"
    "⑤ 聽不出來的地方原樣保留,並在後面加上 [?];"
    "⑥ 不要寫開場白或結語,直接輸出整理後的內文。"
)

def split_text(raw, limit):
    """依行切段,盡量不切在句子中間"""
    parts, buf = [], ""
    def flush():
        nonlocal buf
        if buf.strip():
            parts.append(buf)
        buf = ""
    for line in raw.splitlines(keepends=True):
        while len(line) > limit:          # 整篇沒換行的長文,只好硬切
            flush()
            parts.append(line[:limit])
            line = line[limit:]
        if len(buf) + len(line) > limit:
            flush()
        buf += line
    flush()
    return parts

def call(text):
    body = json.dumps({
        "model": MODEL,
        "temperature": 0.2,
        "messages": [{"role": "system", "content": SYSTEM},
                     {"role": "user",   "content": text}],
    }).encode("utf-8")
    req = urllib.request.Request(API, data=body, headers={
        "Authorization": "Bearer " + KEY,
        "Content-Type": "application/json",
    })
    with urllib.request.urlopen(req, timeout=180) as r:
        return json.load(r)["choices"][0]["message"]["content"].strip()

raw   = open(sys.argv[1], encoding="utf-8").read()
parts = split_text(raw, LIMIT)
out   = []

for n, p in enumerate(parts, 1):
    print(f"清洗第 {n}/{len(parts)} 段…")
    for attempt in range(3):
        try:
            out.append(call(p))
            break
        except urllib.error.HTTPError as e:
            detail = e.read().decode("utf-8", "ignore")[:300]
            if e.code == 429 and attempt < 2:      # 額度滿了,等一下再試
                print("   額度滿了,等 35 秒再試")
                time.sleep(35)
                continue
            sys.exit(f"CLEAN_FAIL http={e.code} {detail}")
    time.sleep(2)                                   # 段與段之間刻意留間隔

open(sys.argv[2], "w", encoding="utf-8").write("\n\n".join(out))
print(f"CLEAN_OK pieces={len(parts)} out={sys.argv[2]}")

跑它:

uv run python clean.py "lesson01.txt" "lesson01-講義.md"

成功的樣子:一段一段印出進度,最後印 CLEAN_OK pieces=<數字> out=lesson01-講義.md

5-3 為什麼要分段、為什麼中間要等

| 設計 | 理由 | |------|------| | LIMIT = 1200(一段 1,200 字) | 免費方案 llama-3.3-70b-versatile 每分鐘只有 12,000 token。一小時的逐字稿一次送必定 429 | | 段與段之間 sleep 2 | 讓每分鐘的 token 用量攤平,不會集中在前 10 秒燒完 | | 遇到 429 等 35 秒重試 | token 額度是每分鐘重置的,等一下就會恢復 |

🔴 不要把 LIMIT 調大想跑快一點。 調大只會讓你更快撞到 429,然後每次都要等 35 秒,整體反而更慢
5-4 想要別的產出,改 SYSTEM 那段就好

clean.py 裡的 SYSTEM 字串就是「你要它做什麼」。改那一段,這支程式就變成另一個工具:

| 你想要 | SYSTEM 改成 | |--------|--------------| | 講義(預設) | 上面那段 | | 課堂重點摘要 | 「把逐字稿整理成 5~8 條重點,每條一行,用繁體中文…」 | | 學生自學版(口語轉書面) | 「把逐字稿改寫成適合國中生自己閱讀的說明文…」 | | 抽出考題素材 | 「從逐字稿裡找出可以出題的概念,列成清單…」 |

💡 這就是「把 API 變成工具」的意思——同一支程式、同一把金鑰,換一段話就換一個工具。
5-5 ✅ B 線驗收:換一份新素材,同一句話能不能再跑一次

| # | 做什麼 | 通過的樣子 | |---|--------|-----------| | 1 | 打開 lesson01-講義.md | 是通順的繁體中文段落,不是一長串沒標點的字 | | 2 | 跟原逐字稿對照 | 意思沒被改掉、沒有多出原文沒講過的內容 | | 3 | 找一段你記得自己講錯又更正的地方 | 更正前那半句被刪掉了 | | 4 | 換一份完全不同的逐字稿,重跑一次 | 一樣跑得完 | | 5 | 🔴 步驟六做完之後:對 OpenCode 說「幫我把 <新檔名> 整理成講義」 | 它自己做完,你沒有再貼指令這一項才是真的通過 |

⚠️ 第 2 項要真的看。語言模型偶爾會「幫你補充」——補出來的東西看起來很專業,但你根本沒講過。 發講義前自己讀一遍,這件事不能外包。

步驟六:把整條流程包成 Skill(🔴 這是作品⑤ 的正題)

前面五步都是「跑一次」。跑一次的東西下個月一定忘記。

現在把它變成一個技能:以後在任何資料夾、對 OpenCode 說一句人話,它就會自己做完。

6-1 建立技能資料夾

Windows

New-Item -ItemType Directory -Force "$HOME\.config\opencode\skills\audio-notes"

macOS / Linux

mkdir -p ~/.config/opencode/skills/audio-notes
⚠️ 資料夾名稱要和技能的 name 一模一樣,檔名要是全大寫的 SKILL.md,OpenCode 才掃得到。(規矩同 08-workflow-skills。)
6-2 把兩支小程式搬進去
Copy-Item "$HOME\Documents\groq-subtitle\make_srt.py" "$HOME\.config\opencode\skills\audio-notes\"
Copy-Item "$HOME\Documents\groq-subtitle\clean.py"    "$HOME\.config\opencode\skills\audio-notes\"
🔴 搬之前先確認這兩支檔案裡沒有金鑰。 它們讀的是 os.environ.get("GROQ_API_KEY"),本來就沒有——但如果你剛剛「為了測試方便」把金鑰貼死在裡面,現在就是把它拿掉的時候。
6-3 建立 SKILL.md

把下面整段內容原封不動寫入 ~/.config/opencode/skills/audio-notes/SKILL.md不要把最外層的 ``` ` `` 圍籬符號寫進檔案,檔案第一行必須是 ---`):

---
name: audio-notes
description: 把音訊或影片轉成字幕檔(.srt),以及把逐字稿清洗成可讀講義(.md)。當使用者說「轉字幕」、「上字幕」、「做逐字稿」、「這個錄音幫我轉文字」、「把逐字稿整理成講義」、「把這段錄音變成講義」時,請一定要使用此技能。走 Groq 的 Whisper 與文字模型,金鑰讀自環境變數 GROQ_API_KEY。
---

# 音訊 → 字幕 → 講義

## 🔴 執行前必問(不可跳過)

動手之前,先問使用者這一句,**等到回答再繼續**:

> 「這個音檔裡面**有沒有學生、家長的聲音**,或是輔導、個案相關的內容?
> 有的話這條流程不能用——語音會上傳到雲端。」

- 回答「有」或「不確定」 → **停止,不要呼叫任何 API**,把原因說清楚
- 回答「沒有,只有我自己的聲音」 → 繼續

順便提醒一次:**上傳前先聽檔案的最後 30 秒**,課堂錄音常常錄到下課後的對話。

## 金鑰

金鑰一律從環境變數 `GROQ_API_KEY` 讀取。

- **不准**把金鑰寫進本檔案、寫進任何 `.py`、寫進任何會被 commit 的檔案
- **不准**把金鑰印在終端機上、或複述給使用者看
- 環境變數不存在 → 停下來,請使用者依懶人包 `09-groq-api` 步驟二設定,**不要自己想辦法繞過**

## 模式 A:音訊 → 字幕(.srt)

1. **檢查工具**:`ffmpeg -version`、`ffprobe -version`。缺了就停下來告訴使用者(懶人包 `02-file-toolkit` B 段)
2. **檔名先換成英數字**(中文檔名在 Windows 命令列可能亂碼),全部跑完再改回來
3. **壓縮**:
   `ffmpeg -i <來源> -ar 16000 -ac 1 -map 0:a -c:a flac clean.flac`
4. **看大小**。25 MB 以內 → 跳到第 6 步;超過 → 第 5 步
5. **切段**(每段 600 秒):
   `ffmpeg -i clean.flac -f segment -segment_time 600 -c:a flac chunk_%03d.flac`
6. **逐段呼叫 API**,每次之間停 3 秒:
   端點 `https://api.groq.com/openai/v1/audio/transcriptions`
   參數 `model=whisper-large-v3`、`language=zh`、`temperature=0`、`response_format=verbose_json`
   輸出存成與音訊同名的 `.json`
   - 🚨 **`response_format` 不可以填 `srt` 或 `vtt`**,Groq 不支援,會回 400
7. **檢查每個 `.json`**:檔案太小(幾百 bytes)代表那一段失敗了,是錯誤訊息不是字幕。**先處理再往下**
8. **組 srt**(json 一定要照檔名排序傳入):
   `uv run python <本技能資料夾>/make_srt.py <輸出>.srt chunk_000.json chunk_001.json ...`
9. **繁體化**:把產出的 srt 逐句改成繁體中文並修錯字。
   🔴 **時間碼那幾行一個字都不准改,行數與編號不准增減,不准合併或拆開任何一段。**
10. **回報**:段數、輸出路徑,並提醒使用者「請拖進播放器確認**最後 2 分鐘**的字幕還對得上」

## 模式 B:逐字稿 → 講義(.md)

1. 來源是 `.srt` → 先去掉編號與時間碼,只留文字,存成 `.txt`
2. 執行:`uv run python <本技能資料夾>/clean.py <逐字稿>.txt <輸出>.md`
3. 拿到 `CLEAN_OK` 才算成功
4. 回報時**一定要加這句**:「語言模型偶爾會補充原文沒有的內容,**發給學生前請自己讀一遍**」

## 常見狀況

| 狀況 | 處理 |
|------|------|
| `400` 且訊息提到 `response_format` | 用了 `srt`/`vtt`,改成 `verbose_json` |
| `413` 或檔案太大 | 沒壓縮,或壓完仍超過 25 MB → 回去切段 |
| `429` | 額度滿了,等 35 秒再試,**不要換模型硬闖** |
| `401` / `invalid_api_key` | `GROQ_API_KEY` 沒設或設錯 → 停下來請使用者處理 |
| 模型名稱不存在 | 跑 `https://api.groq.com/openai/v1/models` 看現在有哪些,不要亂猜 |
| 字幕越後面越對不上 | `ffprobe` 沒裝,`make_srt.py` 量不到每段實際長度。補裝 FFmpeg 後**重跑 make_srt.py 就好,不用重轉** |

## 不該做的事

- ❌ 使用者沒回答隱私那一題就開始上傳
- ❌ 把金鑰寫進任何檔案或印出來
- ❌ 修改 srt 的時間碼
- ❌ 為了「快一點」把清洗的分段字數調大(只會更快撞 429)
- ❌ 一次送幾十個很短的小檔(最低計費 10 秒,額度會白白燒光)

## 注意事項

- 所有訊息使用**繁體中文**
- Windows 用 PowerShell,串指令用 `;` 不要用 `&&`
- Windows 呼叫 curl 要打 `curl.exe`(有 `.exe`)
6-4 允許這個技能

打開(或建立)~/.config/opencode/opencode.json,確認 permission.skill 裡有這一項:

{
  "permission": {
    "skill": {
      "audio-notes": "allow"
    }
  }
}
⚠️ 如果 opencode.json 已經有內容(前面幾包加的 MCP 設定、08-workflow-skills 加的三個技能),不要整份覆蓋,只要把 audio-notes 這一行併進既有的 permission.skill 區塊就好。
6-5 重啟 OpenCode

OpenCode 只有啟動時才會掃描技能目錄。 剛剛新增的資料夾,要完全關掉再重開才會被認得(關視窗不算,要真的結束程序)。

6-6 ✅ 最終驗收:這一包成不成功,看這裡

| # | 做什麼 | 通過的樣子 | |---|--------|-----------| | 1 | 對 OpenCode 說「列出你現在可用的技能」 | 清單裡有 audio-notes | | 2 | 拿一份全新的、沒跑過的錄音,說「幫我把 <檔名> 轉成字幕」 | 它自己壓縮、呼叫 API、組 srt、繁體化,你沒有貼任何指令 | | 3 | 檢查它有沒有先問隱私那一題 | 有問 ← 沒問的話回去看 SKILL.md 是不是貼漏了 | | 4 | 拿一份全新的逐字稿,說「幫我把 <檔名> 整理成講義」 | 它自己做完 | | 5 | 打開 SKILL.mdmake_srt.pyclean.py 搜尋 gsk_ | 🔴 三個檔案裡都搜不到 ← 搜到就是失敗,回步驟二 |

## 🔴 第 2 項和第 4 項就是這一包的分水嶺。 做得到 = 你有了一個工具。做不到 = 你只是跑過一次。 差別在下個月:有工具的人會再用,跑過一次的人早就忘了。

常見卡關
金鑰與帳號

| 症狀 | 原因 / 解法 | |------|------------| | KEY_MISSING | 環境變數沒生效。重開終端機(連 OpenCode 也要完全關掉重開) | | 401 / invalid_api_key | 金鑰複製時少一截,或前後多了空白。回 Console 重新產一把 | | 金鑰畫面關掉了才發現沒複製 | 看不回來,只能刪掉重產。 這是設計,不是壞掉 | | 註冊時要求信用卡 | 你走進升級流程了。 按上一頁退出來,免費方案不需要卡 | | 金鑰不小心 push 上 GitHub | 刪檔案沒用。立刻回 Console 刪掉那把金鑰、重產一把。 然後更新環境變數 |

A 線(轉字幕)

| 症狀 | 原因 / 解法 | |------|------------| | 400 提到 response_format | 🚨 陷阱 ①。填了 srtvtt,Groq 不支援。改 verbose_json,用 make_srt.py 組 | | 413 / 檔案太大 | 🚨 陷阱 ②。免費方案單檔 25 MB。回 A1 壓縮,還是太大就切段 | | 轉出來是簡體 | 🚨 陷阱 ③。看 A5 兩條路線 | | .srt 拖進播放器沒反應 | 檔名要和影片同名lesson01.mp4lesson01.srt),或播放器裡手動載入 | | 字幕全部集中在前 10 分鐘 | 🚨 陷阱 ④。只餵了一個 json,或檔案順序亂了。回 4-3 | | 越後面偏差越大 | ffprobe 沒裝。補裝 FFmpeg,重跑 make_srt.py 就好,不用重轉(json 都還在) | | 中間有一整段沒字幕 | 那一段的 .json 是錯誤訊息。回 4-2 檢查每個 json 的大小 | | SRT_OK segments=0 | API 回的 json 裡沒有 segments——多半是 response_format 沒填 verbose_json | | 字幕內容整段重複 | 音檔裡有長時間靜音,Whisper 的已知行為。剪掉靜音再轉 | | ffmpeg 說找不到檔案,但檔案就在 | 中文檔名 / 路徑有空白。先改成英數字檔名,或整個路徑用雙引號包起來 | | 一句一個檔跑,額度突然沒了 | 最低計費 10 秒。改成切 10 分鐘大段 |

B 線(文字清洗)

| 症狀 | 原因 / 解法 | |------|------------| | 429 | 每分鐘 token 額度滿了。程式會自己等 35 秒重試;一直發生就把 LIMIT 調(例如 800) | | CLEAN_FAIL http=404 | 模型改名了。跑 /v1/models 看現在叫什麼,改 clean.pyMODEL | | 講義裡出現我沒講過的內容 | 語言模型「幫你補充」了。把 SYSTEM 的第 ④ 條講更重,或改用比較大的模型 | | 輸出還是簡體 | SYSTEM 第 ① 條被忽略了。在使用者訊息開頭再寫一次「請用繁體中文(台灣)輸出」 | | 跑很慢 | 正常。免費方案要分段+等待。一小時的逐字稿約 10 段,大約 2~3 分鐘 | | 專有名詞一直錯 | 語音辨識階段就聽錯了。把正確寫法列在 SYSTEM 裡,例如「本文的專有名詞:正三角形、外心、內心」 |

Skill 與環境

| 症狀 | 原因 / 解法 | |------|------------| | 技能沒出現在清單 | ①檔名要全大寫 SKILL.md ②資料夾名要等於 name ③檔案第一行必須是 ---(圍籬符號不要寫進去)④沒重啟 OpenCode | | 技能叫得動但找不到 .py | SKILL.md 裡要用技能資料夾的完整路徑叫程式,不是相對路徑 | | uv 說找不到 | 回 00-env-setup。或直接用 python make_srt.py ...,這兩支程式只用內建模組 | | ffprobe 找不到 | 只裝了 yt-dlp 自帶的 FFmpeg。winget install --id Gyan.FFmpeg -e(沒管理員權限加 --scope user) | | PowerShell 串指令語法錯誤 | PowerShell 5.1 不支援 &&,改用 ; | | curl 行為很奇怪 | Windows 要打 curl.exe(有 .exe),否則叫到 PowerShell 的別名 | | 中文參數傳給 curl 變亂碼 | PowerShell 5.1 的編碼問題。改用 -F "欄位=<檔名" 從檔案讀(見 A5) | | opencode.json 存檔後 OpenCode 起不來 | JSON 格式:大括號要成對、引號要用英文半形 "、Windows 路徑反斜線寫兩條 \\ |


✅ 成功訊號(機器可判讀)

執行完請完整輸出下面這個區塊,讓後續的 agent 可以直接判讀。

=== OPENCODE_GROQ_API ===
result=PASS                  # PASS | PARTIAL | FAIL
os=windows                   # windows | macos | linux
privacy_confirmed=yes        # yes | no(使用者已確認素材不含學生/家長聲音)
key_set=yes                  # yes | no(環境變數 GROQ_API_KEY 存在)
key_in_files=no              # no | yes(SKILL.md 與 .py 內是否搜得到 gsk_)
api_ok=yes                   # yes | no(/v1/models 回傳正常)
ffmpeg=ok                    # ok | missing
ffprobe=ok                   # ok | missing(缺了會退用 API 的 duration)
model_stt=whisper-large-v3   # 實際使用的語音模型
model_llm=llama-3.3-70b-versatile   # 實際使用的文字模型
srt_single=OK                # OK | FAIL | SKIP(單檔轉字幕)
srt_chunked=OK               # OK | FAIL | SKIP(長檔切段合併)
srt_tail_aligned=yes         # yes | no(影片最後 2 分鐘字幕仍對得上)
clean_ok=OK                  # OK | FAIL | SKIP
skill_installed=yes          # yes | no(~/.config/opencode/skills/audio-notes/)
skill_listed=yes             # yes | no(重啟後 OpenCode 掃得到)
skill_rerun_srt=OK           # OK | FAIL(換新素材,一句話跑完 A 線)
skill_rerun_clean=OK         # OK | FAIL(換新素材,一句話跑完 B 線)
=== END ===

判定規則(agent 請照這個判斷,不要自行認定):

| 結果 | 條件 | |------|------| | PASS | skill_rerun_srt=OK skill_rerun_clean=OK key_in_files=no | | PARTIAL | A、B 兩線手動跑得起來,但技能沒包成/換素材沒跑通 | | FAIL | api_ok=no,或 A、B 兩線都跑不起來 |

## 🔴 兩條會直接讓整體變成 FAIL 的: key_in_files=yes —— 金鑰出現在任何檔案裡,不管其他項目多漂亮,一律 FAILprivacy_confirmed=no —— 沒跟使用者確認過素材內容就上傳,一律 FAIL,而且要在回報裡明確寫出來。 這兩條不准「先繼續、之後再補」。

完成回報格式
## Groq API 工具化結果

### 隱私確認
- 已向使用者確認素材不含學生/家長/輔導內容:是 / 否
- 已提醒「上傳前聽最後 30 秒」:是 / 否
- Zero Data Retention 已開啟:是 / 否 / 未確認

### 金鑰
- 存放方式:環境變數 / 專案 .env
- `.gitignore` 已擋住 `.env`(`git check-ignore -v .env` 有輸出):是 / 否 / 不適用
- SKILL.md 與 .py 內搜尋 `gsk_`:查無 / ⚠️ 有(立刻撤銷重產)
- 金鑰驗證(`/v1/models`):PASS / FAIL

### A 線:轉字幕
- 使用模型:<實際用的>
- 來源檔長度:<分鐘> / 壓縮後大小:<MB>
- 是否切段:否 / 是(<N> 段,每段 600 秒)
- 產出:`<路徑>.srt`(<N> 句)
- 時間碼驗證(看**最後 2 分鐘**):PASS / FAIL
- 繁體化方式:prompt 參數 / OpenCode 改字 / 兩者都做

### B 線:文字清洗
- 使用模型:<實際用的>
- 分段數:<N>
- 產出:`<路徑>.md`
- 已提醒使用者「發給學生前自己讀一遍」:是 / 否

### 工具化(本包正題)
- 技能位置:`~/.config/opencode/skills/audio-notes/`
- 隨附程式:`make_srt.py`、`clean.py`
- `opencode.json` 權限已加:是 / 否
- 已重啟 OpenCode:是 / 否
- 技能出現在清單:是 / 否
- 🔴 **換新素材、一句話跑完 A 線**:PASS / FAIL
- 🔴 **換新素材、一句話跑完 B 線**:PASS / FAIL

### 待處理
- <把 FAIL 的項目與原因列在這裡;全部 PASS 就寫「無」>

如果做壞了,怎麼重來

對 OpenCode 說:「Groq 這一包做壞了,幫我清乾淨重來。

| 想重來的東西 | 做法 | |-------------|------| | 只是某一段轉壞了 | 刪掉那一段的 .json,重跑那一段就好。其他段不用重轉 | | 時間碼歪了 | 補裝 ffprobe重跑 make_srt.py。API 不用重打,json 都還在 | | 字幕整份要重來 | 刪掉 chunk_*.json.srt,從 4-2 重跑 | | 技能沒被掃到 | 檢查檔名是不是 SKILL.md(全大寫)、資料夾名是不是 audio-notes、第一行是不是 ---。改完完全關閉 OpenCode 再開 | | 技能整個不要了 | 刪掉 ~/.config/opencode/skills/audio-notes/ 資料夾,並從 opencode.jsonpermission.skill 移除那一行 | | 金鑰外洩 | 🔴 Console → API Keys → 刪掉那把、重產一把 → 更新環境變數。這件事優先於其他所有事 |

⚠️ 重跑 API 之前先想一下額度:免費方案每小時 7,200 音訊秒。 一支 1 小時的錄音重轉三次就用掉一半。先確認是哪一步壞了,不要整條重來。

下一步
## 🚀 下午:把這個工具變成網頁 → 10-netlify 現在你有一個「只有你的電腦能用」的工具。 下午那一包會把它放上網路——別的老師打開網址就能用,不用裝 OpenCode、不用申請金鑰。 今天早上寫的 clean.py、學到的「金鑰放環境變數不放檔案」,下午會原封不動用上。 (尤其是金鑰那條:網頁上線之後,金鑰放錯地方是全世界都看得到。這件事今天先練,下午就不會慌。)

其他方向:

  • 素材從哪裡來(下載影片、抽音軌、抓現成字幕)→ 02-file-toolkit 的 B 段
  • 產出的講義要收進第二大腦 → 04-obsidian
  • 工具要備份、換電腦也帶著走 → 07-github
  • 想讓「轉字幕」也能被開工/收工流程接住 → 08-workflow-skills
📌 提醒一件事~/.config/opencode/skills/ 底下的技能不在任何專案的 repo 裡。 換一台電腦,這個技能不會跟著過去。要帶著走,就把 audio-notes 這個資料夾也放進你的 GitHub(見 07-github)——但記得,裡面永遠不要有金鑰。
按 MIT 许可原样转载,未经改动 · 在 GitHub 查看 →

评论

登录即可评论;带「已验证安装」的,是发布者名下有本店的安装或持有记录。