GameSentenceMiner 2.7.8__py3-none-any.whl → 2.7.10__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.
@@ -52,7 +52,7 @@ def get_screenshot_for_line(video_file, game_line):
52
52
  return get_screenshot(video_file, get_screenshot_time(video_file, game_line))
53
53
 
54
54
 
55
- def get_screenshot_time(video_path, game_line, default_beginning=False, vad_beginning=None, vad_end=None):
55
+ def get_screenshot_time(video_path, game_line, default_beginning=False, vad_beginning=None, vad_end=None, doing_multi_line=False):
56
56
  if game_line:
57
57
  line_time = game_line.time
58
58
  else:
@@ -68,7 +68,7 @@ def get_screenshot_time(video_path, game_line, default_beginning=False, vad_begi
68
68
  screenshot_offset = get_config().screenshot.seconds_after_line
69
69
 
70
70
  # Calculate screenshot time from the beginning by adding the offset
71
- if vad_beginning and vad_end:
71
+ if vad_beginning and vad_end and not doing_multi_line:
72
72
  logger.debug("Using VAD to determine screenshot time")
73
73
  screenshot_time_from_beginning = line_timestamp_in_video + vad_end - screenshot_offset
74
74
  elif get_config().screenshot.use_new_screenshot_logic:
GameSentenceMiner/gsm.py CHANGED
@@ -141,7 +141,7 @@ class VideoToAudioHandler(FileSystemEventHandler):
141
141
 
142
142
  ss_timing = 1
143
143
  if mined_line and line_cutoff or mined_line and get_config().screenshot.use_beginning_of_line_as_screenshot:
144
- ss_timing = ffmpeg.get_screenshot_time(video_path, mined_line, vad_beginning, vad_end)
144
+ ss_timing = ffmpeg.get_screenshot_time(video_path, mined_line, vad_beginning, vad_end, bool(selected_lines))
145
145
 
146
146
  if get_config().anki.update_anki and last_note:
147
147
  anki.update_anki_card(last_note, note, audio_path=final_audio_output, video_path=video_path,
GameSentenceMiner/obs.py CHANGED
@@ -60,7 +60,7 @@ def get_obs_websocket_config_values():
60
60
 
61
61
  # Extract values
62
62
  server_enabled = config.get("server_enabled", False)
63
- server_port = config.get("server_port", 4455) # Default to 4455 if not set
63
+ server_port = config.get("server_port", 7274) # Default to 4455 if not set
64
64
  server_password = config.get("server_password", None)
65
65
 
66
66
  if not server_enabled:
@@ -0,0 +1,26 @@
1
+ from dataclasses import dataclass
2
+ from dataclasses_json import dataclass_json
3
+ from typing import List, Optional
4
+
5
+ @dataclass_json
6
+ @dataclass
7
+ class Monitor:
8
+ left: int
9
+ top: int
10
+ width: int
11
+ height: int
12
+ index: int
13
+
14
+ @dataclass_json
15
+ @dataclass
16
+ class Rectangle:
17
+ monitor: Monitor
18
+ coordinates: List[int]
19
+ is_excluded: bool
20
+
21
+ @dataclass_json
22
+ @dataclass
23
+ class OCRConfig:
24
+ scene: str
25
+ rectangles: List[Rectangle]
26
+ window: Optional[str] = None
@@ -1,275 +1,294 @@
1
- import ctypes
2
1
  import sys
2
+ from multiprocessing import Process, Manager
3
+ import mss
4
+ from GameSentenceMiner import obs
5
+
6
+ from GameSentenceMiner.util import sanitize_filename
7
+ from PIL import Image, ImageTk
3
8
  import tkinter as tk
4
- from tkinter import messagebox, simpledialog
5
9
  import json
6
10
  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 = []
11
+ import re
12
+
13
+ try:
14
+ import tkinter as tk
15
+ selector_available = True
16
+ except:
17
+ selector_available = False
18
+
19
+ MIN_RECT_WIDTH = 25 # Minimum width in pixels
20
+ MIN_RECT_HEIGHT = 25 # Minimum height in pixels
21
+
22
+ class ScreenSelector:
23
+ def __init__(self, result, window_name):
24
+ obs.connect_to_obs()
25
+ self.window_name = window_name
26
+ print(window_name)
27
+ self.sct = mss.mss()
28
+ self.monitors = self.sct.monitors[1:]
29
+ self.root = None
30
+ self.result = result
31
+ self.rectangles = [] # List to store (monitor, coordinates, is_excluded) tuples
32
+ self.drawn_rect_ids = [] # List to store canvas rectangle IDs
33
+ self.current_rect_id = None
24
34
  self.start_x = None
25
35
  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
36
+ self.canvas_windows = {} # Dictionary to store canvas per monitor
37
+ self.load_existing_rectangles()
38
+ self.image_mode = True
39
+ self.monitor_windows = {}
40
+ self.redo_stack = []
39
41
 
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)
42
+ def get_scene_ocr_config(self):
43
+ """Return the path to the OCR config file in GameSentenceMiner/ocr_config."""
44
+ app_dir = Path.home() / "AppData" / "Roaming" / "GameSentenceMiner"
45
+ ocr_config_dir = app_dir / "ocr_config"
46
+ ocr_config_dir.mkdir(parents=True, exist_ok=True)
47
+ scene = sanitize_filename(obs.get_current_scene())
48
+ config_path = ocr_config_dir / f"{scene}.json"
49
+ return config_path
50
50
 
51
- def initialize_ui(self):
51
+ def load_existing_rectangles(self):
52
+ config_path = self.get_scene_ocr_config()
52
53
  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)
