本篇要解決的問題
之前寫過二篇開源的語音辨識功能:
免費開源的語音辨識功能:Google Colab + Whisper large v3
免費開源的語音辨識功能:Google Colab + Faster Whisper
這篇算是第三篇,是這幾天想調整一下 Cloudflare 上的設定時,看到有多了 Workers AI 的功能,點一點後意外發現的。
原本很開心的以為終於有個好操作的免費版可以使用,但實際使用時,發現 Workers AI 對檔案大小有限制,而且是超過 2MB 就會直接跳「AiError」不給辨識。
不能超過 2MB 的檔案?
想了一想,應該就只有短影音之類的了,所以覺得用 Workers AI 來語音辨識好像不怎麼實用。
只是都已經研究出使用方式了,就還是整理為本篇筆記文,期待以後會再放寬檔案大小的限制。
註冊 Cloudflare 帳號
Cloudflare 是佛心來的,免費帳號就可以擁有很多功能,包含今天這篇 Workers AI。
進到官方網站後,點右上角的「註冊」按鈕,就可以免費註冊:
https://www.cloudflare.com/zh-tw
開通 Speech to Text App 功能
註冊成功後,左側選單點擊「AI > Workers AI」,接著右側點擊「從 Worker 範本建立」:

可以看到 Workers AI 的範本有很多,有興趣的朋友可以玩玩其他的。
本篇我們要使用的是語音轉文字,所以點擊「Speech to Text App」:

點擊後,會看見頁面上 Cloudflare 已經提供了需要的檔案,基本的程式碼也寫出來了。
這一步需要做的,就是修改名稱,然後按下「部署」:

名稱會影響的是後續我們調用 API 時的 URL,可以取一個自己能辨識的。
Cloudflare 部署進度很快,不用 10 秒就會部署完成,成功後會看到以下畫面:

修改程式碼
Workers AI 給的程式碼是基本的使用方式,我們要調整成我們好用的。
本篇,August 會把程式碼調整成前端可以用 API 的方式來取得辨識的結果。
以下程式碼,是 ChatGPT + Claude AI 提供的程式碼,August 再稍為修改一下而成的,上圖中點擊「編輯代碼」後,把以下程式碼複製、貼上去後,再按鈕部署,這步驟就完成了:
const CORS_HEADERS = { 'Access-Control-Allow-Origin': '*', // 這邊可以限制網域 'Access-Control-Allow-Methods': 'POST, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization', }; export default { async fetch(request, env) { if (request.method === 'OPTIONS') { return new Response(null, { headers: CORS_HEADERS }); } if (request.method !== 'POST') { return new Response('Method Not Allowed', { status: 405, headers: CORS_HEADERS, }); } const contentType = request.headers.get('Content-Type'); if (!contentType || !contentType.includes('multipart/form-data')) { return new Response(JSON.stringify({ error: 'Invalid Content-Type' }), { status: 400, headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' }, }); } try { const formData = await request.formData(); const file = formData.get('file'); if (!file) { return new Response(JSON.stringify({ error: 'No file uploaded' }), { status: 400, headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' }, }); } const blob = await file.arrayBuffer(); const inputs = { audio: [...new Uint8Array(blob)], }; const response = await env.AI.run('@cf/openai/whisper', inputs); return new Response(JSON.stringify(response), { headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' }, }); } catch (error) { return new Response(JSON.stringify({ error: error }), { status: 500, headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' }, }); } } };
'Access-Control-Allow-Origin': '*'
這行記得修改,可以限制我們自己的網域才能使用,*
代表全宇宙都可以使用。為了本篇的示範方便,August 這邊才寫為 *
。
最後的畫面會像這樣:

取得 API URL
Cloudflare 部署 Workers AI 後,在我們第一部修改名稱時,就會看到這個對外的網址,如果忘記了,可以點擊畫面中的「workers.dev」取得:

點擊後會新開一個頁籤,這個頁籤的網址就是我們下一步調用 API 時的 URL。
前端建立頁面調用 API
前端的工,就是放一個 input type="file"
,再放一個 button
執行點擊後調用 API,收到回應後再把回應值塞到指定的 div
裡。
之前寫語音辨識為文字的筆記文,有人留言說需要字幕檔的方式,所以以下的程式碼也有加上「下載為字幕檔」的功能。
當然,有了 ChatGPT 的時代,很多程式碼都不用自己從 0 到 1 了,以下程式碼是 ChatGPT 生成一版後,August 再稍微調整的:
<!DOCTYPE html> <html lang="zh-Hant"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>語音轉文字</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/dark.css"> </head> <body> <h1>語音轉文字</h1> <input type="file" id="fileInput" accept="audio/*,video/*" required> <button id="submit" type="button">上傳並轉換</button> <div class="output-section"> <h2>原始辨識結果</h2> <pre id="originalOutput"></pre> </div> <div class="output-section"> <h2>字幕檔 (SRT)</h2> <pre id="srtOutput"></pre> <button id="downloadButton" style="display: none;">下載字幕檔</button> </div> <script> document.getElementById('submit').addEventListener('click', async (event) => { event.preventDefault(); const fileInput = document.getElementById('fileInput'); if (fileInput.files.length === 0) { alert('請選擇一個音頻檔案'); return; } const formData = new FormData(); formData.append('file', fileInput.files[0]); const uri = 'https://xxx.xxx.xxx'; // 替換成自己的 URL const response = await fetch(uri, { method: 'POST', body: formData }); if (response.ok) { const data = await response.json(); if (data.vtt) { document.getElementById('originalOutput').innerText = data.text.replace(/ /g, ','); const srtContent = vttToSrt(data.vtt); document.getElementById('srtOutput').innerText = srtContent; document.getElementById('downloadButton').style.display = 'block'; document.getElementById('downloadButton').addEventListener('click', () => { const blob = new Blob([srtContent], { type: 'text/srt' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'subtitles.srt'; a.click(); URL.revokeObjectURL(url); }); } else { document.getElementById('originalOutput').innerText = '無法取得字幕檔案'; document.getElementById('srtOutput').innerText = ''; document.getElementById('downloadButton').style.display = 'none'; } } else { document.getElementById('originalOutput').innerText = '語音轉文字失敗,請檢查伺服器設置。'; document.getElementById('srtOutput').innerText = ''; document.getElementById('downloadButton').style.display = 'none'; } }); function vttToSrt(vtt) { const lines = vtt.split('n'); let srt = ''; let counter = 1; for (let i = 0; i < lines.length; i++) { if (lines[i].includes('-->')) { srt += `${counter}n`; srt += lines[i].replace('.', ',') + 'n'; counter++; } else { srt += lines[i] + 'n'; } } return srt; } </script> </body> </html>
複製貼上後,需要手動修改的是這行:
const uri = 'https://xxx.xxx.xxx';
換成我們在上一步,從 Cloudflare Workers AI 取得的 URL 即可。
頁面打開來,會長得像這樣:

要注意一下,這邊沒有寫 loading 效果,所以當選好了檔案,點擊「上傳並轉換」後,實際上背後已經在調用 API 了,請自己開啟 Chrome 的 Network 面版查看。
成功的話頁面上會秀出辨識結果。
失敗的話,要從 Console 面版去看錯誤訊息,通常失敗的原因就是檔案大小超過 2MB。

