Communitygithub.com

demo-video

製作一個短產品展示影片,在真實模擬器上操作 warden(或範例應用程式),並在手機旁顯示正在輸入的終端機。

demo-video 是什麼?

此專業技能可自動化生成產品展示影片,可用於文件、社交平台或重新錄製。它在租用的 iOS 模擬器上錄製操作,並渲染出 1920x1080 的影片,左側顯示標題和即時輸入命令的終端機,右側顯示帶圓角邊框的手機畫面。開發人員透過編寫 `*.demo.ts` 定義檔來描述展示步驟,每個步驟都對應一個真實的 `warden` 命令,確保展示內容的誠實性。錄製完成後,系統會自動檢查影片的每一幀,確認手機畫面與命令日誌同步,且無任何偽造的提示框或動畫。本技能支援多種展示管道,包括標準影片、`warden dev` 開發儀表板展示,以及終端機為主的批次測試影片。每個定義檔都可配置應用程式設定、啟動 deep link、模擬狀態列,並遵循嚴格的規則,確保所有展示的命令都真實執行過,未被修改。最終產出的 MP4 和 JPG 檔案會儲存在 `apps/docs/public/demos/` 目錄下,並可透過 MDX 元件 `<DemoClip>` 或 `<DemoReel>` 嵌入文件中。

相容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/delacournz/warden/tree/HEAD/.claude/skills/demo-video

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

demo-video 是做什麼的?

bun run demo <feature>/<clip> 在租用的 iOS 模擬器上錄製一段影片,然後渲染成 1920x1080 的影片:左側是標題,一個終端機會逐字打出每個真實的 warden 命令,並在步驟結束時印出輸出;右側是手機畫面(圓角,3px 邊框)。工具包:scripts/demo/(demo.ts CLI、record.ts、render.py、timeline.ts、demo.types.ts)。定義檔:apps/example/demos/<feature>/<clip>.demo.ts(name 必須等於 <feature>/<clip>);靜態資源:apps/docs/public/demos/<feature>/<clip>.mp4|jpg。每個功能一個資料夾(biometrics、network、location、clock),小寫字母,連字號。內部使用:無需變更集(changeset)。

1. 何時製作

  • 功能的效果在畫面上可見且在幾秒內發生(例如生物辨識、警示、手勢)。不適用於日誌、效能數字或任何用表格呈現更好的內容。
  • 當 App UI 或某個命令的行為變更後:需要重新錄製,永遠不要編輯 mp4。
  • 每個剪輯一個故事,長度 10-25 秒,包含 3-6 個附有說明的步驟。

需要 ffmpeg、ffprobe、uv(brew install ffmpeg uv)、Xcode 以及 node_modules/@expo-google-fonts 中的字型(bun install)。

2. 撰寫定義檔

導出 demo: DemoDefinition(scripts/demo/demo.types.ts)。複製 apps/example/demos/biometrics/faceid.demo.ts。

欄位意義
title / subtitle / terminalTitle / footer靜態文字;title 是主張,subtitle 是一行說明
phoneCaption手機下方的說明文字,預設為 真實模擬器 · 非動畫;設為 "" 可隱藏
claim{ biometrics?, profile? } -> warden claim ios --json --label demo-<feature>-<clip> --ttl 30m [--biometrics]
appbundleId、kind(預設 simulator)、project(相對於儲存庫的 cwd)、deepLink(以 --settle 開啟)、appearance(light/dark,透過 simctl ui 在租用的 udid 上設定,錄製後恢復)
setup在應用程式開啟後、deep link 之前離線執行的 warden 參數;["ui","network","set","online","--relaunch"] 會為 iOS dylib 啟動網路 / 時鐘步驟提供所需的環境。每個條目是一個 argv 或 { argv, optional: true }:可選步驟若以非零退出碼結束,僅被記錄(optional setup warden ... -> <code>)而不會導致錄製失敗(例如可能不會出現的權限提示)
ready在 deep link 之後、影片開始之前必須通過的 warden ui wait 選擇器(例如 id=bio-screen);步驟絕不會按下未掛載的畫面
leadInMs / tailHoldMs步驟 1 之前的閒置時間 / 最後一幀的停留時間,由渲染器填充(錄製尾部為 1 秒)
steps[]argv(warden 之後的參數;錄製器會附加 --udid)、output(ok 繪製一個向量勾號,plain 為靜音模式,lines 印出多行靜音文字)、pauseAfterMs、shown?、hidden?、quote?
outro最後一個輸出之後輸入的 # 註解 行

