GameSentenceMiner 2.19.5__py3-none-any.whl → 2.19.7__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.
@@ -1,4 +1,6 @@
1
+ import math
1
2
  import os
3
+ import re
2
4
  import subprocess
3
5
  import json
4
6
  import tkinter as tk
@@ -6,8 +8,9 @@ from tkinter import messagebox
6
8
  import ttkbootstrap as ttk
7
9
  from PIL import Image, ImageTk
8
10
 
11
+ from GameSentenceMiner.util import ffmpeg
9
12
  from GameSentenceMiner.util.gsm_utils import sanitize_filename
10
- from GameSentenceMiner.util.configuration import get_temporary_directory, logger, ffmpeg_base_command_list, get_ffprobe_path
13
+ from GameSentenceMiner.util.configuration import get_config, get_temporary_directory, logger, ffmpeg_base_command_list, get_ffprobe_path, ffmpeg_base_command_list_info
11
14
 
12
15
 
13
16
  class ScreenshotSelectorDialog(tk.Toplevel):
@@ -65,7 +68,7 @@ class ScreenshotSelectorDialog(tk.Toplevel):
65
68
  # Force always on top to ensure visibility
66
69
 
67
70
  def _extract_frames(self, video_path, timestamp, mode):
68
- """Extracts frames using ffmpeg. Encapsulated from the original script."""
71
+ """Extracts frames using ffmpeg, with automatic black bar removal."""
69
72
  temp_dir = os.path.join(
70
73
  get_temporary_directory(False),
71
74
  "screenshot_frames",
@@ -87,17 +90,36 @@ class ScreenshotSelectorDialog(tk.Toplevel):
87
90
  logger.warning(f"Timestamp {timestamp_number} exceeds video duration {video_duration}.")
88
91
  return [], None
89
92
 
93
+ video_filters = []
94
+
95
+ if get_config().screenshot.trim_black_bars_wip:
96
+ crop_filter = ffmpeg.find_black_bars(video_path, timestamp_number)
97
+ if crop_filter:
98
+ video_filters.append(crop_filter)
99
+
100
+ # Always add the frame extraction filter
101
+ video_filters.append(f"fps=1/{0.25}")
102
+
90
103
  try:
104
+ # Build the final command for frame extraction
91
105
  command = ffmpeg_base_command_list + [
92
- "-y",
106
+ "-y", # Overwrite output files without asking
93
107
  "-ss", str(timestamp_number),
94
- "-i", video_path,
95
- "-vf", f"fps=1/{0.25}",
108
+ "-i", video_path
109
+ ]
110
+
111
+ # Chain all collected filters (crop and fps) together with a comma
112
+ command.extend(["-vf", ",".join(video_filters)])
113
+
114
+ command.extend([
96
115
  "-vframes", "20",
97
116
  os.path.join(temp_dir, "frame_%02d.png")
98
- ]
117
+ ])
118
+
119
+ logger.debug(f"Executing frame extraction command: {' '.join(command)}")
99
120
  subprocess.run(command, check=True, capture_output=True, text=True)
100
121
 
122
+ # The rest of your logic remains the same
101
123
  for i in range(1, 21):
102
124
  frame_path = os.path.join(temp_dir, f"frame_{i:02d}.png")
103
125
  if os.path.exists(frame_path):
@@ -122,7 +144,7 @@ class ScreenshotSelectorDialog(tk.Toplevel):
122
144
  except Exception as e:
123
145
  logger.error(f"An unexpected error occurred during frame extraction: {e}")
124
146
  return [], None
125
-
147
+
126
148
  def _build_image_grid(self, image_paths, golden_frame):
127
149
  """Creates and displays the grid of selectable images."""
128
150
  self.images = [] # Keep a reference to images to prevent garbage collection
@@ -12,7 +12,7 @@ from logging.handlers import RotatingFileHandler
12
12
  from os.path import expanduser
13
13
  from sys import platform
14
14
  import time
15
- from typing import List, Dict
15
+ from typing import Any, List, Dict
16
16
  import sys
17
17
  from enum import Enum
18
18
 
@@ -59,6 +59,28 @@ supported_formats = {
59
59
  'm4a': 'aac',
60
60
  }
61
61
 
