Skip to main content
Glama

screencast-desktop

checks License: MIT Platform: Windows Python 3.10+

一个 Claude Code 插件,让智能体操作 Windows 应用程序,并将该会话转化为成品演示视频——其中镜头会在每次点击发生前半秒就开始向点击处推进。

  • 视觉闭环驱动桌面操作。 每个动作都会在同一回复中返回一张全新截图,因此智能体以"看→做→看"的方式工作,而不是盲目地编写脚本。控件通过 Windows UI Automation 按名称查找;不暴露控件树的 Electron 应用则回退到截图和坐标(或在调试端口开启时通过 CDP 访问其 DOM)。

  • 录制窗口,而非屏幕。 采集通过 Windows Graphics Capture 进行,绑定到窗口句柄,因此应用背后的桌面永远不会进入画面,窗口位于哪个显示器或 GPU 上也无关紧要。

  • 视频由事件日志构建,而非由录像构建。 每次点击、每段键入的字符串以及完整的光标路径都会在发生时被打上时间戳。镜头运动即根据该日志计算得出。

  • Screen Studio 级别的成片效果,自动生成: 缓动镜头推进与平移、带柔和阴影的绘制光标、点击波纹、运动模糊、渐变背景上的圆角、暗角与颗粒感——外加死寂片段移除,可压缩智能体自身的思考时间。

  • 旁白精准踩点。 可选的 ElevenLabs 配音,在运行之前生成,使点击恰好落在描述它的句子内部。

  • 更完整的蒙太奇层,只需一个脚本。 标题卡与片尾卡、字幕,以及完整的音效设计流程——点击音轨、镜头呼啸声、落地冲击声、结尾渐强、闪避音乐垫、响度归一化——都位于 cinematic.py、sfx.py 和 sfx_bank.py 中。它们由 server/make_showcase.py 驱动,而非 desktop_render 工具;参见已知限制。

  • 免费重渲染。 渲染从不触碰应用程序:更改缩放上限、旁白或特效,想重做多少次同一镜头都可以。


为什么与众不同

点击时缩放与点击前缩放对比

每个做"自动缩放"的屏幕录制器——Screen Studio 及其 Windows 模仿者——工作方式都一样:先录制,然后回头通过鼠标钩子或录像本身来猜测有趣时刻在哪里。这种顺序有一个任何人都无法绕过的硬性后果:缩放无法在点击之前开始,因为在点击发生的瞬间,录制器才刚刚得知一次点击即将到来。 它最多只能在点击时开始移动,并在稍后到达。人类剪辑师则相反——他们引导观众提前进入,使眼睛在按钮被按下时已经停留在按钮上。

在这里,智能体生成动作,因此坐标和时间在合成任何一帧之前就已确定。镜头可以被赋予提前量(server/camera.py 中的 LEAD_IN = 0.55 s),因此当按钮被按下时,镜头已经到达并稳定下来。同样的预知能力还带来了事后工具无法拥有的三样东西:

  • 无泵动效应。 同一区域内的连续点击会合并为一个稳定的镜头,而不是镜头在每个列表项上反复推近拉远。

  • 恰到好处的旁白。 语音在运行之前生成(voice.plan()),其真实时长用 ffprobe 测量,智能体的停顿根据这些数字设定——因此点击落在描述它的句子内部,无需手动微调。先录制再配音,最终总是变成语音在 Save 已被点击一秒后才说"我点击 Save"。

  • 机器可读的事件记录。 desktop_describe_take 将一次拍摄读回为编号步骤("3. [12.4s] 点击 Multiply by")——这是比一堆截图更好的产物,也足以据此编写可复用的技能。

在同类项目中的位置

谁在行动,以及是否产出视频

这个想法并不冷门——只是在 Windows 上难以实现。四个实现"从动作日志编辑"的浏览器端项目在 2026 年 3 月的两周内相继出现(argo、testreel、pagecast、playwright-recast),因为 Playwright 免费提供了日志。而在 Windows 上,日志必须与输入驱动一起构建,在同样的五个月内没有任何项目出现:录制器没有智能体,智能体不产出画面。