54
+ with open(config_path, 'r') as f:
55
+ config_data = json.load(f)
56
+ if "rectangles" in config_data:
57
+ self.rectangles = []
58
+ for rect_data in config_data["rectangles"]:
59
+ monitor_data = rect_data.get("monitor")
60
+ coords = rect_data.get("coordinates")
61
+ is_excluded = rect_data.get("is_excluded", False)
62
+ if monitor_data and isinstance(coords, list) and len(coords) == 4:
63
+ x, y, w, h = coords
64
+ if w >= MIN_RECT_WIDTH and h >= MIN_RECT_HEIGHT:
65
+ self.rectangles.append((monitor_data, tuple(coords), is_excluded))
76
66
  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
67
+ print(f"Skipping small rectangle from config: {coords}")
68
+ print(f"Loaded existing rectangles from {config_path}")
69
+ except FileNotFoundError:
70
+ print(f"No existing config found at {config_path}")
71
+ except json.JSONDecodeError:
72
+ print(f"Error decoding JSON from {config_path}")
73
+ except Exception as e:
74
+ print(f"An error occurred while loading rectangles: {e}")
160
75
 
161
- def save_rects(self):
76
+ def save_rects(self, event=None):
162
77
  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
78
+ print("Saving rectangles...")
79
+ config_path = self.get_scene_ocr_config()
80
+ print(config_path)
81
+ serializable_rects = []
82
+ for monitor, coords, is_excluded in self.rectangles:
83
+ rect_data = {
84
+ "monitor": monitor,
85
+ "coordinates": list(coords), # Convert tuple to list for JSON
86
+ "is_excluded": is_excluded
87
+ }
88
+ serializable_rects.append(rect_data)
89
+
90
+ print(serializable_rects)
91
+ with open(config_path, 'w', encoding="utf-8") as f:
92
+ json.dump({"scene": sanitize_filename(obs.get_current_scene()), "window": self.window_name, "rectangles": serializable_rects}, f, indent=4)
183
93
  print("Rectangles saved.")
184
- self.on_close()
94
+ self.result['rectangles'] = self.rectangles.copy()
95
+ self.root.destroy()
185
96
  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()
97
+ print(f"Failed to save rectangles: {e}")
190
98
 
191
99
  def undo_last_rect(self, event=None):
192
- if self.saved_rect_coords and self.drawn_rect_ids:
193
- self.saved_rect_coords.pop()
100
+ if self.rectangles and self.drawn_rect_ids:
101
+ last_rect = self.rectangles.pop()
194
102
  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)
103
+
104
+ monitor, coords, is_excluded = last_rect
105
+ self.redo_stack.append((monitor, coords, is_excluded, last_rect_id))
106
+
107
+ for canvas in self.canvas_windows.values():
108
+ try:
109
+ canvas.delete(last_rect_id)
110
+ except tk.TclError:
111
+ pass
202
112
  elif self.current_rect_id is not None:
203
- if self.canvas.winfo_exists():
204
- self.canvas.delete(self.current_rect_id)
113
+ for canvas in self.canvas_windows.values():
114
+ try:
115
+ canvas.delete(self.current_rect_id)
116
+ except tk.TclError:
117
+ pass
205
118
  self.current_rect_id = None
206
119
  self.start_x = None
207
120
  self.start_y = None
208
121
 
122
+
123
+ def redo_last_rect(self, event=None):
124
+ if not self.redo_stack:
125
+ return
126
+
127
+ monitor, coords, is_excluded, rect_id = self.redo_stack.pop()
128
+ canvas = self.canvas_windows.get(monitor['index'])
129
+ if canvas:
130
+ x, y, w, h = coords
131
+ outline_color = 'green' if not is_excluded else 'orange'
132
+ new_rect_id = canvas.create_rectangle(x, y, x + w, y + h, outline=outline_color, width=2)
133
+ self.rectangles.append((monitor, coords, is_excluded))
134
+ self.drawn_rect_ids.append(new_rect_id)
135
+
136
+
137
+ def create_window(self, monitor):
138
+ screenshot = self.sct.grab(monitor)
139
+ img = Image.frombytes('RGB', screenshot.size, screenshot.rgb)
140
+
141
+ if img.width != monitor['width']:
142
+ img = img.resize((monitor['width'], monitor['height']), Image.Resampling.LANCZOS)
143
+
144
+ AscendingScale = 1.0 # For semi-transparent background
145
+ window = tk.Toplevel(self.root)
146
+ window.geometry(f"{monitor['width']}x{monitor['height']}+{monitor['left']}+{monitor['top']}")
147
+ window.overrideredirect(1)
148
+ window.attributes('-topmost', 1)
149
+
150
+ img_tk = ImageTk.PhotoImage(img)
151
+
152
+ canvas = tk.Canvas(window, cursor='cross', highlightthickness=0)
153
+ canvas.pack(fill=tk.BOTH, expand=True)
154
+ canvas.image = img_tk
155
+ canvas.bg_img_id = canvas.create_image(0, 0, image=img_tk, anchor=tk.NW) # Store image ID
156
+ self.canvas_windows[monitor['index']] = canvas
157
+
158
+ # Save monitor and window references for refreshing
159
+ self.monitor_windows[monitor['index']] = {
160
+ 'window': window,
161
+ 'canvas': canvas,
162
+ 'bg_img_id': canvas.bg_img_id,
163
+ }
164
+
165
+ # Draw existing rectangles for this monitor
166
+ for mon, coords, is_excluded in self.rectangles:
167
+ if mon['index'] == monitor['index']:
168
+ x, y, w, h = coords
169
+ outline_color = 'green' if not is_excluded else 'orange'
170
+ rect_id = canvas.create_rectangle(x, y, x + w, y + h, outline=outline_color, width=2)
171
+ self.drawn_rect_ids.append(rect_id)
172
+
173
+ def on_click(event):
174
+ if self.current_rect_id is None:
175
+ self.start_x, self.start_y = event.x, event.y
176
+ outline_color = 'red' if not event.state & 0x0001 else 'purple' # Shift for exclusion
177
+ self.current_rect_id = canvas.create_rectangle(
178
+ self.start_x, self.start_y, self.start_x, self.start_y,
179
+ outline=outline_color, width=2
180
+ )
181
+
182
+ def on_drag(event):
183
+ if self.current_rect_id:
184
+ canvas.coords(self.current_rect_id, self.start_x, self.start_y, event.x, event.y)
185
+
186
+ def on_release(event):
187
+ if self.current_rect_id:
188
+ end_x, end_y = event.x, event.y
189
+ x1 = min(self.start_x, end_x)
190
+ y1 = min(self.start_y, end_y)
191
+ x2 = max(self.start_x, end_x)
192
+ y2 = max(self.start_y, end_y)
193
+ width = abs(x2 - x1)
194
+ height = abs(y2 - y1)
195
+ if width >= MIN_RECT_WIDTH and height >= MIN_RECT_HEIGHT:
196
+ is_excluded = bool(event.state & 0x0001) # Shift key for exclusion
197
+ canvas.itemconfig(self.current_rect_id, outline='green' if not is_excluded else 'orange')
198
+ self.rectangles.append((monitor, (x1, y1, width, height), is_excluded))
199
+ self.drawn_rect_ids.append(self.current_rect_id)
200
+ else:
201
+ canvas.delete(self.current_rect_id)
202
+ print(f"Skipping small rectangle: width={width}, height={height}")
203
+ self.current_rect_id = None
204
+ self.start_x = None
205
+ self.start_y = None
206
+
207
+ def on_right_click(event):
208
+ item = canvas.find_closest(event.x, event.y)
209
+ if item:
210
+ for idx, rect_id in enumerate(self.drawn_rect_ids):
211
+ if rect_id == item[0]:
212
+ canvas.delete(rect_id)
213
+ # Need to find the corresponding rectangle in self.rectangles and remove it
214
+ for i, (mon, coords, excluded) in enumerate(self.rectangles):
215
+ if mon['index'] == monitor['index']:
216
+ x_r, y_r, w_r, h_r = coords
217
+ item_coords = canvas.coords(item[0])
218
+ if x_r == item_coords[0] and y_r == item_coords[1] and x_r + w_r == item_coords[2] and y_r + h_r == item_coords[3]:
219
+ del self.rectangles[i]
220
+ break
221
+ del self.drawn_rect_ids[idx]
222
+ break
223
+
224
+ def toggle_image_mode(event=None):
225
+ self.image_mode = not self.image_mode
226
+ if self.image_mode:
227
+ window.attributes("-alpha", 1.0)
228
+ else:
229
+ window.attributes("-alpha", 0.20)
230
+
231
+
232
+ canvas.bind('<ButtonPress-1>', on_click)
233
+ canvas.bind('<B1-Motion>', on_drag)
234
+ canvas.bind('<ButtonRelease-1>', on_release)
235
+ canvas.bind('<Button-3>', on_right_click)
236
+ window.bind('<Control-s>', self.save_rects)
237
+ window.bind('<Control-z>', self.undo_last_rect)
238
+ window.bind('<s>', self.save_rects)
239
+ window.bind('<z>', self.undo_last_rect)
240
+ window.bind("<Escape>", self.quit_app)
241
+ window.bind("<m>", toggle_image_mode)
242
+ window.bind('<Control-y>', self.redo_last_rect)
243
+ window.bind('<y>', self.redo_last_rect)
244
+
245
+ def start(self):
246
+ self.root = tk.Tk()
247
+ self.root.withdraw()
248
+
249
+ for monitor in self.monitors:
250
+ monitor['index'] = self.monitors.index(monitor)
251
+ self.create_window(monitor)
252
+
253
+ self.root.mainloop()
254
+
209
255
  def quit_app(self, event=None):
