GameSentenceMiner 2.14.7__py3-none-any.whl → 2.14.9__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.
- GameSentenceMiner/config_gui.py +19 -10
- GameSentenceMiner/gsm.py +68 -8
- GameSentenceMiner/locales/en_us.json +4 -0
- GameSentenceMiner/locales/ja_jp.json +4 -0
- GameSentenceMiner/locales/zh_cn.json +4 -0
- GameSentenceMiner/obs.py +12 -8
- {gamesentenceminer-2.14.7.dist-info → gamesentenceminer-2.14.9.dist-info}/METADATA +1 -2
- gamesentenceminer-2.14.9.dist-info/RECORD +24 -0
- GameSentenceMiner/ai/__init__.py +0 -0
- GameSentenceMiner/ai/ai_prompting.py +0 -473
- GameSentenceMiner/ocr/__init__.py +0 -0
- GameSentenceMiner/ocr/gsm_ocr_config.py +0 -174
- GameSentenceMiner/ocr/ocrconfig.py +0 -129
- GameSentenceMiner/ocr/owocr_area_selector.py +0 -629
- GameSentenceMiner/ocr/owocr_helper.py +0 -638
- GameSentenceMiner/ocr/ss_picker.py +0 -140
- GameSentenceMiner/owocr/owocr/__init__.py +0 -1
- GameSentenceMiner/owocr/owocr/__main__.py +0 -9
- GameSentenceMiner/owocr/owocr/config.py +0 -148
- GameSentenceMiner/owocr/owocr/lens_betterproto.py +0 -1238
- GameSentenceMiner/owocr/owocr/ocr.py +0 -1691
- GameSentenceMiner/owocr/owocr/run.py +0 -1817
- GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py +0 -109
- GameSentenceMiner/tools/__init__.py +0 -0
- GameSentenceMiner/tools/audio_offset_selector.py +0 -215
- GameSentenceMiner/tools/ss_selector.py +0 -135
- GameSentenceMiner/tools/window_transparency.py +0 -214
- GameSentenceMiner/util/__init__.py +0 -0
- GameSentenceMiner/util/communication/__init__.py +0 -22
- GameSentenceMiner/util/communication/send.py +0 -7
- GameSentenceMiner/util/communication/websocket.py +0 -94
- GameSentenceMiner/util/configuration.py +0 -1198
- GameSentenceMiner/util/db.py +0 -408
- GameSentenceMiner/util/downloader/Untitled_json.py +0 -472
- GameSentenceMiner/util/downloader/__init__.py +0 -0
- GameSentenceMiner/util/downloader/download_tools.py +0 -194
- GameSentenceMiner/util/downloader/oneocr_dl.py +0 -250
- GameSentenceMiner/util/electron_config.py +0 -259
- GameSentenceMiner/util/ffmpeg.py +0 -571
- GameSentenceMiner/util/get_overlay_coords.py +0 -366
- GameSentenceMiner/util/gsm_utils.py +0 -323
- GameSentenceMiner/util/model.py +0 -206
- GameSentenceMiner/util/notification.py +0 -147
- GameSentenceMiner/util/text_log.py +0 -214
- GameSentenceMiner/web/__init__.py +0 -0
- GameSentenceMiner/web/service.py +0 -132
- GameSentenceMiner/web/static/__init__.py +0 -0
- GameSentenceMiner/web/static/apple-touch-icon.png +0 -0
- GameSentenceMiner/web/static/favicon-96x96.png +0 -0
- GameSentenceMiner/web/static/favicon.ico +0 -0
- GameSentenceMiner/web/static/favicon.svg +0 -3
- GameSentenceMiner/web/static/site.webmanifest +0 -21
- GameSentenceMiner/web/static/style.css +0 -292
- GameSentenceMiner/web/static/web-app-manifest-192x192.png +0 -0
- GameSentenceMiner/web/static/web-app-manifest-512x512.png +0 -0
- GameSentenceMiner/web/templates/__init__.py +0 -0
- GameSentenceMiner/web/templates/index.html +0 -50
- GameSentenceMiner/web/templates/text_replacements.html +0 -238
- GameSentenceMiner/web/templates/utility.html +0 -483
- GameSentenceMiner/web/texthooking_page.py +0 -584
- GameSentenceMiner/wip/__init___.py +0 -0
- gamesentenceminer-2.14.7.dist-info/RECORD +0 -77
- {gamesentenceminer-2.14.7.dist-info → gamesentenceminer-2.14.9.dist-info}/WHEEL +0 -0
- {gamesentenceminer-2.14.7.dist-info → gamesentenceminer-2.14.9.dist-info}/entry_points.txt +0 -0
- {gamesentenceminer-2.14.7.dist-info → gamesentenceminer-2.14.9.dist-info}/licenses/LICENSE +0 -0
- {gamesentenceminer-2.14.7.dist-info → gamesentenceminer-2.14.9.dist-info}/top_level.txt +0 -0
@@ -1,109 +0,0 @@
|
|
1
|
-
from multiprocessing import Process, Manager
|
2
|
-
import mss
|
3
|
-
from PIL import Image
|
4
|
-
|
5
|
-
try:
|
6
|
-
from PIL import ImageTk
|
7
|
-
import tkinter as tk
|
8
|
-
selector_available = True
|
9
|
-
except:
|
10
|
-
selector_available = False
|
11
|
-
|
12
|
-
|
13
|
-
class ScreenSelector:
|
14
|
-
def __init__(self, result):
|
15
|
-
self.sct = mss.mss()
|
16
|
-
self.monitors = self.sct.monitors[1:]
|
17
|
-
self.root = None
|
18
|
-
self.result = result
|
19
|
-
|
20
|
-
def on_select(self, monitor, coordinates):
|
21
|
-
self.result['monitor'] = monitor
|
22
|
-
self.result['coordinates'] = coordinates
|
23
|
-
self.root.destroy()
|
24
|
-
|
25
|
-
def create_window(self, monitor):
|
26
|
-
screenshot = self.sct.grab(monitor)
|
27
|
-
img = Image.frombytes('RGB', screenshot.size, screenshot.rgb)
|
28
|
-
|
29
|
-
if img.width != monitor['width']:
|
30
|
-
img = img.resize((monitor['width'], monitor['height']), Image.Resampling.LANCZOS)
|
31
|
-
|
32
|
-
window = tk.Toplevel(self.root)
|
33
|
-
window.geometry(f"{monitor['width']}x{monitor['height']}+{monitor['left']}+{monitor['top']}")
|
34
|
-
window.overrideredirect(1)
|
35
|
-
window.attributes('-topmost', 1)
|
36
|
-
|
37
|
-
img_tk = ImageTk.PhotoImage(img)
|
38
|
-
|
39
|
-
canvas = tk.Canvas(window, cursor='cross', highlightthickness=0)
|
40
|
-
canvas.pack(fill=tk.BOTH, expand=True)
|
41
|
-
canvas.image = img_tk
|
42
|
-
canvas.create_image(0, 0, image=img_tk, anchor=tk.NW)
|
43
|
-
|
44
|
-
start_x, start_y, rect = None, None, None
|
45
|
-
|
46
|
-
def on_click(event):
|
47
|
-
nonlocal start_x, start_y, rect
|
48
|
-
start_x, start_y = event.x, event.y
|
49
|
-
rect = canvas.create_rectangle(start_x, start_y, start_x, start_y, outline='red')
|
50
|
-
|
51
|
-
def on_drag(event):
|
52
|
-
nonlocal rect, start_x, start_y
|
53
|
-
if rect:
|
54
|
-
canvas.coords(rect, start_x, start_y, event.x, event.y)
|
55
|
-
|
56
|
-
def on_release(event):
|
57
|
-
nonlocal start_x, start_y
|
58
|
-
end_x, end_y = event.x, event.y
|
59
|
-
|
60
|
-
x1 = min(start_x, end_x)
|
61
|
-
y1 = min(start_y, end_y)
|
62
|
-
x2 = max(start_x, end_x)
|
63
|
-
y2 = max(start_y, end_y)
|
64
|
-
|
65
|
-
self.on_select(monitor, (x1, y1, x2 - x1, y2 - y1))
|
66
|
-
|
67
|
-
canvas.bind('<ButtonPress-1>', on_click)
|
68
|
-
canvas.bind('<B1-Motion>', on_drag)
|
69
|
-
canvas.bind('<ButtonRelease-1>', on_release)
|
70
|
-
|
71
|
-
def start(self):
|
72
|
-
self.root = tk.Tk()
|
73
|
-
self.root.withdraw()
|
74
|
-
|
75
|
-
for monitor in self.monitors:
|
76
|
-
self.create_window(monitor)
|
77
|
-
|
78
|
-
self.root.mainloop()
|
79
|
-
self.root.update()
|
80
|
-
|
81
|
-
|
82
|
-
def run_screen_selector(result):
|
83
|
-
selector = ScreenSelector(result)
|
84
|
-
selector.start()
|
85
|
-
|
86
|
-
|
87
|
-
def get_screen_selection():
|
88
|
-
if not selector_available:
|
89
|
-
raise ValueError('tkinter or PIL with tkinter support are not installed, unable to open picker')
|
90
|
-
|
91
|
-
with Manager() as manager:
|
92
|
-
res = manager.dict()
|
93
|
-
process = Process(target=run_screen_selector, args=(res,))
|
94
|
-
|
95
|
-
process.start()
|
96
|
-
process.join()
|
97
|
-
|
98
|
-
if 'monitor' in res and 'coordinates' in res:
|
99
|
-
return res.copy()
|
100
|
-
else:
|
101
|
-
return False
|
102
|
-
|
103
|
-
if __name__ == "__main__":
|
104
|
-
selection = get_screen_selection()
|
105
|
-
if selection:
|
106
|
-
print(f"Selected monitor: {selection['monitor']}")
|
107
|
-
print(f"Selected coordinates: {selection['coordinates']}")
|
108
|
-
else:
|
109
|
-
print("No selection made or process was interrupted.")
|
File without changes
|
@@ -1,215 +0,0 @@
|
|
1
|
-
|
2
|
-
import os
|
3
|
-
import sys
|
4
|
-
|
5
|
-
sys_stdout = sys.stdout
|
6
|
-
sys_stderr = sys.stderr
|
7
|
-
sys.stdout = open(os.devnull, 'w')
|
8
|
-
sys.stderr = open(os.devnull, 'w')
|
9
|
-
import tkinter as tk
|
10
|
-
from tkinter import filedialog, messagebox
|
11
|
-
import soundfile as sf
|
12
|
-
import numpy as np
|
13
|
-
import matplotlib.pyplot as plt
|
14
|
-
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
15
|
-
import sounddevice as sd
|
16
|
-
|
17
|
-
from GameSentenceMiner.util import ffmpeg
|
18
|
-
|
19
|
-
|
20
|
-
class AudioOffsetGUI:
|
21
|
-
|
22
|
-
def __init__(self, master, audio_file_path=None):
|
23
|
-
self.master = master
|
24
|
-
master.title("Audio Offset Adjuster")
|
25
|
-
master.geometry("1000x700")
|
26
|
-
|
27
|
-
master.tk_setPalette(background='#2E2E2E', foreground='white',
|
28
|
-
activeBackground='#4F4F4F', activeForeground='white')
|
29
|
-
|
30
|
-
self.audio_data = None
|
31
|
-
self.samplerate = None
|
32
|
-
self.duration = 0.0
|
33
|
-
|
34
|
-
self.fig, self.ax = plt.subplots(figsize=(10, 4))
|
35
|
-
self.canvas = FigureCanvasTkAgg(self.fig, master=master)
|
36
|
-
self.canvas_widget = self.canvas.get_tk_widget()
|
37
|
-
self.canvas_widget.pack(side=tk.TOP, fill=tk.BOTH, expand=True, padx=10, pady=10)
|
38
|
-
|
39
|
-
plt.style.use('dark_background')
|
40
|
-
self.fig.set_facecolor('#2E2E2E')
|
41
|
-
self.ax.set_facecolor('#2E2E2E')
|
42
|
-
self.ax.tick_params(axis='x', colors='white')
|
43
|
-
self.ax.tick_params(axis='y', colors='white')
|
44
|
-
self.ax.spines['bottom'].set_color('white')
|
45
|
-
self.ax.spines['left'].set_color('white')
|
46
|
-
self.ax.spines['top'].set_color('white')
|
47
|
-
self.ax.spines['right'].set_color('white')
|
48
|
-
self.ax.set_xlabel("Time (s)", color='white')
|
49
|
-
self.ax.set_ylabel("Amplitude", color='white')
|
50
|
-
|
51
|
-
self.beg_offset_line = None
|
52
|
-
# self.end_offset_line is removed as there's no end slider
|
53
|
-
|
54
|
-
self.create_widgets()
|
55
|
-
|
56
|
-
self.load_audio(audio_file_path)
|
57
|
-
|
58
|
-
|
59
|
-
def create_widgets(self):
|
60
|
-
control_frame = tk.Frame(self.master, bg='#2E2E2E')
|
61
|
-
control_frame.pack(side=tk.BOTTOM, fill=tk.X, pady=10, padx=10)
|
62
|
-
|
63
|
-
self.play_button = tk.Button(control_frame, text="Play/Pause Segment", command=self.play_segment, bg='#4F4F4F', fg='white')
|
64
|
-
self.play_button.pack(side=tk.RIGHT, padx=5)
|
65
|
-
|
66
|
-
self.output_button = tk.Button(control_frame, text="Get Offset", command=self.get_offsets, bg='#4F4F4F', fg='white')
|
67
|
-
self.output_button.pack(side=tk.RIGHT, padx=5)
|
68
|
-
|
69
|
-
self.beg_offset_label = tk.Label(control_frame, text="Beginning Offset: 0.00s", bg='#2E2E2E', fg='white')
|
70
|
-
self.beg_offset_label.pack(side=tk.LEFT, padx=10)
|
71
|
-
|
72
|
-
self.end_offset_label = tk.Label(control_frame, text="End Offset: Full Duration", bg='#2E2E2E', fg='white')
|
73
|
-
self.end_offset_label.pack(side=tk.LEFT, padx=10)
|
74
|
-
|
75
|
-
slider_frame = tk.Frame(self.master, bg='#2E2E2E')
|
76
|
-
slider_frame.pack(side=tk.BOTTOM, fill=tk.X, pady=5, padx=10)
|
77
|
-
|
78
|
-
beg_slider_label = tk.Label(slider_frame, text="Start Trim:", bg='#2E2E2E', fg='white')
|
79
|
-
beg_slider_label.pack(side=tk.LEFT)
|
80
|
-
self.beg_slider = tk.Scale(slider_frame, from_=0, to=100, orient=tk.HORIZONTAL, resolution=0.5,
|
81
|
-
command=self.on_slider_change, bg='#2E2E2E', fg='white', troughcolor='#4F4F4F',
|
82
|
-
highlightbackground='#2E2E2E', length=300)
|
83
|
-
self.beg_slider.pack(side=tk.LEFT, expand=True, fill=tk.X, padx=5)
|
84
|
-
|
85
|
-
# Removed end_slider and its associated label
|
86
|
-
|
87
|
-
def load_audio(self, file_path):
|
88
|
-
if file_path:
|
89
|
-
try:
|
90
|
-
self.audio_data, self.samplerate = sf.read(file_path)
|
91
|
-
if self.audio_data.ndim > 1:
|
92
|
-
self.audio_data = self.audio_data[:, 0]
|
93
|
-
self.duration = len(self.audio_data) / self.samplerate
|
94
|
-
self.plot_waveform()
|
95
|
-
self.beg_slider.config(to=self.duration)
|
96
|
-
self.beg_slider.set(0) # Reset start slider to 0
|
97
|
-
except Exception as e:
|
98
|
-
messagebox.showerror("Error", f"Failed to load audio file: {e}")
|
99
|
-
self.audio_data = None
|
100
|
-
self.samplerate = None
|
101
|
-
self.duration = 0.0
|
102
|
-
|
103
|
-
def plot_waveform(self):
|
104
|
-
self.ax.clear()
|
105
|
-
if self.audio_data is not None:
|
106
|
-
time = np.linspace(0, self.duration, len(self.audio_data))
|
107
|
-
self.ax.plot(time, self.audio_data, color='#1E90FF')
|
108
|
-
self.ax.set_xlim(0, self.duration)
|
109
|
-
self.ax.set_ylim(np.min(self.audio_data), np.max(self.audio_data))
|
110
|
-
self.ax.set_title("Audio", color='white')
|
111
|
-
|
112
|
-
if self.beg_offset_line:
|
113
|
-
self.beg_offset_line.remove()
|
114
|
-
# self.end_offset_line.remove() is removed
|
115
|
-
|
116
|
-
self.beg_offset_line = self.ax.axvline(self.beg_slider.get(), color='red', linestyle='--', linewidth=2)
|
117
|
-
# The end line is now always at the duration
|
118
|
-
self.ax.axvline(self.duration, color='green', linestyle='--', linewidth=2)
|
119
|
-
|
120
|
-
self.update_offset_labels()
|
121
|
-
else:
|
122
|
-
self.ax.text(0.5, 0.5, "No audio loaded",
|
123
|
-
horizontalalignment='center', verticalalignment='center',
|
124
|
-
transform=self.ax.transAxes, color='white', fontsize=16)
|
125
|
-
|
126
|
-
self.fig.canvas.draw_idle()
|
127
|
-
|
128
|
-
def on_slider_change(self, val):
|
129
|
-
if self.audio_data is None:
|
130
|
-
return
|
131
|
-
|
132
|
-
beg_val = float(self.beg_slider.get())
|
133
|
-
|
134
|
-
if self.beg_offset_line:
|
135
|
-
self.beg_offset_line.set_xdata([beg_val])
|
136
|
-
|
137
|
-
self.update_offset_labels()
|
138
|
-
self.fig.canvas.draw_idle()
|
139
|
-
|
140
|
-
def play_segment(self):
|
141
|
-
if self.audio_data is None:
|
142
|
-
messagebox.showinfo("Play Audio", "No audio file loaded yet.")
|
143
|
-
return
|
144
|
-
|
145
|
-
if hasattr(self, 'is_playing') and self.is_playing:
|
146
|
-
sd.stop()
|
147
|
-
self.is_playing = False
|
148
|
-
return
|
149
|
-
|
150
|
-
beg_offset = self.beg_slider.get()
|
151
|
-
end_offset = self.duration # End offset is now always full duration
|
152
|
-
|
153
|
-
if beg_offset >= end_offset:
|
154
|
-
messagebox.showwarning("Play Audio", "Start offset must be less than end offset.")
|
155
|
-
return
|
156
|
-
|
157
|
-
start_frame = int(beg_offset * self.samplerate)
|
158
|
-
end_frame = int(end_offset * self.samplerate)
|
159
|
-
|
160
|
-
if start_frame >= len(self.audio_data) or end_frame <= 0:
|
161
|
-
messagebox.showwarning("Play Audio", "Selected segment is out of audio range.")
|
162
|
-
return
|
163
|
-
|
164
|
-
segment_to_play = self.audio_data[start_frame:end_frame]
|
165
|
-
|
166
|
-
try:
|
167
|
-
self.is_playing = True
|
168
|
-
sd.play(segment_to_play, self.samplerate)
|
169
|
-
except Exception as e:
|
170
|
-
self.is_playing = False
|
171
|
-
messagebox.showerror("Audio Playback Error", f"Failed to play audio: {e}")
|
172
|
-
|
173
|
-
def update_offset_labels(self):
|
174
|
-
if self.beg_offset_line: # We no longer have an end_offset_line object
|
175
|
-
beg_val = self.beg_offset_line.get_xdata()[0] - 5.0 # Adjusting for the 5 seconds offset
|
176
|
-
self.beg_offset_label.config(text=f"Beginning Offset: {beg_val:.2f}s")
|
177
|
-
|
178
|
-
def get_offsets(self):
|
179
|
-
if self.audio_data is None:
|
180
|
-
messagebox.showinfo("Offsets", "No audio file loaded yet.")
|
181
|
-
return
|
182
|
-
|
183
|
-
beg_offset = self.beg_slider.get() - 5.0
|
184
|
-
end_offset = self.duration # End offset is always full duration
|
185
|
-
sys.stdout.close()
|
186
|
-
sys.stderr.close()
|
187
|
-
sys.stdout = sys_stdout
|
188
|
-
sys.stderr = sys_stderr
|
189
|
-
print(f"{beg_offset:.2f}")
|
190
|
-
exit(0)
|
191
|
-
|
192
|
-
def run_audio_offset_gui(path=None, beginning_offset=0, end_offset=None):
|
193
|
-
temp_file_path = os.path.join(os.path.dirname(path), "temp_audio.opus")
|
194
|
-
|
195
|
-
if os.path.exists(temp_file_path):
|
196
|
-
os.remove(temp_file_path)
|
197
|
-
|
198
|
-
ffmpeg.trim_audio(path, beginning_offset - 5, end_offset, temp_file_path, True, 0, 0)
|
199
|
-
|
200
|
-
root = tk.Tk()
|
201
|
-
root.protocol("WM_DELETE_WINDOW", lambda: exit(1)) # Exit when the window is closed
|
202
|
-
app = AudioOffsetGUI(root, audio_file_path=temp_file_path)
|
203
|
-
root.mainloop()
|
204
|
-
|
205
|
-
|
206
|
-
if __name__ == "__main__":
|
207
|
-
import argparse
|
208
|
-
|
209
|
-
parser = argparse.ArgumentParser(description="Run Audio Offset GUI")
|
210
|
-
parser.add_argument("--path", type=str, required=True, help="Path to the audio file")
|
211
|
-
parser.add_argument("--beginning_offset", type=float, default=0, help="Beginning offset in seconds")
|
212
|
-
parser.add_argument("--end_offset", type=float, default=None, help="End offset in seconds")
|
213
|
-
|
214
|
-
args = parser.parse_args()
|
215
|
-
run_audio_offset_gui(path=args.path, beginning_offset=args.beginning_offset, end_offset=args.end_offset)
|
@@ -1,135 +0,0 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
import os
|
4
|
-
import sys
|
5
|
-
import subprocess
|
6
|
-
|
7
|
-
# Suppress stdout and stderr during imports
|
8
|
-
sys_stdout = sys.stdout
|
9
|
-
sys_stderr = sys.stderr
|
10
|
-
sys.stdout = open(os.devnull, 'w')
|
11
|
-
sys.stderr = open(os.devnull, 'w')
|
12
|
-
|
13
|
-
import tkinter as tk
|
14
|
-
from PIL import Image, ImageTk
|
15
|
-
from GameSentenceMiner.util.gsm_utils import sanitize_filename
|
16
|
-
from GameSentenceMiner.util.configuration import get_temporary_directory, logger
|
17
|
-
from GameSentenceMiner.util.ffmpeg import ffmpeg_base_command_list
|
18
|
-
from GameSentenceMiner.util import ffmpeg
|
19
|
-
|
20
|
-
def extract_frames(video_path, timestamp, temp_dir, mode):
|
21
|
-
frame_paths = []
|
22
|
-
timestamp_number = float(timestamp)
|
23
|
-
golden_frame_index = 1 # Default to the first frame
|
24
|
-
golden_frame = None
|
25
|
-
video_duration = ffmpeg.get_video_duration(video_path)
|
26
|
-
|
27
|
-
if mode == 'middle':
|
28
|
-
timestamp_number = max(0.0, timestamp_number - 2.5)
|
29
|
-
elif mode == 'end':
|
30
|
-
timestamp_number = max(0.0, timestamp_number - 5.0)
|
31
|
-
|
32
|
-
if video_duration is not None and timestamp_number > video_duration:
|
33
|
-
logger.debug(f"Timestamp {timestamp_number} exceeds video duration {video_duration}.")
|
34
|
-
return None
|
35
|
-
|
36
|
-
try:
|
37
|
-
command = ffmpeg_base_command_list + [
|
38
|
-
"-y",
|
39
|
-
"-ss", str(timestamp_number),
|
40
|
-
"-i", video_path,
|
41
|
-
"-vf", f"fps=1/{0.25}",
|
42
|
-
"-vframes", "20",
|
43
|
-
os.path.join(temp_dir, "frame_%02d.png")
|
44
|
-
]
|
45
|
-
subprocess.run(command, check=True, capture_output=True)
|
46
|
-
for i in range(1, 21):
|
47
|
-
if os.path.exists(os.path.join(temp_dir, f"frame_{i:02d}.png")):
|
48
|
-
frame_paths.append(os.path.join(temp_dir, f"frame_{i:02d}.png"))
|
49
|
-
|
50
|
-
if mode == "beginning":
|
51
|
-
golden_frame = frame_paths[0]
|
52
|
-
if mode == "middle":
|
53
|
-
golden_frame = frame_paths[len(frame_paths) // 2]
|
54
|
-
if mode == "end":
|
55
|
-
golden_frame = frame_paths[-1]
|
56
|
-
except subprocess.CalledProcessError as e:
|
57
|
-
logger.debug(f"Error extracting frames: {e}")
|
58
|
-
logger.debug(f"Command was: {' '.join(command)}")
|
59
|
-
logger.debug(f"FFmpeg output:\n{e.stderr.decode()}")
|
60
|
-
return None
|
61
|
-
except Exception as e:
|
62
|
-
logger.debug(f"An error occurred: {e}")
|
63
|
-
return None
|
64
|
-
return frame_paths, golden_frame
|
65
|
-
|
66
|
-
def timestamp_to_seconds(timestamp):
|
67
|
-
hours, minutes, seconds = map(int, timestamp.split(':'))
|
68
|
-
return hours * 3600 + minutes * 60 + seconds
|
69
|
-
|
70
|
-
def display_images(image_paths, golden_frame):
|
71
|
-
window = tk.Tk()
|
72
|
-
window.configure(bg="black") # Set the background color to black
|
73
|
-
window.title("Image Selector")
|
74
|
-
selected_path = tk.StringVar()
|
75
|
-
image_widgets = []
|
76
|
-
|
77
|
-
def on_image_click(event):
|
78
|
-
widget = event.widget
|
79
|
-
index = image_widgets.index(widget)
|
80
|
-
selected_path.set(image_paths[index])
|
81
|
-
window.quit()
|
82
|
-
|
83
|
-
for i, path in enumerate(image_paths):
|
84
|
-
img = Image.open(path)
|
85
|
-
img.thumbnail((img.width / 8, img.height / 8))
|
86
|
-
img_tk = ImageTk.PhotoImage(img)
|
87
|
-
if golden_frame and path == golden_frame:
|
88
|
-
label = tk.Label(window, image=img_tk, borderwidth=5, relief="solid")
|
89
|
-
label.config(highlightbackground="yellow", highlightthickness=5)
|
90
|
-
else:
|
91
|
-
label = tk.Label(window, image=img_tk)
|
92
|
-
label.image = img_tk
|
93
|
-
label.grid(row=i // 5, column=i % 5, padx=5, pady=5)
|
94
|
-
label.bind("<Button-1>", on_image_click) # Bind click event to the label
|
95
|
-
image_widgets.append(label)
|
96
|
-
|
97
|
-
window.attributes("-topmost", True)
|
98
|
-
window.mainloop()
|
99
|
-
return selected_path.get()
|
100
|
-
|
101
|
-
def run_extraction_and_display(video_path, timestamp_str, mode):
|
102
|
-
temp_dir = os.path.join(get_temporary_directory(False), "screenshot_frames", sanitize_filename(os.path.splitext(os.path.basename(video_path))[0]))
|
103
|
-
os.makedirs(temp_dir, exist_ok=True)
|
104
|
-
image_paths, golden_frame = extract_frames(video_path, timestamp_str, temp_dir, mode)
|
105
|
-
if image_paths:
|
106
|
-
selected_image_path = display_images(image_paths, golden_frame)
|
107
|
-
if selected_image_path:
|
108
|
-
sys.stdout.close()
|
109
|
-
sys.stderr.close()
|
110
|
-
sys.stdout = sys_stdout
|
111
|
-
sys.stderr = sys_stderr
|
112
|
-
print(selected_image_path)
|
113
|
-
else:
|
114
|
-
logger.debug("No image was selected.")
|
115
|
-
else:
|
116
|
-
logger.debug("Frame extraction failed.")
|
117
|
-
|
118
|
-
def main():
|
119
|
-
|
120
|
-
|
121
|
-
# if len(sys.argv) != 3:
|
122
|
-
# print("Usage: python script.py <video_path> <timestamp>")
|
123
|
-
# sys.exit(1)
|
124
|
-
try:
|
125
|
-
video_path = sys.argv[1]
|
126
|
-
timestamp_str = sys.argv[2]
|
127
|
-
mode = sys.argv[3] if len(sys.argv) > 3 else "beginning"
|
128
|
-
run_extraction_and_display(video_path, timestamp_str, mode)
|
129
|
-
except Exception as e:
|
130
|
-
logger.debug(e)
|
131
|
-
sys.exit(1)
|
132
|
-
|
133
|
-
|
134
|
-
if __name__ == "__main__":
|
135
|
-
main()
|
@@ -1,214 +0,0 @@
|
|
1
|
-
import sys
|
2
|
-
import win32gui
|
3
|
-
import win32con
|
4
|
-
import win32api
|
5
|
-
import keyboard
|
6
|
-
import time
|
7
|
-
import threading
|
8
|
-
import signal
|
9
|
-
|
10
|
-
from GameSentenceMiner.util.configuration import logger
|
11
|
-
|
12
|
-
# --- Configuration (equivalent to AHK top-level variables) ---
|
13
|
-
TRANSPARENT_LEVEL = 1 # Almost invisible (0-255 scale)
|
14
|
-
OPAQUE_LEVEL = 255 # Fully opaque
|
15
|
-
HOTKEY = 'ctrl+alt+y'
|
16
|
-
|
17
|
-
# --- Global State Variables (equivalent to AHK global variables) ---
|
18
|
-
is_toggled = False
|
19
|
-
target_hwnd = None
|
20
|
-
# A lock to prevent race conditions when accessing global state from different threads
|
21
|
-
state_lock = threading.Lock()
|
22
|
-
|
23
|
-
# --- Core Functions (equivalent to AHK functions) ---
|
24
|
-
|
25
|
-
def set_window_transparency(hwnd, transparency):
|
26
|
-
"""
|
27
|
-
Sets the transparency of a window.
|
28
|
-
This is the Python equivalent of WinSetTransparent.
|
29
|
-
"""
|
30
|
-
if not hwnd or not win32gui.IsWindow(hwnd):
|
31
|
-
return
|
32
|
-
try:
|
33
|
-
# Get the current window style
|
34
|
-
style = win32gui.GetWindowLong(hwnd, win32con.GWL_EXSTYLE)
|
35
|
-
# Add the WS_EX_LAYERED style, which is required for transparency
|
36
|
-
win32gui.SetWindowLong(hwnd, win32con.GWL_EXSTYLE, style | win32con.WS_EX_LAYERED)
|
37
|
-
# Set the transparency
|
38
|
-
win32gui.SetLayeredWindowAttributes(hwnd, 0, transparency, win32con.LWA_ALPHA)
|
39
|
-
except Exception as e:
|
40
|
-
# Some windows (like system or elevated ones) might deny permission
|
41
|
-
# logger.info(f"Error setting transparency for HWND {hwnd}: {e}")
|
42
|
-
pass
|
43
|
-
|
44
|
-
def set_always_on_top(hwnd, is_on_top):
|
45
|
-
"""
|
46
|
-
Sets or removes the "Always on Top" status for a window.
|
47
|
-
This is the Python equivalent of WinSetAlwaysOnTop.
|
48
|
-
"""
|
49
|
-
if not hwnd or not win32gui.IsWindow(hwnd):
|
50
|
-
return
|
51
|
-
try:
|
52
|
-
rect = win32gui.GetWindowRect(hwnd)
|
53
|
-
position = win32con.HWND_TOPMOST if is_on_top else win32con.HWND_NOTOPMOST
|
54
|
-
# Set the window position without moving or resizing it
|
55
|
-
win32gui.SetWindowPos(hwnd, position, rect[0], rect[1], 0, 0,
|
56
|
-
win32con.SWP_NOMOVE | win32con.SWP_NOSIZE)
|
57
|
-
except Exception as e:
|
58
|
-
# logger.info(f"Error setting always-on-top for HWND {hwnd}: {e}")
|
59
|
-
pass
|
60
|
-
|
61
|
-
def reset_window_state(hwnd):
|
62
|
-
"""A helper to reset a window to its default state."""
|
63
|
-
set_window_transparency(hwnd, OPAQUE_LEVEL)
|
64
|
-
set_always_on_top(hwnd, False)
|
65
|
-
|
66
|
-
# --- Hotkey Callback (equivalent to AHK ^!y::) ---
|
67
|
-
|
68
|
-
def toggle_functionality(window_hwnd=None):
|
69
|
-
"""
|
70
|
-
This function is called when the hotkey is pressed.
|
71
|
-
It manages the toggling logic.
|
72
|
-
"""
|
73
|
-
global is_toggled, target_hwnd
|
74
|
-
|
75
|
-
if window_hwnd:
|
76
|
-
current_hwnd = window_hwnd
|
77
|
-
else:
|
78
|
-
# Get the currently focused window (equivalent to WinGetID("A"))
|
79
|
-
current_hwnd = win32gui.GetForegroundWindow()
|
80
|
-
if not current_hwnd:
|
81
|
-
logger.info("No window is currently active!")
|
82
|
-
return
|
83
|
-
|
84
|
-
with state_lock:
|
85
|
-
# Case 1: The hotkey is pressed on the currently toggled window to disable it.
|
86
|
-
if is_toggled and target_hwnd == current_hwnd:
|
87
|
-
logger.info(f"Disabling functionality for window: {win32gui.GetWindowText(current_hwnd)}")
|
88
|
-
reset_window_state(current_hwnd)
|
89
|
-
is_toggled = False
|
90
|
-
target_hwnd = None
|
91
|
-
# Case 2: Enable functionality for a new window, or switch to a new one.
|
92
|
-
else:
|
93
|
-
# If another window was already toggled, reset it first.
|
94
|
-
if is_toggled and target_hwnd is not None:
|
95
|
-
logger.info(f"Resetting old window: {win32gui.GetWindowText(target_hwnd)}")
|
96
|
-
reset_window_state(target_hwnd)
|
97
|
-
|
98
|
-
# Enable functionality for the new window.
|
99
|
-
logger.info(f"Enabling functionality for window: {win32gui.GetWindowText(current_hwnd)}")
|
100
|
-
is_toggled = True
|
101
|
-
target_hwnd = current_hwnd
|
102
|
-
set_always_on_top(target_hwnd, True)
|
103
|
-
# The mouse_monitor_loop will handle setting the initial transparency
|
104
|
-
|
105
|
-
# --- Mouse Monitoring (equivalent to AHK Loop) ---
|
106
|
-
|
107
|
-
def mouse_monitor_loop():
|
108
|
-
"""
|
109
|
-
A loop that runs in a separate thread to monitor the mouse position.
|
110
|
-
"""
|
111
|
-
global is_toggled, target_hwnd
|
112
|
-
|
113
|
-
while True:
|
114
|
-
# We check the state without a lock first for performance,
|
115
|
-
# then use the lock when we need to read the shared variable.
|
116
|
-
if is_toggled:
|
117
|
-
with state_lock:
|
118
|
-
# Make a local copy of the target handle to work with
|
119
|
-
monitored_hwnd = target_hwnd
|
120
|
-
|
121
|
-
if monitored_hwnd:
|
122
|
-
# Get mouse position and the window handle under the cursor
|
123
|
-
pos = win32gui.GetCursorPos()
|
124
|
-
hwnd_under_mouse = win32gui.WindowFromPoint(pos)
|
125
|
-
|
126
|
-
# WindowFromPoint can return a child window (like a button).
|
127
|
-
# We need to walk up the parent chain to see if it belongs to our target window.
|
128
|
-
is_mouse_over_target = False
|
129
|
-
current_hwnd = hwnd_under_mouse
|
130
|
-
while current_hwnd != 0:
|
131
|
-
if current_hwnd == monitored_hwnd:
|
132
|
-
is_mouse_over_target = True
|
133
|
-
break
|
134
|
-
current_hwnd = win32gui.GetParent(current_hwnd)
|
135
|
-
|
136
|
-
# Apply transparency based on mouse position
|
137
|
-
if is_mouse_over_target:
|
138
|
-
set_window_transparency(monitored_hwnd, OPAQUE_LEVEL)
|
139
|
-
else:
|
140
|
-
set_window_transparency(monitored_hwnd, TRANSPARENT_LEVEL)
|
141
|
-
|
142
|
-
# A small delay to reduce CPU usage
|
143
|
-
time.sleep(0.1)
|
144
|
-
|
145
|
-
class HandleSTDINThread(threading.Thread):
|
146
|
-
def run(self):
|
147
|
-
while True:
|
148
|
-
try:
|
149
|
-
line = input()
|
150
|
-
if "exit" in line.strip().lower():
|
151
|
-
handle_quit()
|
152
|
-
break
|
153
|
-
except EOFError:
|
154
|
-
break
|
155
|
-
|
156
|
-
def handle_quit():
|
157
|
-
if is_toggled and target_hwnd:
|
158
|
-
reset_window_state(target_hwnd)
|
159
|
-
logger.info("Exiting Window Transparency Tool.")
|
160
|
-
|
161
|
-
# --- Main Execution Block ---
|
162
|
-
|
163
|
-
if __name__ == "__main__":
|
164
|
-
import argparse
|
165
|
-
# Start the mouse monitor in a separate, non-blocking thread.
|
166
|
-
# daemon=True ensures the thread will exit when the main script does.
|
167
|
-
monitor_thread = threading.Thread(target=mouse_monitor_loop, daemon=True)
|
168
|
-
monitor_thread.start()
|
169
|
-
|
170
|
-
# get hotkey from args
|
171
|
-
parser = argparse.ArgumentParser(description="Window Transparency Toggle Script")
|
172
|
-
parser.add_argument('--hotkey', type=str, default=HOTKEY, help='Hotkey to toggle transparency (default: ctrl+alt+y)')
|
173
|
-
parser.add_argument('--window', type=str, help='Window title to target (optional)')
|
174
|
-
|
175
|
-
args = parser.parse_args()
|
176
|
-
hotkey = args.hotkey.lower()
|
177
|
-
target_window_title = args.window
|
178
|
-
|
179
|
-
if target_window_title:
|
180
|
-
# Find the window by title if specified
|
181
|
-
target_hwnd = win32gui.FindWindow(None, target_window_title)
|
182
|
-
logger.info(f"Searching for window with title: {target_window_title}")
|
183
|
-
logger.info(f"Target HWND: {target_hwnd}")
|
184
|
-
if not target_hwnd:
|
185
|
-
logger.error(f"Window with title '{target_window_title}' not found.")
|
186
|
-
sys.exit(1)
|
187
|
-
else:
|
188
|
-
logger.info(f"Target window found: {target_window_title}")
|
189
|
-
toggle_functionality(target_hwnd) # Enable functionality for the specified window
|
190
|
-
|
191
|
-
# Register the global hotkey
|
192
|
-
keyboard.add_hotkey(hotkey, toggle_functionality)
|
193
|
-
|
194
|
-
# Handle SigINT/SigTERM gracefully
|
195
|
-
def signal_handler(sig, frame):
|
196
|
-
handle_quit()
|
197
|
-
sys.exit(0)
|
198
|
-
|
199
|
-
signal.signal(signal.SIGINT, signal_handler)
|
200
|
-
signal.signal(signal.SIGTERM, signal_handler)
|
201
|
-
|
202
|
-
logger.info(f"Script running. Press '{hotkey}' on a window to toggle transparency.")
|
203
|
-
logger.info("Press Ctrl+C in this console to exit.")
|
204
|
-
|
205
|
-
HandleSTDINThread().start()
|
206
|
-
|
207
|
-
# Keep the script running to listen for the hotkey.
|
208
|
-
# keyboard.wait() is a blocking call that waits indefinitely.
|
209
|
-
try:
|
210
|
-
keyboard.wait()
|
211
|
-
except KeyboardInterrupt:
|
212
|
-
if is_toggled and target_hwnd:
|
213
|
-
reset_window_state(target_hwnd)
|
214
|
-
logger.info("\nScript terminated by user.")
|
File without changes
|