Related MCP server: DemoMotion

系统要求

操作系统

Windows 11(在此开发和测试)。Windows 10 2004+ 具备插件所依赖的两项操作系统功能——Windows Graphics Capture 和内置 WinRT OCR——但未经测试。

Python

3.10 或更高版本(mcp SDK 需要);在 3.13 上开发。必须存在 tkinter——它随标准 python.org 安装程序一起提供。

ffmpeg

完整构建版本,位于 PATH 上,且 ffprobe 与其并列。winget install Gyan.FFmpeg。精简版缺少音频混音所需的滤镜。

GPU

带 NVENC 的 NVIDIA 显卡。采集写入器和合成器目前都默认要求 h264_nvenc;libx264 代码路径存在,但还没有任何东西自动选择它——参见已知限制。

Claude Code

任何支持插件的近期版本。

ElevenLabs API 密钥

可选。没有它一切照常工作,视频只是静音而已。

Python 包

取自 server/ 中每个模块的导入:

pip install mcp pillow opencv-python numpy windows-capture uiautomation

包

使用位置

用途

mcp

desktop_server.py

MCP 服务器本身(FastMCP)

pillow

desktop_server.py、cinematic.py

截图、标题卡和字幕(Unicode 文本——OpenCV 的 putText 完全无法绘制西里尔字母)

opencv-python

composer.py、cinematic.py、privacy.py

帧合成、镜头扭曲、运动模糊

numpy

合成器、sfx.py、wgc.py

帧和音频缓冲区

windows-capture

wgc.py、doctor.py

Windows Graphics Capture 绑定

uiautomation

desktop_server.py、ui.py

desktop_snapshot 控件树

websocket-client

electron.py

可选——仅用于 desktop_dom*,它通过 CDP 与 Electron 应用通信

隐私扫描不需要任何包:它使用 Windows 自带的 OCR 读取屏幕,通过 PowerShell 驱动。


安装

1. 获取插件

git clone https://github.com/JHamidun/screencast-desktop.git

2. 在 Claude Code 中注册

该仓库本身就是自己的市场(.claude-plugin/marketplace.json),因此将 Claude Code 指向克隆位置并从那里安装:

/plugin marketplace add <path-to-clone>
/plugin install screencast-desktop

.mcp.json 使用 ${CLAUDE_PLUGIN_ROOT} 相对路径注册两个服务器,因此无需全局安装,克隆可以放在任何位置。

3. 运行设置

/screencast-desktop:setup

这会运行 server/doctor.py,它检查那些会静默出问题的事项:

  • DPI 感知——未声明 DPI 感知的进程被告知屏幕是 2560×1440,而实际是 3840×2160,每次点击都会按缩放因子偏移。

  • 显示器布局——坐标在所有屏幕间共享,在副显示器上会变为负数。

  • ffmpeg 和可用的编码器。

  • 窗口采集,真实测试——它采集约 25 帧并检查它们并非全部相同。

  • 旁白——密钥是否存在,以及配置的语音 ID 在账户上是否仍然有效(已删除的语音否则只会以裸 404 失败)。

它将发现的结果写入 machine.json。

4. 获取 UI Automation 二进制文件

windows-ui 服务器是一个外部二进制文件——sbroenne/mcp-windows(MIT)。它刻意不被捆绑进本仓库:它约 60 MB,是别人的项目,在这里固定一份副本只会发布过时的版本。按需下载:

python server/fetch_ui_binary.py          # --force to re-download

该脚本解析最新的 GitHub release,将归档文件与随其发布的 SHA256SUMS.txt 进行校验,任何不匹配都会拒绝安装。它落在 bin/ 中,这正是 .mcp.json 期望的位置。

5. 确认两个服务器都已启动

claude mcp list      # expect: screencast, windows-ui

快速开始

录制 Windows 计算器的演示。/screencast-desktop:record 命令会引导智能体完成此过程,但以下是它实际做的事情,使用真实的工具名称。