210
256
  print("Escape pressed, closing application.")
211
257
  self.on_close()
212
258
 
213
259
  def on_close(self):
214
- self.destroy()
260
+ self.root.destroy()
215
261
 
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
262
 
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
263
+ def run_screen_selector(result, window_name):
264
+ selector = ScreenSelector(result, window_name)
265
+ selector.start()
257
266
 
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
267
 
268
- def run_screen_picker(window_title):
269
- app = DynamicAreaSelector(window_title)
270
- app.mainloop()
268
+ def get_screen_selection(window_name):
269
+ if not selector_available:
270
+ raise ValueError('tkinter is not installed, unable to open picker')
271
+
272
+ with Manager() as manager:
273
+ res = manager.dict()
274
+ process = Process(target=run_screen_selector, args=(res,window_name))
275
+
276
+ process.start()
277
+ process.join()
278
+
279
+ if 'rectangles' in res:
280
+ return res.copy()
281
+ else:
282
+ return False
283
+
271
284
 
272
285
  if __name__ == "__main__":
273
286
  args = sys.argv[1:]
274
- obs.connect_to_obs()
275
- run_screen_picker(args[0] if len(args) > 0 else None)
287
+ window_name = args[0] if args else None
288
+ selection = get_screen_selection(window_name)
289
+ if selection:
290
+ print("Selected rectangles:")
291
+ for monitor, coords, is_excluded in selection['rectangles']:
292
+ print(f"Monitor: {monitor}, Coordinates: {coords}, Excluded: {is_excluded}")
293
+ else:
294
+ print("No selection made or process was interrupted.")