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.
- snapstep-0.2.0/LICENSE +21 -0
- snapstep-0.2.0/PKG-INFO +186 -0
- snapstep-0.2.0/README.md +148 -0
- snapstep-0.2.0/pyproject.toml +72 -0
- snapstep-0.2.0/setup.cfg +4 -0
- snapstep-0.2.0/src/snapstep/__init__.py +3 -0
- snapstep-0.2.0/src/snapstep/__main__.py +6 -0
- snapstep-0.2.0/src/snapstep/capture.py +161 -0
- snapstep-0.2.0/src/snapstep/cli.py +298 -0
- snapstep-0.2.0/src/snapstep/config.py +109 -0
- snapstep-0.2.0/src/snapstep/export/__init__.py +68 -0
- snapstep-0.2.0/src/snapstep/export/docx_export.py +38 -0
- snapstep-0.2.0/src/snapstep/export/html_export.py +59 -0
- snapstep-0.2.0/src/snapstep/export/markdown_export.py +34 -0
- snapstep-0.2.0/src/snapstep/log.py +41 -0
- snapstep-0.2.0/src/snapstep/models.py +106 -0
- snapstep-0.2.0/src/snapstep/privacy.py +27 -0
- snapstep-0.2.0/src/snapstep/recorder.py +374 -0
- snapstep-0.2.0/src/snapstep/resources/icon.ico +0 -0
- snapstep-0.2.0/src/snapstep/resources/icon.png +0 -0
- snapstep-0.2.0/src/snapstep/resources/icon_rec.png +0 -0
- snapstep-0.2.0/src/snapstep/resources/templates/report.html.j2 +56 -0
- snapstep-0.2.0/src/snapstep/ui/__init__.py +1 -0
- snapstep-0.2.0/src/snapstep/ui/settings.py +149 -0
- snapstep-0.2.0/src/snapstep/ui/tray.py +415 -0
- snapstep-0.2.0/src/snapstep/writer.py +263 -0
- snapstep-0.2.0/src/snapstep.egg-info/PKG-INFO +186 -0
- snapstep-0.2.0/src/snapstep.egg-info/SOURCES.txt +36 -0
- snapstep-0.2.0/src/snapstep.egg-info/dependency_links.txt +1 -0
- snapstep-0.2.0/src/snapstep.egg-info/entry_points.txt +2 -0
- snapstep-0.2.0/src/snapstep.egg-info/requires.txt +14 -0
- snapstep-0.2.0/src/snapstep.egg-info/top_level.txt +1 -0
- snapstep-0.2.0/tests/test_config_recorder.py +90 -0
- snapstep-0.2.0/tests/test_export.py +61 -0
- snapstep-0.2.0/tests/test_finalize.py +96 -0
- snapstep-0.2.0/tests/test_imports.py +51 -0
- snapstep-0.2.0/tests/test_models.py +41 -0
- 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.
|
snapstep-0.2.0/PKG-INFO
ADDED
|
@@ -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
|
+
[](https://github.com/Felixssss-106/snapstep/actions/workflows/test.yml)
|
|
45
|
+
[](LICENSE)
|
|
46
|
+
[](pyproject.toml)
|
|
47
|
+
|
|
48
|
+

|
|
49
|
+
|
|
50
|
+
| 生成的 HTML 教程 | 导出的 Markdown |
|
|
51
|
+
|---|---|
|
|
52
|
+
|  |  |
|
|
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!
|
snapstep-0.2.0/README.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# SnapStep 📸
|
|
2
|
+
|
|
3
|
+
**按一次快捷键,把你的操作变成图文教程。**
|
|
4
|
+
Record your screen, get a step-by-step guide — automatically.
|
|
5
|
+
|
|
6
|
+
[](https://github.com/Felixssss-106/snapstep/actions/workflows/test.yml)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
[](pyproject.toml)
|
|
9
|
+
|
|
10
|
+

|
|
11
|
+
|
|
12
|
+
| 生成的 HTML 教程 | 导出的 Markdown |
|
|
13
|
+
|---|---|
|
|
14
|
+
|  |  |
|
|
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"]
|
snapstep-0.2.0/setup.cfg
ADDED
|
@@ -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")
|