預告影片
為一個正在執行的應用程式產製一部達到發表品質的預告片(約 30-45 秒):進行分鏡、透過驅動真實 UI 來拍攝、使用 ffmpeg 剪輯,最後交付 MP4 以及一個用於 README 的 GIF。
產出必須看起來像產品影片,而非螢幕錄影。差別在於導向——場景調度、構圖與節奏——而非解析度。
參考實作放在 reference/ 目錄中;它們是可運作的 ESM 模組,不是虛擬碼。請在一個暫存目錄中設定,絕對不要在專案目錄內進行:
cp -r <skill>/reference/* "$SCRATCH/" && cd "$SCRATCH" && npm install
export TEASER_BASE_URL="https://the-app.example.com"
reference/package.json 宣告了 "type": "module" 以及三個依賴套件。每個腳本都會讀取 TEASER_BASE_URL,因此沒有東西是針對單一應用程式寫死的。
在開始任何事前:兩條硬性規則
1. 未經明確、逐項的同意,絕不觸發真實世界的副作用。
拍攝真實的應用程式意味著點擊真實的按鈕。其中有些會寄送電子郵件、公開發佈、收取費用,或向第三方提交表單。在你碰觸應用程式之前:
- 列舉你的分鏡中所有會離開機器的動作。
- 告訴使用者每個動作的作用以及次數,然後獲得他們對次數的同意。
- 優先選擇在不可逆步驟之前停止的模式(預覽、試執行、你可以拍攝然後拒絕的確認閘門)。
對於一個正在運作的系統,不存在所謂的唯讀探測。一個落在已執行工作階段的「診斷性」點擊是真實發生的。如果你正在除錯為什麼某個動作沒有生效,請假設你的下一次點擊會生效,並做好準備。
如果有你未預期的事件觸發了:立即在源頭停止該程序(終止工作階段/工作者,而不僅僅是 UI 按鈕,因為按鈕可能不會生效),透過回讀來驗證停止狀態,並在你的最終訊息中清楚告知使用者——包括次數。
2. 假設每一幀都是公開的且充滿 PII。
真實的帳戶帶有名稱、電子郵件、地址和文件。預先與使用者決定政策:
- 構圖避開它(預設)- 安排排除個人身分的鏡頭。在 ffmpeg 階段裁切掉持續存在的介面元素(側邊欄、頭像、帳戶選單)。
- 在開拍前隱藏 - 在攝影機開始錄製之前,使用
page.evaluate對有問題的卡片設定visibility: hidden。絕不要拍攝它然後指望在剪輯時繞開。 - 使用預先建立的示範帳戶 - 當有這類帳戶存在時最為乾淨。
在以全解析度交付最終剪輯之前,審查其中的每一幀。不是只看縮圖,而是看每一幀本身。
工作流程
1. 偵察與分鏡
首先使用 reference/scout.js 拍攝每個候選畫面的已認證螢幕截圖。仔細觀察它們。然後撰寫一個分鏡表:場景、鏡頭、觀眾應該感覺到什麼,以及長度。一個沒有指定任務的鏡頭在被拍攝之前就會被剪掉。
一個有效的敘事弧線:緊張(問題)→ 揭示(產品)→ 證明(它真的做到了)→ 規模(結果)→ 信任(它在哪裡運行)→ 結尾。
在分鏡中註明哪些畫面含有 PII,哪些是安全的。
2. 佈置場景
在開拍前策劃,切勿在事後:
- 挑選資料看起來最理想的畫面——完整的圖表、活躍的清單、可辨識的名稱、非零的計數。絕不拍攝「載入中」動畫、空白狀態或渲染一半的圖表。
- 透過 init 腳本或開拍前的
page.evaluate,有意識地設定 UI 狀態(面板寬度、摺疊/展開、篩選條件、滾動位置)。 - 在鏡頭外關閉提示訊息、橫幅和 Cookie 同意欄。
- 讓資料穩定下來:
networkidle加上數秒的等待。
3. 拍攝
使用 CDP 螢幕截取(Page.startScreencast),而非螢幕錄影。它能夠以約 100fps 在螢幕外捕捉頁面,因此使用者正在做的事情都不會出現在畫面中,且機器仍可正常使用。Playwright 負責驅動;CDP 負責拍攝。reference/rig.js 實作了這個機制。
- 每個鏡頭只有一個主體。專注於拍攝「那個東西」,而不是「整個應用程式」。
- 鏡頭移動來自頁面內部的腳本化緩動滾動(
cameraScroll),絕不使用濾鏡。請參閱下方的 zoompan 警告。 - 游標是一位演員:使用注入的 SVG 游標(
cursorClick),它會以緩動方式滑動並在按下時下沉,或者隱藏它。不要讓游標閒置漂移。 - 彩排每個鏡頭,提取幀,審視它們,調整,然後再正式拍攝。
拍攝外部視窗(應用程式本身開啟的瀏覽器、桌面應用程式)需要使用作業系統的畫面擷取功能 - gdigrab/x11grab/avfoundation(rig 中的 recordDesktop)。這會捕捉螢幕上的所有畫面,包括使用者自己的工作內容。警告使用者,請他們離開,並逐幀審查結果;如果捕捉到任何私人內容,請立即刪除。
4. 標題卡片
使用動態 HTML/CSS 在瀏覽器內拍攝,而非靜態圖片。將卡片標記注入到應用程式自己的來源網域中的一個空白頁面,以便其網頁字型與配色能夠正確解析,然後進行螢幕截取。從正在運作的頁面上提取真實的顯示字型(getComputedStyle),並固定一個已知良好的等寬字型堆疊,而非去猜測一個。
配合應用程式的動態語言(它自己的緩動曲線與持續時間)。如果專案沒有既定的卡片風格,請在設計它們之前載入 frontend-design 技能。
5. 剪輯
reference/build.js 將每個片段正規化為統一的尺寸/影格率,然後對整個序列進行交叉淡入淡出。保持剪輯節奏約每 2.5-4 秒一次。在流暢場景中讓關鍵時刻有足夠時間,同時壓縮其周圍的常規操作。
6. 審查,然後交付
- 產生一個聯絡表以及每秒的全解析度幀。
- 檢查這些幀是否有 PII、被裁切的面板以及難以閱讀的文字。
- 使用探測工具驗證持續時間與尺寸。
- 交付一個 MP4,以及一個用於 README 的調色板最佳化 GIF(GitHub 不會內嵌播放提交的 MP4)。將 GIF 保持在約 10 MB 以下。
- 除非被要求,否則保持無聲;你無法授權音樂。請明確說明。
辛苦得來的具體細節
絕對不要使用 zoompan 做推近鏡頭。 它會逐幀重新計算縮放比例,並將裁切視窗四捨五入到整數像素,因此影像會前後抖動——看起來像地震。請使用固定鏡頭加上真實的頁面內滾動。如果你必須要有推近鏡頭,請先將來源解析度放大 4 倍,然後使用幀差異測量(reference/jitter.js)驗證:一個鎖定的靜態鏡頭應該測量到遠低於 1.0 的平均差異;可見的晃動則讀數在 3 以上。
當動畫靜止時,螢幕截取會停止發送幀。 一個動畫在 1.2 秒後結束的卡片只會產生一個 1.2 秒的片段。使用 tpad=stop_mode=clone:stop_duration=N 來填充,並用 -t 裁剪到你想要的長度。
組裝帶有時間戳的幀,而非固定速率。 螢幕截取的幀會附帶 metadata.timestamp;撰寫一個使用實際每幀持續時間的 ffmpeg concat 列表,然後重新取樣為固定的 60fps。假設一個固定的間隔會產生抖動。
將 ffmpeg-static 作為開發依賴,並透過模組路徑解析。絕不要依賴系統 PATH 上的 ffmpeg。(ffprobe 不在那個套件中——請解析 ffmpeg -i 的 stderr 輸出。)
認證擷取: 先在可視模式下登入一次,儲存 storageState,然後在每一次拍攝中重複使用它(reference/login.js)。工作階段會過期——請在同一天內完成拍攝,或重新執行登入。同時在那裡設定任何 localStorage 的 UI 偏好設定。
從已部署的來源網域存取 localhost 會被阻止,這是因為 Chrome 的區域網路存取政策。如果頁面必須連線到本地服務,請使用 --disable-features=LocalNetworkAccessChecks,PrivateNetworkAccessChecks,BlockInsecurePrivateNetworkRequests 啟動。
以固定偏移量裁切持續存在的介面元素。 以 1920 + railWidth 的視窗寬度進行拍攝,然後使用 crop=1920:1080:railWidth:0 移除側邊欄,精確地得到 1080p。
交付物
回報路徑、持續時間與尺寸;清楚說明仍然需要從使用者那裡獲得什麼(音樂、一個 GitHub 原生影片上傳);並揭露在拍攝過程中發生的、任何使用者會想知道的事情。