1 — 摆放窗口。 如果有副显示器,将其放在副显示器上,这样它就不会覆盖你的工作内容:

desktop_monitors()
desktop_place_window(window="Calculator", monitor=1, fit=0.7)

阅读回复。应用程序没有义务变成被告知的大小——这里一个 UWP 窗口被要求 2380×1490,结果返回 3967×2426 并超出屏幕。desktop_place_window 会测量结果、进行修正,并明确说明窗口是否适合。

2 — 检查画面中是否有任何隐私内容。

desktop_screenshot(window="Calculator")
desktop_privacy_check(window="Calculator")

检查只报告,不阻止:它对画面进行 OCR 并标记卡号、API 密钥、电子邮件地址、电话号码和个人姓名。录制前请开启勿扰模式。

3 — 排练。 使用真实工具走一遍路线,并从返回的截图中确认你点击的正是你以为要点击的内容。此时尚未录制任何内容:

ui_snapshot(windowHandle=…)          # windows-ui: controls by name — try this first
desktop_snapshot(window="Calculator")# or the built-in UIA walk, which returns e1, e2, … refs
desktop_click(ref="e7")

4 — 重置应用。 排练时留下的搜索框最终会出现在拍摄中。

5 — 录制正式内容。

desktop_record_start(window="Calculator")
desktop_click(ref="e12")                 # every click from here is logged for the camera
desktop_type("128")
desktop_click(ref="e19")
desktop_record_stop()

desktop_record_stop 报告时长、帧数以及有多少次点击进入了日志,然后告诉你用于渲染的 out_dir。

6 — 渲染,然后查看。

desktop_render(out_dir="%USERPROFILE%/screencasts/take-143502", max_zoom=2.0)
desktop_render_status(out_dir="…")       # rendering runs in a child process

打开文件并亲眼检查:镜头是否到达了应在的位置、任何边缘是否有黑边、光标是否可见。错误的坐标会产生一个技术上有效、但镜头什么都没对准的文件。如果不对,就用不同的设置重新运行 desktop_render —— 不会再次启动应用程序,也不会重新录制屏幕。

工具参考

screencast 服务器 — desktop_monitors、desktop_windows、desktop_screenshot、desktop_snapshot、desktop_dom、desktop_dom_launch、desktop_click、desktop_type、desktop_key、desktop_move_mouse、desktop_scroll、desktop_focus、desktop_launch、desktop_place_window、desktop_privacy_check、desktop_record_start、desktop_record_stop、desktop_render、desktop_render_status、desktop_describe_take。

desktop_click、desktop_type、desktop_key、desktop_scroll、desktop_focus 和 desktop_launch 都接受 see="shot"(默认)或 see="none" —— 后者在你已经知道屏幕内容时节省上下文。

windows-ui 服务器(公开的子集)— ui_snapshot、ui_find、ui_click、ui_type、ui_select、ui_read、ui_wait、window_management、app。


工作原理

日志是唯一的事实来源