62
+ KNOWN_ASPECT_RATIOS = [
63
+ # --- Classic / Legacy ---
64
+ {"name": "4:3 (SD / Retro Games)", "ratio": 4 / 3},
65
+ {"name": "5:4 (Old PC Monitors)", "ratio": 5 / 4},
66
+ {"name": "3:2 (Handheld / GBA / DS / DSLR)", "ratio": 3 / 2},
67
+
68
+ # --- Modern Displays ---
69
+ {"name": "16:10 (PC Widescreen)", "ratio": 16 / 10},
70
+ {"name": "16:9 (Standard HD / 1080p / 4K)", "ratio": 16 / 9},
71
+ {"name": "18:9 (Mobile / Some Modern Laptops)", "ratio": 18 / 9},
72
+ {"name": "19.5:9 (Modern Smartphones)", "ratio": 19.5 / 9},
73
+ {"name": "21:9 (UltraWide)", "ratio": 21 / 9},
74
+ {"name": "24:10 (UltraWide+)", "ratio": 24 / 10},
75
+ {"name": "32:9 (Super UltraWide)", "ratio": 32 / 9},
76
+
77
+ # --- Vertical / Mobile ---
78
+ {"name": "9:16 (Portrait Mode)", "ratio": 9 / 16},
79
+ {"name": "3:4 (Portrait 4:3)", "ratio": 3 / 4},
80
+ {"name": "1:1 (Square / UI Capture)", "ratio": 1 / 1},
81
+ ]
82
+
83
+ KNOWN_ASPECT_RATIOS_DICT = {item["name"]: item["ratio"] for item in KNOWN_ASPECT_RATIOS}
62
84
 
63
85
  def is_linux():
64
86
  return platform == 'linux'
@@ -490,6 +512,7 @@ class Screenshot:
490
512
  use_new_screenshot_logic: bool = False
491
513
  screenshot_timing_setting: str = 'beginning' # 'middle', 'end'
492
514
  use_screenshot_selector: bool = False
515
+ trim_black_bars_wip: bool = True
493
516
 
494
517
  def __post_init__(self):
495
518
  if not self.screenshot_timing_setting and self.use_beginning_of_line_as_screenshot:
@@ -632,6 +655,7 @@ class Ai:
632
655
  use_canned_translation_prompt: bool = True
633
656
  use_canned_context_prompt: bool = False
634
657
  custom_prompt: str = ''
658
+ custom_texthooker_prompt: str = ''
635
659
  dialogue_context_length: int = 10
636
660
 
637
661
  def __post_init__(self):
@@ -1321,10 +1345,11 @@ class AnkiUpdateResult:
1321
1345
  video_in_anki: str = ''
1322
1346
  word_path: str = ''
1323
1347
  word: str = ''
1348
+ extra_tags: List[str] = field(default_factory=list)
1324
1349
 
1325
1350
  @staticmethod
1326
1351
  def failure():
1327
- return AnkiUpdateResult(success=False, audio_in_anki='', screenshot_in_anki='', prev_screenshot_in_anki='', sentence_in_anki='', multi_line=False, video_in_anki='', word_path='', word='')
1352
+ return AnkiUpdateResult(success=False, audio_in_anki='', screenshot_in_anki='', prev_screenshot_in_anki='', sentence_in_anki='', multi_line=False, video_in_anki='', word_path='', word='', extra_tags=[])
1328
1353
 
1329
1354
 
1330
1355
  @dataclass_json
@@ -1376,6 +1401,8 @@ def get_ffprobe_path():
1376
1401
 
1377
1402
  ffmpeg_base_command_list = [get_ffmpeg_path(), "-hide_banner", "-loglevel", "error", '-nostdin']
1378
1403
 
1404
+ ffmpeg_base_command_list_info = [get_ffmpeg_path(), "-hide_banner", "-loglevel", "info", '-nostdin']
1405
+
1379
1406
 
1380
1407
  # logger.debug(f"Running in development mode: {is_dev}")
1381
1408
  # logger.debug(f"Running on Beangate's PC: {is_beangate}")
@@ -1,5 +1,6 @@
1
1
  import json
2
2
  import os
3
+ import re
3
4
  import subprocess
4
5
  import sys
5
6
  import tempfile
@@ -12,8 +13,8 @@ import shutil
12
13
 
13
14
  from GameSentenceMiner import obs
14
15
  from GameSentenceMiner.ui.config_gui import ConfigApp
