螢幕錄影
Chrome 的螢幕錄影會將瀏覽器本身錄製下來,並重新編碼為 60 fps 的 H.264 .mp4 檔案。字幕、游標、縮放和音效會在之後添加,因此無論頁面正在執行什麼操作,錄影都能保持流暢。
腳本會從當前儲存庫的 node_modules 中解析 playwright,因此請在儲存庫根目錄執行它們。<skill-dir> 是此技能所在的資料夾。
0. 先詢問
除非請求中已說明,否則請在一次訊息中詢問:
- 要說什麼故事? 哪個應用程式和 URL、哪些頁面、觀眾應該看到的 3–6 個重點。誰會觀看(團隊成員、審查者、客戶)?
- 要配音嗎? 僅有字幕,還是要旁白(AI 語音旁白)?客戶影片一定要有旁白。
- 影片要去哪裡? 僅提供
.mp4檔案(預設)、GitHub PR 或 Issue、Linear 任務、Slack 或其他地方。請詢問 PR/Issue/頻道;切勿代他人建立票證。 - 要使用什麼資料? 要針對哪個本地資料庫或帳號進行錄製。絕對不能使用生產環境或真實客戶資料。
1. 錄製
-
先在本地執行應用程式,並將每個頁面各打開一次:否則開發伺服器的首次編譯過程會出現在影片中。
-
基於
scripts/record-lib.mjs編寫一個流程腳本(可複製examples/playwright-site/flow.mjs,這是一個針對公開網站的可執行錄影範例)。 使用step(id, caption, action)(或僅用caption())來為每個步驟在螢幕上顯示一行字幕:位於底部中央,稍微高於播放器的控制列,半透明(使用captionStyle: 'top'可將其設為橫幅)。腳本長度最好控制在大約 1 分鐘。 使用流程的click(locator)/hover(locator)來點擊,而不是locator.click():前者會先將繪製的游標滑動到目標位置。單純使用locator.click()仍然有效,但游標會直接跳過去。zoom(locator, { scale })會平滑地放大到目標,unzoom()則會縮小回來,而click(locator, { zoom: true })會在點擊前先放大。縮放狀態會保持直到你移動它;在縮放的視圖之外點擊會先平移到該處。縮放預算:應該感覺像一個人湊近看,而不是一個反射動作。
- 只有在「小字體本身就是重點」時才縮放:例如 diff、數值變化、對話框訊息。絕不要為了導覽、開啟選單或任何人都能看到的按鈕按下動作而縮放。
- 大約每 15–20 秒縮放一次。如果需要連續縮放兩次:在兩者之間平移(對下一個目標呼叫
zoom),而不是先縮小再放大。 - 在相同區域進行後續操作時保持縮放狀態;當故事轉移到其他地方時再呼叫
unzoom()。 - 縮放比例維持在 1.3–1.5。只有對於非常微小的東西(例如單一圖示)才使用 2。每次縮放至少保持 2 秒。
- 與其縮放,不如考慮加入旁白停頓:如果字幕在原始大小下就能清楚閱讀,就不要縮放。
使用
type(locator, text, { clear })來輸入文字:它會以人類打字的速度進行。click和type會將每次點擊和按鍵記錄到<video>.sfx.json,而narrate.mjs會將錄製好的音效(來自sounds/資料夾的滑鼠點擊和機械鍵盤樣本)疊加到語音下方。SFX_VOLUME用來調整音效音量(預設為 1),SFX=0則表示無音效。使用page.keyboard.type和單純的locator.click()不會產生音效。 -
執行
node flow.mjs會輸出.mp4檔案的路徑。這需要 ffmpeg(來自$FFMPEG環境變數或 PATH 路徑;如果沒有 ffmpeg?執行npm i --prefix <scratch> ffmpeg-static)。
畫質
record(flow, { scale }) 會以 size(預設為 1280×800 CSS 像素,因此 UI 看起來正常)佈局頁面,並以 scale 倍的像素密度渲染:2(預設)是 2560×1600,3 是 3840×2400。文字會更清晰,而不會變小。在 2 倍縮放下,預計每分鐘約 15–40 MB,3 倍則為其 2–3 倍。對於有大小限制的目標平台(GitHub 免費方案 10 MB,Slack 預覽),請使用 scale: 1 或 CRF=24。
旁白(當要求語音時)
- 編寫
script.json:[{ "id": "intro", "say": "…" }, …],每個step的 id 對應一句口白。 撰寫時要考慮聽覺體驗:使用短句,數字用文字表示(例如 "fifteen hundred")。 - 執行
GOOGLE_API_KEY=… node <skill-dir>/scripts/tts.mjs script.json <audio-dir>會為每句口白生成一個 WAV 檔案和durations.json。Gemini 3.8 Flash TTS(可透過TTS_MODEL、TTS_VOICE環境變數覆蓋)聽起來比 OpenAI 的gpt-4o-mini-tts更自然。請在 aistudio.google.com 獲取 API 金鑰。 - 使用
{ narration: '<audio-dir>/durations.json' }選項進行錄製。每個step會等到對應的口白播放完畢後才繼續,開始時間會記錄在<video>.mp4.marks.json中。 - 執行
node <skill-dir>/scripts/narrate.mjs <video.mp4> <audio-dir> <out.mp4>來混合影片剪輯和音效(使用CRF參數重新編碼,預設為 18)。最終交付<out.mp4>。
TTS 注意事項:
- 僅發送口白內容。諸如 "Say warmly: …" 的前綴會被朗讀出來,而
systemInstruction會返回 400 錯誤。 - Gemini 返回的是包含一個完整 WAV 區塊和一個來源證明區塊的資料;將第二個區塊包裝成第二個檔案頭會導致每句口白的頭尾出現靜電噪音。
tts.mjs因此會提取資料區塊;請保留此邏輯。 - 要檢查同步,可以對 mp4 音訊進行帶時間戳的轉錄(使用 OpenAI
whisper-1,搭配response_format=verbose_json),然後與標記進行比對。
可能導致需要重拍的陷阱
- 在已預先填入內容的輸入欄位中,使用
.fill('')清空後再使用pressSequentially,因為它只會附加文字。 - 每次錄製都會變更資料(發佈、接受、儲存)。請在每次錄製之間重設資料。
- 在進行下一步之前,請等待自動儲存和背景 AI 填充完成,否則 UI 可能會跳動。
- 滾輪滾動可能會折疊可摺疊的標題,並隱藏下一個按鈕。建議透過
locator.evaluate使用el.scrollIntoView()。 - 絕不要讓頁面進入瀏覽器的全螢幕模式:畫面尺寸會改變,影片會被裁剪到左上角。在全螢幕按鈕之前,執行
document.documentElement.requestFullscreen = undefined。
演示資料
- 將故事所需的確切狀態植入本地資料庫,使用可信的人物資訊(
example.com電子郵件、555開頭的電話號碼)。絕對不要使用真實客戶、預覽或生產環境資料庫。 - 歷史記錄、稽核和事件表是僅追加的:每次重拍都會增加資料列,讓下一次錄影看起來像事先安排好的。每次錄影都使用全新的記錄,或者將故事限定在不留下痕跡的操作上。
- 先進行乾執行:在流程中使用
DRY=1分支,對每個破壞性確認對話框點擊取消。在幾個步驟標記處提取畫面,修正定位器與構圖,然後進行唯一的一次正式錄製。 - 錄製完成後,檢查資料庫的最終狀態是否與字幕或旁白所述一致。
- 變更資料意味著需要為描述變更的台詞重新執行
tts.mjs。 - zsh 不會對
$VAR進行單詞分割。請直接傳遞畫面時間給frames.mjs,或在bash下使用迴圈。
客戶教學影片(入門、操作指南)
- 一定要有旁白。字幕保持簡短;細節由語音傳達。
- 電子郵件步驟使用 Gmail:從
scripts/gmail-scene.mjs匯入mountGmail(page, { emailHtml, subject, sender, me })。傳入應用程式真正渲染的電子郵件。它會將target="_blank"改寫為_top,因為彈出視窗會讓錄影停在錯誤的頁面上等待。回覆功能已內建:#reply-btn、#reply-text、#send-btn。傳入mailbox: 'sam'可處理多個收件匣(透過/sam和/sam/message路徑提供服務)。回覆永遠不會到達應用程式,因此請將其效果直接寫入本地資料庫。 - 等待元素,而不是
waitForURL:身份驗證重定向鏈會中止導航(ERR_ABORTED),且某些頁面永遠不會觸發load事件。為導覽用的點擊動作加上{ noWaitAfter: true }選項。 - 在(重新)啟動開發伺服器後,對錄影會觸及的每個頁面和 API 路由進行預熱(先訪問一次)。
- 每次錄製前都要重新播種資料。魔法連結 Token 是一次性的,因此失敗的嘗試會消耗它們。
2. 檢查
執行 node <skill-dir>/scripts/frames.mjs <video.mp4> 10 30 50 會在影片旁生成 frame-<t>.jpg 檔案。在交付前先查看它們。對於有旁白的影片,請要求使用者聽一次:僅靠畫面無法發現音訊缺陷。檢查是否有真實客戶資料(姓名、電子郵件、電話):上傳等同於將這些資料發佈給所有能看到目標位置的人。
3. 交付
僅提供檔案(預設)
輸出最終 .mp4 檔案的絕對路徑。完成。
GitHub PR、Issue 或評論(可內嵌播放)
GitHub 僅支援來自 https://github.com/user-attachments/assets/<uuid> 格式的 URL 且位於獨立一行的影片內嵌播放,而該 URL 只能透過 GitHub 的網頁上傳獲得:gh CLI、REST API 和已提交的檔案都只會提供下載連結。上傳的內容對儲存庫成員是私有的,且永遠不會被提交。
node <skill-dir>/scripts/attach-video.mjs <GitHub PR or issue URL> <video.mp4>
- 會開啟一個可見的 Chromium 視窗。如果已儲存的登入狀態已過期,它會顯示 GitHub 的登入頁面:請使用者自行登入。絕對不要詢問密碼,也絕對不要讀取瀏覽器的 Cookie。
- 透過頁面的評論框上傳;評論絕不會被發佈。會輸出
ATTACHMENT_URL https://github.com/user-attachments/assets/<uuid>。 - 會將登入狀態保存在
~/.cache/screen-video/gh-profile。設定FORGET_SESSION=1可在上傳後刪除它。如果同時有另一個上傳任務在進行,它會等待這個設定檔(訊息:WAITING_FOR_PROFILE)。
將該 URL 單獨放在一行(gh pr edit <n> --body-file <file>、gh issue comment <n> --body-file <file>)。若要確認,請檢查 PR 的 body_html 中是否有 <video src=…private-user-images…> 標籤;對資產 URL 執行 curl -sI 可能返回 404,即使它實際上可以播放。檔案大小限制:免費方案為 10 MB,付費方案為 100 MB。
Linear 任務
Linear MCP 可以直接上傳,無需瀏覽器。
- 使用
issue、filename、contentType(設為video/mp4)和size(精確位元組數:macOS 使用stat -f%z <file>,Linux 使用stat -c%s)來呼叫prepare_attachment_upload。 - 在 60 秒內,使用
curl -X PUT --data-binary @<file> "<uploadRequest.url>"上傳,並原封不動地包含uploadRequest.headers中的每一項。改變任何標頭的大小寫或遺漏任一項都會導致 403 錯誤。一次只上傳一個檔案。 - 使用
issue和assetUrl呼叫create_attachment_from_upload。 - 為了讓它在討論串中播放,使用
save_comment加上以及一行說明文字。打開該任務一次以確認它顯示為播放器。 - 若要更換影片:上傳新的,編輯同一則評論(使用
id參數呼叫save_comment),然後使用delete_attachment刪除舊的。
Slack
使用 Slack MCP,在使用者確認頻道後(分享無法撤銷):
- 使用
filename和content_length(精確位元組數)呼叫slack_get_file_upload_url。 - 執行
URL='<upload_url>' && curl -X POST "$URL" -H 'Content-Type: video/mp4' --data-binary @<file>。請確保存放 URL 的變數內容與原始 URL 完全一致;如果收到 401 錯誤,表示 URL 可能已被篡改或過期,請重新獲取一個。 - 使用
file_id、channel_id(一個C…/D…格式的 ID,不是頻道名稱;請使用slack_search_channels查詢)、可選的thread_ts和initial_comment來呼叫slack_complete_file_upload。
如果沒有 MCP,請將檔案路徑提供給使用者,讓他們手動拖曳上傳。
其他平台
提供檔案路徑和大小;如果目標平台有大小限制,請提及可以使用 scale: 1 或 CRF=24 來降低檔案大小。