用文字描述同样的事情,给任何在终端里阅读本文的人:

  AGENT                                                   server/
  ─────                                                   ───────
  desktop_click / desktop_type / …                        desktop_server.py
        │                                                 (MCP, FastMCP)
        ├──► real SendInput: cursor eased to the target,  driver.py
        │    clicked, keys sent                           ── moves + clicks
        │                                                    the real desktop
        │
        ├──► EVENT LOG  t, kind, x, y, label, dur         driver.py → events.json
        │    + the sampled cursor path (track)               ◄── the ground truth
        │
        └──► screenshot back to the agent in the same reply

  desktop_record_start                                    recorder_proc.py (child process)
        └──► Windows Graphics Capture, bound to the HWND  wgc.py
             ├─ frames arrive only when the picture       ── writer thread re-sends
             │  changes …                                    the last frame on a
             └─ … so a writer thread feeds ffmpeg at a       fixed clock
                constant rate, logging the true
                wall-clock time of every frame
                                                          → raw.mp4 + frame_times.json

  desktop_render                                          render_proc.py (child process)
        │
        ├─ 1. TIMELINE   collapse the dead air            timeline.py
        │      keep 1.1 s before and 1.5 s after every
        │      action, squeeze the gaps to 0.55 s
        │
        ├─ 2. CAMERA     event log → keyframes            camera.py
        │      lead-in 0.55 s BEFORE each click,
        │      nearby clicks merged into one shot,
        │      pan instead of pumping in and out
        │
        ├─ 3. COMPOSITOR one affine matrix per frame      composer.py
        │      recording on a gradient backdrop, rounded     + cinematic.py
        │      corners, drop shadow, drawn cursor, click     (vignette, grain;
        │      ripples, motion blur, breathing idle,          title cards and
        │      vignette, grain                                captions available)
        │                                                 → silent.mp4
        │
        └─ 4. SOUND      optional narration               voice.py
               ElevenLabs TTS mixed onto the cut          → demo.mp4

  make_showcase.py — the fuller montage, run as a script rather than a tool:
        the same four stages plus title/outro cards, captions, and the whole
        sound design pass (clicks, whooshes, impacts, riser, ducked music bed,
        loudness normalisation)                           sfx.py + sfx_bank.py

文件布局绝大部分可以通过两个设计决策来解释:

捕获和渲染在子进程中运行。 在 MCP 服务器进程内导入捕获库或 OpenCV 会使其卡死,而一次渲染需要数分钟,任何工具调用都不应一直占用这么长时间。recorder_proc.py 和 render_proc.py 的存在完全是为了这个原因。它们通过文件(started.json、stop、render.log)通信,并且两者都以 stdin=DEVNULL 启动——继承了服务器 stdin 的子进程会开始吞噬本应发给服务器的 JSON-RPC 请求。

帧按时间戳匹配,而不是按索引匹配。 如果机器跟不上,源帧 N 并不在 N/fps 处。frame_times.json 携带每一帧的真实捕获时间,合成器通过它来查找帧——这正是机器卡顿时镜头仍能跟随点击的原因。


配置

渲染

desktop_render(out_dir, name="demo.mp4", max_zoom=2.0, narration="") — narration 接受一个 JSON 列表,格式为 {"text": …, "at": 秒}。

其他一切都是模块常量,直接就地修改:

常量

文件

默认值

作用

LEAD_IN

camera.py

0.55

事件前镜头开始移动的秒数

MAX_ZOOM / MIN_ZOOM

camera.py

2.0 / 1.0

缩放范围;超过 2× 后 4K 源开始升采样

ZOOM_IN_DUR / ZOOM_OUT_DUR

camera.py

0.85 / 0.7

推近和拉远的持续时间

HOLD_AFTER

camera.py

1.05

对包含信息的镜头的最短保持时间

MERGE_GAP / MIN_GROUP_ZOOM

camera.py

3.6 / 1.7

邻近点击聚合为一个镜头的激进程度

KEEP_BEFORE / KEEP_AFTER

timeline.py

1.1 / 1.5

每个动作前后以全速保留的秒数

IDLE_KEEP / MIN_GAP

timeline.py

0.55 / 1.4

折叠后的停顿被缩短到的值

OUT_W × OUT_H

composer.py

1920×1080

输出分辨率

PADDING

composer.py

0.90

未缩放录制内容占画面的比例

CORNER_R, CURSOR_PX, SHADOW_DROP

composer.py

22, 58, 26

圆角、光标高度、阴影偏移(输出像素)

SHUTTER_ANGLE

composer.py

200.0

运动模糊;360 = 快门在整个帧期间打开

vignette_strength / grain_amount

composer.compose()

0.20 / 0.045

胶片质感

breathe

composer.compose()

True

长时间静态镜头上的亚像素漂移,使保持的帧不会显得冻结

desktop_record_start(window, out_dir="", fps=30) 默认路径为 ~/screencasts/take-HHMMSS。

如果没有给出 max_zoom,camera.build() 会自行选择一个上限,使窗口高度的至少 70% 保持在画面内——一个高窄窗口放进 16:9 画面时本来就很小,再强制 2× 会裁掉承载动作意义的部分。(在计算器上,它截掉了显示结果的那部分屏幕。)

