snapstep 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. snapstep-0.2.0/LICENSE +21 -0
  2. snapstep-0.2.0/PKG-INFO +186 -0
  3. snapstep-0.2.0/README.md +148 -0
  4. snapstep-0.2.0/pyproject.toml +72 -0
  5. snapstep-0.2.0/setup.cfg +4 -0
  6. snapstep-0.2.0/src/snapstep/__init__.py +3 -0
  7. snapstep-0.2.0/src/snapstep/__main__.py +6 -0
  8. snapstep-0.2.0/src/snapstep/capture.py +161 -0
  9. snapstep-0.2.0/src/snapstep/cli.py +298 -0
  10. snapstep-0.2.0/src/snapstep/config.py +109 -0
  11. snapstep-0.2.0/src/snapstep/export/__init__.py +68 -0
  12. snapstep-0.2.0/src/snapstep/export/docx_export.py +38 -0
  13. snapstep-0.2.0/src/snapstep/export/html_export.py +59 -0
  14. snapstep-0.2.0/src/snapstep/export/markdown_export.py +34 -0
  15. snapstep-0.2.0/src/snapstep/log.py +41 -0
  16. snapstep-0.2.0/src/snapstep/models.py +106 -0
  17. snapstep-0.2.0/src/snapstep/privacy.py +27 -0
  18. snapstep-0.2.0/src/snapstep/recorder.py +374 -0
  19. snapstep-0.2.0/src/snapstep/resources/icon.ico +0 -0
  20. snapstep-0.2.0/src/snapstep/resources/icon.png +0 -0
  21. snapstep-0.2.0/src/snapstep/resources/icon_rec.png +0 -0
  22. snapstep-0.2.0/src/snapstep/resources/templates/report.html.j2 +56 -0
  23. snapstep-0.2.0/src/snapstep/ui/__init__.py +1 -0
  24. snapstep-0.2.0/src/snapstep/ui/settings.py +149 -0
  25. snapstep-0.2.0/src/snapstep/ui/tray.py +415 -0
  26. snapstep-0.2.0/src/snapstep/writer.py +263 -0
  27. snapstep-0.2.0/src/snapstep.egg-info/PKG-INFO +186 -0
  28. snapstep-0.2.0/src/snapstep.egg-info/SOURCES.txt +36 -0
  29. snapstep-0.2.0/src/snapstep.egg-info/dependency_links.txt +1 -0
  30. snapstep-0.2.0/src/snapstep.egg-info/entry_points.txt +2 -0
  31. snapstep-0.2.0/src/snapstep.egg-info/requires.txt +14 -0
  32. snapstep-0.2.0/src/snapstep.egg-info/top_level.txt +1 -0
  33. snapstep-0.2.0/tests/test_config_recorder.py +90 -0
  34. snapstep-0.2.0/tests/test_export.py +61 -0
  35. snapstep-0.2.0/tests/test_finalize.py +96 -0
  36. snapstep-0.2.0/tests/test_imports.py +51 -0
  37. snapstep-0.2.0/tests/test_models.py +41 -0
  38. snapstep-0.2.0/tests/test_writer.py +127 -0
