GameSentenceMiner 2.6.5__py3-none-any.whl → 2.7.0__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.
@@ -0,0 +1,275 @@
1
+ import ctypes
2
+ import sys
3
+ import tkinter as tk
4
+ from tkinter import messagebox, simpledialog
5
+ import json
6
+ from pathlib import Path
7
+ import time # Optional: for debugging timing if needed
8
+ from PIL import Image, ImageTk
9
+ import io
10
+ import keyboard
11
+
12
+ from GameSentenceMiner import obs, util
13
+
14
+ class DynamicAreaSelector(tk.Tk):
15
+ def __init__(self, window_title):
16
+ super().__init__()
17
+ self.title("Dynamic Area Selector")
18
+ self.attributes('-fullscreen', True)
19
+ self.attributes("-topmost", True) # Make the window always on top
20
+ self.attributes("-alpha", 0.20)
21
+
22
+ self.window_title = window_title
23
+ self.rects = []
24
+ self.start_x = None
25
+ self.start_y = None
26
+ self.current_rect_id = None
27
+ self.drawn_rect_ids = []
28
+ self.saved_rect_coords = []
29
+ self.excluded_rect_coords = [] # New list for excluded rectangles
30
+ self.drawn_excluded_rect_ids = [] # New list for excluded rect ids
31
+ self.image_mode = False
32
+ self.image_item = None
33
+ self.image_tk = None
34
+ self.canvas = None
35
+
36
+ if not self.initialize_ui():
37
+ self.after(0, self.destroy)
38
+ return
39
+
40
+ self.canvas.bind("<ButtonPress-1>", self.on_press)
41
+ self.canvas.bind("<B1-Motion>", self.on_drag)
42
+ self.canvas.bind("<ButtonRelease-1>", self.on_release)
43
+ self.canvas.bind("<Button-3>", self.on_right_click) # Bind right-click
44
+ self.bind("<Control-s>", self.save_rects_event)
45
+ self.bind("<Control-z>", self.undo_last_rect)
46
+ self.bind("<Escape>", self.quit_app)
47
+ self.bind("<Control-i>", self.toggle_image_mode)
48
+
49
+ self.protocol("WM_DELETE_WINDOW", self.on_close)
50
+
51
+ def initialize_ui(self):
52
+ try:
53
+ self.canvas = tk.Canvas(self, highlightthickness=0)
54
+ self.canvas.pack(fill=tk.BOTH, expand=True)
55
+
56
+ self.load_and_draw_existing_rects()
57
+ return True
58
+
59
+ except Exception as e:
60
+ messagebox.showerror("Initialization Error", f"Failed: {e}")
61
+ print(f"Initialization error details: {e}")
62
+ return False
63
+
64
+ def load_and_draw_existing_rects(self):
65
+ if self.get_scene_ocr_config().exists():
66
+ try:
67
+ with open(self.get_scene_ocr_config(), 'r') as f:
68
+ config_data = json.load(f)
69
+ loaded_rects = []
70
+ loaded_excluded_rects = []
71
+ for r in config_data.get("rectangles", []):
72
+ try:
73
+ coords = tuple(map(float, r))
74
+ if len(coords) == 4:
75
+ loaded_rects.append(coords)
76
+ else:
77
+ print(f"Skipping invalid rectangle data: {r}")
78
+ except (ValueError, TypeError) as coord_err:
79
+ print(f"Skipping invalid coordinate data in rectangle {r}: {coord_err}")
80
+
81
+ for r in config_data.get("excluded_rectangles", []):
82
+ try:
83
+ coords = tuple(map(float, r))
84
+ if len(coords) == 4:
85
+ loaded_excluded_rects.append(coords)
86
+ else:
87
+ print(f"Skipping invalid excluded rectangle data: {r}")
88
+ except (ValueError, TypeError) as coord_err:
89
+ print(f"Skipping invalid coordinate data in excluded rectangle {r}: {coord_err}")
90
+
91
+ self.saved_rect_coords = loaded_rects
92
+ self.excluded_rect_coords = loaded_excluded_rects
93
+ for rect_id in self.drawn_rect_ids:
94
+ self.canvas.delete(rect_id)
95
+ for rect_id in self.drawn_excluded_rect_ids:
96
+ self.canvas.delete(rect_id)
97
+ self.drawn_rect_ids = []
98
+ self.drawn_excluded_rect_ids = []
99
+
100
+ for rect in self.saved_rect_coords:
101
+ x1, y1, x2, y2 = rect
102
+ rect_id = self.canvas.create_rectangle(x1, y1, x2, y2, outline='blue', width=2)
103
+ self.drawn_rect_ids.append(rect_id)
104
+
105
+ for rect in self.excluded_rect_coords:
106
+ x1, y1, x2, y2 = rect
107
+ rect_id = self.canvas.create_rectangle(x1, y1, x2, y2, outline='orange', width=2)
108
+ self.drawn_excluded_rect_ids.append(rect_id)
109
+
110
+ except json.JSONDecodeError:
111
+ messagebox.showwarning("Config Load Warning", f"Could not parse {self.get_scene_ocr_config()}. Starting with no rectangles.")
112
+ self.saved_rect_coords = []
113
+ self.excluded_rect_coords = []
114
+ except FileNotFoundError:
115
+ self.saved_rect_coords = []
116
+ self.excluded_rect_coords = []
117
+ except Exception as e:
118
+ messagebox.showerror("Config Load Error", f"Error loading rectangles: {e}")
119
+ self.saved_rect_coords = []
120
+ self.excluded_rect_coords = []
121
+
122
+ def on_press(self, event):
123
+ if self.current_rect_id is None:
124
+ self.start_x = self.canvas.canvasx(event.x)
125
+ self.start_y = self.canvas.canvasy(event.y)
126
+ outline_color = 'red' if not event.state & 0x0001 else 'purple' # check if shift is pressed
127
+ self.current_rect_id = self.canvas.create_rectangle(
128
+ self.start_x, self.start_y, self.start_x, self.start_y,
129
+ outline=outline_color, width=2
130
+ )
131
+
132
+ def on_drag(self, event):
133
+ if self.current_rect_id is not None:
134
+ cur_x = self.canvas.canvasx(event.x)
135
+ cur_y = self.canvas.canvasy(event.y)
136
+ self.canvas.coords(self.current_rect_id, self.start_x, self.start_y, cur_x, cur_y)
137
+
138
+ def on_release(self, event):
139
+ if self.current_rect_id is not None:
140
+ end_x = self.canvas.canvasx(event.x)
141
+ end_y = self.canvas.canvasy(event.y)
142
+ x1 = min(self.start_x, end_x)
143
+ y1 = min(self.start_y, end_y)
144
+ x2 = max(self.start_x, end_x)
145
+ y2 = max(self.start_y, end_y)
146
+
147
+ self.canvas.coords(self.current_rect_id, x1, y1, x2, y2)
148
+ if event.state & 0x0001: # shift is pressed
149
+ self.canvas.itemconfig(self.current_rect_id, outline='orange')
150
+ self.excluded_rect_coords.append((x1, y1, x2, y2))
151
+ self.drawn_excluded_rect_ids.append(self.current_rect_id)
152
+ else:
153
+ self.canvas.itemconfig(self.current_rect_id, outline='green')
154
+ self.saved_rect_coords.append((x1, y1, x2, y2))
155
+ self.drawn_rect_ids.append(self.current_rect_id)
156
+
157
+ self.current_rect_id = None
158
+ self.start_x = None
159
+ self.start_y = None
160
+
161
+ def save_rects(self):
162
+ try:
163
+ serializable_rects = [
164
+ tuple(map(int, rect)) for rect in self.saved_rect_coords
165
+ ]
166
+ serializable_excluded_rects = [
167
+ tuple(map(int, rect)) for rect in self.excluded_rect_coords
168
+ ]
169
+ with open(self.get_scene_ocr_config(), 'w') as f:
170
+ json.dump({"window": self.window_title if self.window_title else "", "scene": obs.get_current_scene(), "rectangles": serializable_rects, "excluded_rectangles": serializable_excluded_rects}, f, indent=4)
171
+ for rect_id in self.drawn_rect_ids:
172
+ if self.canvas.winfo_exists():
173
+ try:
174
+ self.canvas.itemconfig(rect_id, outline='blue')
175
+ except tk.TclError:
176
+ pass
177
+ for rect_id in self.drawn_excluded_rect_ids:
178
+ if self.canvas.winfo_exists():
179
+ try:
180
+ self.canvas.itemconfig(rect_id, outline='orange')
181
+ except tk.TclError:
182
+ pass
183
+ print("Rectangles saved.")
184
+ self.on_close()
185
+ except Exception as e:
186
+ messagebox.showerror("Error", f"Failed to save rectangles: {e}")
187
+
188
+ def save_rects_event(self, event=None):
189
+ self.save_rects()
190
+
191
+ def undo_last_rect(self, event=None):
192
+ if self.saved_rect_coords and self.drawn_rect_ids:
193
+ self.saved_rect_coords.pop()
194
+ last_rect_id = self.drawn_rect_ids.pop()
195
+ if self.canvas.winfo_exists():
196
+ self.canvas.delete(last_rect_id)
197
+ elif self.excluded_rect_coords and self.drawn_excluded_rect_ids:
198
+ self.excluded_rect_coords.pop()
199
+ last_rect_id = self.drawn_excluded_rect_ids.pop()
200
+ if self.canvas.winfo_exists():
201
+ self.canvas.delete(last_rect_id)
202
+ elif self.current_rect_id is not None:
203
+ if self.canvas.winfo_exists():
204
+ self.canvas.delete(self.current_rect_id)
205
+ self.current_rect_id = None
206
+ self.start_x = None
207
+ self.start_y = None
208
+
209
+ def quit_app(self, event=None):
210
+ print("Escape pressed, closing application.")
211
+ self.on_close()
212
+
213
+ def on_close(self):
214
+ self.destroy()
215
+
216
+ def get_scene_ocr_config(self):
217
+ app_dir = Path.home() / "AppData" / "Roaming" / "GameSentenceMiner"
218
+ ocr_config_dir = app_dir / "ocr_config"
219
+ ocr_config_dir.mkdir(parents=True, exist_ok=True)
220
+ scene = util.sanitize_filename(obs.get_current_scene())
221
+ config_path = ocr_config_dir / f"{scene}.json"
222
+ return config_path
223
+
224
+ def on_right_click(self, event):
225
+ """Deletes the rectangle clicked with the right mouse button."""
226
+ item = self.canvas.find_closest(event.x, event.y)
227
+
228
+ if item:
229
+ if item[0] in self.drawn_rect_ids: # Check if it's a saved rectangle
230
+ index = self.drawn_rect_ids.index(item[0])
231
+ self.canvas.delete(item[0])
232
+ del self.drawn_rect_ids[index]
233
+ del self.saved_rect_coords[index]
234
+ elif item[0] in self.drawn_excluded_rect_ids:
235
+ index = self.drawn_excluded_rect_ids.index(item[0])
236
+ self.canvas.delete(item[0])
237
+ del self.drawn_excluded_rect_ids[index]
238
+ del self.excluded_rect_coords[index]
239
+
240
+ def setup_hotkey(self):
241
+ keyboard.add_hotkey('F13', self.lift_window) # Example hotkey
242
+
243
+ def lift_window(self):
244
+ self.lift() # Bring the window to the front
245
+
246
+ def toggle_image_mode(self, event=None):
247
+ self.image_mode = not self.image_mode
248
+ if self.image_mode:
249
+ self.attributes("-alpha", 1.0)
250
+ self.load_image_from_obs()
251
+ else:
252
+ self.attributes("-alpha", 0.20)
253
+ if self.image_item:
254
+ self.canvas.delete(self.image_item)
255
+ self.image_item = None
256
+ self.image_tk = None
257
+
258
+ def load_image_from_obs(self):
259
+ try:
260
+ image_path = obs.get_screenshot()
261
+ image = Image.open(image_path)
262
+ self.image_tk = ImageTk.PhotoImage(image)
263
+ self.image_item = self.canvas.create_image(0, 0, anchor=tk.NW, image=self.image_tk)
264
+ self.canvas.tag_lower(self.image_item)
265
+ except Exception as e:
266
+ messagebox.showerror("Image Load Error", f"Failed to load image from OBS: {e}")
267
+
268
+ def run_screen_picker(window_title):
269
+ app = DynamicAreaSelector(window_title)
270
+ app.mainloop()
271
+
272
+ if __name__ == "__main__":
273
+ args = sys.argv[1:]
274
+ obs.connect_to_obs()
275
+ run_screen_picker(args[0] if len(args) > 0 else None)
@@ -0,0 +1,306 @@
1
+ # area_selector.py
2
+ import asyncio
3
+ import base64
4
+ import difflib
5
+ import logging
6
+ import os
7
+ import queue
8
+ import threading
9
+ import time
10
+ from datetime import datetime
11
+ from logging.handlers import RotatingFileHandler
12
+ from tkinter import messagebox
13
+
14
+ import mss
15
+ import websockets
16
+ from PIL import Image, ImageDraw
17
+ import json
18
+ from pathlib import Path
19
+
20
+ from GameSentenceMiner import obs, util
21
+ from GameSentenceMiner.configuration import get_config, get_app_directory
22
+ from GameSentenceMiner.gametext import get_line_history
23
+ from GameSentenceMiner.owocr.owocr import screen_coordinate_picker, run
24
+ from GameSentenceMiner.owocr.owocr.run import TextFiltering
25
+
26
+ CONFIG_FILE = Path("ocr_config.json")
27
+ DEFAULT_IMAGE_PATH = r"C:\Users\Beangate\Pictures\msedge_acbl8GL7Ax.jpg" # CHANGE THIS
28
+
29
+ logger = logging.getLogger("GSM_OCR")
30
+ logger.setLevel(logging.DEBUG)
31
+
32
+ # Create a file handler for logging
33
+ log_file = os.path.join(get_app_directory(), "logs", "ocr_log.txt")
34
+ os.makedirs(os.path.join(get_app_directory(), "logs"), exist_ok=True)
35
+
36
+ file_handler = RotatingFileHandler(log_file, maxBytes=1024 * 1024, backupCount=5, encoding='utf-8')
37
+ file_handler.setLevel(logging.DEBUG)
38
+
39
+ # Create a formatter and set it for the handler
40
+ formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
41
+ file_handler.setFormatter(formatter)
42
+
43
+ # Add the handler to the logger
44
+ logger.addHandler(file_handler)
45
+
46
+ def get_new_game_cords():
47
+ """Allows multiple coordinate selections."""
48
+ coords_list = []
49
+ while True:
50
+ cords = screen_coordinate_picker.get_screen_selection()
51
+ coords_list.append({"coordinates": cords})
52
+ if messagebox.askyesno("Add Another Region", "Do you want to add another region?"):
53
+ continue
54
+ else:
55
+ break
56
+
57
+ app_dir = Path.home() / "AppData" / "Roaming" / "GameSentenceMiner"
58
+ ocr_config_dir = app_dir / "ocr_config"
59
+ ocr_config_dir.mkdir(parents=True, exist_ok=True)
60
+ obs.connect_to_obs()
61
+ scene = util.sanitize_filename(obs.get_current_scene())
62
+ config_path = ocr_config_dir / f"{scene}.json"
63
+ with open(config_path, 'w') as f:
64
+ json.dump(coords_list, f, indent=4)
65
+ print(f"Saved OCR config to {config_path}")
66
+ return coords_list
67
+
68
+
69
+ def get_ocr_config():
70
+ """Loads multiple screen capture areas from the corresponding JSON file."""
71
+ app_dir = Path.home() / "AppData" / "Roaming" / "GameSentenceMiner"
72
+ ocr_config_dir = app_dir / "ocr_config"
73
+ obs.connect_to_obs()
74
+ scene = util.sanitize_filename(obs.get_current_scene())
75
+ config_path = ocr_config_dir / f"{scene}.json"
76
+ if not config_path.exists():
77
+ raise Exception(f"No config file found at {config_path}.")
78
+
79
+ if not config_path.exists():
80
+ print("Config Screen picker failed to make file. Please run again.")
81
+ return
82
+
83
+ with open(config_path, 'r') as f:
84
+ coords_list = json.load(f)
85
+ return coords_list
86
+
87
+
88
+ websocket_server_thread = None
89
+ websocket_queue = queue.Queue()
90
+ paused = False
91
+
92
+
93
+ class WebsocketServerThread(threading.Thread):
94
+ def __init__(self, read):
95
+ super().__init__(daemon=True)
96
+ self._loop = None
97
+ self.read = read
98
+ self.clients = set()
99
+ self._event = threading.Event()
100
+
101
+ @property
102
+ def loop(self):
103
+ self._event.wait()
104
+ return self._loop
105
+
106
+ async def send_text_coroutine(self, message):
107
+ for client in self.clients:
108
+ await client.send(message)
109
+
110
+ async def server_handler(self, websocket):
111
+ self.clients.add(websocket)
112
+ try:
113
+ async for message in websocket:
114
+ if self.read and not paused:
115
+ websocket_queue.put(message)
116
+ try:
117
+ await websocket.send('True')
118
+ except websockets.exceptions.ConnectionClosedOK:
119
+ pass
120
+ else:
121
+ try:
122
+ await websocket.send('False')
123
+ except websockets.exceptions.ConnectionClosedOK:
124
+ pass
125
+ except websockets.exceptions.ConnectionClosedError:
126
+ pass
127
+ finally:
128
+ self.clients.remove(websocket)
129
+
130
+ def send_text(self, text, line_time: datetime):
131
+ if text:
132
+ return asyncio.run_coroutine_threadsafe(self.send_text_coroutine(json.dumps({"sentence": text, "time": line_time.isoformat()})), self.loop)
133
+
134
+ def stop_server(self):
135
+ self.loop.call_soon_threadsafe(self._stop_event.set)
136
+
137
+ def run(self):
138
+ async def main():
139
+ self._loop = asyncio.get_running_loop()
140
+ self._stop_event = stop_event = asyncio.Event()
141
+ self._event.set()
142
+ self.server = start_server = websockets.serve(self.server_handler, get_config().general.websocket_uri.split(":")[0], get_config().general.websocket_uri.split(":")[1], max_size=1000000000)
143
+ async with start_server:
144
+ await stop_event.wait()
145
+ asyncio.run(main())
146
+
147
+ all_cords = None
148
+ rectangles = None
149
+
150
+ def text_callback(text, rectangle):
151
+ global twopassocr, ocr2, last_oneocr_results
152
+ if not text:
153
+ return
154
+ if not twopassocr or not ocr2:
155
+ websocket_server_thread.send_text(text, datetime.now())
156
+ return
157
+ with mss.mss() as sct:
158
+ line_time = datetime.now()
159
+ logger.info(f"Received message: {text}, ATTEMPTING LENS OCR")
160
+ if rectangles:
161
+ cords = rectangles[rectangle]
162
+ i = rectangle
163
+ else:
164
+ i = 0
165
+ mon = sct.monitors
166
+ cords = [mon[1]['left'], mon[1]['top'], mon[1]['width'], mon[1]['height']]
167
+ similarity = difflib.SequenceMatcher(None, last_oneocr_results[i], text).ratio()
168
+ if similarity > .8:
169
+ return
170
+ logger.debug(f"Similarity for region {i}: {similarity}")
171
+ last_oneocr_results[i] = text
172
+ last_result = ([], -1)
173
+ try:
174
+ sct_params = {'left': cords[0], 'top': cords[1], 'width': cords[2], 'height': cords[3]}
175
+ sct_img = sct.grab(sct_params)
176
+ img = Image.frombytes('RGB', sct_img.size, sct_img.bgra, 'raw', 'BGRX')
177
+ img = img.convert("RGBA")
178
+ draw = ImageDraw.Draw(img)
179
+ for exclusion in ocr_config.get("excluded_rectangles", []):
180
+ left, top, right, bottom = exclusion
181
+ draw.rectangle((left, top, right, bottom), fill=(0, 0, 0, 0))
182
+ # draw.rectangle((left, top, right, bottom), fill=(0,0,0))
183
+ orig_text, text = run.process_and_write_results(img, None, None, last_result, TextFiltering(),
184
+ engine=ocr2)
185
+ if ":gsm_prefix:" in text:
186
+ text = text.split(":gsm_prefix:")[1]
187
+ websocket_server_thread.send_text(text, line_time)
188
+ except json.JSONDecodeError:
189
+ print("Invalid JSON received.")
190
+ except Exception as e:
191
+ logger.exception(e)
192
+ print(f"Error processing message: {e}")
193
+
194
+ done = False
195
+
196
+ def run_oneocr(ocr_config, i):
197
+ global done
198
+ run.run(read_from="screencapture", write_to="callback",
199
+ screen_capture_area=",".join(str(c) for c in ocr_config['rectangles'][i]) if ocr_config['rectangles'] else 'screen_1',
200
+ screen_capture_window=ocr_config.get("window", None),
201
+ screen_capture_only_active_windows=True if ocr_config.get("window", None) else False,
202
+ screen_capture_delay_secs=.25, engine=ocr1,
203
+ text_callback=text_callback,
204
+ screen_capture_exclusions=ocr_config.get('excluded_rectangles', None),
205
+ rectangle=i)
206
+ done = True
207
+
208
+
209
+ # async def websocket_client():
210
+ # uri = "ws://localhost:7331" # Replace with your hosted websocket address
211
+ # print("Connecting to WebSocket...")
212
+ # async with websockets.connect(uri) as websocket:
213
+ # print("Connected to WebSocket.")
214
+ #
215
+ # try:
216
+ # while True:
217
+ # message = await websocket.recv()
218
+ # if not message:
219
+ # continue
220
+ # line_time = datetime.now()
221
+ # get_line_history().add_secondary_line(message)
222
+ # print(f"Received message: {message}, ATTEMPTING LENS OCR")
223
+ # if ":gsm_prefix:" in message:
224
+ # i = int(message.split(":gsm_prefix:")[0])
225
+ # cords = all_cords[i] if i else all_cords[0]
226
+ # similarity = difflib.SequenceMatcher(None, last_oneocr_results[i], message).ratio()
227
+ # if similarity > .8:
228
+ # continue
229
+ # print(f"Similarity for region {i}: {similarity}")
230
+ # last_oneocr_results[i] = message
231
+ # last_result = ([], -1)
232
+ # try:
233
+ # sct_params = {'top': cords[1], 'left': cords[0], 'width': cords[2], 'height': cords[3]}
234
+ # with mss.mss() as sct:
235
+ # sct_img = sct.grab(sct_params)
236
+ # img = Image.frombytes('RGB', sct_img.size, sct_img.bgra, 'raw', 'BGRX')
237
+ # draw = ImageDraw.Draw(img)
238
+ # for exclusion in ocr_config.get("excluded_rectangles", []):
239
+ # exclusion = tuple(exclusion)
240
+ # draw.rectangle(exclusion, fill="black")
241
+ # orig_text, text = run.process_and_write_results(img, "results.txt", None, last_result, TextFiltering(), engine="glens")
242
+ # if ":gsm_prefix:" in text:
243
+ # text = text.split(":gsm_prefix:")[1]
244
+ # websocket_server_thread.send_text(text, line_time)
245
+ # except json.JSONDecodeError:
246
+ # print("Invalid JSON received.")
247
+ # except Exception as e:
248
+ # logger.exception(e)
249
+ # print(f"Error processing message: {e}")
250
+ # except websockets.exceptions.ConnectionClosed:
251
+ # print("WebSocket connection closed.")
252
+ # except Exception as e:
253
+ # print(f"WebSocket error: {e}")
254
+
255
+
256
+ if __name__ == "__main__":
257
+ global ocr1, ocr2, twopassocr
258
+ import sys
259
+
260
+ args = sys.argv[1:]
261
+
262
+ if len(args) == 3:
263
+ ocr1 = args[0]
264
+ ocr2 = args[1]
265
+ twopassocr = bool(int(args[2]))
266
+ elif len(args) == 2:
267
+ ocr1 = args[0]
268
+ ocr2 = args[1]
269
+ twopassocr = True
270
+ elif len(args) == 1:
271
+ ocr1 = args[0]
272
+ ocr2 = None
273
+ twopassocr = False
274
+ else:
275
+ ocr1 = "oneocr"
276
+
277
+ logger.info(f"Received arguments: ocr1={ocr1}, ocr2={ocr2}, twopassocr={twopassocr}")
278
+ global ocr_config
279
+ ocr_config = get_ocr_config()
280
+ rectangles = ocr_config['rectangles']
281
+ last_oneocr_results = [""] * len(rectangles) if rectangles else [""]
282
+ oneocr_threads = []
283
+ run.init_config(False)
284
+ if rectangles:
285
+ for i, rectangle in enumerate(rectangles):
286
+ thread = threading.Thread(target=run_oneocr, args=(ocr_config,i,), daemon=True)
287
+ oneocr_threads.append(thread)
288
+ thread.start()
289
+ else:
290
+ single_ocr_thread = threading.Thread(target=run_oneocr, args=(ocr_config, 0,), daemon=True)
291
+ oneocr_threads.append(single_ocr_thread)
292
+ single_ocr_thread.start()
293
+
294
+ websocket_server_thread = WebsocketServerThread(read=True)
295
+ websocket_server_thread.start()
296
+
297
+ try:
298
+ while not done:
299
+ time.sleep(1)
300
+ except KeyboardInterrupt as e:
301
+ pass
302
+
303
+ for thread in oneocr_threads:
304
+ thread.join()
305
+
306
+ # asyncio.run(websocket_client())
@@ -0,0 +1 @@
1
+ from owocr.ocr import *
@@ -0,0 +1,8 @@
1
+ from .run import run
2
+
3
+ def main():
4
+ run()
5
+
6
+
7
+ if __name__ == '__main__':
8
+ main()