教學影片
腳本 → 每場景語音 → 錄製每場景剛好對應旁白長度 → ffmpeg。無需手動編輯。已在網頁入口、Chrome 擴充功能彈出視窗與 Expo 應用程式中驗證(PMC, 2026年10月)。
基本規則(先問,絕不假設)
- 僅在測試帳號上執行真實操作,且必須在使用者同意後才能進行。錄製會真實點擊(打卡、確認、核准)。當帳號必須維持原樣時,偏好使用包含模擬資料的環境(模式 B)。
- 絕不顯示憑證。 使用者在鏡頭外登入(驅動程式會等待登入完成)。模糊化電子郵件(疊加層處理),在螢幕截圖中像素化帳戶頁尾。開發日誌可能輸出輸入內容(如密碼):絕不將其 grep 到對話中;編輯日誌檔案。
- 語言 = 母語語音,而不只是翻譯文字:除非另有指示,否則 OpenAI
gpt-4o-mini-tts會保留美國口音。有效設定(德文):語音marin+ 指令「Sprache: Deutsch. Sprich wie eine deutsche Muttersprachlerin aus Deutschland, klares Hochdeutsch … kein amerikanischer Akzent … Speak German as a native speaker; no American accent.」。讓使用者憑聽覺選擇(產生 4–6 個單句語音樣本,並附上音訊播放器發布)。 - 在宣告一個鏡頭完成前,先檢查畫面(在場景中間點產生 6–8 幀的聯絡表)。
專案中的佈局
<app>/tutorial/
script.<lang>.json title, subtitle, endTitle, endSubtitle, brand {color, logo, logoOnDark}, voice, scenes [{id, chapter, vo}]
record.mjs 專案專用:每個場景要做什麼(複製模板)
stage.html 僅模式 B/C(複製模板)
out/ git-ignored: vo/, frames/, timeline.json, 最終 mp4
腳本存放在此技能目錄的 scripts/ 中,模板在 templates/ 中。從包含 playwright 的 node_modules 的資料夾執行腳本(或設定 PLAYWRIGHT_FROM)。
工作流程
- 腳本 (
templates/script.example.json):6–10 個場景,每個場景一或兩個句子(約 6–12 秒口語長度)。場景 ID 驅動錄製器的ACTIONS。開頭/結尾為卡片。以母語人士的寫法在目標語言中撰寫旁白;字幕顯示相同文字。 - 語音:
node <skill>/scripts/voice.mjs <lang> <tutorial-dir>→out/<lang>/vo/*.wav+vo.json(包含持續時間)。金鑰:優先使用環境變數OPENAI_API_KEY,其次是最上層的.env檔案,或是鑰匙圈中的openai-api-key;設定VOICE=say可使用 macOS 的免費語音草稿。兩端的靜音會被修剪。 - 錄製(選擇模式):
- A · 已登入的網頁應用 —
scripts/driver.mjs <workdir> <sign-in URL> <signed-in path regex>會以有頭模式開啟 Chrome for Testing;使用者登入;然後將指令模組放入<workdir>/cmd/(偵查 → 鏡頭外重置 →templates/record-web.mjs)。錄製器會注入scripts/overlay.js(游標、波紋、圈選、章節、字幕、卡片;需要填入__BRAND__、__LOGO__為內聯 SVG 標記、__END_TITLE__、__END_SUBTITLE__)並使用 CDP 的Page.startScreencast進行捕捉。 - B · 瀏覽器擴充功能彈出視窗/使用模擬資料的 UI —
templates/stage-popup.html(一個瀏覽器框架,內含 iframe 的真實建置彈出視窗)+templates/demo-mock.js(在頁面中回應 chrome.* 和 API,一個可移動的時鐘__demo.setNow)+templates/record-stage.mjs。確定性,不觸及任何帳號。 - C · iOS 應用程式 —
templates/record-sim.mjs:使用simctl io recordVideo錄製影片,並透過 argent(describe/gesture-tap)根據輔助功能標籤進行點擊。然後scripts/render-phone.mjs將手機畫面合成到templates/stage-phone.html中,旁邊顯示章節和字幕,並在每次點擊時顯示一個圈選。
- A · 已登入的網頁應用 —
- 渲染:A/B 模式使用
TUTORIAL_NAME=x node <skill>/scripts/render-frames.mjs <lang> <dir>;C 模式使用TUTORIAL_NAME=x node <skill>/scripts/render-phone.mjs <lang> <dir>。輸出:H.264 1080p30, AAC, -16 LUFS,背景音樂在語音下方閃避 16–23 dB。 - 審查聯絡表,修正,重新錄製(先在鏡頭外重置狀態),然後發布(為使用者提供包含
<video>播放器的成品;將 mp4 和海報圖複製到產品頁面所需的位置)。
耗費大量時間的陷阱
- 畫面尺寸:Chrome 全新無頭模式會將螢幕截圖裁切為 993 px 高 — 在模式 B 中使用 無頭 shell(
ms-playwright/chromium_headless_shell-*/…/chrome-headless-shell);在模式 A 中,於 1536×864 的視窗上使用deviceScaleFactor: 1.25的有頭 Chrome for Testing 會產生約 1920×1080 的畫面(渲染會裁剪/縮放到精確的 1920×1080)。 - 同步:A/B 模式以洋紅色閃爍開始;渲染會找到閃爍後的第一幀。C 模式:
recordVideo僅在螢幕變化時寫入幀 — 檔案從啟動時間(recStart)開始,但在最後一次變化時結束,所以絕不要使用stop − duration。 - 等待:開發伺服器在首次造訪時會編譯(5–15 秒)。等待真實內容出現(使用
locator.waitFor),錄製時設定page.setDefaultTimeout(9000),這樣缺少元素時會跳過而非凍結 30 秒;高亮動作絕不能阻塞(在 try/catch 中使用boundingBox({timeout}))。 - 選擇器:側邊欄經常存在兩次(桌面版 + 隱藏的行動版 sheet)→ 使用
a[href*="…"]:visible。已停用的行(鎖定的週次)→ 使用:not([disabled])。群組可能需要先展開。在編寫場景腳本前,先執行一個唯讀的偵查模組(按鈕、對話框、核取方塊狀態、螢幕截圖)。 - 真實應用程式中的對話框通常需要額外輸入(例如至少 3 個字元的理由、「已閱讀通知」核取方塊、每日次數)。在鏡頭外檢查它們;絕不要勾選你沒讀過的核取方塊(有一次錄製勾選了「這天沒工作」)。
- 鏡頭間的狀態:每次真實錄製都會改變資料。寫一個鏡頭外的
reset函式(結束正在進行的班次、確認、關閉對話框)並執行它直到起始狀態恢復。使用「Später」/「稍後」關閉的提示可能不會在導航後持續存在。 - 疊加層:
addInitScript會隨著每次錄製累積 — 疊加層會移除較舊的根元素,最新的會生效。使用<img>(路徑或 data:)的標誌可能會被應用程式的 CSP 阻擋 → 使用內聯 SVG 標記。隱藏僅開發用的 UI(TanStack 開發工具按鈕、Expo 工具 FAB)。 - Expo 開發用戶端 FAB(每張截圖中都會出現的齒輪圖示):
xcrun simctl spawn <udid> defaults write <bundle-id> EXDevMenuShowFloatingActionButton -bool NO,然後重新啟動。使用已安裝應用程式的真實 bundle ID(使用simctl listapps查詢)。 - 深層導覽堆疊中的深層連結會延遲或無作用(如果模態框開啟)→ 在應用程式內導覽(返回、點擊行);這樣看起來也更自然。
- 模擬器上的登入偵測:不要從像素猜測(曾有錄製在使用者輸入密碼時點擊了畫面);等待 Metro 日誌中出現應用程式的第一個已登入請求。
- ffmpeg 8 移除了
-filter_complex_script→ 改用-/filter_complex <file>。奇數寬度會破壞 yuv420p → 縮放/裁切到 1920×1080。 - 鎖定檔案:將工具加入 pnpm workspace 可能會重新解析共享的依賴項 — 檢查
git diff pnpm-lock.yaml | grep '^-[^-]'。
其他語言
複製 script.de.json → script.<lang>.json,以母語作者的身份翻譯(而非原始語言的詞序),為該語言設定語音指令,然後執行語音生成與渲染;僅當 UI 語言也必須更改時才需要重新錄製。