GameSentenceMiner 2.10.10__py3-none-any.whl → 2.10.11__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/gsm.py CHANGED
@@ -452,6 +452,13 @@ def cleanup():
452
452
  if icon:
453
453
  icon.stop()
454
454
 
455
+ for video in gsm_state.videos_to_remove:
456
+ try:
457
+ if os.path.exists(video):
458
+ os.remove(video)
459
+ except Exception as e:
460
+ logger.error(f"Error removing temporary video file {video}: {e}")
461
+
455
462
  settings_window.window.destroy()
456
463
  time.sleep(5)
457
464
  logger.info("Cleanup complete.")
@@ -20,6 +20,7 @@ except ImportError:
20
20
 
21
21
  try:
22
22
  import tkinter as tk
23
+ from tkinter import font as tkfont # NEW: Import for better font control
23
24
 
24
25
  selector_available = True
25
26
  except ImportError:
@@ -206,6 +207,66 @@ class ScreenSelector:
206
207
  self.drawn_rect_ids.append(new_rect_id)
207
208
  print("Redo: Restored rectangle.")
208
209
 
210
+ # --- NEW METHOD TO DISPLAY INSTRUCTIONS ---
211
+ def _create_instructions_widget(self, canvas):
212
+ """Creates a text box with usage instructions on the canvas."""
213
+ instructions_text = (
214
+ "How to Use:\n"
215
+ " • Left Click + Drag: Create a capture area (green).\n"
216
+ " • Shift + Left Click + Drag: Create an exclusion area (orange).\n"
217
+ " • Right-Click on a box: Delete it.\n\n"
218
+ "Hotkeys:\n"
219
+ " • Ctrl + S: Save and Quit\n"
220
+ " • Ctrl + Z / Ctrl + Y: Undo / Redo\n"
221
+ " • M: Toggle background visibility\n"
222
+ " • I: Toggle these instructions\n"
223
+ " • Esc: Quit without saving"
224
+ " "
225
+ )
226
+
227
+ # Use a common, readable font
228
+ instruction_font = tkfont.Font(family="Segoe UI", size=10, weight="normal")
229
+
230
+ # Create the text item first to get its size
231
+ text_id = canvas.create_text(
232
+ 20, 20, # Position with a small margin
233
+ text=instructions_text,
234
+ anchor=tk.NW,
235
+ fill='white',
236
+ font=instruction_font,
237
+ justify=tk.LEFT
238
+ )
239
+
240
+ # Get the bounding box of the text to draw a background
241
+ text_bbox = canvas.bbox(text_id)
242
+
243
+ # Create a background rectangle with padding
244
+ rect_id = canvas.create_rectangle(
245
+ text_bbox[0] - 10, # left
246
+ text_bbox[1] - 10, # top
247
+ text_bbox[2] + 10, # right
248
+ text_bbox[3] + 10, # bottom
249
+ fill='#2B2B2B', # Dark, semi-opaque background
250
+ outline='white',
251
+ width=1
252
+ )
253
+
254
+ # Lower the rectangle so it's behind the text
255
+ canvas.tag_lower(rect_id, text_id)
256
+
257
+ def toggle_instructions(self, event=None):
258
+ canvas = event.widget.winfo_toplevel().winfo_children()[0]
259
+ # Find all text and rectangle items (assuming only one of each for instructions)
260
+ text_items = [item for item in canvas.find_all() if canvas.type(item) == 'text']
261
+ rect_items = [item for item in canvas.find_all() if canvas.type(item) == 'rectangle']
262
+
263
+ if text_items and rect_items:
264
+ current_state = canvas.itemcget(text_items[0], 'state')
265
+ new_state = tk.NORMAL if current_state == tk.HIDDEN else tk.HIDDEN
266
+ for item in text_items + rect_items:
267
+ canvas.itemconfigure(item, state=new_state)
268
+ print("Toggled instructions visibility.")
269
+
209
270
  def start(self):