旁白

在环境变量中,或在插件根目录的 .env 文件中设置 ELEVENLABS_API_KEY(KEY=value,每行一个——该文件被 git 忽略)。可选地通过 ELEVENLABS_VOICE_ID 固定一个声音;如果未设置,则使用账户上的第一个声音。模型:eleven_multilingual_v2。如果你的密钥放在其他位置,请将 SCREENCAST_ENV_FILE 指向别处。

voice.resolve_voice() 在使用某个声音前,会对照账户的实际声音列表检查配置的 id,因为已删除的声音否则会以无法解释的 404 失败。

没有密钥也不会出问题。doctor.py 会将其报告为警告而不是错误,并且 desktop_render 会生成一个无声视频——这也是在提供密钥但未传入 narration 参数时它生成的结果。

声音设计层在没有密钥时会降级而不是失效:sfx_bank.build() 需要 ElevenLabs 来生成音色库,但 sfx_bank.build_synthetic() 可以用 numpy 在离线状态下合成同样的声音家族(*_syn1.wav / *_syn2.wav 文件),而 sfx.click_samples() 在找不到采样资源时会回退到 synth_click()。因此,离线的机器仍然能获得点击声、嗖嗖声和撞击声——只是失去了人声。


已知限制

这是一份诚实的清单。这些问题真实存在、目前仍然成立,而且大多是在开发过程中遇到的。关于解决这些问题的计划(按优先级排序,并附有每项背后的测量数据)见 ROADMAP.md。

  • desktop_render 工具渲染的内容少于代码库所能渲染的。 它调用 composer.compose() 时没有 intro、outro 或 captions,并且只混入旁白——没有点击音轨、没有嗖嗖声、没有音乐底垫。其他一切都已实现并可正常工作,但目前只能通过 server/make_showcase.py 访问,那是一个带有自己硬编码拍摄路径和节拍列表的脚本。把这些参数接入工具是本仓库中最明显的待办任务。

  • 没有拖拽。 没有拖拽或拖放工具。鼠标按下和鼠标抬起总是在同一位置发出,因此任何需要按下-移动-释放手势的操作——滑块、重新排序、画布绘制、通过抓手调整大小——都无法实现。

  • 按键不会写入事件日志。 desktop_key 发送击键但不会记录,因此镜头永远不会对纯键盘步骤做出反应,它们也不会出现在 desktop_describe_take 中。点击和键入的文本(desktop_type)会被记录;单独的按键不会。

  • UWP 窗口必须通过其框架窗口来寻址。 Windows 图形捕获要求一个顶级窗口句柄。对于 UWP/商店应用,那就是可见的 ApplicationFrameWindow——内部的 CoreWindow 不是可用的捕获目标。实际操作中:通过可见的窗口标题来解析应用(这正是工具所做的),不要试图越过它。

  • desktop_screenshot 是屏幕的裁剪,而不是窗口捕获。 它抓取窗口所占的屏幕区域。任何与窗口重叠的内容——另一个窗口、通知 toast、工具提示——都会出现在截图中。(录制没有这个问题:WGC 捕获的是窗口本身。)在信任截图之前,请确保目标窗口位于最上层。

  • Electron 应用几乎不会向 UI Automation 暴露任何东西。 在 Windows 11 上实测:一个典型的 Electron 应用返回 2 到 6 个命名元素——是窗口包装器,而不是界面。回退到截图和坐标,或者在调试端口可用时使用 desktop_dom。desktop_dom_launch 会打开应用的第二个副本,使用独立的配置文件,该副本未登录。

  • NVENC 实际上不可或缺。 wgc.WindowRecorder 和 composer.compose() 都默认使用 h264_nvenc。libx264 路径已实现,doctor.py 会检测正确的编码器并写入 machine.json,但目前还没有任何东西自动接通这一选择。在没有 NVENC 的机器上,你必须自己传入编码器。

  • 文件很大。 胶片颗粒是逐帧应用的,这会破坏帧间压缩——一个演示短片会比同样无颗粒的素材更重。如果文件大小比观感更重要,请设置 grain_amount=0。

  • 一次拍摄最长 15 分钟。 recorder_proc.py 会自行停止,因此忘记停止的录制不会永远运行下去。

  • 仅限 Windows,且仅限交互式桌面。 SendInput、UI Automation、WGC 和 WinRT OCR 都是 Windows API;这些在无人值守会话、服务中或锁屏状态下都无法工作。

  • 窗口是一个实时应用程序。 状态会跨拍摄保留——彩排时留下的搜索框把一个键入的词变成了 "githubgithub"。在正式拍摄前重置应用。

  • 隐私检查只报告,不阻止。 匹配是字面匹配;菜单中的名称不算泄露,OCR 也会漏掉东西。在发布前亲自查看画面。