15
- from GameSentenceMiner.util.configuration import ffmpeg_base_command_list, get_ffprobe_path, logger, get_config, \
16
- get_temporary_directory, gsm_state, is_linux
16
+ from GameSentenceMiner.util.configuration import ffmpeg_base_command_list, get_ffprobe_path, get_master_config, logger, get_config, \
17
+ get_temporary_directory, gsm_state, is_linux, ffmpeg_base_command_list_info, KNOWN_ASPECT_RATIOS
17
18
  from GameSentenceMiner.util.gsm_utils import make_unique_file_name, get_file_modification_time
18
19
  from GameSentenceMiner.util import configuration
19
20
  from GameSentenceMiner.util.text_log import initial_time
@@ -223,53 +224,307 @@ def get_screenshot(video_file, screenshot_timing, try_selector=False):
223
224
  return output
224
225
  else:
225
226
  logger.error("Frame extractor script failed to run or returned no output, defaulting")
227
+
226
228
  output_image = make_unique_file_name(os.path.join(
227
229
  get_temporary_directory(), f"{obs.get_current_game(sanitize=True)}.{get_config().screenshot.extension}"))
228
- # FFmpeg command to extract the last frame of the video
230
+
231
+ # Base command for extracting the frame
229
232
  ffmpeg_command = ffmpeg_base_command_list + [
230
233
  "-ss", f"{screenshot_timing}",
231
234
  "-i", f"{video_file}",
232
235
  "-vframes", "1" # Extract only one frame
233
236
  ]
237
+
238
+ video_filters = []
239
+
240
+ if get_config().screenshot.trim_black_bars_wip:
241
+ crop_filter = find_black_bars(video_file, screenshot_timing)
242
+ if crop_filter:
243
+ video_filters.append(crop_filter)
244
+
245
+ if get_config().screenshot.width or get_config().screenshot.height:
246
+ # Add scaling to the filter chain
247
+ scale_filter = f"scale={get_config().screenshot.width or -1}:{get_config().screenshot.height or -1}"
248
+ video_filters.append(scale_filter)
249
+
250
+ # If we have any filters (crop, scale, etc.), chain them together with commas
251
+ if video_filters:
252
+ ffmpeg_command.extend(["-vf", ",".join(video_filters)])
234
253
 
235
254
  if get_config().screenshot.custom_ffmpeg_settings:
236
255
  ffmpeg_command.extend(get_config().screenshot.custom_ffmpeg_settings.replace("\"", "").split(" "))
237
256
  else:
238
- ffmpeg_command.extend(["-compression_level", "6", "-q:v", get_config().screenshot.quality])
239
-
240
- if get_config().screenshot.width or get_config().screenshot.height:
241
- ffmpeg_command.extend(
242
- ["-vf", f"scale={get_config().screenshot.width or -1}:{get_config().screenshot.height or -1}"])
257
+ # Ensure quality settings are strings
258
+ ffmpeg_command.extend(["-compression_level", "6", "-q:v", str(get_config().screenshot.quality)])
243
259
 
244
260
  ffmpeg_command.append(f"{output_image}")
245
261
 
246
- logger.debug(f"FFMPEG SS Command: {ffmpeg_command}")
262
+ logger.debug(f"FFMPEG SS Command: {' '.join(map(str, ffmpeg_command))}")
247
263
 
248
264
  try:
265
+ # Changed the retry loop to be more robust
249
266
  for i in range(3):
250
- logger.debug(" ".join(ffmpeg_command))
251
- result = subprocess.run(ffmpeg_command)
252
- if result.returncode != 0 and i < 2:
253
- raise RuntimeError(f"FFmpeg command failed with return code {result.returncode}")
254
- else:
255
- break
267
+ logger.debug("Executing FFmpeg command...")
268
+ result = subprocess.run(ffmpeg_command, capture_output=True, text=True)
269
+ if result.returncode == 0:
270
+ break # Success!
271
+ logger.warning(f"FFmpeg attempt {i+1} failed. Stderr: {result.stderr}")
272
+ if i == 2: # Last attempt failed
273
+ raise RuntimeError(f"FFmpeg command failed after 3 attempts. Stderr: {result.stderr}")
256
274
  except Exception as e:
257
275
  logger.error(f"Error running FFmpeg command: {e}. Defaulting to standard PNG.")
258
276
  output_image = make_unique_file_name(os.path.join(
259
277
  get_temporary_directory(),
260
278
  f"{obs.get_current_game(sanitize=True)}.png"))