snapstep-0.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Felixssss-106
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,186 @@
1
+ Metadata-Version: 2.4
2
+ Name: snapstep
3
+ Version: 0.2.0
4
+ Summary: 录屏自动生成图文教程 / Record your screen, get a step-by-step guide with screenshots — automatically.
5
+ Author: Felixssss-106
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/Felixssss-106/snapstep
8
+ Project-URL: Issues, https://github.com/Felixssss-106/snapstep/issues
9
+ Keywords: screenshot,documentation,scribe,sop,tutorial,screen-recording,ai
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Environment :: X11 Applications :: Qt
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: End Users/Desktop
14
+ Classifier: Operating System :: Microsoft :: Windows
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Topic :: Multimedia :: Graphics :: Capture :: Screen Capture
22
+ Classifier: Topic :: Documentation
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: pynput>=1.7
27
+ Requires-Dist: mss>=9.0
28
+ Requires-Dist: pillow>=10.0
29
+ Requires-Dist: jinja2>=3.1
30
+ Requires-Dist: python-docx>=1.1
31
+ Requires-Dist: PySide6>=6.6
32
+ Requires-Dist: uiautomation>=2.0; sys_platform == "win32"
33
+ Provides-Extra: dev
34
+ Requires-Dist: pytest>=8; extra == "dev"
35
+ Requires-Dist: pyinstaller>=6.0; extra == "dev"
36
+ Requires-Dist: ruff>=0.6; extra == "dev"
37
+ Dynamic: license-file
38
+
39
+ # SnapStep 📸
40
+
41
+ **按一次快捷键,把你的操作变成图文教程。**
42
+ Record your screen, get a step-by-step guide — automatically.
43
+
44
+ [![Test](https://github.com/Felixssss-106/snapstep/actions/workflows/test.yml/badge.svg)](https://github.com/Felixssss-106/snapstep/actions/workflows/test.yml)
45
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
46
+ [![Python](https://img.shields.io/badge/python-3.10%2B-blue)](pyproject.toml)
47
+
48
+ ![SnapStep 演示:录屏自动生成图文教程](docs/demo.gif)
49
+
50
+ | 生成的 HTML 教程 | 导出的 Markdown |
51
+ |---|---|
52
+ | ![HTML 教程](docs/screenshots/guide-html.png) | ![Markdown 成品](docs/screenshots/guide-md.png) |
53
+
54
+ SnapStep 在后台监听你的鼠标和键盘:每次点击自动截图并标记位置,输入的文本自动记录。
55
+ 停止录制后,一份带序号截图的图文教程就生成好了 —— 支持 **Markdown / HTML / Word**,
56
+ 可选接入大模型把文案写得更自然。**开源版 Scribe / Tango 平替,数据完全保存在本地。**
57
+
58
+ ---
59
+
60
+ ## ✨ 特性
61
+
62
+ - **一键录制**:系统托盘常驻,全局快捷键(默认 `Ctrl+Alt+S`)随时开始/停止
63
+ - **自动截屏标注**:点击处自动画高亮圈 + 步骤序号,输入的文本自动记进步骤里
64
+ - **三种导出**:Markdown(贴 wiki/仓库)、HTML(单文件内嵌截图,直接发人)、Word(正式文档)
65
+ - **AI 文案(可选)**:填一个 OpenAI 兼容 API 即可让大模型写标题和步骤描述,
66
+ 预设支持 GLM / DeepSeek / OpenAI,本地模型(Ollama / LM Studio)也行
67
+ - **零配置可用**:不配 AI 也能用,本地模板自动生成每步文案
68
+ - **隐私优先**:本机处理,无任何遥测;隐私模式一键停止截屏;密码框输入自动隐藏(尽力检测)
69
+
70
+ ## 📦 安装
71
+
72
+ **Windows(推荐)**:从 [Releases](https://github.com/Felixssss-106/snapstep/releases) 下载 `SnapStep.exe`,双击即用。
73
+
74
+ **pip**:
75
+
76
+ ```bash
77
+ pip install snapstep # PyPI 正式包
78
+ pip install git+https://github.com/Felixssss-106/snapstep.git # 或直接从源码安装
79
+ snapstep # 启动托盘 GUI
80
+ ```
81
+
82
+ **从源码**:
83
+
84
+ ```bash
85
+ git clone https://github.com/Felixssss-106/snapstep.git
86
+ cd snapstep
87
+ pip install -e ".[dev]"
88
+ pytest # 跑测试
89
+ ```
90
+
91
+ ## 🚀 快速开始
92
+
93
+ ### 托盘 GUI(默认)
94
+
95
+ 启动后托盘出现图标:
96
+
97
+ 1. 按 `Ctrl+Alt+S`(或托盘菜单「开始录制」)
98
+ 2. 像平常一样操作 —— 每次左键点击自动成为一步
99
+ 3. 再按 `Ctrl+Alt+S` 停止,教程自动导出并弹窗提示
100
+
101
+ 右键托盘图标可打开会话文件夹、重新导出、修改设置。
102
+
103
+ ### 命令行
104
+
105
+ ```bash
106
+ snapstep record # 录制,回车结束,自动导出
107
+ snapstep record --format md --no-ai # 指定格式、跳过 AI
108
+ snapstep export ~/.snapstep/sessions/20260912-101010 -f all # 导出已有会话
109
+ snapstep config set api.base_url https://api.deepseek.com/v1 # 命令行改配置
110
+ snapstep demo # 不录制,直接生成一份示例教程验证安装
111
+ ```
112
+
113
+ ## 🤖 AI 文案配置
114
+
115
+ 设置界面选预设填 API Key 即可;也可命令行:
116
+
117
+ ```bash
118
+ snapstep config set api.base_url https://open.bigmodel.cn/api/paas/v4
119
+ snapstep config set api.model glm-4-flash
120
+ snapstep config set api.api_key 你的key
121
+ ```
122
+
123
+ | 预设 | Base URL | 模型示例 |
124
+ |---|---|---|
125
+ | GLM 智谱 | `https://open.bigmodel.cn/api/paas/v4` | `glm-4-flash` |
126
+ | DeepSeek | `https://api.deepseek.com/v1` | `deepseek-chat` |
127
+ | OpenAI | `https://api.openai.com/v1` | `gpt-4o-mini` |
128
+ | 本地模型 | `http://127.0.0.1:11434/v1` | Ollama / LM Studio 任意模型 |
129
+
130
+ 不配置或调用失败时自动退回本地模板文案,**教程永远能生成**。
131
+
132
+ ## 🔒 隐私设计
133
+
134
+ - 截图、文案、导出全部在本机完成;只有你主动配置了 AI 才会把**操作文字摘要**发给该 API
135
+ - 「隐私模式」:完全不截屏,只记录步骤文字
136
+ - 「密码隐藏」:通过 Windows UI Automation 尽力识别密码框,其中的键入以 `••••` 代替
137
+ - API 端点仅允许 http/https,并阻止云元数据地址与重定向,防止密钥被转发
138
+ - 配置与数据都在 `~/.snapstep/`,删掉目录即彻底清除
139
+
140
+ ## 🆚 与在线 SaaS 工具对比
141
+
142
+ | | SnapStep | Scribe / Tango |
143
+ |---|---|---|
144
+ | 价格 | 免费开源(MIT) | 免费档限步数,完整功能按席位付费 |
145
+ | 数据 | 全程本机 | 操作录屏上传云端 |
146
+ | 离线 | 完全可用(无 AI 模式) | 不可用 |
147
+ | 导出 | Markdown / HTML / Word | 受付费档限制 |
148
+ | AI | 自带 key,任意 OpenAI 兼容模型 | 内置不可换 |
149
+ | 平台 | Windows(macOS 在路线图) | 浏览器扩展 |
150
+
151
+ ## 🗺 Roadmap
152
+
153
+ - [ ] 录屏讲解语音 → 自动转写进步骤文案(ASR)
154
+ - [ ] AI 视觉模型读懂截图,写出更准确的按钮/控件描述
155
+ - [ ] 敏感信息智能打码(手机号/邮箱/头像区域)
156
+ - [ ] PDF 导出、团队模板
157
+ - [ ] macOS 支持
158
+
159
+ ## 注意:软件可能存在问题
160
+ V0.1.0版本已知问题:快捷键无法使用,截屏延迟不合适,v0.2.0已修复(v0.1.0已废弃);
161
+ 自动过滤无效点击功能可能会漏记录,目前正在修复
162
+
163
+ 欢迎按 [issues](https://github.com/Felixssss-106/snapstep/issues) 提需求。
164
+
165
+ ## 🧪 开发
166
+
167
+ ```bash
168
+ pip install -e ".[dev]"
169
+ pytest # 测试
170
+ ruff check src tests scripts # lint
171
+ python scripts/make_icon.py # 重新生成图标
172
+ pyinstaller snapstep.spec # 本地打包 exe
173
+ ```
174
+
175
+ 架构速览:`recorder.py`(pynput 钩子 + 事件聚合)→ `capture.py`(mss 截屏 + Pillow 标注)
176
+ → `writer.py`(AI/模板双路文案)→ `export/`(md/html/docx)。核心逻辑不依赖输入钩子,纯逻辑可单测。
177
+
178
+ ## 🤝 贡献
179
+
180
+ Issue / PR 都欢迎。提交前请跑通 `pytest` 和 `ruff check`。
181
+
182
+ ## License
183
+
184
+ [MIT](LICENSE)
185
+ See the Chinese sections above for full docs — the UI is Chinese-first,
186
+ and English UI is on the roadmap. English issues/PRs are welcome!
@@ -0,0 +1,148 @@
1
+ # SnapStep 📸
2
+
3
+ **按一次快捷键,把你的操作变成图文教程。**
4
+ Record your screen, get a step-by-step guide — automatically.
5
+
6
+ [![Test](https://github.com/Felixssss-106/snapstep/actions/workflows/test.yml/badge.svg)](https://github.com/Felixssss-106/snapstep/actions/workflows/test.yml)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
8
+ [![Python](https://img.shields.io/badge/python-3.10%2B-blue)](pyproject.toml)
9
+
10
+ ![SnapStep 演示:录屏自动生成图文教程](docs/demo.gif)
11
+
12
+ | 生成的 HTML 教程 | 导出的 Markdown |
13
+ |---|---|
14
+ | ![HTML 教程](docs/screenshots/guide-html.png) | ![Markdown 成品](docs/screenshots/guide-md.png) |
15
+
16
+ SnapStep 在后台监听你的鼠标和键盘:每次点击自动截图并标记位置,输入的文本自动记录。
17
+ 停止录制后,一份带序号截图的图文教程就生成好了 —— 支持 **Markdown / HTML / Word**,
18
+ 可选接入大模型把文案写得更自然。**开源版 Scribe / Tango 平替,数据完全保存在本地。**
19
+
20
+ ---
21
+
22
+ ## ✨ 特性
23
+
24
+ - **一键录制**:系统托盘常驻,全局快捷键(默认 `Ctrl+Alt+S`)随时开始/停止
25
+ - **自动截屏标注**:点击处自动画高亮圈 + 步骤序号,输入的文本自动记进步骤里
26
+ - **三种导出**:Markdown(贴 wiki/仓库)、HTML(单文件内嵌截图,直接发人)、Word(正式文档)
27
+ - **AI 文案(可选)**:填一个 OpenAI 兼容 API 即可让大模型写标题和步骤描述,
28
+ 预设支持 GLM / DeepSeek / OpenAI,本地模型(Ollama / LM Studio)也行
29
+ - **零配置可用**:不配 AI 也能用,本地模板自动生成每步文案
30
+ - **隐私优先**:本机处理,无任何遥测;隐私模式一键停止截屏;密码框输入自动隐藏(尽力检测)
31
+
32
+ ## 📦 安装
33
+
34
+ **Windows(推荐)**:从 [Releases](https://github.com/Felixssss-106/snapstep/releases) 下载 `SnapStep.exe`,双击即用。
35
+
36
+ **pip**:
37
+
38
+ ```bash
39
+ pip install snapstep # PyPI 正式包
40
+ pip install git+https://github.com/Felixssss-106/snapstep.git # 或直接从源码安装
41
+ snapstep # 启动托盘 GUI
42
+ ```
43
+
44
+ **从源码**:
45
+
46
+ ```bash
47
+ git clone https://github.com/Felixssss-106/snapstep.git
48
+ cd snapstep
49
+ pip install -e ".[dev]"
50
+ pytest # 跑测试
51
+ ```
52
+
53
+ ## 🚀 快速开始
54
+
55
+ ### 托盘 GUI(默认)
56
+
57
+ 启动后托盘出现图标:
58
+
59
+ 1. 按 `Ctrl+Alt+S`(或托盘菜单「开始录制」)
60
+ 2. 像平常一样操作 —— 每次左键点击自动成为一步
61
+ 3. 再按 `Ctrl+Alt+S` 停止,教程自动导出并弹窗提示
62
+
63
+ 右键托盘图标可打开会话文件夹、重新导出、修改设置。
64
+
65
+ ### 命令行
66
+
67
+ ```bash
68
+ snapstep record # 录制,回车结束,自动导出
69
+ snapstep record --format md --no-ai # 指定格式、跳过 AI
70
+ snapstep export ~/.snapstep/sessions/20260912-101010 -f all # 导出已有会话
71
+ snapstep config set api.base_url https://api.deepseek.com/v1 # 命令行改配置
72
+ snapstep demo # 不录制,直接生成一份示例教程验证安装
73
+ ```
74
+
75
+ ## 🤖 AI 文案配置
76
+
77
+ 设置界面选预设填 API Key 即可;也可命令行:
78
+
79
+ ```bash
80
+ snapstep config set api.base_url https://open.bigmodel.cn/api/paas/v4
81
+ snapstep config set api.model glm-4-flash
82
+ snapstep config set api.api_key 你的key
83
+ ```
84
+
85
+ | 预设 | Base URL | 模型示例 |
86
+ |---|---|---|
87
+ | GLM 智谱 | `https://open.bigmodel.cn/api/paas/v4` | `glm-4-flash` |
88
+ | DeepSeek | `https://api.deepseek.com/v1` | `deepseek-chat` |
89
+ | OpenAI | `https://api.openai.com/v1` | `gpt-4o-mini` |
90
+ | 本地模型 | `http://127.0.0.1:11434/v1` | Ollama / LM Studio 任意模型 |
91
+
92
+ 不配置或调用失败时自动退回本地模板文案,**教程永远能生成**。
93
+
94
+ ## 🔒 隐私设计
95
+
96
+ - 截图、文案、导出全部在本机完成;只有你主动配置了 AI 才会把**操作文字摘要**发给该 API
97
+ - 「隐私模式」:完全不截屏,只记录步骤文字
98
+ - 「密码隐藏」:通过 Windows UI Automation 尽力识别密码框,其中的键入以 `••••` 代替
99
+ - API 端点仅允许 http/https,并阻止云元数据地址与重定向,防止密钥被转发
100
+ - 配置与数据都在 `~/.snapstep/`,删掉目录即彻底清除
101
+
102
+ ## 🆚 与在线 SaaS 工具对比
103
+
104
+ | | SnapStep | Scribe / Tango |
105
+ |---|---|---|
106
+ | 价格 | 免费开源(MIT) | 免费档限步数,完整功能按席位付费 |
107
+ | 数据 | 全程本机 | 操作录屏上传云端 |
108
+ | 离线 | 完全可用(无 AI 模式) | 不可用 |
109
+ | 导出 | Markdown / HTML / Word | 受付费档限制 |
110
+ | AI | 自带 key,任意 OpenAI 兼容模型 | 内置不可换 |
111
+ | 平台 | Windows(macOS 在路线图) | 浏览器扩展 |
112
+
113
+ ## 🗺 Roadmap
114
+
115
+ - [ ] 录屏讲解语音 → 自动转写进步骤文案(ASR)
116
+ - [ ] AI 视觉模型读懂截图,写出更准确的按钮/控件描述
117
+ - [ ] 敏感信息智能打码(手机号/邮箱/头像区域)
118
+ - [ ] PDF 导出、团队模板
119
+ - [ ] macOS 支持
120
+
121
+ ## 注意:软件可能存在问题
122
+ V0.1.0版本已知问题:快捷键无法使用,截屏延迟不合适,v0.2.0已修复(v0.1.0已废弃);
123
+ 自动过滤无效点击功能可能会漏记录,目前正在修复
124
+
125
+ 欢迎按 [issues](https://github.com/Felixssss-106/snapstep/issues) 提需求。
126
+
127
+ ## 🧪 开发
128
+
129
+ ```bash
130
+ pip install -e ".[dev]"
131
+ pytest # 测试
132
+ ruff check src tests scripts # lint
133
+ python scripts/make_icon.py # 重新生成图标
134
+ pyinstaller snapstep.spec # 本地打包 exe
135
+ ```
136
+
137
+ 架构速览:`recorder.py`(pynput 钩子 + 事件聚合)→ `capture.py`(mss 截屏 + Pillow 标注)
138
+ → `writer.py`(AI/模板双路文案)→ `export/`(md/html/docx)。核心逻辑不依赖输入钩子,纯逻辑可单测。
139
+
140
+ ## 🤝 贡献
141
+
142
+ Issue / PR 都欢迎。提交前请跑通 `pytest` 和 `ruff check`。
143
+
144
+ ## License
145
+
146
+ [MIT](LICENSE)
147
+ See the Chinese sections above for full docs — the UI is Chinese-first,
148
+ and English UI is on the roadmap. English issues/PRs are welcome!
@@ -0,0 +1,72 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "snapstep"
7
+ version = "0.2.0"
8
+ description = "录屏自动生成图文教程 / Record your screen, get a step-by-step guide with screenshots — automatically."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "Felixssss-106" }]
14
+ keywords = [
15
+ "screenshot",
16
+ "documentation",
17
+ "scribe",
18
+ "sop",
19
+ "tutorial",
20
+ "screen-recording",
21
+ "ai",
22
+ ]
23
+ classifiers = [
24
+ "Development Status :: 4 - Beta",
25
+ "Environment :: X11 Applications :: Qt",
26
+ "Intended Audience :: Developers",
27
+ "Intended Audience :: End Users/Desktop",
28
+ "Operating System :: Microsoft :: Windows",
29
+ "Programming Language :: Python :: 3",
30
+ "Programming Language :: Python :: 3.10",
31
+ "Programming Language :: Python :: 3.11",
32
+ "Programming Language :: Python :: 3.12",
33
+ "Programming Language :: Python :: 3.13",
34
+ "Programming Language :: Python :: 3.14",
35
+ "Topic :: Multimedia :: Graphics :: Capture :: Screen Capture",
36
+ "Topic :: Documentation",
37
+ ]
38
+ dependencies = [
39
+ "pynput>=1.7",
40
+ "mss>=9.0",
41
+ "pillow>=10.0",
42
+ "jinja2>=3.1",
43
+ "python-docx>=1.1",
44
+ "PySide6>=6.6",
45
+ "uiautomation>=2.0; sys_platform == 'win32'",
46
+ ]
47
+
48
+ [project.optional-dependencies]
49
+ dev = ["pytest>=8", "pyinstaller>=6.0", "ruff>=0.6"]
50
+
51
+ [project.scripts]
52
+ snapstep = "snapstep.cli:main"
53
+
54
+ [project.urls]
55
+ Homepage = "https://github.com/Felixssss-106/snapstep"
56
+ Issues = "https://github.com/Felixssss-106/snapstep/issues"
57
+
58
+ [tool.setuptools.packages.find]
59
+ where = ["src"]
60
+
61
+ [tool.setuptools.package-data]
62
+ snapstep = ["resources/**/*"]
63
+
64
+ [tool.pytest.ini_options]
65
+ testpaths = ["tests"]
66
+
67
+ [tool.ruff]
68
+ line-length = 100
69
+ target-version = "py310"
70
+
71
+ [tool.ruff.lint]
72
+ select = ["E", "F", "W", "I", "UP", "B"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """SnapStep — 录屏自动生成图文教程。"""
2
+
3
+ __version__ = "0.2.0"
@@ -0,0 +1,6 @@
1
+ """python -m snapstep 入口。"""
2
+
3
+ from .cli import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -0,0 +1,161 @@
1
+ """屏幕截图与步骤标注。
2
+
3
+ 导入本模块只需 Pillow;mss 延迟到真正截屏时才加载,
4
+ 保证导出器/测试在没有屏幕的环境里也能工作。
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import time
10
+ from pathlib import Path
11
+
12
+ from PIL import Image, ImageDraw, ImageFont
13
+
14
+ # 点击高亮圈颜色(快照红)
15
+ ACCENT = (245, 63, 63)
16
+
17
+
18
+ class ScreenCapture:
19
+ """基于 mss 的截屏器。线程注意:mss 实例不跨线程,按线程懒加载。"""
20
+
21
+ def __init__(self) -> None:
22
+ self._sct = None
23
+
24
+ def _client(self):
25
+ if self._sct is None:
26
+ import mss
27
+
28
+ self._sct = mss.mss()
29
+ return self._sct
30
+
31
+ def monitor_for_point(self, x: int, y: int) -> dict:
32
+ """返回包含该点显示器的 mss monitor 字典;找不到时退回主显示器。"""
33
+ monitors = self._client().monitors
34
+ for mon in monitors[1:]:
35
+ if (
36
+ mon["left"] <= x < mon["left"] + mon["width"]
37
+ and mon["top"] <= y < mon["top"] + mon["height"]
38
+ ):
39
+ return dict(mon)
40
+ return dict(monitors[1] if len(monitors) > 1 else monitors[0])
41
+
42
+ def capture_point(self, x: int, y: int) -> tuple[Image.Image, dict]:
43
+ """截取包含 (x, y) 的显示器,返回 (PIL 图像, monitor 字典)。"""
44
+ mon = self.monitor_for_point(x, y)
45
+ shot = self._client().grab(mon)
46
+ img = Image.frombytes("RGB", shot.size, shot.bgra, "raw", "BGRX")
47
+ return img, mon
48
+
49
+
50
+ def save_screenshot(
51
+ img: Image.Image,
52
+ session_dir: Path,
53
+ index: int,
54
+ image_format: str = "png",
55
+ suffix: str = "",
56
+ ) -> str:
57
+ """保存原始截图,返回相对 session 目录的 POSIX 风格路径。
58
+
59
+ suffix 用于区分同一步骤的候选帧(如 "-imm" / "-set")。
60
+ """
61
+ images_dir = session_dir / "images"
62
+ images_dir.mkdir(parents=True, exist_ok=True)
63
+ path = images_dir / f"step-{index:02d}{suffix}.{image_format}"
64
+ img.save(path)
65
+ return path.relative_to(session_dir).as_posix()
66
+
67
+
68
+ # 图片相似度:缩到 96x54 灰度后逐像素求平均绝对差(0~255)。
69
+ # < 2.0 视为「画面没变」,> 4.0 视为「明显变化」。
70
+ _SIMILARITY_SIZE = (96, 54)
71
+
72
+
73
+ def mean_diff(a: Path | Image.Image, b: Path | Image.Image) -> float:
74
+ """两张截图的平均像素差(0~255),越小越相似。"""
75
+ pa = a if isinstance(a, Image.Image) else Image.open(a)
76
+ pb = b if isinstance(b, Image.Image) else Image.open(b)
77
+ ga = pa.convert("L").resize(_SIMILARITY_SIZE)
78
+ gb = pb.convert("L").resize(_SIMILARITY_SIZE)
79
+ da = list(ga.getdata())
80
+ db = list(gb.getdata())
81
+ total = sum(abs(x - y) for x, y in zip(da, db, strict=True))
82
+ return total / len(da)
83
+
84
+
85
+ def settle_grab(
86
+ capture: ScreenCapture,
87
+ monitor: dict,
88
+ max_wait_ms: int,
89
+ poll_ms: int = 150,
90
+ stable_diff: float = 1.5,
91
+ ) -> tuple[Image.Image, int]:
92
+ """轮询截屏直到画面连续两帧几乎不变(界面稳定),或超时。
93
+
94
+ 返回 (稳定帧图像, 实际等待毫秒数)。
95
+ """
96
+ deadline = time.monotonic() + max(max_wait_ms, 0) / 1000
97
+ last = capture.grab_monitor(monitor)
98
+ waited = 0
99
+ while True:
100
+ if time.monotonic() >= deadline:
101
+ return last, waited
102
+ time.sleep(poll_ms / 1000)
103
+ waited += poll_ms
104
+ current = capture.grab_monitor(monitor)
105
+ if mean_diff(last, current) < stable_diff:
106
+ return current, waited
107
+ last = current
108
+
109
+
110
+ def _load_font(size: int) -> ImageFont.FreeTypeFont | ImageFont.ImageFont:
111
+ for name in ("msyh.ttc", "arial.ttf"): # 优先微软雅黑,回退 Arial
112
+ try:
113
+ return ImageFont.truetype(name, size)
114
+ except OSError:
115
+ continue
116
+ return ImageFont.load_default()
117
+
118
+
119
+ def annotate(
120
+ img: Image.Image, rel_x: float, rel_y: float, number: int, radius: int = 28
121
+ ) -> Image.Image:
122
+ """在截图上标注点击位置:红色高亮圈 + 序号徽章,返回新图。"""
123
+ out = img.convert("RGBA")
124
+ overlay = Image.new("RGBA", out.size, (0, 0, 0, 0))
125
+ d = ImageDraw.Draw(overlay)
126
+
127
+ cx, cy = rel_x * out.width, rel_y * out.height
128
+ ring_w = max(3, out.width // 450)
129
+ # 外圈柔光 + 内圈实线
130
+ d.ellipse(
131
+ [cx - radius - 8, cy - radius - 8, cx + radius + 8, cy + radius + 8],
132
+ outline=ACCENT + (80,),
133
+ width=ring_w + 3,
134
+ )
135
+ d.ellipse(
136
+ [cx - radius, cy - radius, cx + radius, cy + radius],
137
+ outline=ACCENT + (255,),
138
+ width=ring_w,
139
+ )
140
+
141
+ # 序号徽章放在圈右上,越界时往里收
142
+ badge_r = max(14, int(radius * 0.62))
143
+ bx = min(max(cx + radius + 6, badge_r), out.width - badge_r - 2)
144
+ by = max(min(cy - radius - 6, out.height - badge_r - 2), badge_r + 2)
145
+ d.ellipse(
146
+ [bx - badge_r, by - badge_r, bx + badge_r, by + badge_r],
147
+ fill=ACCENT + (235,),
148
+ )
149
+ out = Image.alpha_composite(out, overlay)
150
+
151
+ dd = ImageDraw.Draw(out)
152
+ text = str(number)
153
+ font = _load_font(badge_r * 2 - 4)
154
+ left, top, right, bottom = dd.textbbox((0, 0), text, font=font)
155
+ dd.text(
156
+ (bx - (right - left) / 2 - left, by - (bottom - top) / 2 - top),
157
+ text,
158
+ font=font,
159
+ fill=(255, 255, 255, 255),
160
+ )
161
+ return out.convert("RGB")