致谢与许可

  • sbroenne/mcp-windows(MIT)——执行 UI Automation 的 windows-ui MCP 服务器。此处不随附;由 server/fetch_ui_binary.py 按需下载,并对照该发布版自带的 SHA256SUMS.txt 进行校验和验证。

  • ffmpeg——捕获编码、音频混音和 ffprobe 时长测量。以外部二进制方式调用;不捆绑。许可取决于你安装的构建版本(LGPL 或 GPL)。

  • 声音库。 server/sfx_bank/ 下的 .wav 文件是生成的,不是采样:server/sfx_bank.py 通过 ElevenLabs 声音生成 API 根据提示配方一次性构建它们并缓存,而且每个候选都会经过测量筛选(一个峰值出现在 200 ms 处的“点击声”,无论听起来多好都会被拒绝)。第二组文件——*_syn1.wav、*_syn2.wav——完全在 sfx_bank.build_synthetic() 中使用 numpy 离线合成,因此没有 API 密钥的机器仍然能获得声音设计。这里不重新分发任何第三方采样库。将 SCREENCAST_SFX_DIR 指向你自己的采样文件夹,sfx.py 就会优先使用这些点击声和音乐底垫,而不是随附的声音库;如果未设置,它会使用 server/sfx_bank/;如果没找到任何内容,它就会合成。无论哪种方式都没有硬性依赖。

  • ElevenLabs——可选的,用于旁白和构建声音库。请自带密钥;该插件不附带任何由某人声音生成的音频。

  • 字体——标题和字幕使用随 Windows 附带的 Segoe UI,以 Arial 作为后备。不重新分发任何字体文件。

  • Model Context Protocol Python SDK(MIT)——服务器框架。

该插件自身的代码以 MIT 许可证发布。参见 LICENSE。


参与贡献

欢迎提交 Issue 和 Pull Request。以下几点可以加快审查速度:

  • 仅限 Windows。 请在真实桌面上测试;没有 CI 能替你点击按钮。

  • 先运行 doctor(python server/doctor.py),并将其输出粘贴到 bug 报告中——这里的大多数问题都是环境性的(DPI 缩放、显示器布局、缺少编码器),doctor 会直接指出它们。

  • 如果它静默失败了,请在评论中说明。 这个代码库中满是解释某行代码为何如此的注释,因为几乎每一行都曾是一次看似成功实则失败的教训:相同的帧被当作有效录制、摄像头漂移出画面边缘、空的事件日志生成了完全没有缩放的视频。保留这些注释是刻意为之。

  • 对摄像头或时间轴常量的修改需要提供前后对比片段。 这些是对结果观感的判断,任何测试都无法定论。

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to see, locate UI elements, and operate any Windows desktop app through natural language, using accessibility-tree matching with optional vision-model fallback, plus an autonomous visual loop with introspection and meta-learning.
    13
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables AI assistants to autonomously control Windows 11 and 10 desktops via sub-10ms screen capture, native UI Automation element inspection, and zero-lag keyboard and mouse input. Combines a visual plane with a semantic plane and stall detection so agents can operate real applications reliably without vision-only guessing.
    4 npm
    171 PyPI
    5
    MIT