output: { kind: "lines", text: [...] } 會將每一行印出為一條靜音文字(大約保持 6 行;每增加一行會消耗版面上 0.4 個條目的間距),用於顯示檔案中的程式碼或文字。搭配 quote: { file }(路徑相對於 app.project):步驟完成後,錄製器會檢查每一行印出的文字(去除前後空白)是否存在於該檔案中,否則會拋出 quoted line not in <file>: <line> 錯誤,因此引用行絕不會是虛構的。這些行仍然會經過 maskSecrets 處理。

時間絕不是手動撰寫的:錄製器會根據影片時鐘測量每個步驟的開始和結束時間,輸出行會在步驟結束時出現。

3. 循環流程

bun run demo <feature>/<clip> --record-only      # 聲明、9:41 狀態列、啟動、錄製、依 ID 釋放
bun run demo <feature>/<clip> --render-only --sheet   # 建置 mp4 + jpg,寫入 <work>/sheet.jpg
bun run demo <feature>/<clip>          # 兩者都執行

工作目錄:$TMPDIR/warden-demo/<feature>/<clip>(--out dir):take.mp4、steps.json、plan.json、sheet.jpg;若錄製失敗則有 failure-snapshot.txt(錯誤 + ui snapshot -i)。其中的資料都不會提交。

  1. 錄製。
  2. 讀取 sheet.jpg(4x4 格,每秒一格,時間點已標示)。與日誌比對:每個輸出行出現時機須與手機畫面上的反應一致;任何畫格不得顯示不相關的提示、鍵盤、紅框或錯誤的狀態列;最後一幀需顯示結束狀態。
  3. 如需更仔細檢查:ffmpeg -ss 7 -i take.mp4 -frames:v 1 f.png 並「讀取」它。
  4. 在定義檔中修正(pauseAfterMs、刪除/隱藏步驟、shown)並執行 --render-only;僅在錄製內容本身有問題時才重新錄製。批次修正:每次循環只錄製一次。
  5. 將 mp4 + jpg 提交到 apps/docs/public/demos/<feature>/。

4. 誠實規則

  • 每個印出的命令都實際執行過,且是在真實的應用程式上執行。shown 的意義不能偏離 argv(它僅用於修剪 --timeout 等旗標)。
  • 輸出文字是手機實際上做的事情,而非命令本身印出的內容。如果某個按壓操作沒有可見效果(例如在畫面的進入動畫期間被忽略、沒有任何改變),則不要為其加上說明:使用 hidden: true,或透過延長上一個 pauseAfterMs 來調整時機。
  • 首次執行的權限提示只在全新安裝時顯示;聲明會重複使用已暖機的建置,因此除非影片中有顯示該提示,否則不要為其加上說明。
  • 不得繞過應用程式端或使用模擬的提示:標題中的主張必須在影片中為真。
  • 機密資訊:maskSecrets 會隱藏 key=value、ghp_/sk- 風格的權杖以及印出文字中的長十六進位/base64 字串。但仍需檢查手機畫面中是否有權杖、電子郵件、裝置名稱。
  • 真實模擬器,真實 iOS UI。絕不合成假的畫格。

5. 注意事項(內建於工具包中)

  • simctl video 是 VFR 且會丟棄閒置的尾部:渲染器會進行 fps 標準化並將最後一幀填補(tpad)至計畫長度。
  • 影片時間與命令日誌的差異約在 0.5 秒內;錄製器在 video start 之後立即啟動時鐘。
  • Martian Mono 沒有檢查符號字形:render.py 將其繪製為向量。
  • 畫面使用的是 docs Device Hub 深色調色板(Mona Sans + Martian Mono);設定 app.appearance: "dark" 以讓手機畫面相符。錄製器會在之後恢復模擬器的外觀。
  • 終端機溢出:computeTerminalLayout 會將間距和字型縮小為 17 px,然後將最早輸入的條目滾出畫面。
  • 裝置影片限制為 3 分鐘;無論如何請將錄製保持在 40 秒以下。
  • 狀態列:xcrun simctl status_bar <udid> override 允許在租用的 udid 上使用;錄製器會在之後清除它。

6. X 平台匯出

1920x1080,H.264 High,yuv420p,30 fps,+faststart,無音訊,長度不超過 2:20,檔案小(工具包目標是透過 CRF 階梯 24..36 控制在不超過 2 MB)。檢查:

ffprobe -v error -show_entries stream=codec_name,profile,width,height,r_frame_rate -show_entries format=duration,size apps/docs/public/demos/<feature>/<clip>.mp4

7. 靜態資源與文件嵌入

