GameSentenceMiner 2.16.1__tar.gz → 2.16.3__tar.gz
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-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/config_gui.py +15 -3
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/gsm.py +0 -1
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/locales/en_us.json +4 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/locales/ja_jp.json +4 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/locales/zh_cn.json +4 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/util/configuration.py +5 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/util/db.py +33 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/util/get_overlay_coords.py +59 -2
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/vad.py +2 -2
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/web/database_api.py +170 -5
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/web/static/js/shared.js +67 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/web/static/js/stats.js +141 -2
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/web/templates/anki_stats.html +4 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/web/templates/components/navigation.html +4 -1
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/web/templates/database.html +3 -0
- gamesentenceminer-2.16.3/GameSentenceMiner/web/templates/index.html +50 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/web/templates/search.html +3 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/web/templates/stats.html +36 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/web/texthooking_page.py +7 -1
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner.egg-info/PKG-INFO +1 -1
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/PKG-INFO +1 -1
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/pyproject.toml +1 -1
- gamesentenceminer-2.16.1/GameSentenceMiner/web/templates/index.html +0 -50
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/__init__.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/ai/__init__.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/ai/ai_prompting.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/anki.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/assets/__init__.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/assets/icon.png +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/assets/icon128.png +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/assets/icon256.png +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/assets/icon32.png +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/assets/icon512.png +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/assets/icon64.png +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/assets/pickaxe.png +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/gametext.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/obs.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/ocr/__init__.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/ocr/gsm_ocr_config.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/ocr/ocrconfig.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/ocr/owocr_area_selector.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/ocr/owocr_helper.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/ocr/ss_picker.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/owocr/owocr/__init__.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/owocr/owocr/__main__.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/owocr/owocr/config.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/owocr/owocr/lens_betterproto.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/owocr/owocr/ocr.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/owocr/owocr/run.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/tools/__init__.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/tools/audio_offset_selector.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/tools/furigana_filter_preview.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/tools/ss_selector.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/tools/window_transparency.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/util/__init__.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/util/communication/__init__.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/util/communication/send.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/util/communication/websocket.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/util/downloader/Untitled_json.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/util/downloader/__init__.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/util/downloader/download_tools.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/util/downloader/oneocr_dl.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/util/electron_config.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/util/ffmpeg.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/util/gsm_utils.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/util/model.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/util/notification.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/util/text_log.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/util/win10toast/__init__.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/util/win10toast/__main__.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/web/__init__.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/web/events.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/web/service.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/web/static/__init__.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/web/static/apple-touch-icon.png +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/web/static/css/kanji-grid.css +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/web/static/css/search.css +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/web/static/css/shared.css +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/web/static/css/stats.css +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/web/static/favicon-96x96.png +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/web/static/favicon.ico +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/web/static/favicon.svg +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/web/static/js/anki_stats.js +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/web/static/js/database.js +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/web/static/js/kanji-grid.js +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/web/static/js/search.js +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/web/static/site.webmanifest +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/web/static/style.css +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/web/static/web-app-manifest-192x192.png +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/web/static/web-app-manifest-512x512.png +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/web/stats.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/web/templates/components/theme-styles.html +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/web/templates/utility.html +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/web/websockets.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/wip/__init___.py +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner.egg-info/SOURCES.txt +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner.egg-info/dependency_links.txt +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner.egg-info/entry_points.txt +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner.egg-info/requires.txt +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner.egg-info/top_level.txt +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/LICENSE +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/README.md +0 -0
- {gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/setup.cfg +0 -0
@@ -391,9 +391,10 @@ class ConfigApp:
|
|
391
391
|
self.use_canned_context_prompt_value = tk.BooleanVar(value=self.settings.ai.use_canned_context_prompt)
|
392
392
|
self.ai_dialogue_context_length_value = tk.StringVar(value=str(self.settings.ai.dialogue_context_length))
|
393
393
|
|
394
|
-
#
|
394
|
+
# Overlay Settings
|
395
395
|
self.overlay_websocket_port_value = tk.StringVar(value=str(self.settings.overlay.websocket_port))
|
396
396
|
self.overlay_websocket_send_value = tk.BooleanVar(value=self.settings.overlay.monitor_to_capture)
|
397
|
+
self.overlay_engine_value = tk.StringVar(value=self.settings.overlay.engine)
|
397
398
|
|
398
399
|
# Master Config Settings
|
399
400
|
self.switch_to_default_if_not_found_value = tk.BooleanVar(value=self.master_config.switch_to_default_if_not_found)
|
@@ -623,7 +624,8 @@ class ConfigApp:
|
|
623
624
|
),
|
624
625
|
overlay=Overlay(
|
625
626
|
websocket_port=int(self.overlay_websocket_port_value.get()),
|
626
|
-
monitor_to_capture=self.overlay_monitor.current() if self.monitors else 0
|
627
|
+
monitor_to_capture=self.overlay_monitor.current() if self.monitors else 0,
|
628
|
+
engine=OverlayEngine(self.overlay_engine_value.get()).value if self.overlay_engine_value.get() else OverlayEngine.LENS.value
|
627
629
|
)
|
628
630
|
# wip=WIP(
|
629
631
|
# overlay_websocket_port=int(self.overlay_websocket_port_value.get()),
|
@@ -2221,7 +2223,17 @@ class ConfigApp:
|
|
2221
2223
|
self.overlay_monitor.current(0)
|
2222
2224
|
self.overlay_monitor.config(state="disabled")
|
2223
2225
|
self.current_row += 1
|
2224
|
-
|
2226
|
+
|
2227
|
+
# Overlay Engine Selection
|
2228
|
+
overlay_engine_i18n = overlay_i18n.get('overlay_engine', {})
|
2229
|
+
HoverInfoLabelWidget(overlay_frame, text=overlay_engine_i18n.get('label', '...'),
|
2230
|
+
tooltip=overlay_engine_i18n.get('tooltip', '...'),
|
2231
|
+
row=self.current_row, column=0)
|
2232
|
+
self.overlay_engine = ttk.Combobox(overlay_frame, values=[e.value for e in OverlayEngine], state="readonly",
|
2233
|
+
textvariable=self.overlay_engine_value)
|
2234
|
+
self.overlay_engine.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
2235
|
+
self.current_row += 1
|
2236
|
+
|
2225
2237
|
if self.monitors:
|
2226
2238
|
# Ensure the index is valid
|
2227
2239
|
monitor_index = self.settings.overlay.monitor_to_capture
|
@@ -568,6 +568,10 @@
|
|
568
568
|
"label": "Monitor to Capture:",
|
569
569
|
"tooltip": "Select the monitor to capture (1-based index).",
|
570
570
|
"not_detected": "OwOCR Not Detected"
|
571
|
+
},
|
572
|
+
"overlay_engine": {
|
573
|
+
"label": "Overlay Engine:",
|
574
|
+
"tooltip": "Select the OCR engine for the overlay. If you use lens, and are on windows, it will use OneOCR to optimize the scan."
|
571
575
|
}
|
572
576
|
},
|
573
577
|
"wip": {
|
@@ -567,6 +567,10 @@
|
|
567
567
|
"label": "キャプチャ対象モニター:",
|
568
568
|
"tooltip": "キャプチャするモニターを選択(1から始まるインデックス)。",
|
569
569
|
"not_detected": "OwOCRが検出されません"
|
570
|
+
},
|
571
|
+
"overlay_engine": {
|
572
|
+
"label": "オーバーレイエンジン:",
|
573
|
+
"tooltip": "オーバーレイのOCRエンジンを選択します。Lensを使用していてWindowsの場合、スキャンを最適化するためにOneOCRを使用します。"
|
570
574
|
}
|
571
575
|
},
|
572
576
|
"wip": {
|
@@ -556,6 +556,10 @@
|
|
556
556
|
"label": "捕获的显示器:",
|
557
557
|
"tooltip": "选择要捕获的显示器(从1开始的索引)。",
|
558
558
|
"not_detected": "未检测到 OwOCR"
|
559
|
+
},
|
560
|
+
"overlay_engine": {
|
561
|
+
"label": "覆盖层引擎:",
|
562
|
+
"tooltip": "为覆盖层选择 OCR 引擎。如果您使用的是 lens,并且在 windows 上,它将使用 OneOCR 来优化扫描。"
|
559
563
|
}
|
560
564
|
},
|
561
565
|
"wip": {
|
{gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/util/configuration.py
RENAMED
@@ -630,10 +630,15 @@ class Ai:
|
|
630
630
|
self.gemini_model = 'gemini-2.5-flash-lite'
|
631
631
|
|
632
632
|
|
633
|
+
class OverlayEngine(str, Enum):
|
634
|
+
LENS = 'lens'
|
635
|
+
ONEOCR = 'oneocr'
|
636
|
+
|
633
637
|
@dataclass_json
|
634
638
|
@dataclass
|
635
639
|
class Overlay:
|
636
640
|
websocket_port: int = 55499
|
641
|
+
engine: str = OverlayEngine.LENS.value
|
637
642
|
monitor_to_capture: int = 0
|
638
643
|
|
639
644
|
def __post_init__(self):
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
import json
|
4
4
|
import os
|
5
|
+
import shutil
|
5
6
|
import sqlite3
|
6
7
|
from sys import platform
|
7
8
|
import time
|
@@ -10,6 +11,7 @@ import threading
|
|
10
11
|
|
11
12
|
from GameSentenceMiner.util.text_log import GameLine
|
12
13
|
from GameSentenceMiner.util.configuration import logger, is_dev
|
14
|
+
import gzip
|
13
15
|
|
14
16
|
|
15
17
|
class SQLiteDB:
|
@@ -399,6 +401,37 @@ def get_db_directory():
|
|
399
401
|
return os.path.join(config_dir, 'gsm.db')
|
400
402
|
|
401
403
|
|
404
|
+
# Backup and compress the database on load, with today's date, up to 5 days ago (clean up old backups)
|
405
|
+
def backup_db(db_path: str):
|
406
|
+
backup_dir = os.path.join(os.path.dirname(db_path), "backup", "database")
|
407
|
+
os.makedirs(backup_dir, exist_ok=True)
|
408
|
+
today = time.strftime("%Y-%m-%d")
|
409
|
+
backup_file = os.path.join(backup_dir, f"gsm_{today}.db.gz")
|
410
|
+
|
411
|
+
# Test, remove backups older than 60 minutes
|
412
|
+
# cutoff = time.time() - 60 * 60
|
413
|
+
# Clean up backups older than 5 days
|
414
|
+
cutoff = time.time() - 5 * 24 * 60 * 60
|
415
|
+
for fname in os.listdir(backup_dir):
|
416
|
+
fpath = os.path.join(backup_dir, fname)
|
417
|
+
if fname.startswith("gsm_") and fname.endswith(".db.gz"):
|
418
|
+
try:
|
419
|
+
file_time = os.path.getmtime(fpath)
|
420
|
+
if file_time < cutoff:
|
421
|
+
os.remove(fpath)
|
422
|
+
logger.info(f"Old backup removed: {fpath}")
|
423
|
+
except Exception as e:
|
424
|
+
logger.warning(f"Failed to remove old backup {fpath}: {e}")
|
425
|
+
|
426
|
+
# Create backup if not already present for today
|
427
|
+
if not os.path.exists(backup_file):
|
428
|
+
with open(db_path, "rb") as f_in, open(backup_file, "wb") as f_out:
|
429
|
+
with gzip.GzipFile(fileobj=f_out, mode="wb") as gz_out:
|
430
|
+
shutil.copyfileobj(f_in, gz_out)
|
431
|
+
logger.info(f"Database backup created: {backup_file}")
|
432
|
+
|
433
|
+
backup_db(get_db_directory())
|
434
|
+
|
402
435
|
gsm_db = SQLiteDB(get_db_directory())
|
403
436
|
|
404
437
|
for cls in [AIModelsTable, GameLinesTable]:
|
{gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/util/get_overlay_coords.py
RENAMED
@@ -1,19 +1,67 @@
|
|
1
1
|
import asyncio
|
2
2
|
import io
|
3
3
|
import base64
|
4
|
+
import json
|
4
5
|
import math
|
5
6
|
import os
|
6
7
|
import time
|
7
8
|
from PIL import Image
|
8
9
|
from typing import Dict, Any, List, Tuple
|
10
|
+
import json
|
11
|
+
from rapidfuzz.distance import Levenshtein
|
9
12
|
|
10
13
|
# Local application imports
|
11
14
|
from GameSentenceMiner.ocr.gsm_ocr_config import set_dpi_awareness
|
12
|
-
from GameSentenceMiner.util.configuration import get_config, is_windows
|
15
|
+
from GameSentenceMiner.util.configuration import OverlayEngine, get_config, is_windows, is_beangate, logger
|
13
16
|
from GameSentenceMiner.util.electron_config import get_ocr_language
|
14
|
-
from GameSentenceMiner.obs import get_screenshot_PIL
|
17
|
+
from GameSentenceMiner.obs import get_screenshot_PIL
|
15
18
|
from GameSentenceMiner.web.texthooking_page import send_word_coordinates_to_overlay
|
16
19
|
|
20
|
+
# def align_and_correct(ocr_json, reference_text):
|
21
|
+
# logger.info(f"Starting align_and_correct with reference_text: '{reference_text}'")
|
22
|
+
# corrected = []
|
23
|
+
# ref_chars = list(reference_text)
|
24
|
+
# logger.info(f"Reference chars: {ref_chars}")
|
25
|
+
|
26
|
+
# for block_idx, block in enumerate(ocr_json):
|
27
|
+
# logger.info(f"Processing block {block_idx}: {block}")
|
28
|
+
# ocr_chars = [w["text"] for w in block["words"]]
|
29
|
+
# ocr_str = "".join(ocr_chars)
|
30
|
+
|
31
|
+
# # Compute edit operations from OCR → Reference
|
32
|
+
# ops = Levenshtein.editops(ocr_str, "".join(ref_chars))
|
33
|
+
|
34
|
+
# corrected_words = block["words"][:]
|
35
|
+
|
36
|
+
# # Apply corrections
|
37
|
+
# for op_idx, (op, i, j) in enumerate(ops):
|
38
|
+
# logger.info(f"Operation {op_idx}: {op}, i={i}, j={j}")
|
39
|
+
# if op == "replace":
|
40
|
+
# logger.info(f"Replacing word at index {i} ('{corrected_words[i]['text']}') with reference char '{ref_chars[j]}'")
|
41
|
+
# corrected_words[i]["text"] = ref_chars[j]
|
42
|
+
# elif op == "insert":
|
43
|
+
# if i > 0:
|
44
|
+
# prev = corrected_words[i - 1]["bounding_rect"]
|
45
|
+
# bbox = prev # simple: copy neighbor bbox
|
46
|
+
# else:
|
47
|
+
# bbox = corrected_words[0]["bounding_rect"]
|
48
|
+
# corrected_words.insert(i, {
|
49
|
+
# "text": ref_chars[j],
|
50
|
+
# "bounding_rect": bbox,
|
51
|
+
# "confidence": 1.0
|
52
|
+
# })
|
53
|
+
# elif op == "delete":
|
54
|
+
# logger.info(f"Deleting word at index {i} ('{corrected_words[i]['text']}')")
|
55
|
+
# corrected_words[i]["text"] = "" # mark empty
|
56
|
+
|
57
|
+
# corrected_words = [w for w in corrected_words if w["text"]]
|
58
|
+
|
59
|
+
# block["words"] = corrected_words
|
60
|
+
# block["text"] = "".join(w["text"] for w in corrected_words)
|
61
|
+
# corrected.append(block)
|
62
|
+
|
63
|
+
# return corrected
|
64
|
+
|
17
65
|
# Conditionally import OCR engines
|
18
66
|
try:
|
19
67
|
if os.path.exists(os.path.expanduser('~/.config/oneocr/oneocr.dll')):
|
@@ -218,6 +266,15 @@ class OverlayProcessor:
|
|
218
266
|
)
|
219
267
|
else:
|
220
268
|
composite_image = full_screenshot
|
269
|
+
|
270
|
+
# If User Home is beangate
|
271
|
+
if is_beangate:
|
272
|
+
with open("oneocr_results.json", "w", encoding="utf-8") as f:
|
273
|
+
f.write(json.dumps(oneocr_results, ensure_ascii=False, indent=2))
|
274
|
+
|
275
|
+
if get_config().overlay.engine == OverlayEngine.ONEOCR.value and self.oneocr:
|
276
|
+
logger.info("Using OneOCR results for overlay as configured.")
|
277
|
+
return oneocr_results
|
221
278
|
|
222
279
|
# 4. Use Google Lens on the cleaner composite image for higher accuracy
|
223
280
|
res = self.lens(
|
@@ -202,7 +202,7 @@ class WhisperVADProcessor(VADProcessor):
|
|
202
202
|
logger.info(
|
203
203
|
"Unknown single character segment, not skipping, but logging, please report if this is a mistake: " + segment.text)
|
204
204
|
|
205
|
-
if segment.no_speech_prob and segment.no_speech_prob > 0.
|
205
|
+
if segment.no_speech_prob and segment.no_speech_prob > 0.9:
|
206
206
|
logger.debug(f"Skipping segment with high no_speech_prob: {segment.no_speech_prob} for segment {segment.text} at {segment.start}-{segment.end}")
|
207
207
|
continue
|
208
208
|
|
@@ -211,7 +211,7 @@ class WhisperVADProcessor(VADProcessor):
|
|
211
211
|
logger.debug(f"Skipping segment with low unique words: {unique_words} for segment {segment.text} at {segment.start}-{segment.end}")
|
212
212
|
continue
|
213
213
|
|
214
|
-
if
|
214
|
+
if segment.seek > 0 and segment.no_speech_prob > .3:
|
215
215
|
logger.debug(f"Skipping segment after long pause with high no_speech_prob after: {segment.no_speech_prob} for segment {segment.text} at {segment.start}-{segment.end}")
|
216
216
|
continue
|
217
217
|
|
@@ -1,5 +1,7 @@
|
|
1
1
|
import datetime
|
2
2
|
import re
|
3
|
+
import csv
|
4
|
+
import io
|
3
5
|
from collections import defaultdict
|
4
6
|
|
5
7
|
import flask
|
@@ -135,8 +137,8 @@ def register_database_api_routes(app):
|
|
135
137
|
'date_range': f"{min_date.strftime('%Y-%m-%d')} to {max_date.strftime('%Y-%m-%d')}" if min_date != max_date else min_date.strftime('%Y-%m-%d')
|
136
138
|
})
|
137
139
|
|
138
|
-
# Sort by
|
139
|
-
games_data.sort(key=lambda x: x['
|
140
|
+
# Sort by total characters (most characters first)
|
141
|
+
games_data.sort(key=lambda x: x['total_characters'], reverse=True)
|
140
142
|
|
141
143
|
return jsonify({'games': games_data}), 200
|
142
144
|
|
@@ -732,7 +734,8 @@ def register_database_api_routes(app):
|
|
732
734
|
color = colors[i % len(colors)]
|
733
735
|
|
734
736
|
datasets.append({
|
735
|
-
"label": f"{game}
|
737
|
+
"label": f"{game}",
|
738
|
+
"for": "Lines Received",
|
736
739
|
"data": final_data[game]['lines'],
|
737
740
|
"borderColor": color,
|
738
741
|
"backgroundColor": f"{color}33", # Semi-transparent for fill
|
@@ -740,7 +743,8 @@ def register_database_api_routes(app):
|
|
740
743
|
"tension": 0.1
|
741
744
|
})
|
742
745
|
datasets.append({
|
743
|
-
"label": f"{game}
|
746
|
+
"label": f"{game}",
|
747
|
+
"for": "Characters Read",
|
744
748
|
"data": final_data[game]['chars'],
|
745
749
|
"borderColor": color,
|
746
750
|
"backgroundColor": f"{color}33",
|
@@ -780,4 +784,165 @@ def register_database_api_routes(app):
|
|
780
784
|
"currentGameStats": current_game_stats,
|
781
785
|
"allGamesStats": all_games_stats,
|
782
786
|
"allLinesData": all_lines_data
|
783
|
-
})
|
787
|
+
})
|
788
|
+
|
789
|
+
@app.route('/api/import-exstatic', methods=['POST'])
|
790
|
+
def api_import_exstatic():
|
791
|
+
"""
|
792
|
+
Import ExStatic CSV data into GSM database.
|
793
|
+
Expected CSV format: uuid,given_identifier,name,line,time
|
794
|
+
"""
|
795
|
+
try:
|
796
|
+
# Check if file is provided
|
797
|
+
if 'file' not in request.files:
|
798
|
+
return jsonify({'error': 'No file provided'}), 400
|
799
|
+
|
800
|
+
file = request.files['file']
|
801
|
+
if file.filename == '':
|
802
|
+
return jsonify({'error': 'No file selected'}), 400
|
803
|
+
|
804
|
+
# Validate file type
|
805
|
+
if not file.filename.lower().endswith('.csv'):
|
806
|
+
return jsonify({'error': 'File must be a CSV file'}), 400
|
807
|
+
|
808
|
+
# Read and parse CSV
|
809
|
+
try:
|
810
|
+
# Read file content as text with proper encoding handling
|
811
|
+
file_content = file.read().decode('utf-8-sig') # Handle BOM if present
|
812
|
+
|
813
|
+
# First, get the header line manually to avoid issues with multi-line content
|
814
|
+
lines = file_content.split('\n')
|
815
|
+
if len(lines) == 1 and not lines[0].strip():
|
816
|
+
return jsonify({'error': 'Empty CSV file'}), 400
|
817
|
+
|
818
|
+
header_line = lines[0].strip()
|
819
|
+
logger.info(f"Header line: {header_line}")
|
820
|
+
|
821
|
+
# Parse headers manually
|
822
|
+
header_reader = csv.reader([header_line])
|
823
|
+
try:
|
824
|
+
headers = next(header_reader)
|
825
|
+
headers = [h.strip() for h in headers] # Clean whitespace
|
826
|
+
logger.info(f"Parsed headers: {headers}")
|
827
|
+
except StopIteration:
|
828
|
+
return jsonify({'error': 'Could not parse CSV headers'}), 400
|
829
|
+
|
830
|
+
# Validate headers
|
831
|
+
expected_headers = {'uuid', 'given_identifier', 'name', 'line', 'time'}
|
832
|
+
actual_headers = set(headers)
|
833
|
+
|
834
|
+
if not expected_headers.issubset(actual_headers):
|
835
|
+
missing_headers = expected_headers - actual_headers
|
836
|
+
# Check if this looks like a stats CSV instead of lines CSV
|
837
|
+
if 'client' in actual_headers and 'chars_read' in actual_headers:
|
838
|
+
return jsonify({
|
839
|
+
'error': 'This appears to be an ExStatic stats CSV. Please upload the ExStatic lines CSV file instead. The lines CSV should contain columns: uuid, given_identifier, name, line, time'
|
840
|
+
}), 400
|
841
|
+
else:
|
842
|
+
return jsonify({
|
843
|
+
'error': f'Invalid CSV format. Missing required columns: {", ".join(missing_headers)}. Expected format: uuid, given_identifier, name, line, time. Found headers: {", ".join(actual_headers)}'
|
844
|
+
}), 400
|
845
|
+
|
846
|
+
# Now parse the full CSV with proper handling for multi-line fields
|
847
|
+
file_io = io.StringIO(file_content)
|
848
|
+
csv_reader = csv.DictReader(file_io, quoting=csv.QUOTE_MINIMAL, skipinitialspace=True)
|
849
|
+
|
850
|
+
# Process CSV rows
|
851
|
+
imported_lines = []
|
852
|
+
games_set = set()
|
853
|
+
errors = []
|
854
|
+
seen_uuids = set() # Track UUIDs within this import batch
|
855
|
+
|
856
|
+
for row_num, row in enumerate(csv_reader):
|
857
|
+
try:
|
858
|
+
# Extract and validate required fields
|
859
|
+
uuid = row.get('uuid', '').strip()
|
860
|
+
name = row.get('name', '').strip()
|
861
|
+
line = row.get('line', '').strip()
|
862
|
+
time_str = row.get('time', '').strip()
|
863
|
+
|
864
|
+
# Validate required fields
|
865
|
+
if not uuid:
|
866
|
+
errors.append(f"Row {row_num}: Missing UUID")
|
867
|
+
continue
|
868
|
+
if not name:
|
869
|
+
errors.append(f"Row {row_num}: Missing name")
|
870
|
+
continue
|
871
|
+
if not line:
|
872
|
+
errors.append(f"Row {row_num}: Missing line text")
|
873
|
+
continue
|
874
|
+
if not time_str:
|
875
|
+
errors.append(f"Row {row_num}: Missing time")
|
876
|
+
continue
|
877
|
+
|
878
|
+
# Check for duplicates within this import batch
|
879
|
+
if uuid in seen_uuids:
|
880
|
+
logger.info(f"Skipping duplicate UUID within import batch: {uuid}")
|
881
|
+
continue
|
882
|
+
seen_uuids.add(uuid)
|
883
|
+
|
884
|
+
# Convert time to timestamp
|
885
|
+
try:
|
886
|
+
timestamp = float(time_str)
|
887
|
+
except ValueError:
|
888
|
+
errors.append(f"Row {row_num}: Invalid time format: {time_str}")
|
889
|
+
continue
|
890
|
+
|
891
|
+
# Clean up line text (remove extra whitespace and newlines)
|
892
|
+
line_text = line.strip()
|
893
|
+
|
894
|
+
# Check if this UUID already exists in database
|
895
|
+
existing_line = GameLinesTable.get(uuid)
|
896
|
+
if existing_line:
|
897
|
+
logger.info(f"Skipping duplicate UUID already in database: {uuid}")
|
898
|
+
continue
|
899
|
+
|
900
|
+
# Create GameLinesTable entry
|
901
|
+
game_line = GameLinesTable(
|
902
|
+
id=uuid,
|
903
|
+
game_name=name,
|
904
|
+
line_text=line_text,
|
905
|
+
timestamp=timestamp
|
906
|
+
)
|
907
|
+
|
908
|
+
imported_lines.append(game_line)
|
909
|
+
games_set.add(name)
|
910
|
+
|
911
|
+
except Exception as e:
|
912
|
+
errors.append(f"Row {row_num}: Error processing row - {str(e)}")
|
913
|
+
continue
|
914
|
+
|
915
|
+
# Import lines into database
|
916
|
+
imported_count = 0
|
917
|
+
for game_line in imported_lines:
|
918
|
+
try:
|
919
|
+
game_line.add()
|
920
|
+
imported_count += 1
|
921
|
+
except Exception as e:
|
922
|
+
logger.error(f"Failed to import line {game_line.id}: {e}")
|
923
|
+
errors.append(f"Failed to import line {game_line.id}: {str(e)}")
|
924
|
+
|
925
|
+
# Prepare response
|
926
|
+
response_data = {
|
927
|
+
'message': f'Successfully imported {imported_count} lines from {len(games_set)} games',
|
928
|
+
'imported_count': imported_count,
|
929
|
+
'games_count': len(games_set),
|
930
|
+
'games': list(games_set)
|
931
|
+
}
|
932
|
+
|
933
|
+
if errors:
|
934
|
+
response_data['warnings'] = errors
|
935
|
+
response_data['warning_count'] = len(errors)
|
936
|
+
|
937
|
+
logger.info(f"ExStatic import completed: {imported_count} lines from {len(games_set)} games")
|
938
|
+
|
939
|
+
return jsonify(response_data), 200
|
940
|
+
|
941
|
+
except csv.Error as e:
|
942
|
+
return jsonify({'error': f'CSV parsing error: {str(e)}'}), 400
|
943
|
+
except UnicodeDecodeError:
|
944
|
+
return jsonify({'error': 'File encoding error. Please ensure the CSV is UTF-8 encoded.'}), 400
|
945
|
+
|
946
|
+
except Exception as e:
|
947
|
+
logger.error(f"Error in ExStatic import: {e}")
|
948
|
+
return jsonify({'error': f'Import failed: {str(e)}'}), 500
|
{gamesentenceminer-2.16.1 → gamesentenceminer-2.16.3}/GameSentenceMiner/web/static/js/shared.js
RENAMED
@@ -491,6 +491,70 @@ function escapeRegex(string) {
|
|
491
491
|
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
492
492
|
}
|
493
493
|
|
494
|
+
// Screenshot functionality
|
495
|
+
function initializeScreenshotButton() {
|
496
|
+
const screenshotButton = document.getElementById('screenshotToggle');
|
497
|
+
|
498
|
+
if (!screenshotButton) {
|
499
|
+
return; // Screenshot button not available on this page
|
500
|
+
}
|
501
|
+
|
502
|
+
screenshotButton.addEventListener('click', takeScreenshot);
|
503
|
+
}
|
504
|
+
|
505
|
+
async function takeScreenshot() {
|
506
|
+
try {
|
507
|
+
// Check if html2canvas is available
|
508
|
+
if (typeof html2canvas === 'undefined') {
|
509
|
+
console.error('html2canvas library not loaded');
|
510
|
+
return;
|
511
|
+
}
|
512
|
+
|
513
|
+
// Generate timestamp for filename
|
514
|
+
const now = new Date();
|
515
|
+
const timestamp = now.getFullYear() + '-' +
|
516
|
+
String(now.getMonth() + 1).padStart(2, '0') + '-' +
|
517
|
+
String(now.getDate()).padStart(2, '0') + '_' +
|
518
|
+
String(now.getHours()).padStart(2, '0') + '-' +
|
519
|
+
String(now.getMinutes()).padStart(2, '0') + '-' +
|
520
|
+
String(now.getSeconds()).padStart(2, '0');
|
521
|
+
|
522
|
+
const filename = `screenshot_${timestamp}.png`;
|
523
|
+
|
524
|
+
// Capture the entire page
|
525
|
+
const canvas = await html2canvas(document.body, {
|
526
|
+
useCORS: true,
|
527
|
+
allowTaint: true,
|
528
|
+
scale: 1,
|
529
|
+
scrollX: 0,
|
530
|
+
scrollY: 0,
|
531
|
+
width: document.body.scrollWidth,
|
532
|
+
height: document.body.scrollHeight
|
533
|
+
});
|
534
|
+
|
535
|
+
// Convert canvas to blob
|
536
|
+
canvas.toBlob(function(blob) {
|
537
|
+
// Create download link
|
538
|
+
const link = document.createElement('a');
|
539
|
+
link.download = filename;
|
540
|
+
link.href = URL.createObjectURL(blob);
|
541
|
+
|
542
|
+
// Trigger download
|
543
|
+
document.body.appendChild(link);
|
544
|
+
link.click();
|
545
|
+
document.body.removeChild(link);
|
546
|
+
|
547
|
+
// Clean up the URL object after a short delay to avoid race condition
|
548
|
+
setTimeout(function() {
|
549
|
+
URL.revokeObjectURL(link.href);
|
550
|
+
}, 100);
|
551
|
+
}, 'image/png');
|
552
|
+
|
553
|
+
} catch (error) {
|
554
|
+
console.error('Screenshot failed:', error);
|
555
|
+
}
|
556
|
+
}
|
557
|
+
|
494
558
|
// Initialize shared functionality when DOM loads
|
495
559
|
document.addEventListener('DOMContentLoaded', function() {
|
496
560
|
// Initialize theme toggle
|
@@ -499,6 +563,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
499
563
|
// Initialize modal handlers
|
500
564
|
initializeModalHandlers();
|
501
565
|
|
566
|
+
// Initialize screenshot button
|
567
|
+
initializeScreenshotButton();
|
568
|
+
|
502
569
|
// Initialize settings manager if settings toggle exists
|
503
570
|
if (document.getElementById('settingsToggle')) {
|
504
571
|
new SettingsManager();
|