210
271
  self.root = tk.Tk()
211
272
  self.root.withdraw()
@@ -230,6 +291,10 @@ class ScreenSelector:
230
291
  canvas.pack(fill=tk.BOTH, expand=True)
231
292
  canvas.create_image(0, 0, image=self.photo_image, anchor=tk.NW)
232
293
 
294
+ # --- MODIFIED: CALL THE INSTRUCTION WIDGET CREATOR ---
295
+ self._create_instructions_widget(canvas)
296
+ # --- END MODIFICATION ---
297
+
233
298
  # Draw existing rectangles (which were converted to absolute pixels on load)
234
299
  for _, abs_coords, is_excluded in self.rectangles:
235
300
  x_abs, y_abs, w_abs, h_abs = abs_coords
@@ -275,17 +340,37 @@ class ScreenSelector:
275
340
  self.current_rect_id = self.start_x = self.start_y = None
276
341
 
277
342
  def on_right_click(event):
278
- items = canvas.find_closest(event.x, event.y)
279
- if items and items[0] in self.drawn_rect_ids:
280
- item_id = items[0]
281
- idx_to_del = self.drawn_rect_ids.index(item_id)
282
- del self.drawn_rect_ids[idx_to_del]
283
- del self.rectangles[idx_to_del]
284
- self.redo_stack.clear()
285
- canvas.delete(item_id)
343
+ # Iterate through our rectangles in reverse to find the topmost one.
344
+ for i in range(len(self.rectangles) - 1, -1, -1):
345
+ _monitor, abs_coords, _is_excluded = self.rectangles[i]
346
+ x_abs, y_abs, w_abs, h_abs = abs_coords
347
+ canvas_x1 = x_abs - self.bounding_box['left']
348
+ canvas_y1 = y_abs - self.bounding_box['top']
349
+ canvas_x2 = canvas_x1 + w_abs
350
+ canvas_y2 = canvas_y1 + h_abs
351
+
352
+ if canvas_x1 <= event.x <= canvas_x2 and canvas_y1 <= event.y <= canvas_y2:
353
+ # --- UNDO/REDO CHANGE ---
354
+ # We found the rectangle. Prepare the 'remove' action.
355
+ # We need to save the data AND its original index to restore it correctly.
356
+ rect_tuple_to_del = self.rectangles[i]
357
+ item_id_to_del = self.drawn_rect_ids[i]
358
+
359
+ self.redo_stack.append((*rect_tuple_to_del, i))
360
+
361
+ # Now, perform the deletion
362
+ del self.rectangles[i]
363
+ del self.drawn_rect_ids[i]
364
+ canvas.delete(item_id_to_del)
365
+ print("Deleted rectangle.")
366
+
367
+ break # Stop after deleting the topmost one
286
368
 
287
369
  def toggle_image_mode(e=None):
288
- self.image_mode = not self.image_mode; window.attributes("-alpha", 1.0 if self.image_mode else 0.25)
370
+ self.image_mode = not self.image_mode
371
+ # Only change alpha of the main window, not the text widget
372
+ window.attributes("-alpha", 1.0 if self.image_mode else 0.25)
373
+ print("Toggled background visibility.")
289
374
 
290
375
  def on_enter(e=None):
291
376
  canvas.focus_set()
@@ -296,13 +381,15 @@ class ScreenSelector:
296
381
  canvas.bind('<ButtonRelease-1>', on_release)
297
382
  canvas.bind('<Button-3>', on_right_click)
298
383
  canvas.bind('<Control-s>', self.save_rects)
299
- canvas.bind('<Control-z>', self.undo_last_rect)
300
384
  canvas.bind('<Control-y>', self.redo_last_rect)
385
+ canvas.bind('<Control-z>', self.undo_last_rect)
301
386
  canvas.bind("<Escape>", self.quit_app)
302
387
  canvas.bind("<m>", toggle_image_mode)