輸出:apps/docs/public/demos/<feature>/<clip>.mp4 + <clip>.jpg(海報 = 最後一幀),透過 /demos/<feature>/<clip>.mp4 提供服務(Vite public/)。使用 DemoClip MDX 元件(apps/docs/src/components/demo-clip.tsx)嵌入:自動播放、靜音、循環播放;在減少動作模式下會停留在海報畫面。src 不包含副檔名;label 說明剪輯的內容(與說明文字相符)。使用原始 <video> 標籤會導致 apps/docs/src/lib/source.test.ts 測試失敗。

<DemoClip src="/demos/<feature>/<clip>" label="warden ui ... 正在租用的模擬器上執行 ..." />

新增一個說明句子,命名所使用的命令。批次展示影片 apps/docs/public/demos/e2e/batch.*(使用 <DemoReel> 嵌入)是另一條不同的管線(bun run demo:compose)。

8. warden dev 展示影片(dev/hero)

第三條管線:warden 的真實開發儀表板(左側,在終端機卡片中)與模擬器 warden dev 自行聲明的顯示(右側)並排。沒有輸入或偽造的內容:dev-take.ts 在一個 PTY(Bun.spawn({ terminal }))中生成 warden dev ios --label demo-dev-<clip> --headless --no-shutdown,並將其內容鏡像到 @xterm/headless(用於等待的幀讀取),並將每個 PTY 區塊寫入 tui.cast;時鐘原點是生成時刻。它輪詢 warden ls --json 以取得已標記的裝置租約,然後設定深色模式 + 9:41,解除安裝應用程式並啟動 ui screen video(在此之前手機畫面是黑色)。開發子程序有自己的 WARDEN_SESSION_ID:warden dev 會接管呼叫者 session 所持有的裝置租約,而每個協作者代理共用一個 session,如果沒有這個設定,錄製可能會驅動到其他人的模擬器。

bun run demo:dev dev/hero --record-only        # 聲明、解除安裝 app、9:41 + 深色模式、錄製、依 ID 釋放
bun run demo:dev dev/hero --render-only --sheet # 聚合 + ffmpeg -> apps/docs/public/demos/dev/hero.{mp4,webm,jpg},<work>/sheet.jpg
bun run demo:dev dev/hero                      # 兩者都執行
  • 工具包:dev-demo.ts(CLI)、dev-take.ts(錄製器,runBeats)、dev-render.ts(聚合 + 合成,DEV_LAYOUT)、dev-frame.py(卡片、邊框、遮罩)、dev-timeline.ts(投射文字、斜坡區段、說明文字)。型別:DevDemoDefinition / DevBeat,位於 demo.types.ts。定義檔:apps/example/demos/dev/<clip>.demo.ts;工作目錄 $TMPDIR/warden-demo/dev/<clip>(take.mp4、tui.cast、dev.json、sheet.jpg、failure-frame.txt)。
  • 一個節拍 = waitScreen(對 TUI 幀進行正則表達式比對)+ waitPhone(ui wait 選擇器)-> 動作(向 TUI 輸入按鍵,和/或對儲存庫檔案進行真實的 edit)-> afterPhone 選擇器(熱重載的著陸點)-> pauseAfterMs。dev.json 保存每個節拍在影片時鐘上的開始 / 就緒 / 結束時間;說明文字從一個節拍的動作執行到間下一個節拍的動作。
  • 編輯是真實的原始碼編輯(welcome.tsx 的字串內容),並在 finally 中逐位元組還原;錄製後檢查 git diff apps/example/src 是否為空。
  • uninstallFirst 會從租用的模擬器中移除應用程式,以便 warden dev 從快取中安裝開發版本(在鏡頭前)。首次基於指紋的錄製會是本機建置(數分鐘,dev build 行會編譯):先錄製一次以暖機快取,然後重新錄製。warden app ensure --kind dev 不會暖機 warden dev 所讀取的那個快取。
  • 速度斜坡:DevBeat.ramp: N 會將該節拍的等待時間加快 N 倍(保留 1.2 秒的開頭和 1 秒的尾部以真實速度播放),並且展示影片會繪製一個 × N 速度 標籤。如果暖機錄製在開發用戶端等待時速度較慢,可以對其進行斜坡加速。按鍵輸入和熱重載始終是即時速度。
  • 誠實性:<DemoReel> 說明文字中的事實來自錄製的 TUI 行(dev build 3.5s、Metro 3.9s、dev client 161ms)和 dev.json(編輯 -> ui wait 看到新文字:0.76 秒)。每次重新錄製後都重新讀取它們;它們可能會改變。
  • 限制:儀表板沒有單獨的聲明行(它在 preparing the ios dev build 內部);--headless 所以不會開啟 Device Hub 視窗;100x26 PTY(100 列是 HUD 的最小值)。一個有 stopVideoFirst 的節拍會在其動作之前停止影片(q 會結束租約,之後 warden ui 會拒絕)。在 q 之後,錄製會驗證 warden ls 沒有持有該 session 的任何租約(否則失敗),並且投射內容會在儀表板離開備用畫面處被截斷,以便展示影片以其最後一幀結束。開發啟動器的白色載入畫面和藍色的 Refreshing... 橫幅是真實的應用程式;Metro 重新載入也可能來自另一個正在編輯 apps/example/src 的代理。
  • 斜坡:ramp 僅在等待時間減去其 1.2 秒開頭和 1 秒尾部後,至少還有 6 秒時(RAMP_MIN_S)才應用。英雄展示影片將其開發用戶端等待時間加速 3 倍,並帶有標籤。
  • devEnv 會為 warden dev 子程序添加環境變數;英雄展示影片設定 EXPO_NO_DEPENDENCY_VALIDATION=1(@expo/cli checkDependenciesOnStart)以移除 Expo 在 Metro 面板中顯示的「有可用更新」提示。僅用於減少雜訊,絕不改變行為。
  • 使用 <DemoReel src="/demos/dev/hero" command="warden dev ios" facts="..." label="..." /> 嵌入(登陸頁面英雄、sims/dev.mdx、getting-started/quickstart.mdx)。海報 = posterBeat 動作幀 + 1 秒(HUD 已開啟,所有行已完成,應用程式正在執行)。

