ascript 1.3.27__py3-none-win_amd64.whl
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.
- ascript/__init__.py +8 -0
- ascript/hooks/__init__.py +0 -0
- ascript/hooks/hook-ascript.py +2 -0
- ascript/windows/__init__.py +29 -0
- ascript/windows/daos/__init__.py +0 -0
- ascript/windows/daos/screen.py +143 -0
- ascript/windows/hardware/__init__.py +5 -0
- ascript/windows/hardware/keyboard.py +279 -0
- ascript/windows/hardware/logitech_driver.py +179 -0
- ascript/windows/hardware/mouse.py +308 -0
- ascript/windows/license/__init__.py +16 -0
- ascript/windows/license/achttp.cp310-win_amd64.pyd +0 -0
- ascript/windows/license/achttp.cp311-win_amd64.pyd +0 -0
- ascript/windows/license/achttp.cp312-win_amd64.pyd +0 -0
- ascript/windows/license/achttp.cp313-win_amd64.pyd +0 -0
- ascript/windows/license/achttp.cp314-win_amd64.pyd +0 -0
- ascript/windows/license/achttp.cp38-win_amd64.pyd +0 -0
- ascript/windows/license/achttp.cp39-win_amd64.pyd +0 -0
- ascript/windows/media/__init__.py +0 -0
- ascript/windows/resources/__init__.py +0 -0
- ascript/windows/resources/libs/logitech/x64/LogitechGkeyEnginesWrapper.dll +0 -0
- ascript/windows/resources/libs/logitech/x86/LogitechGkeyEnginesWrapper.dll +0 -0
- ascript/windows/resources/templates/__init__.py +0 -0
- ascript/windows/resources/templates/__init__py.win.template +51 -0
- ascript/windows/resources/templates/build.as.win.template +14 -0
- ascript/windows/resources/templates/build.py +71 -0
- ascript/windows/resources/templates/logo_windows.ico +0 -0
- ascript/windows/resources/templates/windows/main.py +50 -0
- ascript/windows/resources/templates/windows/pyproject.toml +44 -0
- ascript/windows/resources/templates/windows/res/img/logo.ico +0 -0
- ascript/windows/screen/__init__.py +13 -0
- ascript/windows/screen/as_cv.py +382 -0
- ascript/windows/screen/canny.py +128 -0
- ascript/windows/screen/code_scanner.py +72 -0
- ascript/windows/screen/color_algorithms.py +277 -0
- ascript/windows/screen/find_colors.py +251 -0
- ascript/windows/screen/find_images.py +217 -0
- ascript/windows/screen/ocr/__init__.py +4 -0
- ascript/windows/screen/ocr/ocr.py +223 -0
- ascript/windows/screen/ocr/rapidocr_hook.py +301 -0
- ascript/windows/screen/ocrx.py +169 -0
- ascript/windows/screen/screen_core.py +139 -0
- ascript/windows/store/__init__.py +21 -0
- ascript/windows/system/__init__.py +8 -0
- ascript/windows/system/device.py +355 -0
- ascript/windows/system/easy_print.py +115 -0
- ascript/windows/system/env.py +492 -0
- ascript/windows/system/exception/__init__.py +1 -0
- ascript/windows/system/key_value.py +145 -0
- ascript/windows/system/pip.py +175 -0
- ascript/windows/system/r.py +165 -0
- ascript/windows/system/runtime.py +62 -0
- ascript/windows/tools/__init__.py +27 -0
- ascript/windows/tools/__main__.py +21 -0
- ascript/windows/tools/action.json +33 -0
- ascript/windows/tools/action.py +168 -0
- ascript/windows/tools/colors_tool.py +391 -0
- ascript/windows/tools/config.py +26 -0
- ascript/windows/tools/dumptool.py +18 -0
- ascript/windows/tools/eel_api.py +495 -0
- ascript/windows/tools/gpplug/__init__.py +0 -0
- ascript/windows/tools/gpplug/plug.py +70 -0
- ascript/windows/tools/mouse_catch.py +168 -0
- ascript/windows/tools/plug_manager.py +38 -0
- ascript/windows/tools/tool_api.py +906 -0
- ascript/windows/tools/tool_window.py +56 -0
- ascript/windows/tools/uia_worker.py +365 -0
- ascript/windows/tools/web/app_store_window.html +481 -0
- ascript/windows/tools/web/colors.html +532 -0
- ascript/windows/tools/web/css/screen.css +1342 -0
- ascript/windows/tools/web/css/viewtree.css +357 -0
- ascript/windows/tools/web/flot_control.html +195 -0
- ascript/windows/tools/web/img/ico/favicon.png +0 -0
- ascript/windows/tools/web/img/ico/paintpalette.png +0 -0
- ascript/windows/tools/web/img/svg/find_colors.svg +1 -0
- ascript/windows/tools/web/img/svg/find_images.svg +1 -0
- ascript/windows/tools/web/img/svg/illustration-6.svg +89 -0
- ascript/windows/tools/web/img/svg/img_default.svg +1 -0
- ascript/windows/tools/web/img/svg/ocr.svg +1 -0
- ascript/windows/tools/web/js/screen/colors.js +466 -0
- ascript/windows/tools/web/js/screen/gp_plug.js +108 -0
- ascript/windows/tools/web/js/screen/image_mouse.js +464 -0
- ascript/windows/tools/web/js/screen/img_view.js +558 -0
- ascript/windows/tools/web/js/screen/plug.js +246 -0
- ascript/windows/tools/web/js/screen/screen.js +690 -0
- ascript/windows/tools/web/js/screen/screen_api.js +778 -0
- ascript/windows/tools/web/js/screen/screen_maker.js +10 -0
- ascript/windows/tools/web/js/utils/map.js +73 -0
- ascript/windows/tools/web/js/utils/upload.js +157 -0
- ascript/windows/tools/web/js/viewtree/niv_js.js +27 -0
- ascript/windows/tools/web/js/viewtree/vtree.js +1510 -0
- ascript/windows/tools/web/libs/bootstrap/css/bootstrap.min.css +6 -0
- ascript/windows/tools/web/libs/bootstrap/js/bootstrap.bundle.min.js +7 -0
- ascript/windows/tools/web/libs/bootstrap-icons/0-circle.svg +4 -0
- ascript/windows/tools/web/libs/bootstrap-icons/0-square.svg +4 -0
- ascript/windows/tools/web/libs/bootstrap-icons/font/bootstrap-icons.css +2078 -0
- ascript/windows/tools/web/libs/bootstrap-icons/font/bootstrap-icons.json +2052 -0
- ascript/windows/tools/web/libs/bootstrap-icons/font/bootstrap-icons.min.css +5 -0
- ascript/windows/tools/web/libs/bootstrap-icons/font/bootstrap-icons.scss +2090 -0
- ascript/windows/tools/web/libs/bootstrap-icons/font/fonts/bootstrap-icons.woff +0 -0
- ascript/windows/tools/web/libs/bootstrap-icons/font/fonts/bootstrap-icons.woff2 +0 -0
- ascript/windows/tools/web/libs/feather-icons/dist/feather.min.js +13 -0
- ascript/windows/tools/web/libs/feather-icons/dist/icons.json +1 -0
- ascript/windows/tools/web/libs/jquery/core.js +399 -0
- ascript/windows/tools/web/libs/jquery/jquery.js +10598 -0
- ascript/windows/tools/web/libs/jquery/jquery.min.js +4 -0
- ascript/windows/tools/web/libs/jquery/jquery.min.js.zip +0 -0
- ascript/windows/tools/web/libs/jquery/jquery.slim.js +8495 -0
- ascript/windows/tools/web/libs/jquery/jquery.slim.min.js +2 -0
- ascript/windows/tools/web/libs/jquery-menu/jquery.contextMenu.js +2134 -0
- ascript/windows/tools/web/libs/jquery-menu/jquery.contextMenu.min.css +16 -0
- ascript/windows/tools/web/libs/jquery-zTree/css/zTreeStyle.css +97 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/1_close.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/1_open.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/2.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/3.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/4.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/5.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/6.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/7.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/8.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/9.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/line_conn.gif +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/loading.gif +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/tree-node-btn.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/zTreeStandard.gif +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/zTreeStandard.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/js/jquery.ztree.all.min.js +3 -0
- ascript/windows/tools/web/libs/treeview/css/jquery.treeview.css +79 -0
- ascript/windows/tools/web/libs/treeview/img/file.png +0 -0
- ascript/windows/tools/web/libs/treeview/img/ico_loading.png +0 -0
- ascript/windows/tools/web/libs/treeview/img/icon4.png +0 -0
- ascript/windows/tools/web/libs/treeview/img/icon5.png +0 -0
- ascript/windows/tools/web/libs/treeview/img/icon6.png +0 -0
- ascript/windows/tools/web/libs/treeview/img/py.png +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-black-line.gif +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-black.gif +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-default-line.gif +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-default.gif +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-famfamfam-line.gif +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-famfamfam.gif +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-gray-line.gif +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-gray.gif +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-red-line.gif +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-red.gif +0 -0
- ascript/windows/tools/web/libs/treeview/js/jquery.treeview.edit.js +37 -0
- ascript/windows/tools/web/libs/treeview/js/jquery.treeview.js +250 -0
- ascript/windows/tools/web/license_window.html +255 -0
- ascript/windows/tools/web/plugs/a.html +653 -0
- ascript/windows/tools/web/plugs/demo.html +52 -0
- ascript/windows/tools/web/plugs/find_colors.html +629 -0
- ascript/windows/tools/web/plugs/find_images.html +699 -0
- ascript/windows/tools/web/plugs/rapidocr.html +954 -0
- ascript/windows/tools/web/vtree.html +496 -0
- ascript/windows/tools/webview/assets/index-HymBPl-I.css +1 -0
- ascript/windows/tools/webview/assets/index-PqA_m8tL.js +17 -0
- ascript/windows/tools/webview/index.html +13 -0
- ascript/windows/ui/__init__.py +6 -0
- ascript/windows/ui/app_store_window.py +272 -0
- ascript/windows/ui/draw_window.py +231 -0
- ascript/windows/ui/flot_test.py +297 -0
- ascript/windows/ui/flot_window.py +488 -0
- ascript/windows/ui/license_window.py +111 -0
- ascript/windows/ui/web_window.py +466 -0
- ascript/windows/utils/__init__.py +0 -0
- ascript/windows/utils/node_utils.py +20 -0
- ascript/windows/utils/screen_utils.py +280 -0
- ascript/windows/window/Selector.py +1015 -0
- ascript/windows/window/Window.py +747 -0
- ascript/windows/window/__init__.py +5 -0
- ascript/windows/window/launcher.py +213 -0
- ascript/windows/window/uia_runtime.py +26 -0
- ascript-1.3.27.dist-info/METADATA +41 -0
- ascript-1.3.27.dist-info/RECORD +177 -0
- ascript-1.3.27.dist-info/WHEEL +5 -0
- ascript-1.3.27.dist-info/entry_points.txt +2 -0
- ascript-1.3.27.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import re
|
|
3
|
+
import importlib.abc
|
|
4
|
+
import importlib.util
|
|
5
|
+
import importlib
|
|
6
|
+
from dataclasses import dataclass, asdict
|
|
7
|
+
from typing import List, Optional, Tuple, Union, TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
# 假设你的硬件操作模块路径如下
|
|
10
|
+
from ...hardware.mouse import Mouse
|
|
11
|
+
|
|
12
|
+
# --- 1. 类型存根 (仅用于 PyCharm 等 IDE 的代码补全提示) ---
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
try:
|
|
15
|
+
from rapidocr.utils.output import RapidOCROutput as BaseOutput
|
|
16
|
+
except ImportError:
|
|
17
|
+
class BaseOutput:
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class RapidOCROutput(BaseOutput):
|
|
22
|
+
"""RapidOCR 输出结果的类型存根(仅用于 IDE 代码补全提示)。
|
|
23
|
+
|
|
24
|
+
声明 find 和 find_all 方法签名,使 IDE 能正确提示动态注入的方法。
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def find(self, text_re: str = None, score: float = 0.85) -> 'OCRItem': ...
|
|
28
|
+
|
|
29
|
+
def find_all(self, text_re: str = None, score: float = 0.85) -> List['OCRItem']: ...
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# --- 2. 核心结果对象定义 ---
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class OCRItem:
|
|
36
|
+
"""OCR 单条识别结果的数据封装类。
|
|
37
|
+
|
|
38
|
+
封装一条文字识别结果的完整信息,支持直接点击识别到的文字位置。
|
|
39
|
+
|
|
40
|
+
Attributes:
|
|
41
|
+
txt: 识别出的文本内容。
|
|
42
|
+
box: 文字区域的四个顶点坐标,格式为 [[x1,y1], [x2,y2], [x3,y3], [x4,y4]]。
|
|
43
|
+
score: 识别置信度,范围 0.0 ~ 1.0。
|
|
44
|
+
center: 文字区域的中心点坐标 (x, y)。
|
|
45
|
+
rect: 文字区域的外接矩形 [left, top, right, bottom]。
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
txt: str
|
|
49
|
+
box: List[List[float]]
|
|
50
|
+
score: float
|
|
51
|
+
center: Tuple[int, int]
|
|
52
|
+
rect: List[int]
|
|
53
|
+
|
|
54
|
+
def click(self, window=None):
|
|
55
|
+
"""点击该识别结果所在的位置。
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
window: 可选的 Window 对象。传入时调用 window.click() 进行窗口内相对坐标点击;
|
|
59
|
+
不传时调用全局 Mouse.click() 进行屏幕绝对坐标点击。
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
OCRItem: 返回自身,支持链式调用。
|
|
63
|
+
"""
|
|
64
|
+
x, y = self.center
|
|
65
|
+
if window:
|
|
66
|
+
# 让窗口对象处理相对坐标点击
|
|
67
|
+
window.click(x, y)
|
|
68
|
+
else:
|
|
69
|
+
# 全屏绝对点击
|
|
70
|
+
Mouse.click(x, y)
|
|
71
|
+
return self
|
|
72
|
+
|
|
73
|
+
def to_dict(self) -> dict:
|
|
74
|
+
"""将识别结果转换为字典格式。
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
dict: 包含 txt、box、score、center、rect 等字段的字典。
|
|
78
|
+
"""
|
|
79
|
+
return {
|
|
80
|
+
**asdict(self),
|
|
81
|
+
"text": self.txt,
|
|
82
|
+
"confidence": self.score,
|
|
83
|
+
"center_x": int(self.center[0]),
|
|
84
|
+
"center_y": int(self.center[1]),
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def text(self):
|
|
89
|
+
return self.txt
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def confidence(self):
|
|
93
|
+
return self.score
|
|
94
|
+
|
|
95
|
+
def __bool__(self):
|
|
96
|
+
"""支持布尔判断,OCRItem 实例始终为 True。
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
bool: 始终返回 True,表示存在有效识别结果。
|
|
100
|
+
"""
|
|
101
|
+
return True
|
|
102
|
+
|
|
103
|
+
def __repr__(self):
|
|
104
|
+
"""返回 OCRItem 的可读字符串表示。
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
str: 包含文本、置信度和中心坐标的描述字符串。
|
|
108
|
+
"""
|
|
109
|
+
return f"<OCRItem txt='{self.txt}' score={self.score:.2f} center={self.center}>"
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class NullOCRItem:
|
|
113
|
+
"""空 OCR 结果对象(空对象模式)。
|
|
114
|
+
|
|
115
|
+
当 find 方法未找到匹配结果时返回此对象,防止链式调用因 None 而报错。
|
|
116
|
+
所有属性均为默认空值,布尔值为 False。
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
def __init__(self):
|
|
120
|
+
"""初始化空 OCR 结果,所有属性设为默认空值。"""
|
|
121
|
+
self.txt, self.box, self.score = "", [], 0.0
|
|
122
|
+
self.center, self.rect = (0, 0), [0, 0, 0, 0]
|
|
123
|
+
|
|
124
|
+
def click(self, window=None):
|
|
125
|
+
"""静默处理点击操作,不执行任何动作。
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
window: 忽略此参数。
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
NullOCRItem: 返回自身,支持链式调用。
|
|
132
|
+
"""
|
|
133
|
+
return self
|
|
134
|
+
|
|
135
|
+
def to_dict(self) -> dict:
|
|
136
|
+
"""转换为空字典。
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
dict: 空字典。
|
|
140
|
+
"""
|
|
141
|
+
return {}
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def text(self):
|
|
145
|
+
return ""
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
def confidence(self):
|
|
149
|
+
return 0.0
|
|
150
|
+
|
|
151
|
+
def __bool__(self):
|
|
152
|
+
"""支持布尔判断,NullOCRItem 始终为 False。
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
bool: 始终返回 False,表示未找到结果。
|
|
156
|
+
"""
|
|
157
|
+
return False
|
|
158
|
+
|
|
159
|
+
def __repr__(self):
|
|
160
|
+
"""返回 NullOCRItem 的可读字符串表示。
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
str: 空结果的描述字符串。
|
|
164
|
+
"""
|
|
165
|
+
return "<NullOCRItem (Not Found)>"
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# --- 3. 运行时 Hook 逻辑 ---
|
|
169
|
+
|
|
170
|
+
class RapidOCRHook(importlib.abc.MetaPathFinder):
|
|
171
|
+
"""RapidOCR 运行时 Hook,动态注入 find/find_all 扩展方法。
|
|
172
|
+
|
|
173
|
+
通过 Python 的 MetaPathFinder 机制拦截 rapidocr.utils.output 模块的导入,
|
|
174
|
+
在其 RapidOCROutput 类上注入 find() 和 find_all() 方法,
|
|
175
|
+
使 OCR 结果对象直接支持文字搜索和定位功能。
|
|
176
|
+
"""
|
|
177
|
+
|
|
178
|
+
def find_spec(self, fullname, path, target=None):
|
|
179
|
+
"""拦截模块导入,在目标模块加载时注入扩展方法。
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
fullname: 正在导入的模块全名。
|
|
183
|
+
path: 模块搜索路径。
|
|
184
|
+
target: 目标模块对象(可选)。
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
ModuleSpec: 当拦截到目标模块时返回其 spec,否则返回 None。
|
|
188
|
+
"""
|
|
189
|
+
# 监听 RapidOCR 的核心输出模块
|
|
190
|
+
if fullname == "rapidocr.utils.output":
|
|
191
|
+
importlib.invalidate_caches() # 动态感知新安装的包
|
|
192
|
+
try:
|
|
193
|
+
spec = importlib.util.find_spec(fullname)
|
|
194
|
+
if spec is None: return None
|
|
195
|
+
|
|
196
|
+
if self in sys.meta_path:
|
|
197
|
+
sys.meta_path.remove(self)
|
|
198
|
+
|
|
199
|
+
module = importlib.util.module_from_spec(spec)
|
|
200
|
+
spec.loader.exec_module(module)
|
|
201
|
+
self._patch(module)
|
|
202
|
+
sys.modules[fullname] = module
|
|
203
|
+
return spec
|
|
204
|
+
except Exception:
|
|
205
|
+
return None
|
|
206
|
+
return None
|
|
207
|
+
|
|
208
|
+
def _patch(self, module):
|
|
209
|
+
"""向目标模块的 RapidOCROutput 类动态注入 find 和 find_all 方法。
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
module: 已加载的 rapidocr.utils.output 模块对象。
|
|
213
|
+
"""
|
|
214
|
+
|
|
215
|
+
def _build_item(txt, box, score) -> OCRItem:
|
|
216
|
+
"""根据原始 OCR 数据构建 OCRItem 结果对象。
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
txt: 识别出的文本内容。
|
|
220
|
+
box: 文字区域的四个顶点坐标数组。
|
|
221
|
+
score: 识别置信度。
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
OCRItem: 封装好的识别结果对象。
|
|
225
|
+
"""
|
|
226
|
+
l, t = float(box[0][0]), float(box[0][1])
|
|
227
|
+
r, b = float(box[2][0]), float(box[2][1])
|
|
228
|
+
center = (int((l + r) / 2), int((t + b) / 2))
|
|
229
|
+
rect = [int(l), int(t), int(r), int(b)]
|
|
230
|
+
return OCRItem(
|
|
231
|
+
txt=str(txt),
|
|
232
|
+
box=box.tolist() if hasattr(box, 'tolist') else box,
|
|
233
|
+
score=float(score),
|
|
234
|
+
center=center,
|
|
235
|
+
rect=rect
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
def find(self, text_re: str = None, score: float = 0.85) -> Union[OCRItem, NullOCRItem]:
|
|
239
|
+
"""在 OCR 识别结果中查找第一个匹配的文本。
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
text_re: 正则表达式模式,用于匹配文本内容。为 None 时匹配所有文本。
|
|
243
|
+
score: 最低置信度阈值,默认为 0.85。
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
Union[OCRItem, NullOCRItem]: 匹配到的结果对象,未找到时返回 NullOCRItem。
|
|
247
|
+
"""
|
|
248
|
+
txts, boxes, scores = getattr(self, 'txts', None), getattr(self, 'boxes', None), getattr(self, 'scores',
|
|
249
|
+
None)
|
|
250
|
+
if txts is not None and boxes is not None:
|
|
251
|
+
for i in range(len(txts)):
|
|
252
|
+
if scores[i] >= score:
|
|
253
|
+
if text_re is None or re.search(text_re, str(txts[i])):
|
|
254
|
+
return _build_item(txts[i], boxes[i], scores[i])
|
|
255
|
+
return NullOCRItem()
|
|
256
|
+
|
|
257
|
+
def find_all(self, text_re: str = None, score: float = 0.85) -> List[OCRItem]:
|
|
258
|
+
"""在 OCR 识别结果中查找所有匹配的文本,按从上到下、从左到右排序。
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
text_re: 正则表达式模式,用于匹配文本内容。为 None 时匹配所有文本。
|
|
262
|
+
score: 最低置信度阈值,默认为 0.85。
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
List[OCRItem]: 匹配结果列表,按坐标排序(从上到下、从左到右)。
|
|
266
|
+
"""
|
|
267
|
+
results = []
|
|
268
|
+
txts, boxes, scores = getattr(self, 'txts', None), getattr(self, 'boxes', None), getattr(self, 'scores',
|
|
269
|
+
None)
|
|
270
|
+
if txts is not None and boxes is not None:
|
|
271
|
+
for i in range(len(txts)):
|
|
272
|
+
if scores[i] >= score:
|
|
273
|
+
if text_re is None or re.search(text_re, str(txts[i])):
|
|
274
|
+
results.append(_build_item(txts[i], boxes[i], scores[i]))
|
|
275
|
+
|
|
276
|
+
# 按从上到下、从左到右排序
|
|
277
|
+
results.sort(key=lambda x: (x.center[1], x.center[0]))
|
|
278
|
+
return results
|
|
279
|
+
|
|
280
|
+
# 将方法绑定到目标类
|
|
281
|
+
target_class = "RapidOCROutput"
|
|
282
|
+
if hasattr(module, target_class):
|
|
283
|
+
cls = getattr(module, target_class)
|
|
284
|
+
if not hasattr(cls, "find"):
|
|
285
|
+
cls.find = find
|
|
286
|
+
cls.find_all = find_all
|
|
287
|
+
# print(f"✅ [RapidOCR-Hook] 成功注入 find/find_all 扩展")
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def install():
|
|
291
|
+
"""注册 RapidOCR Hook 的入口函数。
|
|
292
|
+
|
|
293
|
+
如果 rapidocr.utils.output 已加载,直接对其进行补丁;
|
|
294
|
+
否则将 RapidOCRHook 注册到 sys.meta_path,等待模块导入时自动注入。
|
|
295
|
+
"""
|
|
296
|
+
target_mod = "rapidocr.utils.output"
|
|
297
|
+
if target_mod in sys.modules:
|
|
298
|
+
RapidOCRHook()._patch(sys.modules[target_mod])
|
|
299
|
+
return
|
|
300
|
+
if not any(isinstance(h, RapidOCRHook) for h in sys.meta_path):
|
|
301
|
+
sys.meta_path.insert(0, RapidOCRHook())
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
import cv2
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from .color_algorithms import color_mask
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
_RADIX_ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@}{[])(*?><~`$^"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _to_radix(value: int) -> str:
|
|
15
|
+
if value == 0:
|
|
16
|
+
return "0"
|
|
17
|
+
base = len(_RADIX_ALPHABET)
|
|
18
|
+
output = []
|
|
19
|
+
while value:
|
|
20
|
+
value, remainder = divmod(value, base)
|
|
21
|
+
output.append(_RADIX_ALPHABET[remainder])
|
|
22
|
+
return "".join(reversed(output))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _from_radix(value: str) -> int:
|
|
26
|
+
base = len(_RADIX_ALPHABET)
|
|
27
|
+
result = 0
|
|
28
|
+
for char in value:
|
|
29
|
+
index = _RADIX_ALPHABET.find(char)
|
|
30
|
+
if index < 0:
|
|
31
|
+
raise ValueError(f"字库包含无效字符: {char}")
|
|
32
|
+
result = result * base + index
|
|
33
|
+
return result
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class OcrXResult:
|
|
38
|
+
text: str
|
|
39
|
+
fonts: List[dict]
|
|
40
|
+
|
|
41
|
+
def getText(self):
|
|
42
|
+
return self.text
|
|
43
|
+
|
|
44
|
+
def to_dict(self):
|
|
45
|
+
return {"text": self.text, "fonts": self.fonts}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class OcrX:
|
|
49
|
+
"""兼容 Android ``.t`` 点阵字库格式的轻量识字实现。"""
|
|
50
|
+
|
|
51
|
+
@staticmethod
|
|
52
|
+
def append_to_lib(lib_path, content):
|
|
53
|
+
path = Path(lib_path)
|
|
54
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
55
|
+
prefix = "\n" if path.exists() and path.stat().st_size else ""
|
|
56
|
+
with path.open("a", encoding="utf-8") as stream:
|
|
57
|
+
stream.write(prefix + str(content).strip())
|
|
58
|
+
|
|
59
|
+
@staticmethod
|
|
60
|
+
def _trim_mask(mask):
|
|
61
|
+
ys, xs = np.where(mask)
|
|
62
|
+
if not len(xs):
|
|
63
|
+
return mask[:0, :0], 0, 0
|
|
64
|
+
left, right = int(xs.min()), int(xs.max()) + 1
|
|
65
|
+
top, bottom = int(ys.min()), int(ys.max()) + 1
|
|
66
|
+
return mask[top:bottom, left:right], left, top
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
def make_font(image, colors: str, name: str = "字") -> str:
|
|
70
|
+
mask, _, _, _ = color_mask(image, colors, similarity=1.0)
|
|
71
|
+
mask, _, _ = OcrX._trim_mask(mask)
|
|
72
|
+
if mask.size == 0:
|
|
73
|
+
raise ValueError("选定颜色在图像中不存在")
|
|
74
|
+
rows = [_to_radix(int("".join("1" if value else "0" for value in row), 2)) for row in mask]
|
|
75
|
+
return f"{name}|{colors}|{int(mask.sum())}|{mask.shape[1]}|{'&'.join(rows)}"
|
|
76
|
+
|
|
77
|
+
@staticmethod
|
|
78
|
+
def _load(font_lib):
|
|
79
|
+
content = str(font_lib or "")
|
|
80
|
+
if isinstance(font_lib, (str, Path)):
|
|
81
|
+
try:
|
|
82
|
+
path = Path(font_lib)
|
|
83
|
+
if path.exists() and path.is_file():
|
|
84
|
+
content = path.read_text(encoding="utf-8")
|
|
85
|
+
except OSError:
|
|
86
|
+
pass
|
|
87
|
+
fonts = []
|
|
88
|
+
for line_number, line in enumerate(content.splitlines(), 1):
|
|
89
|
+
line = line.strip()
|
|
90
|
+
if not line:
|
|
91
|
+
continue
|
|
92
|
+
parts = line.split("|", 4)
|
|
93
|
+
if len(parts) != 5:
|
|
94
|
+
raise ValueError(f"字库第 {line_number} 行格式错误")
|
|
95
|
+
name, colors, count, width, encoded = parts
|
|
96
|
+
width = int(width)
|
|
97
|
+
rows = []
|
|
98
|
+
for row in encoded.split("&"):
|
|
99
|
+
bits = bin(_from_radix(row))[2:].zfill(width)[-width:]
|
|
100
|
+
rows.append([1 if char == "1" else 0 for char in bits])
|
|
101
|
+
matrix = np.asarray(rows, dtype=np.float32)
|
|
102
|
+
fonts.append({"text": name, "colors": colors, "count": max(1, int(count)), "matrix": matrix})
|
|
103
|
+
if not fonts:
|
|
104
|
+
raise ValueError("字库为空")
|
|
105
|
+
return fonts
|
|
106
|
+
|
|
107
|
+
@staticmethod
|
|
108
|
+
def _iou(first, second):
|
|
109
|
+
left = max(first[0], second[0])
|
|
110
|
+
top = max(first[1], second[1])
|
|
111
|
+
right = min(first[2], second[2])
|
|
112
|
+
bottom = min(first[3], second[3])
|
|
113
|
+
if right <= left or bottom <= top:
|
|
114
|
+
return 0.0
|
|
115
|
+
intersection = (right - left) * (bottom - top)
|
|
116
|
+
first_area = (first[2] - first[0]) * (first[3] - first[1])
|
|
117
|
+
second_area = (second[2] - second[0]) * (second[3] - second[1])
|
|
118
|
+
return intersection / max(1, first_area + second_area - intersection)
|
|
119
|
+
|
|
120
|
+
@staticmethod
|
|
121
|
+
def recognize(font_lib, rect=None, region=0.9, image=None) -> OcrXResult:
|
|
122
|
+
fonts = OcrX._load(font_lib)
|
|
123
|
+
candidates = []
|
|
124
|
+
for font in fonts:
|
|
125
|
+
mask, _, offset_x, offset_y = color_mask(image, font["colors"], rect=rect, similarity=1.0)
|
|
126
|
+
mask, trim_x, trim_y = OcrX._trim_mask(mask)
|
|
127
|
+
template = font["matrix"]
|
|
128
|
+
if mask.size == 0 or mask.shape[0] < template.shape[0] or mask.shape[1] < template.shape[1]:
|
|
129
|
+
continue
|
|
130
|
+
scores = cv2.matchTemplate(mask.astype(np.float32), template, cv2.TM_CCORR) / float(font["count"])
|
|
131
|
+
local_max = scores >= cv2.dilate(scores, np.ones((3, 3), np.uint8))
|
|
132
|
+
ys, xs = np.where((scores >= float(region)) & local_max)
|
|
133
|
+
for x, y in zip(xs, ys):
|
|
134
|
+
width, height = template.shape[1], template.shape[0]
|
|
135
|
+
left, top = int(x + trim_x + offset_x), int(y + trim_y + offset_y)
|
|
136
|
+
candidates.append({
|
|
137
|
+
"text": font["text"], "score": round(float(scores[y, x]), 4),
|
|
138
|
+
"rect": [left, top, left + width, top + height],
|
|
139
|
+
"x": left, "y": top, "width": width, "height": height,
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
selected = []
|
|
143
|
+
for candidate in sorted(candidates, key=lambda item: (-item["score"], item["y"], item["x"])):
|
|
144
|
+
if any(OcrX._iou(candidate["rect"], old["rect"]) > 0.3 for old in selected):
|
|
145
|
+
continue
|
|
146
|
+
selected.append(candidate)
|
|
147
|
+
selected.sort(key=lambda item: (item["y"] + item["height"] // 2, item["x"]))
|
|
148
|
+
|
|
149
|
+
lines = []
|
|
150
|
+
for item in selected:
|
|
151
|
+
center_y = item["y"] + item["height"] // 2
|
|
152
|
+
line = next((line for line in lines if abs(line[0] - center_y) <= max(3, item["height"] // 2)), None)
|
|
153
|
+
if line is None:
|
|
154
|
+
line = [center_y, []]
|
|
155
|
+
lines.append(line)
|
|
156
|
+
line[1].append(item)
|
|
157
|
+
lines.sort(key=lambda line: line[0])
|
|
158
|
+
text_lines = []
|
|
159
|
+
ordered = []
|
|
160
|
+
for _, items in lines:
|
|
161
|
+
items.sort(key=lambda item: item["x"])
|
|
162
|
+
ordered.extend(items)
|
|
163
|
+
text_lines.append("".join(item["text"] for item in items))
|
|
164
|
+
return OcrXResult("\n".join(text_lines), ordered)
|
|
165
|
+
|
|
166
|
+
@staticmethod
|
|
167
|
+
def find_all(font_lib: str, rect=None, region=0.9, image=None):
|
|
168
|
+
result = OcrX.recognize(font_lib, rect=rect, region=region, image=image)
|
|
169
|
+
return result.text or None
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import base64
|
|
3
|
+
import ctypes
|
|
4
|
+
from io import BytesIO
|
|
5
|
+
from typing import Literal, Union, Optional, List
|
|
6
|
+
import numpy as np
|
|
7
|
+
from PIL import Image
|
|
8
|
+
from mss import mss
|
|
9
|
+
|
|
10
|
+
# 解决 Windows 高分屏缩放导致的截图坐标偏差
|
|
11
|
+
try:
|
|
12
|
+
ctypes.windll.shcore.SetProcessDpiAwareness(1)
|
|
13
|
+
except Exception:
|
|
14
|
+
try:
|
|
15
|
+
ctypes.windll.user32.SetProcessDPIAware()
|
|
16
|
+
except Exception:
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
class Screen:
|
|
20
|
+
"""屏幕截图与显示器信息工具类。
|
|
21
|
+
|
|
22
|
+
基于 mss 库实现高性能屏幕截图,支持 Windows 高分屏 DPI 自适应。
|
|
23
|
+
提供多种输出格式(PIL、NumPy、OpenCV、Base64)及区域截取功能。
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
JPEG_QUALITY = 80
|
|
27
|
+
|
|
28
|
+
@staticmethod
|
|
29
|
+
def encode_image(image: Image.Image, image_format: str = "JPEG") -> bytes:
|
|
30
|
+
"""Encode a PIL image using the same settings as capture()."""
|
|
31
|
+
image_format = image_format.upper()
|
|
32
|
+
output = BytesIO()
|
|
33
|
+
save_args = {"format": image_format}
|
|
34
|
+
if image_format == "JPEG":
|
|
35
|
+
if image.mode != "RGB":
|
|
36
|
+
image = image.convert("RGB")
|
|
37
|
+
save_args["quality"] = Screen.JPEG_QUALITY
|
|
38
|
+
image.save(output, **save_args)
|
|
39
|
+
return output.getvalue()
|
|
40
|
+
|
|
41
|
+
@staticmethod
|
|
42
|
+
def size() -> List[int]:
|
|
43
|
+
"""获取主显示器的分辨率尺寸。
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
List[int]: [宽度, 高度],单位为像素。
|
|
47
|
+
"""
|
|
48
|
+
with mss() as sct:
|
|
49
|
+
monitor = sct.monitors[1]
|
|
50
|
+
return [monitor["width"], monitor["height"]]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@staticmethod
|
|
55
|
+
def capture(
|
|
56
|
+
rect: Optional[List[int]] = None,
|
|
57
|
+
format: str = "pillow",
|
|
58
|
+
save_path: Optional[str] = None
|
|
59
|
+
) -> Union[Image.Image, np.ndarray, str, None]:
|
|
60
|
+
"""截取屏幕图像。
|
|
61
|
+
|
|
62
|
+
支持全屏或指定区域截图,可输出多种格式并可选保存到文件。
|
|
63
|
+
已处理 Windows 高分屏 DPI 缩放问题。
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
rect: 截图区域 [left, top, right, bottom],为 None 时截取整个主显示器。
|
|
67
|
+
format: 输出格式,可选值:
|
|
68
|
+
- 'pillow': 返回 PIL.Image 对象(默认)。
|
|
69
|
+
- 'numpy': 返回 RGB 格式的 NumPy 数组。
|
|
70
|
+
- 'opencv': 返回 BGR 格式的 NumPy 数组。
|
|
71
|
+
- 'base64': 返回 JPEG 格式的 Base64 数据 URI 字符串。
|
|
72
|
+
- 'base64:png': 返回 PNG 格式的 Base64 数据 URI 字符串。
|
|
73
|
+
save_path: 文件保存路径,自动根据后缀名选择存储格式(支持 jpg/png),
|
|
74
|
+
为 None 时不保存。
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Union[Image.Image, numpy.ndarray, str, None]: 根据 format 参数返回对应格式的截图,
|
|
78
|
+
截图失败时返回 None。
|
|
79
|
+
"""
|
|
80
|
+
with mss() as sct:
|
|
81
|
+
try:
|
|
82
|
+
# 1. 区域计算与合法性校验
|
|
83
|
+
if rect and len(rect) == 4:
|
|
84
|
+
left, top, right, bottom = rect
|
|
85
|
+
# 确保 width 和 height 为正数
|
|
86
|
+
width, height = max(0, right - left), max(0, bottom - top)
|
|
87
|
+
if width == 0 or height == 0:
|
|
88
|
+
return None
|
|
89
|
+
monitor = {"left": left, "top": top, "width": width, "height": height}
|
|
90
|
+
else:
|
|
91
|
+
monitor = sct.monitors[1]
|
|
92
|
+
|
|
93
|
+
# 2. 执行截图
|
|
94
|
+
sct_img = sct.grab(monitor)
|
|
95
|
+
|
|
96
|
+
# 3. 构造 PIL 对象 (核心中转)
|
|
97
|
+
img = Image.frombytes("RGB", sct_img.size, sct_img.bgra, "raw", "BGRX")
|
|
98
|
+
|
|
99
|
+
# 4. 磁盘保存逻辑
|
|
100
|
+
if save_path:
|
|
101
|
+
directory = os.path.dirname(save_path)
|
|
102
|
+
if directory and not os.path.exists(directory):
|
|
103
|
+
os.makedirs(directory, exist_ok=True)
|
|
104
|
+
|
|
105
|
+
_, ext = os.path.splitext(save_path)
|
|
106
|
+
ext = ext.lower().strip('.')
|
|
107
|
+
# 自动匹配存储格式
|
|
108
|
+
save_fmt = "JPEG" if ext in ["jpg", "jpeg"] else ("PNG" if ext == "png" else "PNG")
|
|
109
|
+
|
|
110
|
+
# 针对不同格式设置最优参数
|
|
111
|
+
save_args = {"format": save_fmt}
|
|
112
|
+
if save_fmt == "JPEG":
|
|
113
|
+
save_args["quality"] = 95
|
|
114
|
+
save_args["subsampling"] = 0 # 提升色彩保持度
|
|
115
|
+
|
|
116
|
+
img.save(save_path, **save_args)
|
|
117
|
+
|
|
118
|
+
# 5. 返回格式化处理
|
|
119
|
+
if format.startswith("base64"):
|
|
120
|
+
# 默认 jpeg,除非明确指定 base64:png
|
|
121
|
+
sub_format = "PNG" if (":" in format and format.split(":")[1].lower() == "png") else "JPEG"
|
|
122
|
+
image_bytes = Screen.encode_image(img, sub_format)
|
|
123
|
+
img_str = base64.b64encode(image_bytes).decode("utf-8")
|
|
124
|
+
return f"data:image/{sub_format.lower()};base64,{img_str}"
|
|
125
|
+
|
|
126
|
+
elif format == "pillow":
|
|
127
|
+
return img
|
|
128
|
+
|
|
129
|
+
elif format == "numpy":
|
|
130
|
+
return np.array(img)
|
|
131
|
+
|
|
132
|
+
elif format == "opencv":
|
|
133
|
+
return np.array(img)[:, :, ::-1]
|
|
134
|
+
|
|
135
|
+
return img
|
|
136
|
+
|
|
137
|
+
except Exception as e:
|
|
138
|
+
print(f"Screenshot Error: {e}")
|
|
139
|
+
return None
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
|
|
2
|
+
def main():
|
|
3
|
+
from ascript.windows.ui.app_store_window import AppStoreWindow
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
def on_start(app_info, license_data):
|
|
7
|
+
print(app_info)
|
|
8
|
+
print(license_data)
|
|
9
|
+
# package_name = "AScriptProject2"
|
|
10
|
+
user_id = str(app_info["id"])
|
|
11
|
+
file_md5 = app_info['fileMd5']
|
|
12
|
+
|
|
13
|
+
is_ready = app_store.env_manager.smart_deploy(user_id, app_store.target_path, file_md5)
|
|
14
|
+
if not is_ready:
|
|
15
|
+
return False
|
|
16
|
+
return app_store.env_manager.run_app(user_id) is not None
|
|
17
|
+
|
|
18
|
+
root_path = os.path.join(os.getcwd(), "apps")
|
|
19
|
+
default_id = os.environ.get("ASCRIPT_APP_ID") or None
|
|
20
|
+
app_store = AppStoreWindow(default_id=default_id, on_start=on_start, env_path=root_path)
|
|
21
|
+
app_store.show()
|