388
+ canvas.bind("<i>", self.toggle_instructions)
303
389
 
304
390
  canvas.focus_set()
305
- print("Starting UI. Press Esc to quit, Ctrl+S to save, M to toggle background.")
391
+ # The print message is now redundant but kept for console feedback
392
+ print("Starting UI. See on-screen instructions. Press Esc to quit, Ctrl+S to save.")
306
393
  self.root.mainloop()
307
394
 
308
395
  def quit_app(self, event=None):
@@ -350,10 +437,6 @@ if __name__ == "__main__":
350
437
  target_window_title = "Windowed Projector (Preview)" # Default
351
438
  if len(sys.argv) > 1:
352
439
  target_window_title = sys.argv[1]
353
- # else:
354
- # print("Usage: python your_script_name.py \"Target Window Title\"", file=sys.stderr)
355
- # print("Example: python selector.py \"Windowed Projector (Preview)\"", file=sys.stderr)
356
- # sys.exit(1)
357
440
 
358
441
  selection_result = get_screen_selection(target_window_title)
359
442
 
@@ -241,12 +241,12 @@ def text_callback(text, orig_text, time, img=None, came_from_ss=False, filtering
241
241
 
242
242
  line_start_time = time if time else datetime.now()
243
243
 
244
- if not twopassocr:
244
+ if manual or not twopassocr:
245
245
  if previous_text and fuzz.ratio(orig_text_string, previous_orig_text) >= 90:
246
246
  logger.info("Seems like Text we already sent, not doing anything.")
247
247
  return
248
248
  save_result_image(img)
249
- asyncio.run(send_result(text, time))
249
+ asyncio.run(send_result(text, line_start_time))
250
250
  previous_orig_text = orig_text_string
251
251
  previous_text = None
252
252
  previous_img = None
@@ -683,6 +683,7 @@ class GsmAppState:
683
683
  self.last_mined_line = None
684
684
  self.keep_running = True
685
685
  self.current_game = ''
686
+ self.videos_to_remove = set()
686
687
 
687
688
  @dataclass_json
688
689
  @dataclass
@@ -21,7 +21,7 @@ def handle_texthooker_button(video_path='', get_audio_from_video=None):
21
21
  if get_config().advanced.audio_player_path:
22
22
  play_audio_in_external(gsm_state.previous_audio)
23
23
  elif get_config().advanced.video_player_path:
24
- play_video_in_external(line, gsm_state.previous_audio)
24
+ play_video_in_external(line, video_path)
25
25
  else:
26
26
  import sounddevice as sd
27
27
  data, samplerate = gsm_state.previous_audio
@@ -35,9 +35,7 @@ def handle_texthooker_button(video_path='', get_audio_from_video=None):
35
35
  play_audio_in_external(audio)
36
36
  gsm_state.previous_audio = audio
37
37
  elif get_config().advanced.video_player_path:
38
- new_video_path = play_video_in_external(line, video_path)
39
- gsm_state.previous_audio = new_video_path
40
- gsm_state.previous_replay = new_video_path
38
+ play_video_in_external(line, video_path)
41
39
  else:
42
40
  import sounddevice as sd
43
41
  import soundfile as sf
@@ -75,8 +73,8 @@ def handle_texthooker_button(video_path='', get_audio_from_video=None):
75
73
  logger.debug(f"Error Playing Audio/Video: {e}", exc_info=True)
76
74
  return
77
75
  finally:
78
- if video_path and get_config().paths.remove_video and os.path.exists(video_path):
79
- os.remove(video_path)
76
+ gsm_state.previous_replay = video_path
77
+ gsm_state.videos_to_remove.add(video_path)
80
78
 
81
79
 
82
80
  def play_audio_in_external(filepath):
@@ -94,37 +92,28 @@ def play_audio_in_external(filepath):
94
92
 
95
93
 
96
94
  def play_video_in_external(line, filepath):
97
- def move_video_when_closed(p, fp):
98
- p.wait()
99
- os.remove(fp)
100
-
101
- shutil.move(filepath, get_temporary_directory())
102
- new_filepath = os.path.join(get_temporary_directory(), os.path.basename(filepath))
103
-
104
95
  command = [get_config().advanced.video_player_path]
105
96
 
106
- start, _, _, _ = get_video_timings(new_filepath, line)
97
+ start, _, _, _ = get_video_timings(filepath, line)
107
98
 
108
99
  if start:
109
100
  if "vlc" in get_config().advanced.video_player_path:
110
101
  command.extend(["--start-time", convert_to_vlc_seconds(start), '--one-instance'])
111
102
  else:
112
103
  command.extend(["--start", convert_to_vlc_seconds(start)])
113
- command.append(os.path.normpath(new_filepath))
104
+ command.append(os.path.normpath(filepath))
114
105
 
115
106
  logger.info(" ".join(command))
116
107
 
117
108
 
118
109
 
119
110
  try:
120
- proc = subprocess.Popen(command)
121
- print(f"Opened {filepath} in {get_config().advanced.video_player_path}.")
122
- threading.Thread(target=move_video_when_closed, args=(proc, filepath)).start()
111
+ subprocess.Popen(command)
112
+ logger.info(f"Opened {filepath} in {get_config().advanced.video_player_path}.")
123
113
  except FileNotFoundError:
124
- print("VLC not found. Make sure it's installed and in your PATH.")
114
+ logger.error("VLC not found. Make sure it's installed and in your PATH.")
125
115
  except Exception as e:
126
- print(f"An error occurred: {e}")
127
- return new_filepath
116
+ logger.error(f"An error occurred: {e}")
128
117
 
129
118
 
130
119
  def convert_to_vlc_seconds(time_str):
@@ -287,8 +287,8 @@ def get_screenshot():
287
287
  if event_id is None:
288
288
  return jsonify({'error': 'Missing id'}), 400
289
289
  gsm_state.line_for_screenshot = get_line_by_id(event_id)
290
- if gsm_state.previous_line_for_screenshot and gsm_state.line_for_screenshot.id == gsm_state.previous_line_for_screenshot.id:
291
- handle_texthooker_button()
290
+ if gsm_state.previous_line_for_screenshot and gsm_state.line_for_screenshot.id == gsm_state.previous_line_for_screenshot.id or gsm_state.previous_line_for_audio:
291
+ handle_texthooker_button(gsm_state.previous_replay)
292
292
  else:
293
293
  obs.save_replay_buffer()
294
294
  return jsonify({}), 200
@@ -301,8 +301,8 @@ def play_audio():
301
301
  if event_id is None:
302
302
  return jsonify({'error': 'Missing id'}), 400
303
303
  gsm_state.line_for_audio = get_line_by_id(event_id)
304
- if gsm_state.previous_line_for_audio and gsm_state.line_for_audio == gsm_state.previous_line_for_audio:
305
- handle_texthooker_button()
304
+ if gsm_state.previous_line_for_audio and gsm_state.line_for_audio == gsm_state.previous_line_for_audio or gsm_state.previous_line_for_screenshot:
305
+ handle_texthooker_button(gsm_state.previous_replay)
306
306
  else:
307
307
  obs.save_replay_buffer()
308
308
  return jsonify({}), 200
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GameSentenceMiner
3
- Version: 2.10.10
3
+ Version: 2.10.11
4
4
  Summary: A tool for mining sentences from games. Update: Full UI Re-design
5
5
  Author-email: Beangate <bpwhelan95@gmail.com>
6
6
  License: MIT License
@@ -2,7 +2,7 @@ GameSentenceMiner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU
2
2
  GameSentenceMiner/anki.py,sha256=kWw3PV_Jj5-lHcttCB3lRXejHlaAbiJ2Ag_NAGX-RI8,16632
3
3
  GameSentenceMiner/config_gui.py,sha256=h-vDxpFCC347iK_mDJAjwKm7Qubeu-NWaxvd9SvzqzY,90942
4
4
  GameSentenceMiner/gametext.py,sha256=6VkjmBeiuZfPk8T6PHFdIAElBH2Y_oLVYvmcafqN7RM,6747
5
- GameSentenceMiner/gsm.py,sha256=PSL_J723k23SIfgeNhoXgTqlG-V3MQTFJtLDcrZDFqs,24625
5
+ GameSentenceMiner/gsm.py,sha256=p4DVa_Jx1EOsgUxAAdC7st7VXLKWnP2BLDGT78ToO8w,24864
6
6
  GameSentenceMiner/obs.py,sha256=ZV9Vk39hrsJLT-AlIxa3qgncKxXaL3Myl33vVJEDEoA,14670
7
7
  GameSentenceMiner/vad.py,sha256=G0NkaWFJaIfKQAV7LOFxyKoih7pPNYHDuy4SzeFVCkI,16389
8
8
  GameSentenceMiner/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -18,8 +18,8 @@ GameSentenceMiner/assets/pickaxe.png,sha256=VfIGyXyIZdzEnVcc4PmG3wszPMO1W4KCT7Q_
18
18
  GameSentenceMiner/ocr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
19
  GameSentenceMiner/ocr/gsm_ocr_config.py,sha256=0hZmNIvZmlAEcy_NaTukG_ALUORULUT7sQ8q5VlDJU4,4047
20
20
  GameSentenceMiner/ocr/ocrconfig.py,sha256=_tY8mjnzHMJrLS8E5pHqYXZjMuLoGKYgJwdhYgN-ny4,6466
21
- GameSentenceMiner/ocr/owocr_area_selector.py,sha256=GEqIIhRc3WCIAx3HunuYo6ayJsCnZWT-x9fwZMCy2e8,16183
22
- GameSentenceMiner/ocr/owocr_helper.py,sha256=YHhG3PuJsPWP4352TAu4dtdX7itRiOybngzZVT4B50c,20184
21
+ GameSentenceMiner/ocr/owocr_area_selector.py,sha256=boAqarX17jvFscu-7s6C9rqesjQ54s-kfuW0bjCru-M,19834
22
+ GameSentenceMiner/ocr/owocr_helper.py,sha256=wkrobbrBugzzRBnUO9zBnxIwMEHWVTwxfutDn2HY17c,20205
23
23
  GameSentenceMiner/ocr/ss_picker.py,sha256=0IhxUdaKruFpZyBL-8SpxWg7bPrlGpy3lhTcMMZ5rwo,5224
24
24
  GameSentenceMiner/owocr/owocr/__init__.py,sha256=87hfN5u_PbL_onLfMACbc0F5j4KyIK9lKnRCj6oZgR0,49
25
25
  GameSentenceMiner/owocr/owocr/__main__.py,sha256=XQaqZY99EKoCpU-gWQjNbTs7Kg17HvBVE7JY8LqIE0o,157
@@ -30,7 +30,7 @@ GameSentenceMiner/owocr/owocr/run.py,sha256=mZIGDm3fGYrYbSNuFOk7Sbslfgi36YN0YqfC
30
30
  GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py,sha256=Na6XStbQBtpQUSdbN3QhEswtKuU1JjReFk_K8t5ezQE,3395
31
31
  GameSentenceMiner/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
32
  GameSentenceMiner/util/audio_offset_selector.py,sha256=8Stk3BP-XVIuzRv9nl9Eqd2D-1yD3JrgU-CamBywJmY,8542
33
- GameSentenceMiner/util/configuration.py,sha256=iXgfrawPPpAmQdXv2zPR-LuZnXx1ORGAwwP55OmLOs8,28778
33
+ GameSentenceMiner/util/configuration.py,sha256=wuuM39xhXahswx7EhhWXURDQ_KIPbo4RhmQ_wPEbezo,28816
34
34
  GameSentenceMiner/util/electron_config.py,sha256=3VmIrcXhC-wIMMc4uqV85NrNenRl4ZUbnQfSjWEwuig,9852
35
35
  GameSentenceMiner/util/ffmpeg.py,sha256=t0tflxq170n8PZKkdw8fTZIUQfXD0p_qARa9JTdhBTc,21530
36
36
  GameSentenceMiner/util/gsm_utils.py,sha256=_279Fu9CU6FEh4cP6h40TWOt_BrqmPgytfumi8y53Ew,11491
@@ -47,8 +47,8 @@ GameSentenceMiner/util/downloader/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeR
47
47
  GameSentenceMiner/util/downloader/download_tools.py,sha256=mvnOjDHFlV1AbjHaNI7mdnC5_CH5k3N4n1ezqzzbzGA,8139
48
48
  GameSentenceMiner/util/downloader/oneocr_dl.py,sha256=EJbKISaZ9p2x9P4x0rpMM5nAInTTc9b7arraGBcd-SA,10381
49
49
  GameSentenceMiner/web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
50
- GameSentenceMiner/web/service.py,sha256=2o62dZfDSBxBH5zCjrcHCX5yAc3PmGeP2lr07n8-dgo,5779
51
- GameSentenceMiner/web/texthooking_page.py,sha256=rX2rBFIlVlKmVXB8dseuyWfMzcDcjNNQosncwUolMu8,16054
50
+ GameSentenceMiner/web/service.py,sha256=S7bYf2kSk08u-8R9Qpv7piM-pxfFjYZUvU825xupmuI,5279
51
+ GameSentenceMiner/web/texthooking_page.py,sha256=EmcIBEPGWNgI2LGL3kKUsm0rs2Vn0CPq9PVKKnuIt2g,16183
52
52
  GameSentenceMiner/web/static/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
53
53
  GameSentenceMiner/web/static/apple-touch-icon.png,sha256=OcMI8af_68DA_tweOsQ5LytTyMwm7-hPW07IfrOVgEs,46132
54
54
  GameSentenceMiner/web/static/favicon-96x96.png,sha256=lOePzjiKl1JY2J1kT_PMdyEnrlJmi5GWbmXJunM12B4,16502
@@ -62,9 +62,9 @@ GameSentenceMiner/web/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm
62
62
  GameSentenceMiner/web/templates/index.html,sha256=n0J-dV8eksj8JXUuaCTIh0fIxIjfgm2EvxGBdQ6gWoM,214113
63
63
  GameSentenceMiner/web/templates/text_replacements.html,sha256=tV5c8mCaWSt_vKuUpbdbLAzXZ3ATZeDvQ9PnnAfqY0M,8598
64
64
  GameSentenceMiner/web/templates/utility.html,sha256=3flZinKNqUJ7pvrZk6xu__v67z44rXnaK7UTZ303R-8,16946
65
- gamesentenceminer-2.10.10.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
66
- gamesentenceminer-2.10.10.dist-info/METADATA,sha256=KJtMtM6AUz0qc8xsuSrNxd_I53gcYtAPNWO9VkDGSsY,7355
67
- gamesentenceminer-2.10.10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
68
- gamesentenceminer-2.10.10.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
69
- gamesentenceminer-2.10.10.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
70
- gamesentenceminer-2.10.10.dist-info/RECORD,,
65
+ gamesentenceminer-2.10.11.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
66
+ gamesentenceminer-2.10.11.dist-info/METADATA,sha256=pEHEHL90MhO8afUJ3yQTLDjdGvcYz5slrezzJ6biWfk,7355
67
+ gamesentenceminer-2.10.11.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
68
+ gamesentenceminer-2.10.11.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
69
+ gamesentenceminer-2.10.11.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
70
+ gamesentenceminer-2.10.11.dist-info/RECORD,,