GameSentenceMiner 2.7.17__py3-none-any.whl → 2.8.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.
- GameSentenceMiner/anki.py +7 -8
- GameSentenceMiner/config_gui.py +19 -3
- GameSentenceMiner/configuration.py +8 -1
- GameSentenceMiner/ffmpeg.py +1 -3
- GameSentenceMiner/gametext.py +16 -155
- GameSentenceMiner/gsm.py +28 -29
- GameSentenceMiner/obs.py +0 -3
- GameSentenceMiner/ocr/ocrconfig.py +0 -1
- GameSentenceMiner/ocr/oneocr_dl.py +243 -0
- GameSentenceMiner/ocr/owocr_area_selector.py +0 -1
- GameSentenceMiner/ocr/owocr_helper.py +25 -26
- GameSentenceMiner/text_log.py +186 -0
- GameSentenceMiner/util.py +52 -3
- GameSentenceMiner/web/__init__.py +0 -0
- 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 +3 -0
- GameSentenceMiner/web/static/site.webmanifest +21 -0
- GameSentenceMiner/web/static/style.css +292 -0
- GameSentenceMiner/web/static/text_replacements.html +238 -0
- GameSentenceMiner/web/static/utility.html +313 -0
- GameSentenceMiner/web/static/web-app-manifest-192x192.png +0 -0
- GameSentenceMiner/web/static/web-app-manifest-512x512.png +0 -0
- GameSentenceMiner/web/texthooking_page.py +234 -0
- {gamesentenceminer-2.7.17.dist-info → gamesentenceminer-2.8.0.dist-info}/METADATA +2 -1
- gamesentenceminer-2.8.0.dist-info/RECORD +58 -0
- {gamesentenceminer-2.7.17.dist-info → gamesentenceminer-2.8.0.dist-info}/WHEEL +1 -1
- GameSentenceMiner/utility_gui.py +0 -204
- gamesentenceminer-2.7.17.dist-info/RECORD +0 -44
- {gamesentenceminer-2.7.17.dist-info → gamesentenceminer-2.8.0.dist-info}/entry_points.txt +0 -0
- {gamesentenceminer-2.7.17.dist-info → gamesentenceminer-2.8.0.dist-info}/licenses/LICENSE +0 -0
- {gamesentenceminer-2.7.17.dist-info → gamesentenceminer-2.8.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,313 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<meta charset="UTF-8">
|
5
|
+
<title>GSM TextHooker</title>
|
6
|
+
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
7
|
+
<style>
|
8
|
+
body {
|
9
|
+
background-color: #121212;
|
10
|
+
color: #e0e0e0;
|
11
|
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
12
|
+
margin: 20px;
|
13
|
+
}
|
14
|
+
|
15
|
+
h2 {
|
16
|
+
color: #ffffff;
|
17
|
+
text-align: center;
|
18
|
+
font-weight: 300;
|
19
|
+
margin-bottom: 20px;
|
20
|
+
}
|
21
|
+
|
22
|
+
.textline {
|
23
|
+
margin: 15px 0;
|
24
|
+
padding: 15px;
|
25
|
+
display: flex;
|
26
|
+
align-items: center;
|
27
|
+
gap: 15px;
|
28
|
+
}
|
29
|
+
.textline:last-child {
|
30
|
+
border-bottom: none;
|
31
|
+
}
|
32
|
+
|
33
|
+
.textline > p {
|
34
|
+
font-size: 24px;
|
35
|
+
flex: 1;
|
36
|
+
min-width: 200px;
|
37
|
+
}
|
38
|
+
|
39
|
+
.textline > em {
|
40
|
+
color: #aaa;
|
41
|
+
font-size: 0.9em;
|
42
|
+
margin-right: 10px;
|
43
|
+
}
|
44
|
+
|
45
|
+
.textline > button {
|
46
|
+
background-color: #1a73e8;
|
47
|
+
color: #ffffff;
|
48
|
+
border: none;
|
49
|
+
padding: 8px 15px;
|
50
|
+
font-size: 14px;
|
51
|
+
cursor: pointer;
|
52
|
+
transition: background-color 0.3s;
|
53
|
+
border-radius: 5px;
|
54
|
+
}
|
55
|
+
|
56
|
+
.textline > button:hover {
|
57
|
+
background-color: #1669c1;
|
58
|
+
cursor: pointer;
|
59
|
+
}
|
60
|
+
|
61
|
+
.textline-buttons {
|
62
|
+
display: flex;
|
63
|
+
gap: 10px;
|
64
|
+
}
|
65
|
+
|
66
|
+
@media (max-width: 600px) {
|
67
|
+
.textline {
|
68
|
+
flex-direction: column;
|
69
|
+
align-items: flex-start;
|
70
|
+
}
|
71
|
+
.textline-buttons{
|
72
|
+
margin-top: 10px;
|
73
|
+
}
|
74
|
+
.textline > strong{
|
75
|
+
min-width: auto;
|
76
|
+
}
|
77
|
+
}
|
78
|
+
|
79
|
+
.initial-event {
|
80
|
+
margin: 15px 0;
|
81
|
+
padding: 15px;
|
82
|
+
}
|
83
|
+
|
84
|
+
hr.initial-events-separator {
|
85
|
+
border: 0;
|
86
|
+
border-top: 2px solid #aaa;
|
87
|
+
margin: 20px 0;
|
88
|
+
}
|
89
|
+
|
90
|
+
.multi-line-checkbox {
|
91
|
+
transform: scale(1.5);
|
92
|
+
margin-right: 10px;
|
93
|
+
background-color: #00FFFF !important; /* Cyan/Electric Blue */
|
94
|
+
border: 4px solid #00FFFF; /* Keep the border the same color */
|
95
|
+
}
|
96
|
+
|
97
|
+
.multi-line-checkbox:checked {
|
98
|
+
/* You'll likely need to target the checkmark specifically */
|
99
|
+
/* Example assuming it's a pseudo-element with a font-based check: */
|
100
|
+
/* &::before { */
|
101
|
+
/* color: #FFFF00; /* Bright Yellow */
|
102
|
+
/* } */
|
103
|
+
/* If it's a background image, you might need to adjust the background or use a filter. */
|
104
|
+
}
|
105
|
+
|
106
|
+
</style>
|
107
|
+
</head>
|
108
|
+
<body>
|
109
|
+
<div id="initial-events">
|
110
|
+
|
111
|
+
</div>
|
112
|
+
<hr class="initial-events-separator" style="display: none;">
|
113
|
+
<div id="session-events">
|
114
|
+
|
115
|
+
</div>
|
116
|
+
<div>
|
117
|
+
<button onclick="window.location.href='/textreplacements'" style="margin-top: 20px; background-color: #1a73e8; color: #ffffff; border: none; padding: 10px 20px; font-size: 16px; cursor: pointer; transition: background-color 0.3s; border-radius: 5px;">
|
118
|
+
Text Replacements
|
119
|
+
</button>
|
120
|
+
</div>
|
121
|
+
<script>
|
122
|
+
let displayedEventIds = new Set();
|
123
|
+
let isTabActive = true;
|
124
|
+
let isFetching = false; // Flag to track if a fetch is in progress
|
125
|
+
let intervalId = 0;
|
126
|
+
const fetchInterval = 100; // Define the interval as a constant
|
127
|
+
|
128
|
+
// Drag selection variables
|
129
|
+
let isDragging = false;
|
130
|
+
let dragStartCheckbox = null;
|
131
|
+
let newCheckboxState = false;
|
132
|
+
let hoveredCheckboxes = new Set();
|
133
|
+
let checkboxes = []; // Will hold all checkbox elements
|
134
|
+
|
135
|
+
// Shift click selection variable
|
136
|
+
let lastChecked = null;
|
137
|
+
|
138
|
+
async function fetchEvents() {
|
139
|
+
if (document.hidden || isFetching) {
|
140
|
+
return;
|
141
|
+
}
|
142
|
+
isFetching = true
|
143
|
+
try {
|
144
|
+
const res = await fetch('/data');
|
145
|
+
if (!res.ok) {
|
146
|
+
throw new Error(`HTTP error! Status: ${res.status}`);
|
147
|
+
}
|
148
|
+
const events = await res.json();
|
149
|
+
|
150
|
+
events.forEach(ev => {
|
151
|
+
if (!displayedEventIds.has(ev.id)) {
|
152
|
+
addNewEvent(ev)
|
153
|
+
}
|
154
|
+
const checkbox = document.querySelector(`[data-event-id="${ev.id}"]`);
|
155
|
+
if (checkbox) {
|
156
|
+
checkbox.checked = ev.checked; // Update the checkbox state
|
157
|
+
}
|
158
|
+
});
|
159
|
+
checkboxes = Array.from(document.querySelectorAll('#session-events input[type="checkbox"]')); // Update checkboxes array after new events
|
160
|
+
} catch (error) {
|
161
|
+
console.error("Error fetching events:", error);
|
162
|
+
} finally {
|
163
|
+
isFetching = false;
|
164
|
+
}
|
165
|
+
}
|
166
|
+
|
167
|
+
function addNewEvent(event) {
|
168
|
+
const container = document.getElementById('session-events');
|
169
|
+
const div = document.createElement('div');
|
170
|
+
div.className = 'textline';
|
171
|
+
div.innerHTML = `
|
172
|
+
<input type="checkbox"
|
173
|
+
class="multi-line-checkbox"
|
174
|
+
id="multi-line-checkbox-${event.id}"
|
175
|
+
${event.checked ? 'checked' : ''}
|
176
|
+
aria-label="Mark item"
|
177
|
+
data-event-id="${event.id}"
|
178
|
+
onchange="toggleCheckbox('${event.id}', this.checked)">
|
179
|
+
<p>${event.text}</p>
|
180
|
+
<em>${event.time.replace(" GMT", "")}</em>
|
181
|
+
<div class="textline-buttons">
|
182
|
+
<button onclick="buttonClick('${event.id}', 'Screenshot')">Screenshot</button>
|
183
|
+
<button onclick="buttonClick('${event.id}', 'Audio')">Audio</button>
|
184
|
+
</div>
|
185
|
+
`;
|
186
|
+
container.appendChild(div);
|
187
|
+
displayedEventIds.add(event.id);
|
188
|
+
window.scrollTo({
|
189
|
+
top: document.documentElement.scrollHeight,
|
190
|
+
behavior: 'smooth'
|
191
|
+
});
|
192
|
+
}
|
193
|
+
|
194
|
+
function buttonClick(id, action) {
|
195
|
+
console.log(id);
|
196
|
+
const endpoint = action === 'Screenshot' ? '/get-screenshot' : '/play-audio';
|
197
|
+
fetch(endpoint, {
|
198
|
+
method: 'POST',
|
199
|
+
headers: { 'Content-Type': 'application/json' },
|
200
|
+
body: JSON.stringify({ id })
|
201
|
+
})
|
202
|
+
.then(response => {
|
203
|
+
if (!response.ok) {
|
204
|
+
throw new Error(`HTTP error! Status: ${response.status}`);
|
205
|
+
}
|
206
|
+
return response.json();
|
207
|
+
})
|
208
|
+
.then(data => {
|
209
|
+
console.log(`${action} action completed for event ID: ${id}`, data);
|
210
|
+
})
|
211
|
+
.catch(error => {
|
212
|
+
console.error(`Error performing ${action} action for event ID: ${id}`, error);
|
213
|
+
});
|
214
|
+
}
|
215
|
+
|
216
|
+
async function toggleCheckbox(id, checked) {
|
217
|
+
try {
|
218
|
+
const res = await fetch('/update', {
|
219
|
+
method: 'POST',
|
220
|
+
headers: { 'Content-Type': 'application/json' },
|
221
|
+
body: JSON.stringify({ id, checked })
|
222
|
+
});
|
223
|
+
if (!res.ok) {
|
224
|
+
throw new Error(`HTTP error! Status: ${res.status}`);
|
225
|
+
}
|
226
|
+
} catch (error) {
|
227
|
+
console.error("Error updating checkbox:", error);
|
228
|
+
}
|
229
|
+
}
|
230
|
+
|
231
|
+
function handleMouseDown(e) {
|
232
|
+
if (e.target.type === 'checkbox') {
|
233
|
+
newCheckboxState = !e.target.checked;
|
234
|
+
isDragging = true;
|
235
|
+
dragStartCheckbox = e.target;
|
236
|
+
hoveredCheckboxes.add(e.target)
|
237
|
+
}
|
238
|
+
}
|
239
|
+
|
240
|
+
function handleMouseUp(e) {
|
241
|
+
if (e.target === dragStartCheckbox) {
|
242
|
+
isDragging = false;
|
243
|
+
dragStartCheckbox = null;
|
244
|
+
return;
|
245
|
+
}
|
246
|
+
if (isDragging) {
|
247
|
+
isDragging = false;
|
248
|
+
|
249
|
+
hoveredCheckboxes.forEach(checkbox => {
|
250
|
+
checkbox.checked = newCheckboxState; // Set all hovered checkboxes to the new state
|
251
|
+
const eventId = checkbox.dataset.eventId;
|
252
|
+
toggleCheckbox(eventId, newCheckboxState);
|
253
|
+
});
|
254
|
+
isDragging = false;
|
255
|
+
dragStartCheckbox = null;
|
256
|
+
}
|
257
|
+
|
258
|
+
}
|
259
|
+
|
260
|
+
function handleMouseOver(e) {
|
261
|
+
if (!isDragging || e.target.type !== 'checkbox' || e.target === dragStartCheckbox) {
|
262
|
+
return;
|
263
|
+
}
|
264
|
+
e.preventDefault(); // Prevent text selection during drag
|
265
|
+
if (dragStartCheckbox) {
|
266
|
+
hoveredCheckboxes.add(e.target);
|
267
|
+
}
|
268
|
+
}
|
269
|
+
|
270
|
+
function handleCheckboxClick(e) {
|
271
|
+
if (!e.shiftKey) {
|
272
|
+
lastChecked = e.target;
|
273
|
+
return;
|
274
|
+
}
|
275
|
+
|
276
|
+
if (!lastChecked) return;
|
277
|
+
|
278
|
+
let inBetween = false;
|
279
|
+
checkboxes.forEach(checkbox => {
|
280
|
+
if (checkbox === e.target || checkbox === lastChecked) {
|
281
|
+
inBetween = !inBetween;
|
282
|
+
}
|
283
|
+
|
284
|
+
if (inBetween) {
|
285
|
+
checkbox.checked = lastChecked.checked;
|
286
|
+
const eventId = checkbox.dataset.eventId;
|
287
|
+
toggleCheckbox(eventId, lastChecked.checked);
|
288
|
+
}
|
289
|
+
});
|
290
|
+
|
291
|
+
lastChecked = e.target;
|
292
|
+
}
|
293
|
+
|
294
|
+
document.addEventListener('mousedown', handleMouseDown);
|
295
|
+
document.addEventListener('mouseup', handleMouseUp);
|
296
|
+
document.addEventListener('mouseover', handleMouseOver);
|
297
|
+
document.addEventListener('click', handleCheckboxClick);
|
298
|
+
|
299
|
+
console.log("Initial load, fetching events and starting interval...");
|
300
|
+
fetchEvents();
|
301
|
+
intervalId = setInterval(async () => {
|
302
|
+
if (isTabActive) {
|
303
|
+
await fetchEvents();
|
304
|
+
}
|
305
|
+
}, fetchInterval);
|
306
|
+
|
307
|
+
window.scrollTo({
|
308
|
+
top: document.documentElement.scrollHeight,
|
309
|
+
behavior: 'smooth'
|
310
|
+
});
|
311
|
+
</script>
|
312
|
+
</body>
|
313
|
+
</html>
|
Binary file
|
Binary file
|
@@ -0,0 +1,234 @@
|
|
1
|
+
import datetime
|
2
|
+
import json
|
3
|
+
import os
|
4
|
+
from dataclasses import dataclass
|
5
|
+
|
6
|
+
import flask
|
7
|
+
|
8
|
+
from GameSentenceMiner.text_log import GameLine, get_line_by_id
|
9
|
+
from flask import request, jsonify, send_from_directory
|
10
|
+
import webbrowser
|
11
|
+
from GameSentenceMiner import obs
|
12
|
+
from GameSentenceMiner.configuration import logger, get_config
|
13
|
+
from GameSentenceMiner.util import TEXT_REPLACEMENTS_FILE
|
14
|
+
|
15
|
+
port = get_config().general.texthooker_port
|
16
|
+
url = f"http://localhost:{port}"
|
17
|
+
|
18
|
+
|
19
|
+
@dataclass
|
20
|
+
class EventItem:
|
21
|
+
line: 'GameLine'
|
22
|
+
id: str
|
23
|
+
text: str
|
24
|
+
time: datetime.datetime
|
25
|
+
timestamp: float
|
26
|
+
checked: bool = False
|
27
|
+
|
28
|
+
def to_dict(self):
|
29
|
+
return {
|
30
|
+
'id': self.id,
|
31
|
+
'text': self.text,
|
32
|
+
'time': self.time,
|
33
|
+
'timestamp': self.timestamp,
|
34
|
+
'checked': self.checked
|
35
|
+
}
|
36
|
+
|
37
|
+
class EventManager:
|
38
|
+
events: list[EventItem]
|
39
|
+
events_dict: dict[str, EventItem] = {}
|
40
|
+
line_for_audio: GameLine = None
|
41
|
+
line_for_screenshot: GameLine = None
|
42
|
+
|
43
|
+
def __init__(self):
|
44
|
+
self.events = []
|
45
|
+
self.events_dict = {}
|
46
|
+
|
47
|
+
def __iter__(self):
|
48
|
+
return iter(self.events)
|
49
|
+
|
50
|
+
def replace_events(self, new_events: list[EventItem]):
|
51
|
+
self.events = new_events
|
52
|
+
|
53
|
+
def add_gameline(self, line: GameLine):
|
54
|
+
new_event = EventItem(line, line.id, line.text, line.time, line.time.timestamp(), False)
|
55
|
+
self.events_dict[line.id] = new_event
|
56
|
+
self.events.append(new_event)
|
57
|
+
|
58
|
+
def get_events(self):
|
59
|
+
return self.events
|
60
|
+
|
61
|
+
def add_event(self, event):
|
62
|
+
self.events.append(event)
|
63
|
+
|
64
|
+
def get(self, event_id):
|
65
|
+
return self.events_dict.get(event_id)
|
66
|
+
|
67
|
+
event_manager = EventManager()
|
68
|
+
|
69
|
+
server_start_time = datetime.datetime.now().timestamp()
|
70
|
+
|
71
|
+
app = flask.Flask(__name__)
|
72
|
+
|
73
|
+
# Load data from the JSON file
|
74
|
+
def load_data_from_file():
|
75
|
+
if os.path.exists(TEXT_REPLACEMENTS_FILE):
|
76
|
+
with open(TEXT_REPLACEMENTS_FILE, 'r', encoding='utf-8') as file:
|
77
|
+
return json.load(file)
|
78
|
+
return {"enabled": True, "args": {"replacements": {}}}
|
79
|
+
|
80
|
+
# Save data to the JSON file
|
81
|
+
def save_data_to_file(data):
|
82
|
+
with open(TEXT_REPLACEMENTS_FILE, 'w', encoding='utf-8') as file:
|
83
|
+
json.dump(data, file, indent=4, ensure_ascii=False)
|
84
|
+
|
85
|
+
@app.route('/load-data', methods=['GET'])
|
86
|
+
def load_data():
|
87
|
+
try:
|
88
|
+
data = load_data_from_file()
|
89
|
+
return jsonify(data), 200
|
90
|
+
except Exception as e:
|
91
|
+
return jsonify({"error": f"Failed to load data: {str(e)}"}), 500
|
92
|
+
|
93
|
+
@app.route('/save-data', methods=['POST'])
|
94
|
+
def save_data():
|
95
|
+
try:
|
96
|
+
data = request.get_json()
|
97
|
+
if not isinstance(data, dict):
|
98
|
+
return jsonify({"error": "Invalid data format"}), 400
|
99
|
+
|
100
|
+
# Save updated data
|
101
|
+
save_data_to_file(data)
|
102
|
+
return jsonify({"message": "Data saved successfully"}), 200
|
103
|
+
except Exception as e:
|
104
|
+
return jsonify({"error": f"Failed to save data: {str(e)}"}), 500
|
105
|
+
|
106
|
+
def inject_server_start_time(html_content, timestamp):
|
107
|
+
placeholder = '<script>'
|
108
|
+
replacement = f'<script>const serverStartTime = {timestamp};'
|
109
|
+
return html_content.replace(placeholder, replacement)
|
110
|
+
|
111
|
+
@app.route('/favicon.ico')
|
112
|
+
def favicon():
|
113
|
+
return send_from_directory(os.path.join(app.root_path, 'static'),
|
114
|
+
'favicon.ico', mimetype='image/vnd.microsoft.icon')
|
115
|
+
|
116
|
+
@app.route('/<path:filename>')
|
117
|
+
def serve_static(filename):
|
118
|
+
return send_from_directory('pages', filename)
|
119
|
+
|
120
|
+
@app.route('/')
|
121
|
+
def index():
|
122
|
+
with open(os.path.join(app.root_path, 'static', 'utility.html'), encoding='utf-8') as file:
|
123
|
+
return file.read()
|
124
|
+
|
125
|
+
@app.route('/texthooker')
|
126
|
+
def texthooker():
|
127
|
+
with open(os.path.join(app.root_path, 'static', 'utility.html'), encoding='utf-8') as file:
|
128
|
+
return file.read()
|
129
|
+
|
130
|
+
@app.route('/textreplacements')
|
131
|
+
def textreplacements():
|
132
|
+
with open(os.path.join(app.root_path, 'static', 'text_replacements.html'), encoding='utf-8') as file:
|
133
|
+
return file.read()
|
134
|
+
|
135
|
+
@app.route('/data', methods=['GET'])
|
136
|
+
def get_data():
|
137
|
+
return jsonify([event.to_dict() for event in event_manager])
|
138
|
+
|
139
|
+
|
140
|
+
def add_event_to_texthooker(line: GameLine):
|
141
|
+
logger.info("Adding event to web server: %s", line.text)
|
142
|
+
event_manager.add_gameline(line)
|
143
|
+
|
144
|
+
|
145
|
+
@app.route('/update', methods=['POST'])
|
146
|
+
def update_event():
|
147
|
+
data = request.get_json()
|
148
|
+
event_id = data.get('id')
|
149
|
+
checked = data.get('checked')
|
150
|
+
|
151
|
+
logger.info(event_id)
|
152
|
+
logger.info(checked)
|
153
|
+
|
154
|
+
if event_id is None or checked is None:
|
155
|
+
return jsonify({'error': 'Missing id or checked status'}), 400
|
156
|
+
|
157
|
+
logger.info(event_manager.get(event_id))
|
158
|
+
|
159
|
+
event_manager.get(event_id).checked = checked
|
160
|
+
|
161
|
+
return jsonify({'error': 'Event not found'}), 404
|
162
|
+
|
163
|
+
@app.route('/get-screenshot', methods=['Post'])
|
164
|
+
def get_screenshot():
|
165
|
+
"""Endpoint to get a screenshot of the current game screen."""
|
166
|
+
data = request.get_json()
|
167
|
+
event_id = data.get('id')
|
168
|
+
if event_id is None:
|
169
|
+
return jsonify({'error': 'Missing id'}), 400
|
170
|
+
event_manager.line_for_screenshot = get_line_by_id(event_id)
|
171
|
+
obs.save_replay_buffer()
|
172
|
+
return jsonify({}), 200
|
173
|
+
|
174
|
+
@app.route('/play-audio', methods=['POST'])
|
175
|
+
def play_audio():
|
176
|
+
"""Endpoint to play audio for a specific event."""
|
177
|
+
data = request.get_json()
|
178
|
+
event_id = data.get('id')
|
179
|
+
if event_id is None:
|
180
|
+
return jsonify({'error': 'Missing id'}), 400
|
181
|
+
event_manager.line_for_audio = get_line_by_id(event_id)
|
182
|
+
obs.save_replay_buffer()
|
183
|
+
return jsonify({}), 200
|
184
|
+
|
185
|
+
|
186
|
+
# @app.route('/store-events', methods=['POST'])
|
187
|
+
# def store_events():
|
188
|
+
# data = request.get_json()
|
189
|
+
# events_data = data.get('events', [])
|
190
|
+
#
|
191
|
+
# if not isinstance(events_data, list):
|
192
|
+
# return jsonify({'error': 'Invalid data format. Expected an array of events.'}), 400
|
193
|
+
#
|
194
|
+
# for event_data in events_data:
|
195
|
+
# if not all(k in event_data for k in ('id', 'text', 'time', 'checked')):
|
196
|
+
# return jsonify({'error': 'Invalid event structure. Missing keys.'}), 400
|
197
|
+
# if not (isinstance(event_data['id'], (int, float)) and
|
198
|
+
# isinstance(event_data['text'], str) and
|
199
|
+
# isinstance(event_data['time'], str) and
|
200
|
+
# isinstance(event_data['checked'], bool)):
|
201
|
+
# return jsonify({'error': 'Invalid event structure. Incorrect data types.'}), 400
|
202
|
+
#
|
203
|
+
# event_manager.replace_events([EventItem(item['id'], item['text'], item['time'], item.get(['timestamp'], 0), item['checked']) for item in data])
|
204
|
+
#
|
205
|
+
# return jsonify({'message': 'Events successfully stored on server.', 'receivedEvents': data}), 200
|
206
|
+
|
207
|
+
|
208
|
+
def get_selected_lines():
|
209
|
+
return [item.line for item in event_manager if item.checked]
|
210
|
+
|
211
|
+
def are_lines_selected():
|
212
|
+
return any(item.checked for item in event_manager)
|
213
|
+
|
214
|
+
def reset_checked_lines():
|
215
|
+
for item in event_manager:
|
216
|
+
item.checked = False
|
217
|
+
|
218
|
+
def open_texthooker():
|
219
|
+
webbrowser.open(url + '/texthooker')
|
220
|
+
|
221
|
+
def start_web_server():
|
222
|
+
logger.debug("Starting web server...")
|
223
|
+
import logging
|
224
|
+
log = logging.getLogger('werkzeug')
|
225
|
+
log.setLevel(logging.ERROR) # Set to ERROR to suppress most logs
|
226
|
+
|
227
|
+
# Open the default browser
|
228
|
+
if get_config().general.open_multimine_on_startup:
|
229
|
+
open_texthooker()
|
230
|
+
|
231
|
+
app.run(port=port, debug=False) # debug=True provides helpful error messages during development
|
232
|
+
|
233
|
+
if __name__ == '__main__':
|
234
|
+
start_web_server()
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: GameSentenceMiner
|
3
|
-
Version: 2.
|
3
|
+
Version: 2.8.0
|
4
4
|
Summary: A tool for mining sentences from games. Update: Multi-Line Mining! Fixed!
|
5
5
|
Author-email: Beangate <bpwhelan95@gmail.com>
|
6
6
|
License: MIT License
|
@@ -35,6 +35,7 @@ Requires-Dist: pystray
|
|
35
35
|
Requires-Dist: pywin32; sys_platform == "win32"
|
36
36
|
Requires-Dist: google-generativeai
|
37
37
|
Requires-Dist: pygetwindow; sys_platform == "win32"
|
38
|
+
Requires-Dist: flask
|
38
39
|
Dynamic: license-file
|
39
40
|
|
40
41
|
# Game Sentence Miner
|
@@ -0,0 +1,58 @@
|
|
1
|
+
GameSentenceMiner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
GameSentenceMiner/anki.py,sha256=2Oy3a8Od8DC3vs1-ktS6KF1iUj74Ptd0SCe-U0cZcfc,14206
|
3
|
+
GameSentenceMiner/config_gui.py,sha256=8Wc5YdAyaYT09T4pvHiL5Q4NiKRaL19CS3A0JhnSu6k,68822
|
4
|
+
GameSentenceMiner/configuration.py,sha256=NQMsM23p4_keTFXEaKMTMacIx0TCdk4-TIl4usZkJy4,20801
|
5
|
+
GameSentenceMiner/electron_config.py,sha256=dGcPYCISPehXubYSzsDuI2Gl092MYK0u3bTnkL9Jh1Y,9787
|
6
|
+
GameSentenceMiner/ffmpeg.py,sha256=mcEcJnYl06oJGbLaymFUfqClFiHf6Hhf2SXo3UV9tvM,13378
|
7
|
+
GameSentenceMiner/gametext.py,sha256=pPJO-NYW15SqAG-IQ0ZHpPt8py4g0yL1Lx0UlR-en-4,5431
|
8
|
+
GameSentenceMiner/gsm.py,sha256=6BcCYNV9tGQ8FgDh6Y8bPbZ66KWP57oBUqIoVF_nnXg,24667
|
9
|
+
GameSentenceMiner/model.py,sha256=JdnkT4VoPOXmOpRgFdvERZ09c9wLN6tUJxdrKlGZcqo,5305
|
10
|
+
GameSentenceMiner/notification.py,sha256=FY39ChSRK0Y8TQ6lBGsLnpZUFPtFpSy2tweeXVoV7kc,2809
|
11
|
+
GameSentenceMiner/obs.py,sha256=DLTcK48Dqcg-5BYC2Fp-tPCO4dVI9FuVYbwIawECkwQ,8977
|
12
|
+
GameSentenceMiner/package.py,sha256=YlS6QRMuVlm6mdXx0rlXv9_3erTGS21jaP3PNNWfAH0,1250
|
13
|
+
GameSentenceMiner/text_log.py,sha256=tBuZ8ElUgPtiQV8U6U90kmRxposwIkL3fjOYejdzikc,5153
|
14
|
+
GameSentenceMiner/util.py,sha256=H4JxfwysfnfiEe0-jZGeYKEQWX1HN-LvIKAo-OZYaoE,8498
|
15
|
+
GameSentenceMiner/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
16
|
+
GameSentenceMiner/ai/gemini.py,sha256=6kTQPuRH16D-1srhrWa5uPGIy-jBqNm9eRdKBSbpiXA,5423
|
17
|
+
GameSentenceMiner/communication/__init__.py,sha256=_jGn9PJxtOAOPtJ2rI-Qu9hEHVZVpIvWlxKvqk91_zI,638
|
18
|
+
GameSentenceMiner/communication/send.py,sha256=oOJdCS6-LNX90amkRn5FL2xqx6THGm56zHR2ntVIFTE,229
|
19
|
+
GameSentenceMiner/communication/websocket.py,sha256=pTcUe_ZZRp9REdSU4qalhPmbT_1DKa7w18j6RfFLELA,3074
|
20
|
+
GameSentenceMiner/downloader/Untitled_json.py,sha256=RUUl2bbbCpUDUUS0fP0tdvf5FngZ7ILdA_J5TFYAXUQ,15272
|
21
|
+
GameSentenceMiner/downloader/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
22
|
+
GameSentenceMiner/downloader/download_tools.py,sha256=mI1u_FGBmBqDIpCH3jOv8DOoZ3obgP5pIf9o9SVfX2Q,8131
|
23
|
+
GameSentenceMiner/ocr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
24
|
+
GameSentenceMiner/ocr/gsm_ocr_config.py,sha256=zagsB4UD9mmZX_r6dFBCXZqdDa0XGk-RvIqbKoPB9lQ,1932
|
25
|
+
GameSentenceMiner/ocr/ocrconfig.py,sha256=_tY8mjnzHMJrLS8E5pHqYXZjMuLoGKYgJwdhYgN-ny4,6466
|
26
|
+
GameSentenceMiner/ocr/oneocr_dl.py,sha256=o3ANp5IodEQoQ8GPcJdg9Y8JzA_lictwnebFPwwUZVk,10144
|
27
|
+
GameSentenceMiner/ocr/owocr_area_selector.py,sha256=a2i4kW9g9_DJKt6OJ5dlSiqJGiXkKyqAQaAiXBdrq8U,46680
|
28
|
+
GameSentenceMiner/ocr/owocr_helper.py,sha256=wZsWV0e8OfI752lezrJccci05MmXkxMN10QYLMY34uk,16010
|
29
|
+
GameSentenceMiner/owocr/owocr/__init__.py,sha256=opjBOyGGyEqZCE6YdZPnyt7nVfiwyELHsXA0jAsjm14,25
|
30
|
+
GameSentenceMiner/owocr/owocr/__main__.py,sha256=r8MI6RAmbkTWqOJ59uvXoDS7CSw5jX5war9ULGWELrA,128
|
31
|
+
GameSentenceMiner/owocr/owocr/config.py,sha256=738QCJHEWpFhMh966plOcXYWwcshSiRsxjjIwldeTtI,7461
|
32
|
+
GameSentenceMiner/owocr/owocr/lens_betterproto.py,sha256=oNoISsPilVVRBBPVDtb4-roJtAhp8ZAuFTci3TGXtMc,39141
|
33
|
+
GameSentenceMiner/owocr/owocr/ocr.py,sha256=n24Xg8Z8dbcgLpq1u4d22z3tLV1evmf0dK3-Xocv3vs,39878
|
34
|
+
GameSentenceMiner/owocr/owocr/run.py,sha256=_1zAbJooOR051tsFh0iCyjtlVP5BVE0KFEl7i95mUd0,47805
|
35
|
+
GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py,sha256=fjJ3CSXLti3WboGPpmsa7MWOwIXsfpHC8N4zKahGGY0,3346
|
36
|
+
GameSentenceMiner/vad/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
37
|
+
GameSentenceMiner/vad/silero_trim.py,sha256=ULf3zwS-JMsY82cKF7gZxREHw8L6lgpWF2U1YqgE9Oc,1681
|
38
|
+
GameSentenceMiner/vad/vosk_helper.py,sha256=125X8C9NxFPlWWpoNsbOnEqKx8RCjXN109zNx_QXhyg,6070
|
39
|
+
GameSentenceMiner/vad/whisper_helper.py,sha256=JJ-iltCh813XdjyEw0Wn5DaErf6PDqfH0Efu1Md8cIY,3543
|
40
|
+
GameSentenceMiner/web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
41
|
+
GameSentenceMiner/web/texthooking_page.py,sha256=rcxCw81jjgyWPslVTFhQ5gaGs3FVN1X2CF-nmcCjo78,7325
|
42
|
+
GameSentenceMiner/web/static/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
43
|
+
GameSentenceMiner/web/static/apple-touch-icon.png,sha256=OcMI8af_68DA_tweOsQ5LytTyMwm7-hPW07IfrOVgEs,46132
|
44
|
+
GameSentenceMiner/web/static/favicon-96x96.png,sha256=lOePzjiKl1JY2J1kT_PMdyEnrlJmi5GWbmXJunM12B4,16502
|
45
|
+
GameSentenceMiner/web/static/favicon.ico,sha256=7d25r_FBqRSNsAoEHpSzNoT7zyVt2DJRLNDNq_HYoX8,15086
|
46
|
+
GameSentenceMiner/web/static/favicon.svg,sha256=x305AP6WlXGtrXIZlaQspdLmwteoFYUoe5FyJ9MYlJ8,11517
|
47
|
+
GameSentenceMiner/web/static/site.webmanifest,sha256=kaeNT-FjFt-T7JGzOhXH7YSqsrDeiplZ2kDxCN_CFU4,436
|
48
|
+
GameSentenceMiner/web/static/style.css,sha256=bPZK0NVMuyRl5NNDuT7ZTzVLKlvSsdmeVHmAW4y5FM0,7001
|
49
|
+
GameSentenceMiner/web/static/text_replacements.html,sha256=LJ2qBEZvyMaBJLLJzDIw0baiTeusplbn4jkpyHM6ZBI,8613
|
50
|
+
GameSentenceMiner/web/static/utility.html,sha256=DlXaglC2ZkBfEKK1sxuS8Dc5Xj09Yqi27qQuEF0c59w,9812
|
51
|
+
GameSentenceMiner/web/static/web-app-manifest-192x192.png,sha256=EfSNnBmsSaLfESbkGfYwbKzcjKOdzuWo18ABADfN974,51117
|
52
|
+
GameSentenceMiner/web/static/web-app-manifest-512x512.png,sha256=wyqgCWCrLEUxSRXmaA3iJEESd-vM-ZmlTtZFBY4V8Pk,230819
|
53
|
+
gamesentenceminer-2.8.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
54
|
+
gamesentenceminer-2.8.0.dist-info/METADATA,sha256=bzYt80WzMvh_OIfxjdl8Kma1tD-uL9VjNJv2K7BOnS0,5912
|
55
|
+
gamesentenceminer-2.8.0.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
|
56
|
+
gamesentenceminer-2.8.0.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
|
57
|
+
gamesentenceminer-2.8.0.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
|
58
|
+
gamesentenceminer-2.8.0.dist-info/RECORD,,
|