pyautoscene 0.2.0__py3-none-any.whl → 0.2.2__py3-none-any.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.
- pyautoscene/__init__.py +2 -1
- pyautoscene/_types.py +6 -0
- pyautoscene/constants.py +6 -0
- pyautoscene/ocr.py +73 -70
- pyautoscene/ocr_config.yaml +112 -112
- pyautoscene/references.py +100 -78
- pyautoscene/region.py +70 -0
- pyautoscene/scene.py +61 -61
- pyautoscene/session.py +163 -140
- pyautoscene/utils.py +148 -25
- {pyautoscene-0.2.0.dist-info → pyautoscene-0.2.2.dist-info}/METADATA +5 -1
- pyautoscene-0.2.2.dist-info/RECORD +15 -0
- {pyautoscene-0.2.0.dist-info → pyautoscene-0.2.2.dist-info}/licenses/LICENSE +201 -201
- pyautoscene/screen.py +0 -79
- pyautoscene-0.2.0.dist-info/RECORD +0 -13
- {pyautoscene-0.2.0.dist-info → pyautoscene-0.2.2.dist-info}/WHEEL +0 -0
- {pyautoscene-0.2.0.dist-info → pyautoscene-0.2.2.dist-info}/entry_points.txt +0 -0
pyautoscene/scene.py
CHANGED
@@ -1,61 +1,61 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
from typing import Callable, TypedDict
|
4
|
-
|
5
|
-
from statemachine import State
|
6
|
-
|
7
|
-
from pyautoscene.utils import is_valid_variable_name
|
8
|
-
|
9
|
-
from .references import ReferenceElement
|
10
|
-
from .
|
11
|
-
|
12
|
-
|
13
|
-
class ActionInfo(TypedDict):
|
14
|
-
"""Type definition for action information in a scene."""
|
15
|
-
|
16
|
-
action: Callable[..., None]
|
17
|
-
transitions_to: Scene | None
|
18
|
-
|
19
|
-
|
20
|
-
class Scene(State):
|
21
|
-
"""A scene represents a state in the GUI automation state machine."""
|
22
|
-
|
23
|
-
def __init__(
|
24
|
-
self,
|
25
|
-
name: str,
|
26
|
-
elements: list[ReferenceElement] | None = None,
|
27
|
-
initial: bool = False,
|
28
|
-
):
|
29
|
-
assert is_valid_variable_name(name), (
|
30
|
-
f"Invalid scene name: {name}, must be a valid Python identifier."
|
31
|
-
)
|
32
|
-
super().__init__(name, initial=initial)
|
33
|
-
self.elements = elements or []
|
34
|
-
self.actions: dict[str, ActionInfo] = {}
|
35
|
-
|
36
|
-
def action(self, transitions_to: Scene | None = None):
|
37
|
-
"""Decorator to register an action for this scene."""
|
38
|
-
|
39
|
-
def decorator(func: Callable[..., None]) -> Callable[..., None]:
|
40
|
-
if func.__name__ not in self.actions:
|
41
|
-
action_name = func.__name__
|
42
|
-
self.actions[action_name] = {
|
43
|
-
"action": func,
|
44
|
-
"transitions_to": transitions_to,
|
45
|
-
}
|
46
|
-
return func
|
47
|
-
|
48
|
-
return decorator
|
49
|
-
|
50
|
-
def get_action(self, action_name: str) -> ActionInfo | None:
|
51
|
-
"""Get an action by name."""
|
52
|
-
return self.actions.get(action_name)
|
53
|
-
|
54
|
-
def is_on_screen(self, region: Region | None = None) -> bool:
|
55
|
-
"""Check if any reference element is currently on screen."""
|
56
|
-
# TODO: Refactor after text recognition is implemented
|
57
|
-
# elements = (elem for elem in self.elements if isinstance(elem, ReferenceImage))
|
58
|
-
return all(elem.
|
59
|
-
|
60
|
-
def __repr__(self):
|
61
|
-
return f"Scene({self.name!r}, elements={len(self.elements)})"
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import Callable, TypedDict
|
4
|
+
|
5
|
+
from statemachine import State
|
6
|
+
|
7
|
+
from pyautoscene.utils import is_valid_variable_name
|
8
|
+
|
9
|
+
from .references import ReferenceElement
|
10
|
+
from .region import Region
|
11
|
+
|
12
|
+
|
13
|
+
class ActionInfo(TypedDict):
|
14
|
+
"""Type definition for action information in a scene."""
|
15
|
+
|
16
|
+
action: Callable[..., None]
|
17
|
+
transitions_to: Scene | None
|
18
|
+
|
19
|
+
|
20
|
+
class Scene(State):
|
21
|
+
"""A scene represents a state in the GUI automation state machine."""
|
22
|
+
|
23
|
+
def __init__(
|
24
|
+
self,
|
25
|
+
name: str,
|
26
|
+
elements: list[ReferenceElement] | None = None,
|
27
|
+
initial: bool = False,
|
28
|
+
):
|
29
|
+
assert is_valid_variable_name(name), (
|
30
|
+
f"Invalid scene name: {name}, must be a valid Python identifier."
|
31
|
+
)
|
32
|
+
super().__init__(name, initial=initial)
|
33
|
+
self.elements = elements or []
|
34
|
+
self.actions: dict[str, ActionInfo] = {}
|
35
|
+
|
36
|
+
def action(self, transitions_to: Scene | None = None):
|
37
|
+
"""Decorator to register an action for this scene."""
|
38
|
+
|
39
|
+
def decorator(func: Callable[..., None]) -> Callable[..., None]:
|
40
|
+
if func.__name__ not in self.actions:
|
41
|
+
action_name = func.__name__
|
42
|
+
self.actions[action_name] = {
|
43
|
+
"action": func,
|
44
|
+
"transitions_to": transitions_to,
|
45
|
+
}
|
46
|
+
return func
|
47
|
+
|
48
|
+
return decorator
|
49
|
+
|
50
|
+
def get_action(self, action_name: str) -> ActionInfo | None:
|
51
|
+
"""Get an action by name."""
|
52
|
+
return self.actions.get(action_name)
|
53
|
+
|
54
|
+
def is_on_screen(self, region: Region | None = None) -> bool:
|
55
|
+
"""Check if any reference element is currently on screen."""
|
56
|
+
# TODO: Refactor after text recognition is implemented
|
57
|
+
# elements = (elem for elem in self.elements if isinstance(elem, ReferenceImage))
|
58
|
+
return all(elem.locate(region) for elem in self.elements)
|
59
|
+
|
60
|
+
def __repr__(self):
|
61
|
+
return f"Scene({self.name!r}, elements={len(self.elements)})"
|
pyautoscene/session.py
CHANGED
@@ -1,140 +1,163 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
from
|
8
|
-
from statemachine
|
9
|
-
from statemachine.
|
10
|
-
|
11
|
-
from .
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
for scene in scenes
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
self.
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import time
|
4
|
+
from typing import Callable
|
5
|
+
|
6
|
+
import networkx as nx
|
7
|
+
from PIL import Image
|
8
|
+
from statemachine import State, StateMachine
|
9
|
+
from statemachine.factory import StateMachineMetaclass
|
10
|
+
from statemachine.states import States
|
11
|
+
from statemachine.transition_list import TransitionList
|
12
|
+
|
13
|
+
from .references import ImageElement, ReferenceElement
|
14
|
+
from .region import Region
|
15
|
+
from .scene import Scene
|
16
|
+
|
17
|
+
|
18
|
+
class SceneRecognitionError(Exception):
|
19
|
+
pass
|
20
|
+
|
21
|
+
|
22
|
+
def build_dynamic_state_machine(
|
23
|
+
scenes: list[Scene],
|
24
|
+
) -> tuple[StateMachine, dict[str, TransitionList], dict[str, Callable]]:
|
25
|
+
"""Create a dynamic StateMachine class from scenes using StateMachineMetaclass."""
|
26
|
+
|
27
|
+
states = {scene.name: scene for scene in scenes}
|
28
|
+
transitions = {}
|
29
|
+
leaf_actions = {}
|
30
|
+
for scene in scenes:
|
31
|
+
for action_name, action_info in scene.actions.items():
|
32
|
+
target_scene = action_info["transitions_to"]
|
33
|
+
if target_scene is not None:
|
34
|
+
event_name = f"event_{action_name}"
|
35
|
+
new_transition = scene.to(target_scene, event=event_name)
|
36
|
+
new_transition.on(action_info["action"])
|
37
|
+
transitions[event_name] = new_transition
|
38
|
+
else:
|
39
|
+
leaf_actions[action_name] = action_info["action"]
|
40
|
+
|
41
|
+
SessionSM = StateMachineMetaclass(
|
42
|
+
"SessionSM",
|
43
|
+
(StateMachine,),
|
44
|
+
{"states": States(states), **transitions}, # type: ignore[call-arg]
|
45
|
+
)
|
46
|
+
session_sm: StateMachine = SessionSM() # type: ignore[no-redef]
|
47
|
+
|
48
|
+
return session_sm, transitions, leaf_actions
|
49
|
+
|
50
|
+
|
51
|
+
def get_current_scene(scenes: list[Scene], region: Region | None = None) -> Scene:
|
52
|
+
"""Get the current scene from the list of scenes."""
|
53
|
+
current_scenes = [scene for scene in scenes if scene.is_on_screen(region)]
|
54
|
+
if len(current_scenes) == 1:
|
55
|
+
return current_scenes[0]
|
56
|
+
elif len(current_scenes) > 1:
|
57
|
+
raise SceneRecognitionError(
|
58
|
+
f"Multiple scenes are currently on screen.\n{' '.join(str(scene) for scene in current_scenes)}"
|
59
|
+
)
|
60
|
+
else:
|
61
|
+
raise SceneRecognitionError("No scene is currently on screen.")
|
62
|
+
|
63
|
+
|
64
|
+
class Session:
|
65
|
+
"""A session manages the state machine for GUI automation scenes."""
|
66
|
+
|
67
|
+
def __init__(
|
68
|
+
self,
|
69
|
+
scenes: list[Scene],
|
70
|
+
image_locator: Callable[[Image.Image, Image.Image], list[Region]] | None = None,
|
71
|
+
):
|
72
|
+
self._scenes_list = scenes
|
73
|
+
self._scenes_dict = {scene.name: scene for scene in scenes}
|
74
|
+
self.image_locator = image_locator
|
75
|
+
for scene in self._scenes_list:
|
76
|
+
for elem in scene.elements:
|
77
|
+
if isinstance(elem, ImageElement):
|
78
|
+
elem.locator = image_locator
|
79
|
+
|
80
|
+
# Create dynamic StateMachine class and instantiate it
|
81
|
+
self._sm, self.transitions, self.leaf_actions = build_dynamic_state_machine(
|
82
|
+
scenes
|
83
|
+
)
|
84
|
+
self.graph: nx.MultiDiGraph = nx.nx_pydot.from_pydot(self._sm._graph())
|
85
|
+
|
86
|
+
@property
|
87
|
+
def current_scene(self) -> State:
|
88
|
+
"""Get the current state."""
|
89
|
+
return self._sm.current_state
|
90
|
+
|
91
|
+
def expect(self, target_scene: Scene, **kwargs):
|
92
|
+
"""Navigate to a specific scene."""
|
93
|
+
if target_scene.is_on_screen():
|
94
|
+
return
|
95
|
+
|
96
|
+
present_scene = get_current_scene(self._scenes_list)
|
97
|
+
all_paths = list(
|
98
|
+
nx.all_simple_paths(
|
99
|
+
self.graph, source=present_scene.name, target=target_scene.name
|
100
|
+
)
|
101
|
+
)
|
102
|
+
if len(all_paths) == 0:
|
103
|
+
raise SceneRecognitionError(
|
104
|
+
f"No path found from {present_scene.name} to {target_scene.name}"
|
105
|
+
)
|
106
|
+
elif len(all_paths) > 1:
|
107
|
+
raise SceneRecognitionError(
|
108
|
+
f"Multiple paths found from {present_scene.name} to {target_scene.name}"
|
109
|
+
)
|
110
|
+
|
111
|
+
path = all_paths[0]
|
112
|
+
events: list[str] = [
|
113
|
+
self.graph.get_edge_data(path[i], path[i + 1])[0]["label"] # type: ignore
|
114
|
+
for i in range(len(path) - 1)
|
115
|
+
]
|
116
|
+
|
117
|
+
for event in events:
|
118
|
+
self._sm.send(event, **kwargs)
|
119
|
+
|
120
|
+
def invoke(self, action_name: str, **kwargs):
|
121
|
+
"""Invoke an action in the current scene."""
|
122
|
+
event_name = f"event_{action_name}"
|
123
|
+
transition = next(
|
124
|
+
(tr for tr_name, tr in self.transitions.items() if tr_name == event_name),
|
125
|
+
None,
|
126
|
+
)
|
127
|
+
if transition:
|
128
|
+
return self._sm.send(event_name, **kwargs)
|
129
|
+
|
130
|
+
leaf_action = next(
|
131
|
+
(
|
132
|
+
action
|
133
|
+
for name, action in self.leaf_actions.items()
|
134
|
+
if name == action_name
|
135
|
+
),
|
136
|
+
None,
|
137
|
+
)
|
138
|
+
if leaf_action:
|
139
|
+
return leaf_action(**kwargs)
|
140
|
+
|
141
|
+
raise ValueError(
|
142
|
+
f"Action '{action_name}' not found in current scene '{self.current_scene.name}'"
|
143
|
+
)
|
144
|
+
|
145
|
+
def wait_until(self, target: Scene | ReferenceElement, interval: float = 1):
|
146
|
+
"""Wait until the target scene or reference element is on screen."""
|
147
|
+
found = False
|
148
|
+
while not found:
|
149
|
+
if isinstance(target, Scene):
|
150
|
+
found = target.is_on_screen()
|
151
|
+
elif isinstance(target, ReferenceElement):
|
152
|
+
found = target.locate() is not None
|
153
|
+
else:
|
154
|
+
raise TypeError("Target must be a Scene or ReferenceElement.")
|
155
|
+
if not found:
|
156
|
+
time.sleep(interval)
|
157
|
+
|
158
|
+
def __repr__(self):
|
159
|
+
current = self.current_scene
|
160
|
+
current_name = current.name if current else "None"
|
161
|
+
return (
|
162
|
+
f"Session(scenes={list(self._scenes_dict.keys())}, current={current_name})"
|
163
|
+
)
|
pyautoscene/utils.py
CHANGED
@@ -1,25 +1,148 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
import
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
import
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import logging
|
4
|
+
import os
|
5
|
+
import time
|
6
|
+
from keyword import iskeyword
|
7
|
+
from typing import Callable, overload
|
8
|
+
|
9
|
+
import numpy as np
|
10
|
+
import pyautogui as gui
|
11
|
+
from PIL import Image
|
12
|
+
|
13
|
+
from ._types import MouseButton, TowardsDirection
|
14
|
+
from .constants import LOCATE_AND_CLICK_DELAY, POINTER_SPEED
|
15
|
+
from .region import Region, RegionSpec
|
16
|
+
|
17
|
+
logging.basicConfig(level=logging.INFO)
|
18
|
+
logger = logging.getLogger(__name__)
|
19
|
+
|
20
|
+
|
21
|
+
def locate_and_click(
|
22
|
+
reference: Image.Image | str,
|
23
|
+
clicks: int = 1,
|
24
|
+
button: MouseButton = "left",
|
25
|
+
region: RegionSpec | None = None,
|
26
|
+
confidence: float = 0.999,
|
27
|
+
grayscale: bool = True,
|
28
|
+
limit: int = 1,
|
29
|
+
offset: tuple[int, int] = (0, 0),
|
30
|
+
towards: TowardsDirection | None = None,
|
31
|
+
):
|
32
|
+
time.sleep(LOCATE_AND_CLICK_DELAY)
|
33
|
+
found_region = locate_on_screen(
|
34
|
+
reference,
|
35
|
+
region=region,
|
36
|
+
confidence=confidence,
|
37
|
+
grayscale=grayscale,
|
38
|
+
limit=limit,
|
39
|
+
)
|
40
|
+
assert found_region is not None, f"Could not locate {reference} on screen."
|
41
|
+
move_and_click(
|
42
|
+
found_region, clicks=clicks, button=button, offset=offset, towards=towards
|
43
|
+
)
|
44
|
+
time.sleep(LOCATE_AND_CLICK_DELAY)
|
45
|
+
|
46
|
+
|
47
|
+
def move_and_click(
|
48
|
+
target_region: RegionSpec,
|
49
|
+
clicks: int = 1,
|
50
|
+
button: MouseButton = "left",
|
51
|
+
offset: tuple[int, int] = (0, 0),
|
52
|
+
towards: TowardsDirection | None = None,
|
53
|
+
):
|
54
|
+
"""Move to the center or edge of the region and click.
|
55
|
+
|
56
|
+
The offset is always added to the calculated target point.
|
57
|
+
For example, for 'bottom', offset=(0, 5) means 5 pixels below the bottom edge.
|
58
|
+
"""
|
59
|
+
_target_region = Region.from_spec(target_region)
|
60
|
+
base_points = {
|
61
|
+
"top": (_target_region.center[0], _target_region.top),
|
62
|
+
"left": (_target_region.left, _target_region.center[1]),
|
63
|
+
"bottom": (
|
64
|
+
_target_region.center[0],
|
65
|
+
_target_region.top + _target_region.height - 1,
|
66
|
+
),
|
67
|
+
"right": (
|
68
|
+
_target_region.left + _target_region.width - 1,
|
69
|
+
_target_region.center[1],
|
70
|
+
),
|
71
|
+
None: _target_region.center,
|
72
|
+
}
|
73
|
+
if towards not in base_points:
|
74
|
+
raise ValueError(f"Invalid direction: {towards}")
|
75
|
+
base = base_points[towards]
|
76
|
+
target = (base[0] + offset[0], base[1] + offset[1])
|
77
|
+
|
78
|
+
current = gui.position()
|
79
|
+
duration = np.linalg.norm(np.array(target) - np.array(current)) / POINTER_SPEED
|
80
|
+
gui.moveTo(*target, float(duration), gui.easeInOutQuad) # type: ignore
|
81
|
+
gui.click(clicks=clicks, button=button)
|
82
|
+
|
83
|
+
|
84
|
+
def is_valid_variable_name(name):
|
85
|
+
return name.isidentifier() and not iskeyword(name)
|
86
|
+
|
87
|
+
|
88
|
+
@overload
|
89
|
+
def locate_on_screen(
|
90
|
+
reference: Image.Image | str,
|
91
|
+
region: RegionSpec | None = None,
|
92
|
+
confidence: float = 0.999,
|
93
|
+
grayscale: bool = True,
|
94
|
+
limit: int = 1,
|
95
|
+
locator: Callable[[Image.Image, Image.Image], list[Region]] | None = None,
|
96
|
+
) -> Region | None: ...
|
97
|
+
@overload
|
98
|
+
def locate_on_screen(
|
99
|
+
reference: Image.Image | str,
|
100
|
+
region: RegionSpec | None = None,
|
101
|
+
confidence: float = 0.999,
|
102
|
+
grayscale: bool = True,
|
103
|
+
limit: int = 1,
|
104
|
+
locator: Callable[[Image.Image, Image.Image], list[Region]] | None = None,
|
105
|
+
) -> list[Region] | None: ...
|
106
|
+
def locate_on_screen(
|
107
|
+
reference: Image.Image | str,
|
108
|
+
region: RegionSpec | None = None,
|
109
|
+
confidence: float = 0.999,
|
110
|
+
grayscale: bool = True,
|
111
|
+
limit: int = 1,
|
112
|
+
locator: Callable[[Image.Image, Image.Image], list[Region]] | None = None,
|
113
|
+
):
|
114
|
+
"""Locate a region on the screen."""
|
115
|
+
if isinstance(reference, str):
|
116
|
+
if not os.path.exists(reference):
|
117
|
+
raise FileNotFoundError(f"Image file {reference} does not exist.")
|
118
|
+
reference = Image.open(reference)
|
119
|
+
if locator is None:
|
120
|
+
try:
|
121
|
+
location = gui.locateOnScreen(
|
122
|
+
reference,
|
123
|
+
region=Region.from_spec(region).to_box() if region else None,
|
124
|
+
grayscale=grayscale,
|
125
|
+
confidence=confidence,
|
126
|
+
limit=limit,
|
127
|
+
)
|
128
|
+
if location:
|
129
|
+
return Region.from_box(location)
|
130
|
+
except gui.ImageNotFoundException:
|
131
|
+
return None
|
132
|
+
except FileNotFoundError:
|
133
|
+
return None
|
134
|
+
else:
|
135
|
+
screenshot = gui.screenshot(
|
136
|
+
region=Region.from_spec(region).to_box() if region else None
|
137
|
+
)
|
138
|
+
logger.info(
|
139
|
+
f"Searching in region: {Region.from_spec(region).to_box() if region else None}.\nGiven region: {region}"
|
140
|
+
)
|
141
|
+
detections = locator(reference, screenshot)
|
142
|
+
logger.info("total detections: %d", len(detections))
|
143
|
+
if len(detections) == 0:
|
144
|
+
return None
|
145
|
+
elif limit > 1:
|
146
|
+
return detections[:limit]
|
147
|
+
else:
|
148
|
+
return detections[0]
|
@@ -1,14 +1,18 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: pyautoscene
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.2
|
4
4
|
Summary: Advance GUI automation
|
5
5
|
Author-email: pritam-dey3 <pritam.pritamdey.984@gmail.com>
|
6
6
|
License-File: LICENSE
|
7
7
|
Requires-Python: >=3.13
|
8
8
|
Requires-Dist: networkx>=3.5
|
9
|
+
Requires-Dist: opencv-python-headless>=4.12.0.88
|
9
10
|
Requires-Dist: pillow>=11.3.0
|
10
11
|
Requires-Dist: pyautogui>=0.9.54
|
11
12
|
Requires-Dist: python-statemachine[diagrams]
|
13
|
+
Provides-Extra: ocr
|
14
|
+
Requires-Dist: onnxruntime>=1.22.0; extra == 'ocr'
|
15
|
+
Requires-Dist: rapidocr>=3.2.0; extra == 'ocr'
|
12
16
|
Description-Content-Type: text/markdown
|
13
17
|
|
14
18
|
# PyAutoScene
|
@@ -0,0 +1,15 @@
|
|
1
|
+
pyautoscene/__init__.py,sha256=9w2x28VgAHsTZHeLIynHs0qcFfmdUM-FjURLwUahNO0,230
|
2
|
+
pyautoscene/_types.py,sha256=t6hRAUe9OLUVCpzEVRjYHgb5l5iu4gMGinB_ecNOkR4,180
|
3
|
+
pyautoscene/constants.py,sha256=ZkBdvW9gBw002hLqelgiyS-ZsTbu7juyEWEg4Fx16NE,219
|
4
|
+
pyautoscene/ocr.py,sha256=YbPNYXGIvM_5RSB4_Va6D1t0eWIo47R-0utrBCZLqAk,2322
|
5
|
+
pyautoscene/ocr_config.yaml,sha256=EGaKX1a-LyWk0gtI2wUo04LraFqGJ3aZnQ5NCPbgDYI,2231
|
6
|
+
pyautoscene/references.py,sha256=8-VgwAHUt9Te0np2O5TvnIzW5LhNY1Dzm222VdlxgK8,3338
|
7
|
+
pyautoscene/region.py,sha256=X2QQy6gIqbbFPWVTNiqbYaQ12nXv5MU-ptP20kA4u48,2123
|
8
|
+
pyautoscene/scene.py,sha256=VWtx5l9DMFu3TC-EAxhabREQDFCvUPs7-4Jxq35JDrI,1993
|
9
|
+
pyautoscene/session.py,sha256=lxv71Q4UW5xr3-UTYVSOV8gqbpDI5HXMopO5kUSZhM0,5678
|
10
|
+
pyautoscene/utils.py,sha256=ZaxlGlt3xjayCKUfWGr_C73QwqEsacOsnp0SYC6ao7w,4734
|
11
|
+
pyautoscene-0.2.2.dist-info/METADATA,sha256=N4woAGtRHFc2_DNHG3b_g7jXA3HYT6DB1cu369vVtNs,6068
|
12
|
+
pyautoscene-0.2.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
13
|
+
pyautoscene-0.2.2.dist-info/entry_points.txt,sha256=6aKjylfDivCRMrJasIIi7ICU4fZR-8HjcOHhRmHpYpQ,49
|
14
|
+
pyautoscene-0.2.2.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
15
|
+
pyautoscene-0.2.2.dist-info/RECORD,,
|