/screen-record — 腳本化瀏覽器逐步操作影片
使用時機
使用者想要一個網站在執行某項操作的精美影片 — 通常用於 LinkedIn、Twitter、銷售簡報、投資人更新或入門文件。他們描述螢幕上應該出現什麼、游標應該怎麼做、以及旁白大致要說什麼。你負責編寫腳本;引擎會處理游標動畫、透過 ElevenLabs 進行文字轉語音 (TTS)、背景音樂、點擊音效以及字幕內嵌。
請不要使用此技能來處理:已經在進行的真實螢幕錄影(請使用 macOS 截圖或 QuickTime)、GIF(請使用 GIF 工具)、或需要真人配音的影片(你仍然可以編寫腳本,然後請他們重新錄製)。
運作方式
引擎位於 engine.mjs。它匯出 runDemo(config) 函數,其功能如下:
- 透過 ElevenLabs 為每個提示 (cue) 生成一個 TTS 音檔。
- 使用 ffmpeg 生成柔和的背景音樂音軌與一個 UI 點擊音效。
- 以 1920×1080 解析度啟動無頭 Chromium,並錄製影片。
- 執行你的
scenes(helpers)函數來驅動瀏覽器。 - 混合音訊(旁白大聲 + 音樂小聲 + 點擊聲輕柔)並燒錄 ASS 格式的字幕。
- 回傳最終
.mp4、.webm、.srt、.ass檔案的路徑。
工作流程
步驟 1 — 閱讀使用者的請求
明確下列資訊:要錄製的 URL、流程(游標應該做什麼)、語氣(正式/隨意)、長度目標。如果有任何遺漏,請提出一個明確的問題,而不是猜測。
步驟 2 — 檢查 API 金鑰
依照此順序尋找 ELEVENLABS_API_KEY:
$CLAUDE_PLUGIN_ROOT/.env.local(或技能資料夾本身的.env.local)- 當前專案的
.env.local process.env
如果找不到,請告訴使用者:
我需要一個 ElevenLabs API 金鑰。請到 https://elevenlabs.io/app/settings/api-keys 免費申請(每月 10,000 字元,大約可製作 15 個示範)。您可以在對話中貼上金鑰,我會將其儲存到技能的
.env.local,或者您也可以自行新增。
如果金鑰存在,但引擎拋出 ApiKeyDeadError(HTTP 401/402/403),請告訴使用者:
您的 ElevenLabs 金鑰被拒絕了(可能已撤銷、過期或額度用盡)。請到 https://elevenlabs.io/app/settings/api-keys 輪換金鑰,然後更新
.env.local。在您完成之前,我不會重試。
請勿默默重試,除非使用者明確要求,否則不要回退到 macOS 的 say 指令。
步驟 3 — 選擇語音
預設是 Liam (TX3LPaxmHKxFdv7VOQHJ) — 充滿活力的美國男性,現代感。比 George(英國)和 Sarah(美國女性)更輕快。其他免費語音可在 engine.mjs 中的 FREE_TIER_VOICES 下找到。如果使用者說「使用 X 語音」,請在目錄中找到它。如果他們說「低沉/厚重/輕柔/溫暖/女聲」,請據此選擇。
資料庫語音(如 Charlotte、Rachel、Adam 等)需要付費的 ElevenLabs 訂閱。如果使用者要求使用此類語音但他們使用的是免費方案,你會收到 HTTP 402 錯誤 — 此時請回退到最接近的免費語音並告知使用者。
步驟 4 — 探查目標頁面(僅在需要時)
如果你不了解頁面結構,請使用 Playwright 或 curl 檢查 DOM 以尋找選取器。不要猜測 — 選取器很重要,模糊的匹配會導致錄製超時。
對於具有動態內容的 SPA,請優先使用 aria-label、角色 (role) 或文字選取器,而不是 class 名稱。使用 page.locator(s).first() 語義(引擎的輔助函數已經這麼做了)。
步驟 5 — 撰寫場景檔案
在 /tmp/screen-record-<名稱>.mjs 建立一個檔案(不要污染使用者的儲存庫),該檔案匯入引擎並呼叫 runDemo。範本:
import { runDemo } from '/Users/<你的使用者名稱>/Desktop/Projects/skill/screen-record-skills/engine.mjs';
// 或者,如果是透過符號連結安裝:
// import { runDemo } from '/Users/<你的使用者名稱>/.claude/skills/screen-record/engine.mjs';
await runDemo({
baseUrl: 'http://localhost:3000',
outputDir: './output/onboarding-demo',
outputName: 'onboarding',
splash: {
label: '造訪我們於',
url: 'www.yourapp.com',
footer: 'YourApp — 入門流程',
},
cues: [
{ id: 'intro', text: '歡迎使用 YourApp。讓我帶您四處看看。' },
{ id: 'signup', text: '點擊「註冊」來建立帳戶。' },
{ id: 'form', text: '輸入您的電子郵件和密碼。' },
{ id: 'done', text: '就這樣 — 您已經進來了。' },
],
// 可選:模擬後端,使示範結果可預測。
routes: [
{ match: /\/api\/signup$/, fulfill: { status: 200, body: { ok: true } } },
],
// 可選:預先設定一個已驗證的使用者,讓示範跳過登入。
// initialAuth: { id: 'u1', email: '[email protected]', name: 'Demo User' },
scenes: async ({ page, baseUrl, sleep, speak, awaitCue,
clickAt, typeInto, moveCursorTo, hideSplash, typeSplashUrl,
configSwitch, showSwitch, hideSwitch, setAuth }) => {
// 場景 0:URL 啟動畫面(引擎已離線導航至 baseUrl)。
speak('intro');
await typeSplashUrl('www.yourapp.com');
await awaitCue('intro');
await hideSplash();
await sleep(750); // 配合啟動畫面淡出時間
// 場景 1:點擊註冊
speak('signup');
await clickAt('button:has-text("Sign Up")');
await awaitCue('signup');
// 場景 2:填寫表單
speak('form');
await typeInto('input[type="email"]', '[email protected]');
await typeInto('input[type="password"]', 'CorrectHorseBattery');
await awaitCue('form');
// 場景 3:提交
speak('done');
await clickAt('button[type="submit"]');
await sleep(1000);
await awaitCue('done');
},
});
步驟 6 — 執行
cd /Users/<你的使用者名稱>/Desktop/Projects/skill/screen-record-skills
# 僅第一次需要:
npm install --silent
# 每次執行:
node /tmp/screen-record-<名稱>.mjs
每次執行時間約等於最終影片長度加上約 10 秒的額外開銷。
步驟 7 — 回報結果
告訴使用者:
- 最終
.mp4檔案的路徑(適用於 LinkedIn,1920×1080,H.264 + AAC) .srt檔案的路徑(LinkedIn 會自動將其導入作為隱藏式字幕)- 在 macOS 上提供使用
afplay <路徑>播放的選項
輔助函數參考(你的場景可以呼叫的函數)
| 輔助函數 | 功能說明 |
|---|---|
page | 原始的 Playwright Page 物件,如有需要可使用 |
baseUrl | 設定的基礎 URL |
sleep(ms) | await sleep(800) |
moveCursorTo(x, y, durationMs?) | 平滑移動到指定座標 |
moveToSelector(selector, durationMs?, offset?) | 平滑移動到元素中心 |
clickAt(selector, durationMs?) | 移動 + 脈衝 + 點擊(記錄點擊時間用於音效) |
typeInto(selector, text, perChar?) | 點擊 + 清空 + 逐字元輸入文字 |
recordClick() | 手動記錄一個點擊時間戳記(用於音效) |
speak(cueId) | 標記提示開始。結束時間為 start + audioDuration + 0.35s |
awaitCue(cueId) | 暫停直到該提示的旁白播放完畢 |
typeSplashUrl(text, perChar?) | 在啟動畫面 URL 欄位中進行動畫打字效果 |
hideSplash() | 淡出啟動畫面(之後務必 sleep 約 750ms) |
configSwitch({ fromInitial, fromLabel, toInitial, toLabel, title, sub }) | 設定帳號切換卡的內容 |
showSwitch() / hideSwitch() | 切換帳號切換覆蓋層的顯示/隱藏 |
setAuth(user) | 將 localStorage[authStorageKey] 設定為新使用者(與 showSwitch 搭配用於帳號交換示範) |
節奏基本原則
- 每個提示 (cue) 一句話。保持在 12 個詞以內。
- 對於 UI 操作,在動作之前呼叫
speak(cue),這樣旁白會在游標移動時播放。 - 對於打字密集的場景,打字時間會長於旁白時間 — 使用
sleep而非awaitCue來控制節奏。 - 影片總長度通常為
sum(audioDurations) + sum(sleeps)。對於 LinkedIn,目標設定在 30–75 秒。
容易出問題的常見選取器
- 單獨使用
text=Foo可能會匹配到多個元素。請使用page.locator(...).first()(引擎已經這麼做了),或使用button:has-text("Foo")將範圍限定在特定標籤。 - Tailwind 的 class 名稱很脆弱。請優先使用
aria-label、角色 (role)、語義化標籤加文字。 - 模態框/對話框:在點擊內部元素之前,請等待
[role="dialog"]或模態框的標題文字出現。
應避免事項
- 不要將實際的旁白文字寫入場景檔案中 — 將旁白保留在
cues[].text中,以便字幕和音訊保持同步。 - 不要將游標樣式寫入頁面的 CSS 中 — 引擎會注入它。
- 不要在場景中間使用
await page.screenshot()— 你可能會捕捉到處於奇怪位置的游標;如有需要,請事後使用extractFrames。 - 不要提交
.env.local(它已列在.gitignore中)。