GameSentenceMiner 2.13.14__py3-none-any.whl → 2.14.0rc1__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/ai/ai_prompting.py +77 -132
- GameSentenceMiner/anki.py +48 -6
- GameSentenceMiner/config_gui.py +196 -30
- GameSentenceMiner/gametext.py +8 -19
- GameSentenceMiner/gsm.py +5 -4
- GameSentenceMiner/locales/en_us.json +21 -11
- GameSentenceMiner/locales/ja_jp.json +21 -11
- GameSentenceMiner/locales/zh_cn.json +9 -11
- GameSentenceMiner/owocr/owocr/ocr.py +20 -23
- GameSentenceMiner/tools/__init__.py +0 -0
- GameSentenceMiner/util/configuration.py +241 -105
- GameSentenceMiner/util/db.py +408 -0
- GameSentenceMiner/util/ffmpeg.py +2 -10
- GameSentenceMiner/util/get_overlay_coords.py +324 -0
- GameSentenceMiner/util/model.py +8 -2
- GameSentenceMiner/util/text_log.py +1 -1
- GameSentenceMiner/web/texthooking_page.py +1 -1
- GameSentenceMiner/wip/__init___.py +0 -0
- {gamesentenceminer-2.13.14.dist-info → gamesentenceminer-2.14.0rc1.dist-info}/METADATA +5 -1
- {gamesentenceminer-2.13.14.dist-info → gamesentenceminer-2.14.0rc1.dist-info}/RECORD +27 -25
- GameSentenceMiner/util/package.py +0 -37
- GameSentenceMiner/wip/get_overlay_coords.py +0 -535
- /GameSentenceMiner/{util → tools}/audio_offset_selector.py +0 -0
- /GameSentenceMiner/{util → tools}/ss_selector.py +0 -0
- /GameSentenceMiner/{util → tools}/window_transparency.py +0 -0
- {gamesentenceminer-2.13.14.dist-info → gamesentenceminer-2.14.0rc1.dist-info}/WHEEL +0 -0
- {gamesentenceminer-2.13.14.dist-info → gamesentenceminer-2.14.0rc1.dist-info}/entry_points.txt +0 -0
- {gamesentenceminer-2.13.14.dist-info → gamesentenceminer-2.14.0rc1.dist-info}/licenses/LICENSE +0 -0
- {gamesentenceminer-2.13.14.dist-info → gamesentenceminer-2.14.0rc1.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,7 @@
|
|
1
1
|
import logging
|
2
2
|
import textwrap
|
3
3
|
import time
|
4
|
+
import json
|
4
5
|
from abc import ABC, abstractmethod
|
5
6
|
from dataclasses import dataclass
|
6
7
|
from enum import Enum
|
@@ -28,7 +29,7 @@ Translate ONLY the single line of game dialogue specified below into natural-sou
|
|
28
29
|
**Output Requirements:**
|
29
30
|
- Provide only the single, best {get_config().general.get_native_language_name()} translation.
|
30
31
|
- Use expletives if they are natural for the context and enhance the translation's impact, but do not over-exaggerate.
|
31
|
-
- Preserve
|
32
|
+
- Preserve all HTML tags present in the original text on their corresponding words in the translation.
|
32
33
|
- Do not include notes, alternatives, explanations, or any other surrounding text. Absolutely nothing but the translated line.
|
33
34
|
|
34
35
|
**Line to Translate:**
|
@@ -45,7 +46,7 @@ Current Sentence:
|
|
45
46
|
class AIType(Enum):
|
46
47
|
GEMINI = "Gemini"
|
47
48
|
GROQ = "Groq"
|
48
|
-
|
49
|
+
OPENAI = "OpenAI"
|
49
50
|
|
50
51
|
@dataclass
|
51
52
|
class AIConfig:
|
@@ -65,9 +66,9 @@ class GroqAiConfig(AIConfig):
|
|
65
66
|
super().__init__(api_key=api_key, model=model, api_url=None, type=AIType.GROQ)
|
66
67
|
|
67
68
|
@dataclass
|
68
|
-
class
|
69
|
-
def __init__(self, model: str = "
|
70
|
-
super().__init__(api_key=
|
69
|
+
class OpenAIAIConfig(AIConfig):
|
70
|
+
def __init__(self, api_key: str, model: str = "openai/gpt-oss-20b", api_url: Optional[str] = None):
|
71
|
+
super().__init__(api_key=api_key, model=model, api_url=api_url, type=AIType.OPENAI)
|
71
72
|
|
72
73
|
|
73
74
|
class AIManager(ABC):
|
@@ -123,124 +124,62 @@ class AIManager(ABC):
|
|
123
124
|
return full_prompt
|
124
125
|
|
125
126
|
|
126
|
-
class
|
127
|
-
def __init__(self, model, logger: Optional[logging.Logger] = None):
|
128
|
-
super().__init__(
|
127
|
+
class OpenAIManager(AIManager):
|
128
|
+
def __init__(self, model, api_url, api_key, logger: Optional[logging.Logger] = None):
|
129
|
+
super().__init__(OpenAIAIConfig(api_key=api_key, model=model, api_url=api_url), logger)
|
129
130
|
try:
|
130
|
-
import
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
except (ImportError, OSError):
|
135
|
-
self.transformers_available = False
|
136
|
-
self.model_name = self.ai_config.model
|
137
|
-
if MANUAL_MODEL_OVERRIDE:
|
138
|
-
self.model_name = MANUAL_MODEL_OVERRIDE
|
139
|
-
self.logger.warning(f"MANUAL MODEL OVERRIDE ENABLED! Using model: {self.model_name}")
|
140
|
-
self.model = None
|
141
|
-
self.pipe = None
|
142
|
-
self.tokenizer = None
|
143
|
-
self.device = "cuda" if torch.cuda.is_available() else "cpu"
|
144
|
-
self.is_encoder_decoder = False
|
145
|
-
self.is_nllb = "nllb" in self.model_name.lower()
|
146
|
-
|
147
|
-
if not self.transformers_available:
|
148
|
-
self.logger.error("Local AI dependencies not found. Please run: pip install torch transformers sentencepiece")
|
149
|
-
return
|
150
|
-
|
151
|
-
if not self.model_name:
|
152
|
-
self.logger.error("No local model name provided in configuration.")
|
153
|
-
return
|
154
|
-
|
155
|
-
try:
|
156
|
-
self.logger.info(f"Loading local model: {self.model_name}")
|
157
|
-
self.tokenizer = AutoTokenizer.from_pretrained(self.model_name)
|
158
|
-
|
159
|
-
# Try to load as a Causal LM first. If it fails, assume it's a Seq2Seq model.
|
160
|
-
# This is a heuristic to fix the original code's bug of using Seq2Seq for all models.
|
161
|
-
try:
|
162
|
-
self.model = AutoModelForCausalLM.from_pretrained(
|
163
|
-
self.model_name,
|
164
|
-
torch_dtype=torch.bfloat16,
|
165
|
-
)
|
166
|
-
# self.pipe = pipeline(
|
167
|
-
# "text-generation",
|
168
|
-
# model=self.model_name,
|
169
|
-
# torch_dtype=torch.bfloat16,
|
170
|
-
# device=self.device
|
171
|
-
# )
|
172
|
-
# print(self.pipe("Translate this sentence to English: お前は何をしている!?", return_full_text=False))
|
173
|
-
self.is_encoder_decoder = False
|
174
|
-
self.logger.info(f"Loaded {self.model_name} as a CausalLM.")
|
175
|
-
except (ValueError, TypeError, OSError, KeyError) as e:
|
176
|
-
print(e)
|
177
|
-
self.model = AutoModelForSeq2SeqLM.from_pretrained(
|
178
|
-
self.model_name,
|
179
|
-
torch_dtype=torch.bfloat16,
|
131
|
+
import openai
|
132
|
+
self.client = openai.OpenAI(
|
133
|
+
base_url=api_url,
|
134
|
+
api_key=api_key
|
180
135
|
)
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
self.
|
185
|
-
|
186
|
-
|
187
|
-
self.logger.info(f"Local model '{self.model_name}' loaded on {self.device}.")
|
136
|
+
self.model_name = model
|
137
|
+
if MANUAL_MODEL_OVERRIDE:
|
138
|
+
self.model_name = MANUAL_MODEL_OVERRIDE
|
139
|
+
self.logger.warning(f"MANUAL MODEL OVERRIDE ENABLED! Using model: {self.model_name}")
|
140
|
+
self.logger.debug(f"OpenAIManager initialized with model: {self.model_name}")
|
188
141
|
except Exception as e:
|
189
|
-
self.logger.error(f"Failed to
|
190
|
-
self.
|
191
|
-
self.
|
192
|
-
|
193
|
-
# if self.is_nllb:
|
194
|
-
# self.tokenizer = NllbTokenizer().from_pretrained(self.model_name)
|
195
|
-
|
142
|
+
self.logger.error(f"Failed to initialize OpenAI API: {e}")
|
143
|
+
self.openai = None
|
144
|
+
self.model_name = None
|
145
|
+
|
196
146
|
def _build_prompt(self, lines: List[GameLine], sentence: str, current_line: GameLine, game_title: str) -> str:
|
197
|
-
|
198
|
-
|
147
|
+
prompt = super()._build_prompt(lines, sentence, current_line, game_title)
|
148
|
+
return prompt
|
149
|
+
|
199
150
|
def process(self, lines: List[GameLine], sentence: str, current_line: GameLine, game_title: str = "") -> str:
|
200
|
-
if
|
201
|
-
return "Processing failed:
|
151
|
+
if self.client is None:
|
152
|
+
return "Processing failed: OpenAI client not initialized."
|
202
153
|
|
203
|
-
|
204
|
-
|
154
|
+
if not lines or not current_line:
|
155
|
+
self.logger.warning(f"Invalid input for process: lines={len(lines)}, current_line={current_line.index}")
|
156
|
+
return "Invalid input."
|
205
157
|
|
206
158
|
try:
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
tokenized_chat = self.tokenizer.apply_chat_template(
|
230
|
-
messages, tokenize=True, add_generation_prompt=True, return_tensors="pt"
|
231
|
-
).to(self.device)
|
232
|
-
outputs = self.model.generate(tokenized_chat, max_new_tokens=256)
|
233
|
-
result = self.tokenizer.decode(outputs[0][tokenized_chat.shape[-1]:], skip_special_tokens=True)
|
234
|
-
# result = self.pipe(messages, max_new_tokens=50)
|
235
|
-
print(result)
|
236
|
-
# result = result[0]['generated_text']
|
237
|
-
result = result.strip()
|
238
|
-
|
239
|
-
result = result.strip()
|
240
|
-
self.logger.debug(f"Received response from local model:\n{result}")
|
241
|
-
return result
|
159
|
+
prompt = self._build_prompt(lines, sentence, current_line, game_title)
|
160
|
+
self.logger.debug(f"Generated prompt:\n{prompt}")
|
161
|
+
response = self.client.chat.completions.create(
|
162
|
+
model=self.model_name,
|
163
|
+
messages=[
|
164
|
+
{"role": "system", "content": "You are a helpful assistant that translates game dialogue. Provide output in the form of json with a single key 'output'."},
|
165
|
+
{"role": "user", "content": prompt}
|
166
|
+
],
|
167
|
+
temperature=0.3,
|
168
|
+
max_tokens=4096,
|
169
|
+
top_p=1,
|
170
|
+
n=1,
|
171
|
+
stop=None,
|
172
|
+
)
|
173
|
+
if response.choices and response.choices[0].message.content:
|
174
|
+
text_output = response.choices[0].message.content.strip()
|
175
|
+
# get the json at the end of the message
|
176
|
+
if "{" in text_output and "}" in text_output:
|
177
|
+
json_output = text_output[text_output.find("{"):text_output.rfind("}")+1]
|
178
|
+
text_output = json.loads(json_output)['output']
|
179
|
+
self.logger.debug(f"Received response:\n{text_output}")
|
180
|
+
return text_output
|
242
181
|
except Exception as e:
|
243
|
-
self.logger.error(f"
|
182
|
+
self.logger.error(f"OpenAI processing failed: {e}")
|
244
183
|
return f"Processing failed: {e}"
|
245
184
|
|
246
185
|
|
@@ -341,9 +280,9 @@ class GroqAI(AIManager):
|
|
341
280
|
completion = self.client.chat.completions.create(
|
342
281
|
model=self.model_name,
|
343
282
|
messages=[{"role": "user", "content": prompt}],
|
344
|
-
temperature
|
283
|
+
temperature=0,
|
345
284
|
max_completion_tokens=1024,
|
346
|
-
top_p
|
285
|
+
top_p=.9,
|
347
286
|
stream=False,
|
348
287
|
stop=None,
|
349
288
|
)
|
@@ -361,7 +300,7 @@ current_ai_config: Ai | None = None
|
|
361
300
|
def get_ai_prompt_result(lines: List[GameLine], sentence: str, current_line: GameLine, game_title: str = "", force_refresh: bool = False) -> str:
|
362
301
|
global ai_manager, current_ai_config
|
363
302
|
try:
|
364
|
-
is_local_provider = get_config().ai.provider == AIType.
|
303
|
+
is_local_provider = get_config().ai.provider == AIType.OPENAI.value
|
365
304
|
if not is_local_provider and not is_connected():
|
366
305
|
logger.error("No internet connection. Unable to proceed with AI prompt.")
|
367
306
|
return ""
|
@@ -380,12 +319,12 @@ def get_ai_prompt_result(lines: List[GameLine], sentence: str, current_line: Gam
|
|
380
319
|
logger.info(f"Reusing existing Groq AI Manager for model: {get_config().ai.groq_model}")
|
381
320
|
else:
|
382
321
|
ai_manager = GroqAI(model=get_config().ai.groq_model, api_key=get_config().ai.groq_api_key, logger=logger)
|
383
|
-
elif provider == AIType.
|
384
|
-
if get_config().ai.
|
385
|
-
ai_manager = ai_managers[get_config().ai.
|
386
|
-
logger.info(f"Reusing existing
|
322
|
+
elif provider == AIType.OPENAI.value:
|
323
|
+
if get_config().ai.open_ai_model in ai_managers:
|
324
|
+
ai_manager = ai_managers[get_config().ai.open_ai_model]
|
325
|
+
logger.info(f"Reusing existing OpenAI AI Manager for model: {get_config().ai.open_ai_model}")
|
387
326
|
else:
|
388
|
-
ai_manager =
|
327
|
+
ai_manager = OpenAIManager(model=get_config().ai.open_ai_model, api_key=get_config().ai.open_ai_api_key, api_url=get_config().ai.open_ai_url, logger=logger)
|
389
328
|
else:
|
390
329
|
ai_manager = None
|
391
330
|
if ai_manager:
|
@@ -410,7 +349,7 @@ def ai_config_changed(config, current):
|
|
410
349
|
return True
|
411
350
|
if config.provider == AIType.GROQ.value and (config.groq_api_key != current.groq_api_key or config.groq_model != current.groq_model):
|
412
351
|
return True
|
413
|
-
if config.provider == AIType.
|
352
|
+
if config.provider == AIType.OPENAI.value and config.gemini_model != current.gemini_model:
|
414
353
|
return True
|
415
354
|
if config.custom_prompt != current.custom_prompt:
|
416
355
|
return True
|
@@ -422,11 +361,11 @@ def ai_config_changed(config, current):
|
|
422
361
|
|
423
362
|
|
424
363
|
if __name__ == '__main__':
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
364
|
+
logger.setLevel(logging.DEBUG)
|
365
|
+
console_handler = logging.StreamHandler()
|
366
|
+
console_handler.setLevel(logging.DEBUG)
|
367
|
+
logger.addHandler(console_handler)
|
368
|
+
logging.basicConfig(level=logging.DEBUG)
|
430
369
|
lines = [
|
431
370
|
# Sexual/Explicit Japanese words and phrases
|
432
371
|
GameLine(index=0, text="ねぇ、あたしのおっぱい、揉んでみない?", id=None, time=None, prev=None, next=None),
|
@@ -474,11 +413,14 @@ if __name__ == '__main__':
|
|
474
413
|
current_line = lines[6]
|
475
414
|
game_title = "Corrupted Reality"
|
476
415
|
|
477
|
-
get_config().ai.provider =
|
416
|
+
get_config().ai.provider = AIType.OPENAI.value
|
478
417
|
models = [
|
418
|
+
# 'openai/gpt-oss-20b',
|
419
|
+
# 'meta-llama-3.1-8b-instruct',
|
420
|
+
'google/gemma-3n-e4b',
|
479
421
|
# 'google/gemma-2-2b-it',
|
480
422
|
# 'google/gemma-2b-it',
|
481
|
-
'facebook/nllb-200-distilled-600M',
|
423
|
+
# 'facebook/nllb-200-distilled-600M',
|
482
424
|
# 'meta-llama/Llama-3.2-1B-Instruct',
|
483
425
|
# 'facebook/nllb-200-1.3B'
|
484
426
|
]
|
@@ -492,9 +434,12 @@ if __name__ == '__main__':
|
|
492
434
|
# results.append({"model": model,"response": result, "time": time.time() - start_time, "iteration": 1})
|
493
435
|
|
494
436
|
# Second Time after Already Loaded
|
495
|
-
|
437
|
+
|
438
|
+
get_config().ai.open_ai_url = "http://127.0.0.1:1234/v1"
|
439
|
+
get_config().ai.open_ai_api_key = "lm-studio"
|
440
|
+
for i in range(1, 10):
|
496
441
|
for model in models:
|
497
|
-
get_config().ai.
|
442
|
+
get_config().ai.open_ai_model = model
|
498
443
|
start_time = time.time()
|
499
444
|
result = get_ai_prompt_result(lines, sentence, current_line, game_title, True)
|
500
445
|
print(result)
|
GameSentenceMiner/anki.py
CHANGED
@@ -11,6 +11,7 @@ from requests import post
|
|
11
11
|
|
12
12
|
from GameSentenceMiner import obs
|
13
13
|
from GameSentenceMiner.ai.ai_prompting import get_ai_prompt_result
|
14
|
+
from GameSentenceMiner.util.db import GameLinesTable
|
14
15
|
from GameSentenceMiner.util.gsm_utils import make_unique, sanitize_filename, wait_for_stable_file, remove_html_and_cloze_tags, combine_dialogue, \
|
15
16
|
run_new_thread, open_audio_in_external
|
16
17
|
from GameSentenceMiner.util import ffmpeg, notification
|
@@ -69,10 +70,19 @@ def update_anki_card(last_note: AnkiCard, note=None, audio_path='', video_path='
|
|
69
70
|
prev_screenshot_html = f"<img src=\"{prev_screenshot_in_anki}\">"
|
70
71
|
|
71
72
|
|
73
|
+
# Vars for DB update
|
74
|
+
new_audio_path = ''
|
75
|
+
new_screenshot_path = ''
|
76
|
+
new_prev_screenshot_path = ''
|
77
|
+
new_video_path = ''
|
78
|
+
translation = ''
|
79
|
+
anki_audio_path = ''
|
80
|
+
anki_screenshot_path = ''
|
72
81
|
# Move files to output folder if configured
|
73
82
|
if get_config().paths.output_folder and get_config().paths.copy_temp_files_to_output_folder:
|
74
83
|
word_path = os.path.join(get_config().paths.output_folder, sanitize_filename(tango))
|
75
84
|
os.makedirs(word_path, exist_ok=True)
|
85
|
+
|
76
86
|
if audio_path:
|
77
87
|
audio_filename = Path(audio_path).name
|
78
88
|
new_audio_path = os.path.join(word_path, audio_filename)
|
@@ -94,6 +104,10 @@ def update_anki_card(last_note: AnkiCard, note=None, audio_path='', video_path='
|
|
94
104
|
new_video_path = os.path.join(word_path, Path(trimmed_video).name)
|
95
105
|
if os.path.exists(trimmed_video):
|
96
106
|
shutil.copy(trimmed_video, new_video_path)
|
107
|
+
|
108
|
+
if get_config().audio.anki_media_collection:
|
109
|
+
anki_audio_path = os.path.join(get_config().audio.anki_media_collection, audio_in_anki)
|
110
|
+
anki_screenshot_path = os.path.join(get_config().audio.anki_media_collection, screenshot_in_anki)
|
97
111
|
|
98
112
|
# Open to word_path if configured
|
99
113
|
if get_config().paths.open_output_folder_on_card_creation:
|
@@ -146,7 +160,8 @@ def update_anki_card(last_note: AnkiCard, note=None, audio_path='', video_path='
|
|
146
160
|
if tags:
|
147
161
|
tag_string = " ".join(tags)
|
148
162
|
invoke("addTags", tags=tag_string, notes=[last_note.noteId])
|
149
|
-
|
163
|
+
|
164
|
+
# Update GameLine in DB
|
150
165
|
|
151
166
|
logger.info(f"Adding {game_line.id} to Anki Results Dict...")
|
152
167
|
if not reuse_audio:
|
@@ -160,6 +175,7 @@ def update_anki_card(last_note: AnkiCard, note=None, audio_path='', video_path='
|
|
160
175
|
)
|
161
176
|
|
162
177
|
run_new_thread(lambda: check_and_update_note(last_note, note, tags))
|
178
|
+
GameLinesTable.update(line_id=game_line.id, screenshot_path=new_screenshot_path, audio_path=new_audio_path, replay_path=new_video_path, audio_in_anki=anki_audio_path, screenshot_in_anki=anki_screenshot_path, translation=translation)
|
163
179
|
|
164
180
|
def check_and_update_note(last_note, note, tags=[]):
|
165
181
|
selected_notes = invoke("guiSelectedNotes")
|
@@ -196,18 +212,43 @@ def add_image_to_card(last_note: AnkiCard, image_path):
|
|
196
212
|
run_new_thread(lambda: check_and_update_note(last_note, note))
|
197
213
|
|
198
214
|
logger.info(f"UPDATED IMAGE FOR ANKI CARD {last_note.noteId}")
|
215
|
+
|
216
|
+
|
217
|
+
|
218
|
+
# Go through every field in the note and fix whitespace issues
|
219
|
+
# the issues being, a ton of new lines randomly, nothing else
|
220
|
+
def fix_overlay_whitespace(last_note: AnkiCard, note, lines=None):
|
221
|
+
for field in last_note.fields:
|
222
|
+
if not last_note.fields[field]:
|
223
|
+
continue
|
224
|
+
text = last_note.get_field(field)
|
225
|
+
# Count occurrences of excessive whitespace patterns using regex
|
226
|
+
whitespace_patterns = [
|
227
|
+
r'(\r?\n){3,}', # 3 or more consecutive newlines
|
228
|
+
r'(\r){3,}', # 3 or more consecutive carriage returns
|
229
|
+
r'(\n\r\n){2,}', # 2 or more consecutive \n\r\n
|
230
|
+
r'(\r\n\r){2,}', # 2 or more consecutive \r\n\r
|
231
|
+
r'(<br>\s*){3,}' # 3 or more consecutive <br>
|
232
|
+
]
|
233
|
+
for pattern in whitespace_patterns:
|
234
|
+
if re.search(pattern, text):
|
235
|
+
fixed_text = re.sub(pattern, '', text)
|
236
|
+
note['fields'][field] = fixed_text
|
237
|
+
last_note.fields[field].value = fixed_text
|
238
|
+
break
|
239
|
+
return note, last_note
|
199
240
|
|
200
241
|
|
201
242
|
def get_initial_card_info(last_note: AnkiCard, selected_lines):
|
202
243
|
note = {'id': last_note.noteId, 'fields': {}}
|
203
244
|
if not last_note:
|
204
|
-
return note
|
245
|
+
return note, last_note
|
246
|
+
note, last_note = fix_overlay_whitespace(last_note, note, selected_lines)
|
205
247
|
game_line = get_text_event(last_note)
|
206
248
|
sentences = []
|
207
249
|
sentences_text = ''
|
208
250
|
|
209
|
-
|
210
|
-
if get_config().wip.overlay_websocket_send:
|
251
|
+
if get_config().overlay.websocket_port and texthooking_page.overlay_server_thread.has_clients():
|
211
252
|
sentence_in_anki = last_note.get_field(get_config().anki.sentence_field).replace("\n", "").replace("\r", "").strip()
|
212
253
|
if lines_match(game_line.text, remove_html_and_cloze_tags(sentence_in_anki)):
|
213
254
|
logger.info("Found matching line in Anki, Preserving HTML and fix spacing!")
|
@@ -274,7 +315,7 @@ def get_initial_card_info(last_note: AnkiCard, selected_lines):
|
|
274
315
|
note['fields'][get_config().anki.previous_sentence_field] = selected_lines[0].prev.text
|
275
316
|
else:
|
276
317
|
note['fields'][get_config().anki.previous_sentence_field] = game_line.prev.text
|
277
|
-
return note
|
318
|
+
return note, last_note
|
278
319
|
|
279
320
|
|
280
321
|
def store_media_file(path):
|
@@ -404,7 +445,8 @@ def update_card_from_same_sentence(last_card, lines, game_line):
|
|
404
445
|
time.sleep(0.5)
|
405
446
|
anki_result = anki_results[game_line.id]
|
406
447
|
if anki_result.success:
|
407
|
-
|
448
|
+
note, last_card = get_initial_card_info(last_card, lines)
|
449
|
+
update_anki_card(last_card, note=note,
|
408
450
|
game_line=get_mined_line(last_card, lines), reuse_audio=True)
|
409
451
|
else:
|
410
452
|
logger.error(f"Anki update failed for card {last_card.noteId}")
|