9. 終端機為主的剪輯(VID-6)

所有命令都是真實的;每個展示影片只顯示實際執行的內容。一次一個剪輯,每次按標籤釋放。

剪輯管線命令
e2e/parallelbatch-take.ts:warden e2e <suite> --count N --record --headless 進入 <work>/rec,在每個租用模擬器啟動時設定深色模式 + 9:41,然後 compose.ts --height 1080bun run demo:batch e2e/parallel --suite example --specs devtools,gestures,onboarding,sign-in,todo-detail --count 2(--record-only / --compose-only;標頭顯示規格數量;當規格與任務不同時,計數器顯示 N/總分片完成)
dev/worktreesLanesDemoDefinition:每個並發車道一個 recordDev,下方一個 shell 條(warden ls)bun run demo:lanes dev/worktrees(lanes.ts、lanes-frame.py、lanes-demo.ts;車道透過 DevTake.startedAtS 共用同一個時鐘)
golden/cloneDevDemoDefinition.script:一個 shell 腳本在 PTY 中執行,而非 warden devbun run demo:dev golden/clone
debug/failurescript + rawVideobun run demo:dev debug/failure --sheet
  • script 模式:不解安裝,不檢查釋放;錄製以 release --mine --label L --shutdown 和 sims delete <udid> --yes 結束。一個帶有 waitBoot 的節拍會輪詢 simctl io screenshot 直到 PNG 超過 100 KB(HOME_SCREEN_MIN_BYTES):一個新克隆的無頭模擬器會在 claim 返回後 10 秒以上才顯示 Apple 標誌。對該等待進行斜坡加速(3 倍,帶標籤)。
  • rawVideo:使用 simctl io recordVideo(SIGINT 停止)錄製,因為 warden 的 ui screen video 輔助工具與 warden e2e 在同一裝置上衝突。
  • --record 僅記錄在開始時存在的裝置;延遲加入的彈性裝置沒有影片。主機容量閘門決定可以容納多少,因此要求啟動的模擬器數量不超過一開始已開機的數量(此處最多 2 個)。
  • debug/failure 的拋棄式規格寫入 apps/example/e2e/debug-demo.e2e.ts,並由 shell 陷阱移除;之後檢查 git status(中途被終止的錄製可能會留下它)。
  • 使用 <DemoReel src="/demos/<feature>/<clip>" command="..." facts="..." label="..." /> 嵌入;事實來自錄製,每次重新錄製後重新讀取。
  • demo:batch 使用輪詢器(styleWhenBooted:針對標籤使用 warden ls --json,針對 Booted 狀態使用 simctl list,然後執行 ui appearance dark + status_bar override 9:41)來設定模擬器樣式,因為 warden e2e 沒有每個裝置的鉤子;它在 finally 中釋放 --mine --label demo-<feature>-<clip> --shutdown。使用能在乾淨模擬器上通過的規格(perf、settings、todos 不可靠);合成指令的 --height 1080 旗標提供 16:9 佈局(終端機... 被截斷)

相關技能