Add ui record command for MP4 capture#599
Conversation
Adds a new `winapp ui record` command that captures a window or element region to H.264 MP4 using Windows Graphics Capture and Media Foundation, with duration, FPS, max-edge downscale, and optional screen capture for overlays/popups. Also updates the generated CLI schema and UI automation skill/docs to surface the new command. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR adds a new winapp ui record subcommand that captures a target window (or a single element's region) to an H.264 MP4. Frames are grabbed via Windows Graphics Capture (with PrintWindow / screen-DC fallbacks) and encoded incrementally through Media Foundation's IMFSinkWriter, so long recordings never buffer in memory. It fits into the existing UI-automation surface alongside ui screenshot, giving agents/tests video evidence rather than only stills.
Changes:
- New recorder pipeline:
UiRecordCommand,UiAutomationService.Record(capture/crop/scale loop),WgcCapture.Record(persistent frame grabber), andMp4SinkWriterEncoder(Media Foundation encode), plusRecordOptions/RecordCaptureResult/UiRecordResultmodels and newNativeMethods.txtMF projections. - Wiring: registered in
HostBuilderExtensions,UiCommand, theIUiAutomationServiceinterface, plus test stubs/fakes and newUiCommandTests.Recordunit tests. - Docs/schema: regenerated
cli-schema.jsonand updateddocs/usage.md, the hand-written skill fragment, and the two generatedSKILL.mdmirrors.
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
src/winapp-CLI/WinApp.Cli/Commands/UiRecordCommand.cs |
New command: option parsing, input validation, JSON envelope (validation uses the wrong error code). |
src/winapp-CLI/WinApp.Cli/Commands/SharedUiOptions.cs |
Adds --duration-sec, --fps, --max-edge options. |
src/winapp-CLI/WinApp.Cli/Services/UiAutomationService.Record.cs |
Core capture/crop/scale/encode loop and element-rect resolution (WGC-failure fallback diverges from screenshot). |
src/winapp-CLI/WinApp.Cli/Services/WgcCapture.Record.cs |
Persistent free-threaded FrameGrabber that caches the latest frame. |
src/winapp-CLI/WinApp.Cli/Services/Mp4SinkWriterEncoder.cs |
Media Foundation H.264 encoder with deterministic COM release and partial-file cleanup. |
src/winapp-CLI/WinApp.Cli/Services/RecordModels.cs |
RecordOptions / RecordCaptureResult DTOs. |
src/winapp-CLI/WinApp.Cli/Helpers/UiJsonContext.cs |
Adds UiRecordResult for AOT-safe JSON. |
src/winapp-CLI/WinApp.Cli/Helpers/HostBuilderExtensions.cs |
Registers the command handler. |
src/winapp-CLI/WinApp.Cli/Commands/UiCommand.cs |
Adds record as a ui subcommand. |
src/winapp-CLI/WinApp.Cli/Services/IUiAutomationService.cs |
Adds RecordAsync to the interface. |
src/winapp-CLI/WinApp.Cli/NativeMethods.txt |
Adds Media Foundation P/Invoke + interface projections. |
src/winapp-CLI/WinApp.Cli.Tests/UiCommandTests.Record.cs |
Command validation + JSON envelope tests. |
src/winapp-CLI/WinApp.Cli.Tests/FakeUiServices.cs / UiSessionServiceTests.cs |
Fake/stub RecordAsync implementations. |
docs/cli-schema.json, docs/usage.md, docs/fragments/skills/.../ui-automation.md, .github/plugin/.../SKILL.md, .claude/.../SKILL.md, scripts/generate-llm-docs.ps1 |
Schema + docs/skill updates for the new command. |
Notes for the author (files outside this PR, so not inline-commentable): per repo conventions, docs/ui-automation.md should gain a ### record section under ## Commands (it documents every other ui subcommand), and .github/plugin/agents/winapp.agent.md "Key subcommands" list should include ui record.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (durationSec < 0) | ||
| { | ||
| UiJsonError.Emit(json, UiJsonError.CodeInternalError, "--duration-sec must be 0 or greater."); | ||
| logger.LogError("{Symbol} --duration-sec must be 0 or greater.", UiSymbols.Error); | ||
| return 1; | ||
| } | ||
| if (fps < 1) | ||
| { | ||
| UiJsonError.Emit(json, UiJsonError.CodeInternalError, "--fps must be at least 1."); | ||
| logger.LogError("{Symbol} --fps must be at least 1.", UiSymbols.Error); | ||
| return 1; | ||
| } | ||
| if (maxEdge < 0) | ||
| { | ||
| UiJsonError.Emit(json, UiJsonError.CodeInternalError, "--max-edge must be 0 or greater."); | ||
| logger.LogError("{Symbol} --max-edge must be 0 or greater.", UiSymbols.Error); | ||
| return 1; | ||
| } |
| _logger.LogDebug(ex, "WGC recorder init failed; falling back to screen-DC capture"); | ||
| grabber?.Dispose(); | ||
| grabber = null; | ||
| useScreen = true; | ||
| useWgc = false; | ||
| mode = "screen"; | ||
| srcWidth = rect.right - rect.left; | ||
| srcHeight = rect.bottom - rect.top; | ||
| captureOriginLeft = rect.left; | ||
| captureOriginTop = rect.top; |
Build Metrics ReportBinary Sizes
Test Results✅ 1551 passed, 1 skipped out of 1552 tests in 398.0s (+6 tests, -80.1s vs. baseline) Test Coverage❌ 16.8% line coverage, 36.8% branch coverage · CLI Startup Time41ms median (x64, Updated 2026-07-06 03:15:57 UTC · commit |
nmetulev
left a comment
There was a problem hiding this comment.
🤖 AI-generated review — produced by GitHub Copilot CLI at a maintainer's request. Every finding comes from an automated, multi-dimensional review plus a hands-on build-and-test of this branch in an isolated worktree. Treat
file:linereferences as pointers to verify, not gospel. Nothing was pushed. Posting as a comment (no approve / request-changes).
Verdict: 🟡 SHIP-WITH-CHANGES — The core encoder (Windows.Graphics.Capture + Media Foundation, no external deps) works well; the blockers are agent-ergonomics and generated-file sync, not the capture path.
What I actually ran
Built WinApp.Cli (Debug, ~3.5s, 0 warnings); 6/6 Record unit tests pass. Recorded Notepad via -w <hwnd>:
- 3s @15fps → valid MP4 (
ftyp/mp42, h264, 45 frames, 62 KB) ✅ --max-edge 640downscale ✅ correct aspect (1858×1166 → 640×402)- element-region crop ("File" item, 62×48) ✅
--capture-screen→mode":"screen"✅- error paths:
missing_app✅, bad HWND ✅, unwritable path →E_ACCESSDENIED✅
Encoding is a pure Windows stack: WGC (Direct3D11CaptureFramePool, works while occluded) → Media Foundation IMFSinkWriter (incremental H.264, no buffering) → SkiaSharp for crop/scale. Blocks until done; partial files auto-deleted on failure; no temp litter.
Must-fix (High)
- H1 — wrong error codes.
--duration-sec/--fps/--max-edgevalidation emitsCodeInternalError("internal_error") instead ofCodeInvalidArguments(UiRecordCommand.cs:61,67,73). Confirmed at runtime:--fps 0→{"error":{"code":"internal_error",...}}. An agent that treatsinternal_erroras transient will retry-loop on a bad argument. - H2 — agent file not updated.
ui recordis absent from.github/plugin/agents/winapp.agent.md(both the decision tree and the command reference). Copilot agents that rely on that file won't discover the command. - H3 — npm bindings not regenerated.
src/winapp-npm/src/winapp-commands.tshas no typeduiRecord(). Runnpm run generate-commandsand include the result.
Medium / Low
- M1
--capture-screenhelp says "Implies--focus" butrecordhas no--focusoption (SharedUiOptions.cs:86). - M2
mode":"screen"is used for both explicit--capture-screenand the silent WGC-init-failure fallback (UiAutomationService.Record.cs:70,102) — consumers can't detect degradation. Suggest a distinct"screen-fallback". - M3 WGC→screen fallback, element-not-found, and cancel-mid-record paths are untested.
- M4 (agent safety) default
--duration-sec 0records until Ctrl+C — an agent that omits it hangs forever with no signal. Recommend a safe non-zero default (e.g. 30s) with0as an explicit opt-in. - L1
-ohelp reads "e.g., screenshot" on a video command.
Fit & recommendation
Nicely implemented and native (no bundled ffmpeg, no downloads). The honest caveat is audience: agents can't parse H.264, so record is more a human/CI-evidence tool than an agent-perception tool — for state checks, looped screenshot is usually better. It also needs an interactive desktop + WGC-capable GPU, so it's hard to cover in CI. Worth shipping for evidence / UI-regression capture once M4 and the generated-file sync (H2/H3) land.
Summary
winapp ui recordto capture a window or element region to H.264 MP4--capture-screenfor overlays/popupsWhy
This adds first-class video evidence to the UI automation surface so scenarios can be recorded and reviewed instead of relying only on still screenshots.