261
- ffmpeg_command = ffmpeg_base_command_list + [
279
+ # Fallback command without any complex filters
280
+ fallback_command = ffmpeg_base_command_list + [
262
281
  "-ss", f"{screenshot_timing}",
263
282
  "-i", video_file,
264
283
  "-vframes", "1",
265
284
  output_image
266
285
  ]
267
- subprocess.run(ffmpeg_command)
286
+ subprocess.run(fallback_command)
268
287
 
269
288
  logger.debug(f"Screenshot saved to: {output_image}")
270
289
 
271
290
  return output_image
272
291
 
292
+ def get_video_dimensions(video_file):
293
+ """Get the width and height of a video file."""
294
+ try:
295
+ ffprobe_command = [
296
+ get_ffprobe_path(),
297
+ "-v", "error",
298
+ "-select_streams", "v:0",
299
+ "-show_entries", "stream=width,height",
300
+ "-of", "json",
301
+ video_file
302
+ ]
303
+
304
+ result = subprocess.run(
305
+ ffprobe_command,
306
+ capture_output=True,
307
+ text=True,
308
+ check=True
309
+ )
310
+
311
+ output = json.loads(result.stdout)
312
+ width = output['streams'][0]['width']
313
+ height = output['streams'][0]['height']
314
+ return width, height
315
+ except Exception as e:
316
+ logger.error(f"Error getting video dimensions: {e}")
317
+ return None, None
318
+
319
+ # How close the detected ratio needs to be to a known ratio to snap (e.g., 0.05 = 5%)
320
+ RATIO_TOLERANCE = 0.05
321
+
322
+ def _calculate_target_crop(orig_width, orig_height, target_ratio):
323
+ """
324
+ Calculates the new dimensions and offsets for a target aspect ratio.
325
+
326
+ Returns: A tuple (new_width, new_height, x_offset, y_offset)
327
+ """
328
+ orig_ratio = orig_width / orig_height
329
+
330
+ if abs(orig_ratio - target_ratio) < 0.01: # Already at the target ratio
331
+ return orig_width, orig_height, 0, 0
332
+
333
+ if orig_ratio > target_ratio:
334
+ # Original is wider than target (pillarbox scenario)
335
+ # Keep original height, calculate new width
336
+ new_width = round(orig_height * target_ratio)
337
+ new_height = orig_height
338
+ x_offset = round((orig_width - new_width) / 2)
339
+ y_offset = 0
340
+ else:
341
+ # Original is narrower than target (letterbox scenario)
342
+ # Keep original width, calculate new height
343
+ new_width = orig_width
344
+ new_height = round(orig_width / target_ratio)
345
+ x_offset = 0
346
+ y_offset = round((orig_height - new_height) / 2)
347
+
348
+ # Ensure dimensions are even for compatibility
349
+ new_width = new_width if new_width % 2 == 0 else new_width - 1
350
+ new_height = new_height if new_height % 2 == 0 else new_height - 1
351
+ x_offset = x_offset if x_offset % 2 == 0 else x_offset - 1
352
+ y_offset = y_offset if y_offset % 2 == 0 else y_offset - 1
353
+
354
+ return new_width, new_height, x_offset, y_offset
355
+
356
+
357
+ def find_black_bars_with_ratio_snapping(video_file, screenshot_timing):
358
+ logger.info("Attempting to detect black bars with aspect ratio snapping...")
359
+ crop_filter = None
360
+ try:
361
+ orig_width, orig_height = get_video_dimensions(video_file)
362
+ if not orig_width or not orig_height:
363
+ logger.warning("Could not determine video dimensions. Skipping black bar detection.")
364
+ return None
365
+
366
+ orig_aspect = orig_width / orig_height
367
+ logger.debug(f"Original video dimensions: {orig_width}x{orig_height} (Ratio: {orig_aspect:.3f})")
368
+
369
+ cropdetect_command = ffmpeg_base_command_list_info + [
370
+ "-i", video_file,
371
+ "-ss", f"{screenshot_timing}",
372
+ "-t", "5", # Analyze for 5 seconds
373
+ "-vf", "cropdetect=limit=16", # limit=16 for near-black detection, 24 is too aggressive
374
+ "-f", "null", "-"
375
+ ]
376
+
377
+ result = subprocess.run(
378
+ cropdetect_command,
379
+ capture_output=True,
380
+ text=True,
381
+ check=False
382
+ )
383
+
384
+ crop_lines = re.findall(r"crop=\d+:\d+:\d+:\d+", result.stderr)
385
+ if not crop_lines:
386
+ logger.info("cropdetect did not find any black bars to remove.")
387
+ return None
388
+
389
+ last_crop_params = crop_lines[-1]
390
+ match = re.match(r"crop=(\d+):(\d+):(\d+):(\d+)", last_crop_params)
391
+ if not match:
392
+ logger.warning(f"Could not parse cropdetect output: {last_crop_params}")
393
+ return None
394
+
395
+ detected_width = int(match.group(1))
396
+ detected_height = int(match.group(2))
397
+
398
+ if detected_width == orig_width and detected_height == orig_height:
399
+ logger.info("cropdetect suggests no cropping is needed.")
400
+ return None
401
+
402
+ detected_aspect = detected_width / detected_height
403
+ logger.debug(f"cropdetect suggests crop to: {detected_width}x{detected_height} (Ratio: {detected_aspect:.3f})")
404
+
405
+ best_match = None
406
+ min_diff = float('inf')
407
+
408
+ for known in KNOWN_ASPECT_RATIOS:
409
+ diff = abs(detected_aspect - known["ratio"]) / known["ratio"]
410
+ if diff < min_diff:
411
+ min_diff = diff
412
+ best_match = known
413
+
414
+ get_master_config().scenes_info
415
+
416
+ if best_match and min_diff <= RATIO_TOLERANCE:
417
+ target_name = best_match["name"]
418
+ target_ratio = best_match["ratio"]
419
+ logger.info(
420
+ f"Detected ratio ({detected_aspect:.3f}) is close to {target_name} ({target_ratio:.3f}). "
421
+ f"Snapping to the standard ratio."
422
+ )
423
+
424
+ crop_width, crop_height, crop_x, crop_y = _calculate_target_crop(
425
+ orig_width, orig_height, target_ratio
426
+ )
427
+
428
+ area_ratio = (crop_width * crop_height) / (orig_width * orig_height)
429
+ if area_ratio < 0.50:
430
+ logger.warning(
431
+ f"Calculated crop would remove too much video ({1 - area_ratio:.1%}). "
432
+ "Skipping crop to avoid false detection."
433
+ )
434
+ return None
435
+
436
+ crop_filter = f"crop={crop_width}:{crop_height}:{crop_x}:{crop_y}"
437
+ logger.info(f"Applying snapped aspect ratio filter: {crop_filter}")
438
+
439
+ else:
440
+ logger.info(
441
+ f"Detected crop ratio ({detected_aspect:.3f}) is not close enough to any known standard. "
442
+ "Skipping crop to avoid non-standard results."
443
+ )
444
+ return None
445
+
446
+ except Exception as e:
447
+ logger.error(f"Error during black bar detection: {e}. Proceeding without cropping.")
448
+
449
+ return crop_filter
450
+
451
+ def find_black_bars(video_file, screenshot_timing):
452
+ logger.info("Attempting to detect black bars...")
453
+ crop_filter = None
454
+ try:
455
+ # Get original video dimensions
456
+ orig_width, orig_height = get_video_dimensions(video_file)
457
+ if not orig_width or not orig_height:
458
+ logger.warning("Could not determine video dimensions. Skipping black bar detection.")
459
+ return None
460
+
461
+ logger.debug(f"Original video dimensions: {orig_width}x{orig_height}")
462
+
463
+ cropdetect_command = ffmpeg_base_command_list_info + [
464
+ "-i", video_file,
465
+ "-ss", f"{screenshot_timing}", # Start near the screenshot time
466
+ "-t", "1", # Analyze for 1 second
467
+ "-vf", "cropdetect=limit=16", # limit=0 means true black only, round=2 for even dimensions
468
+ "-f", "null", "-" # Discard video output
469
+ ]
470
+
471
+ result = subprocess.run(
472
+ cropdetect_command,
473
+ capture_output=True,
474
+ text=True,
475
+ check=False
476
+ )
477
+
478
+ crop_lines = re.findall(r"crop=\d+:\d+:\d+:\d+", result.stderr)
479
+ if crop_lines:
480
+ crop_params = crop_lines[-1]
481
+ print(crop_params)
482
+ # Parse crop parameters: crop=width:height:x:y
483
+ match = re.match(r"crop=(\d+):(\d+):(\d+):(\d+)", crop_params)
484
+ if match:
485
+ crop_width = int(match.group(1))
486
+ crop_height = int(match.group(2))
487
+
488
+ # Calculate what percentage of the original video would remain
489
+ area_ratio = (crop_width * crop_height) / (orig_width * orig_height)
490
+
491
+ # Calculate aspect ratios
492
+ orig_aspect = orig_width / orig_height
493
+ crop_aspect = crop_width / crop_height
494
+ aspect_diff = abs(orig_aspect - crop_aspect) / orig_aspect
495
+
496
+ logger.debug(f"Crop would be {crop_width}x{crop_height} ({area_ratio:.1%} of original area)")
497
+ logger.debug(f"Original aspect ratio: {orig_aspect:.3f}, Crop aspect ratio: {crop_aspect:.3f}, Difference: {aspect_diff:.1%}")
498
+
499
+ # Safeguards:
500
+ # 1. Crop must retain at least 25% of the original video area
501
+ # 2. Aspect ratio must not change by more than 30%
502
+ if area_ratio < 0.25:
503
+ logger.warning(f"Crop would remove too much of the video ({area_ratio:.1%} remaining). Skipping crop to avoid false detection.")
504
+ return None
505
+
506
+ if aspect_diff > 0.30:
507
+ for ratio in KNOWN_ASPECT_RATIOS:
508
+ known_ratio = ratio["ratio"]
509
+ known_diff = abs(crop_aspect - known_ratio) / known_ratio
510
+ if known_diff < RATIO_TOLERANCE:
511
+ logger.info(f"Crop aspect ratio ({crop_aspect:.3f}) is close to known ratio {ratio['name']} ({known_ratio:.3f}). Accepting crop.")
512
+ break
513
+ else:
514
+ logger.warning(f"Crop would significantly change aspect ratio ({aspect_diff:.1%} difference). Skipping crop to avoid false detection.")
515
+ return None
516
+
517
+ crop_filter = crop_params
518
+ logger.info(f"Detected valid black bars. Applying filter: {crop_filter}")
519
+ else:
520
+ logger.warning("Could not parse crop parameters.")
521
+ else:
522
+ logger.debug("cropdetect did not find any black bars to remove.")
523
+
524
+ except Exception as e:
525
+ logger.error(f"Error during black bar detection: {e}. Proceeding without cropping.")
526
+ return crop_filter
527
+
273
528
  def get_screenshot_for_line(video_file, game_line, try_selector=False):
274
529
  return get_screenshot(video_file, get_screenshot_time(video_file, game_line), try_selector)
275
530
 
@@ -77,10 +77,12 @@ class WebsocketServerThread(threading.Thread):
77
77
  self._event.set()
78
78
  while True:
79
79
  try:
80
- self.server = start_server = websockets.serve(self.server_handler,
81
- get_config().advanced.localhost_bind_address,
82
- self.get_ws_port_func(),
83
- max_size=1000000000)
80
+ self.server = start_server = websockets.serve(
81
+ self.server_handler,
82
+ get_config().advanced.localhost_bind_address,
83
+ self.get_ws_port_func(),
84
+ max_size=1000000000,
85
+ )
84
86
  async with start_server:
85
87
  await stop_event.wait()
86
88
  return
@@ -106,11 +108,11 @@ websocket_server_thread.start()
106
108
  plaintext_websocket_server_thread = None
107
109
  if get_config().advanced.plaintext_websocket_port:
108
110
  plaintext_websocket_server_thread = WebsocketServerThread(
109
- read=False, get_ws_port_func=lambda: get_config().get_field_value('advanced', 'plaintext_websocket_port'))
111
+ read=True, get_ws_port_func=lambda: get_config().get_field_value('advanced', 'plaintext_websocket_port'))
110
112
  plaintext_websocket_server_thread.start()
111
113
 
112
114
  overlay_server_thread = WebsocketServerThread(
113
- read=False, get_ws_port_func=lambda: get_config().get_field_value('overlay', 'websocket_port'))
115
+ read=True, get_ws_port_func=lambda: get_config().get_field_value('overlay', 'websocket_port'))
114
116
  overlay_server_thread.start()
115
117
 
116
118
  websocket_server_threads = [