GameSentenceMiner 2.9.0__py3-none-any.whl → 2.9.2__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.
@@ -2,6 +2,7 @@ import tempfile
2
2
  import warnings
3
3
 
4
4
  import stable_whisper as whisper
5
+ import torch
5
6
  from stable_whisper import WhisperResult
6
7
 
7
8
  from GameSentenceMiner import configuration, ffmpeg
@@ -16,10 +17,9 @@ whisper_model = None
16
17
  def load_whisper_model():
17
18
  global whisper_model
18
19
  if whisper_model is None:
19
- logger.info(f"Loading Whisper model '{get_config().vad.whisper_model}'... This may take a while.")
20
20
  with warnings.catch_warnings(action="ignore"):
21
21
  whisper_model = whisper.load_model(get_config().vad.whisper_model)
22
- logger.info("Whisper model loaded.")
22
+ logger.info(f"Whisper model '{get_config().vad.whisper_model}' loaded.")
23
23
 
24
24
 
25
25
  # Use Whisper to detect voice activity with timestamps in the audio
@@ -35,8 +35,7 @@ def detect_voice_with_whisper(input_audio):
35
35
 
36
36
  # Transcribe the audio using Whisper
37
37
  with warnings.catch_warnings(action="ignore"):
38
- result: WhisperResult = whisper_model.transcribe(temp_wav, vad=True, language='ja')
39
-
38
+ result: WhisperResult = whisper_model.transcribe(temp_wav, vad=True, language=get_config().vad.language, temperature=0.0)
40
39
  voice_activity = []
41
40
 
42
41
  logger.debug(result.to_dict())
@@ -75,7 +74,7 @@ def process_audio_with_whisper(input_audio, output_audio, game_line):
75
74
 
76
75
  if not voice_activity:
77
76
  logger.info("No voice activity detected in the audio.")
78
- return VADResult(False, 0, 0)
77
+ return VADResult(False, 0, 0, WHISPER)
79
78
 
80
79
  # Trim based on the first and last speech detected
81
80
  start_time = voice_activity[0]['start'] if voice_activity else 0
@@ -95,10 +94,12 @@ def process_audio_with_whisper(input_audio, output_audio, game_line):
95
94
 
96
95
  # Trim the audio using FFmpeg
97
96
  ffmpeg.trim_audio(input_audio, start_time + get_config().vad.beginning_offset, end_time + get_config().audio.end_offset, output_audio)
98
- return VADResult(True, start_time + get_config().vad.beginning_offset, end_time + get_config().audio.end_offset)
97
+ return VADResult(True, start_time + get_config().vad.beginning_offset, end_time + get_config().audio.end_offset, WHISPER)
99
98
 
100
99
 
101
100
  # Load Whisper model initially
102
101
  def initialize_whisper_model():
103
102
  load_whisper_model()
104
- logger.info(f"Using Whisper model '{get_config().vad.whisper_model}' for Japanese voice detection")
103
+
104
+ # initialize_whisper_model()
105
+ # process_audio_with_whisper("tmp6x81cy27.opus", "tmp6x81cy27_trimmed.opus", None)
@@ -14,7 +14,7 @@ from GameSentenceMiner.text_log import GameLine, get_line_by_id, initial_time, g
14
14
  from flask import request, jsonify, send_from_directory
15
15
  import webbrowser
16
16
  from GameSentenceMiner import obs
17
- from GameSentenceMiner.configuration import logger, get_config, DB_PATH
17
+ from GameSentenceMiner.configuration import logger, get_config, DB_PATH, gsm_state
18
18
  from GameSentenceMiner.util import TEXT_REPLACEMENTS_FILE
19
19
 
20
20
  port = get_config().general.texthooker_port
@@ -52,8 +52,6 @@ class EventItem:
52
52
  class EventManager:
53
53
  events: list[EventItem]
54
54
  events_dict: dict[str, EventItem] = {}
55
- line_for_audio: GameLine = None
56
- line_for_screenshot: GameLine = None
57
55
 
58
56
  def __init__(self):
59
57
  self.events = []
@@ -100,7 +98,7 @@ class EventManager:
100
98
  self.events_dict[line.id] = new_event
101
99
  self.events.append(new_event)
102
100
  # self.store_to_db(new_event)
103
- event_queue.put(new_event)
101
+ # event_queue.put(new_event)
104
102
  return new_event
105
103
 
106
104
  def reset_checked_lines(self):
@@ -271,14 +269,14 @@ def update_event():
271
269
  def get_screenshot():
272
270
  """Endpoint to get a screenshot of the current game screen."""
273
271
  data = request.get_json()
274
- print(data)
275
272
  event_id = data.get('id')
276
273
  if event_id is None:
277
274
  return jsonify({'error': 'Missing id'}), 400
278
- event_manager.line_for_screenshot = get_line_by_id(event_id)
279
- print(get_all_lines())
280
- print(event_manager.line_for_screenshot)
281
- obs.save_replay_buffer()
275
+ gsm_state.line_for_screenshot = get_line_by_id(event_id)
276
+ if gsm_state.previous_line_for_screenshot and gsm_state.line_for_screenshot.id == gsm_state.previous_line_for_screenshot.id:
277
+ open(os.path.join(get_config().paths.folder_to_watch, "previous.mkv"), 'a').close()
278
+ else:
279
+ obs.save_replay_buffer()
282
280
  return jsonify({}), 200
283
281
 
284
282
  @app.route('/play-audio', methods=['POST'])
@@ -288,8 +286,11 @@ def play_audio():
288
286
  event_id = data.get('id')
289
287
  if event_id is None:
290
288
  return jsonify({'error': 'Missing id'}), 400
291
- event_manager.line_for_audio = get_line_by_id(event_id)
292
- obs.save_replay_buffer()
289
+ gsm_state.line_for_audio = get_line_by_id(event_id)
290
+ if gsm_state.previous_line_for_audio and gsm_state.line_for_audio == gsm_state.previous_line_for_audio:
291
+ open(os.path.join(get_config().paths.folder_to_watch, "previous.mkv"), 'a').close()
292
+ else:
293
+ obs.save_replay_buffer()
293
294
  return jsonify({}), 200
294
295
 
295
296
 
@@ -321,8 +322,8 @@ async def websocket_handler(websocket):
321
322
 
322
323
  async def broadcast_message(message):
323
324
  if connected_clients:
324
- tasks = [client.send(json.dumps(message)) for client in connected_clients]
325
- await asyncio.gather(*tasks)
325
+ for client in connected_clients:
326
+ await client.send(json.dumps(message))
326
327
 
327
328
  # async def main():
328
329
  # async with websockets.serve(websocket_handler, "localhost", 8765): # Choose a port for WebSocket
@@ -380,19 +381,30 @@ def start_web_server():
380
381
 
381
382
  app.run(port=port, debug=False) # debug=True provides helpful error messages during development
382
383
 
383
- async def run_websocket_server(host="0.0.0.0", port=55001):
384
- global websocket_port
385
- while True:
386
- websocket_port = port
387
- try:
388
- async with websockets.serve(websocket_handler, host, port):
389
- logger.debug(f"WebSocket server started at ws://{host}:{port}/")
390
- await asyncio.Future() # Keep the WebSocket server running
391
- except OSError as e:
392
- logger.debug(f"Port {port} is in use. Trying the next port...")
393
- port += 1
384
+ import signal
394
385
 
386
+ async def run_websocket_server(host="0.0.0.0"):
387
+ global websocket_port
388
+ websocket = None
389
+ try:
390
+ websocket_port = get_config().advanced.texthooker_communication_websocket_port
391
+ websocket = await websockets.serve(websocket_handler, host, websocket_port)
392
+ logger.debug(f"WebSocket server started at ws://{host}:{websocket_port}/")
393
+ await asyncio.Future() # Keep the server running
394
+ except asyncio.CancelledError:
395
+ logger.info("WebSocket server shutting down...")
396
+ except OSError as e:
397
+ logger.error(f"TextHooker WebSocket server failed to start on port {websocket_port}: {e}")
398
+ logger.info("You may need to try a different port in GSM's advanced config, and then update that in the Texthooker's settings.")
399
+ finally:
400
+ if websocket:
401
+ websocket.close()
402
+ await asyncio.sleep(1) # Wait before retrying
395
403
 
404
+ def handle_exit_signal(loop):
405
+ logger.info("Received exit signal. Shutting down...")
406
+ for task in asyncio.all_tasks(loop):
407
+ task.cancel()
396
408
 
397
409
  async def texthooker_page_coro():
398
410
  # Run the WebSocket server in the asyncio event loop
@@ -404,7 +416,10 @@ async def texthooker_page_coro():
404
416
  await run_websocket_server()
405
417
 
406
418
  def run_text_hooker_page():
407
- asyncio.run(texthooker_page_coro())
419
+ try:
420
+ asyncio.run(texthooker_page_coro())
421
+ except KeyboardInterrupt:
422
+ logger.info("Shutting down due to KeyboardInterrupt.")
408
423
 
409
424
  if __name__ == '__main__':
410
- asyncio.run(run_text_hooker_page())
425
+ asyncio.run(texthooker_page_coro())
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GameSentenceMiner
3
- Version: 2.9.0
4
- Summary: A tool for mining sentences from games. Update: Multi-Line Mining! Fixed!
3
+ Version: 2.9.2
4
+ Summary: A tool for mining sentences from games.
5
5
  Author-email: Beangate <bpwhelan95@gmail.com>
6
6
  License: MIT License
7
7
  Project-URL: Homepage, https://github.com/bpwhelan/GameSentenceMiner
@@ -38,6 +38,7 @@ Requires-Dist: pygetwindow; sys_platform == "win32"
38
38
  Requires-Dist: flask
39
39
  Requires-Dist: groq
40
40
  Requires-Dist: obsws-python
41
+ Requires-Dist: Flask-SocketIO
41
42
  Dynamic: license-file
42
43
 
43
44
  # GameSentenceMiner (GSM)
@@ -152,6 +153,8 @@ If you encounter issues, please ask for help in my [Discord](https://discord.gg/
152
153
 
153
154
  * [OBS](https://obsproject.com/) and [FFMPEG](https://ffmpeg.org/), without which GSM would not be possible.
154
155
 
156
+ * [Renji's Texthooker](https://github.com/Renji-XD/texthooker-ui)
157
+
155
158
  ## Donations
156
159
 
157
160
  If you've found this or any of my other projects helpful, please consider supporting my work through [GitHub Sponsors](https://github.com/sponsors/bpwhelan) or [Ko-fi](https://ko-fi.com/beangate).
@@ -1,24 +1,24 @@
1
1
  GameSentenceMiner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- GameSentenceMiner/anki.py,sha256=bChJ1YU80muhvR8fjY9KAJEs0M0bpe-X_uMhjJBUC4k,14530
3
- GameSentenceMiner/config_gui.py,sha256=xsk-yvn1ATQ_HvV7Rb2YmDyHQe-U1aQgj0j1TMcHaHE,78155
4
- GameSentenceMiner/configuration.py,sha256=7GnEYF1C5Mb5ZWZIMcHTeikGCpj7OXNxN1cMX7TBeVE,22666
2
+ GameSentenceMiner/anki.py,sha256=JnVfFkLpEfWaPfOLngU0PSQq4vrgWuhd_VLYZEBqNTY,14608
3
+ GameSentenceMiner/config_gui.py,sha256=h4zz85gfhxSphaJ-IZSu9D4jR70mDlKecZ9JRCO5Noc,80927
4
+ GameSentenceMiner/configuration.py,sha256=8CfdTJ0ROJrxyzNg3NaElAVS1bwchg1ih6XfhfDZy1g,25492
5
5
  GameSentenceMiner/electron_config.py,sha256=dGcPYCISPehXubYSzsDuI2Gl092MYK0u3bTnkL9Jh1Y,9787
6
- GameSentenceMiner/ffmpeg.py,sha256=DX-2J1KZBKOPC8syR73YhDHwKIR4oNPrGuPl4l8is-4,18255
7
- GameSentenceMiner/gametext.py,sha256=hcyZQ69B7xB5ZG85wLzM5au7ZPKxmeUXsmUD26oyk_0,5660
8
- GameSentenceMiner/gsm.py,sha256=KI7ls2xvf9j2cCovW4hEutOva3YWArEtN7GR0v1xnec,27695
6
+ GameSentenceMiner/ffmpeg.py,sha256=zVmLJOsXpy71zKb0cLBPrXJ6YpjPVRJmH0uRfd5O30k,18299
7
+ GameSentenceMiner/gametext.py,sha256=sll-6Pficd4ZXYy8yL8hBrEOSpfa53TOye7vtHHKFN4,6218
8
+ GameSentenceMiner/gsm.py,sha256=_Mp_gZFomeFz9FTZqYEXIgqxbICYcAB06KUlFkClX5Q,29831
9
9
  GameSentenceMiner/model.py,sha256=1lRyJFf_LND_4O16h8CWVqDfosLgr0ZS6ufBZ3qJHpY,5699
10
10
  GameSentenceMiner/notification.py,sha256=pXKoLfmRQLH55IQ5G6uxdMuczqX7D6l3ubVEY1e6hXg,2859
11
- GameSentenceMiner/obs.py,sha256=JwcVPnjO-Lm0H5007o3rF-gMf4ypgIm5m8ntthfbTk8,14789
11
+ GameSentenceMiner/obs.py,sha256=DoUJk00Gk0Idley7CEldfIobqJ9na2UBlCv7nclZO4s,14793
12
12
  GameSentenceMiner/obs_back.py,sha256=_N_UV7Nh5cyy3mnH5lOUOzhgZwHMACeFEuBo1Z-bNzg,10894
13
13
  GameSentenceMiner/package.py,sha256=YlS6QRMuVlm6mdXx0rlXv9_3erTGS21jaP3PNNWfAH0,1250
14
14
  GameSentenceMiner/ss_selector.py,sha256=csey9H3561-guRJcT6gQN6hXxvylP0CBI0dp2-kwo2Q,4446
15
- GameSentenceMiner/text_log.py,sha256=MD7LB5D-v4G0Bnm3uGvZQ0aV38Fcj4E0vgq7mmyQ7_4,5157
15
+ GameSentenceMiner/text_log.py,sha256=U2_g8THAYeexRiE2bLk_bCt_2ShiA8SQ9VdJsi4riHs,5181
16
16
  GameSentenceMiner/util.py,sha256=PrDNnxWiJZh1lGuwnp3DjWIlwbkVxweRTYWLtQk94Ao,9122
17
17
  GameSentenceMiner/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
18
  GameSentenceMiner/ai/ai_prompting.py,sha256=xw8et6XNwQiDXOXZnw8iIntVSg8lni4YYZbgWsK7qDE,10013
19
19
  GameSentenceMiner/communication/__init__.py,sha256=_jGn9PJxtOAOPtJ2rI-Qu9hEHVZVpIvWlxKvqk91_zI,638
20
20
  GameSentenceMiner/communication/send.py,sha256=X0MytGv5hY-uUvkfvdCqQA_ljZFmV6UkJ6in1TA1bUE,217
21
- GameSentenceMiner/communication/websocket.py,sha256=pTcUe_ZZRp9REdSU4qalhPmbT_1DKa7w18j6RfFLELA,3074
21
+ GameSentenceMiner/communication/websocket.py,sha256=8eFZaTtoFggEPdqw2Jl4zqHC2I7J3-Gk27CxVX7SyBo,3277
22
22
  GameSentenceMiner/downloader/Untitled_json.py,sha256=RUUl2bbbCpUDUUS0fP0tdvf5FngZ7ILdA_J5TFYAXUQ,15272
23
23
  GameSentenceMiner/downloader/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
24
  GameSentenceMiner/downloader/download_tools.py,sha256=aRfpCqEmKUFRVsGipwY-7PhY6AeWiFJanW4ZCB9e2iE,8124
@@ -27,22 +27,23 @@ GameSentenceMiner/ocr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3h
27
27
  GameSentenceMiner/ocr/gsm_ocr_config.py,sha256=fEQ2o2NXksGRHpueO8c4TfAp75GEdAtAr1ngTFOsdpg,2257
28
28
  GameSentenceMiner/ocr/ocrconfig.py,sha256=_tY8mjnzHMJrLS8E5pHqYXZjMuLoGKYgJwdhYgN-ny4,6466
29
29
  GameSentenceMiner/ocr/owocr_area_selector.py,sha256=Q8ETMHL7BKMA1mbtjrntDLyqCQB0lZ5T4RCZsodjH7Y,47186
30
- GameSentenceMiner/ocr/owocr_helper.py,sha256=EMdq8jga3kKx8u19LSgqwnZIhR8_YDMqydACVzojUDo,17726
30
+ GameSentenceMiner/ocr/owocr_helper.py,sha256=M4Is-Ki5O3r4ixYhILibfjrVGD6xDlOcR3YvVGmETQ4,17363
31
31
  GameSentenceMiner/owocr/owocr/__init__.py,sha256=opjBOyGGyEqZCE6YdZPnyt7nVfiwyELHsXA0jAsjm14,25
32
32
  GameSentenceMiner/owocr/owocr/__main__.py,sha256=XQaqZY99EKoCpU-gWQjNbTs7Kg17HvBVE7JY8LqIE0o,157
33
33
  GameSentenceMiner/owocr/owocr/config.py,sha256=qM7kISHdUhuygGXOxmgU6Ef2nwBShrZtdqu4InDCViE,8103
34
34
  GameSentenceMiner/owocr/owocr/lens_betterproto.py,sha256=oNoISsPilVVRBBPVDtb4-roJtAhp8ZAuFTci3TGXtMc,39141
35
- GameSentenceMiner/owocr/owocr/ocr.py,sha256=rtKoIonyzqAMRPK92GvfYgGsU5M2yIWcWz9MQngNstc,41602
35
+ GameSentenceMiner/owocr/owocr/ocr.py,sha256=V0HqVRQlaE1-12IH480IupfSv1BlDdEcwNPejhQZfS0,42292
36
36
  GameSentenceMiner/owocr/owocr/run.py,sha256=0UyjOKEP0MqSdCaagCUMGdqO-BMexPxCl7ZabGlic4E,54749
37
37
  GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py,sha256=Na6XStbQBtpQUSdbN3QhEswtKuU1JjReFk_K8t5ezQE,3395
38
38
  GameSentenceMiner/vad/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
- GameSentenceMiner/vad/result.py,sha256=C08HsYH4qVjTRh_dvrWrskmXHJ950w0GWxPjGx_BfGY,275
40
- GameSentenceMiner/vad/silero_trim.py,sha256=InYsCy29WjK5EIB4e-KYb91rdHLCc5ZGbKtn9W5WmZI,2021
39
+ GameSentenceMiner/vad/groq_trim.py,sha256=MDYiApduwF7oDx3r0TXL3xQrTkbUC1RinMwNKSbF5gw,3764
40
+ GameSentenceMiner/vad/result.py,sha256=aFlr2px90fn3qXj49dwF9BDXA5m4yXD_HYH01CVvP1U,799
41
+ GameSentenceMiner/vad/silero_trim.py,sha256=u4BC93LieJW0CZ7HToz51FneojqW_SNjSKmJmHMKwUA,2240
41
42
  GameSentenceMiner/vad/vad_utils.py,sha256=_YC6rW2eXSBeLnYbVl_F3na1KCRL90VrnOzKYJ9RhUE,391
42
- GameSentenceMiner/vad/vosk_helper.py,sha256=3ea8P6SoXG2wOkqesFWPOsHfJNmyYTaQWsr_QLU-jZo,6467
43
- GameSentenceMiner/vad/whisper_helper.py,sha256=H4bmEMgUXYl_CxpYUcDa2hWGUy4vOVXZBoWhh5fwvaY,4015
43
+ GameSentenceMiner/vad/vosk_helper.py,sha256=h7yNHrzrzT-J74UniA0T2ZX8cHqhflCzwyDjoIdKLO4,6479
44
+ GameSentenceMiner/vad/whisper_helper.py,sha256=B64-Eq_ZMCIyQX_A8uvYz-c48hSXJAyz6tSXNRaLjtA,4020
44
45
  GameSentenceMiner/web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
45
- GameSentenceMiner/web/texthooking_page.py,sha256=7ccCJNyVD4cokpn-OIyHkJxxuhbkSPBoXyd6KIbL69Q,13869
46
+ GameSentenceMiner/web/texthooking_page.py,sha256=alXNkpm2Kl-ewYIm_aw24_8RdcYedLB59y6YTkp_mrE,14789
46
47
  GameSentenceMiner/web/static/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
48
  GameSentenceMiner/web/static/apple-touch-icon.png,sha256=OcMI8af_68DA_tweOsQ5LytTyMwm7-hPW07IfrOVgEs,46132
48
49
  GameSentenceMiner/web/static/favicon-96x96.png,sha256=lOePzjiKl1JY2J1kT_PMdyEnrlJmi5GWbmXJunM12B4,16502
@@ -56,9 +57,9 @@ GameSentenceMiner/web/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm
56
57
  GameSentenceMiner/web/templates/index.html,sha256=HZKiIjiGJV8PGQ9T2aLDUNSfJn71qOwbYCjbRuSIjpY,213583
57
58
  GameSentenceMiner/web/templates/text_replacements.html,sha256=tV5c8mCaWSt_vKuUpbdbLAzXZ3ATZeDvQ9PnnAfqY0M,8598
58
59
  GameSentenceMiner/web/templates/utility.html,sha256=3flZinKNqUJ7pvrZk6xu__v67z44rXnaK7UTZ303R-8,16946
59
- gamesentenceminer-2.9.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
60
- gamesentenceminer-2.9.0.dist-info/METADATA,sha256=NIokKorIMy-PWNr9QYmc-FDzXU2KSCVhOjBps7uJTGc,7217
61
- gamesentenceminer-2.9.0.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
62
- gamesentenceminer-2.9.0.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
63
- gamesentenceminer-2.9.0.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
64
- gamesentenceminer-2.9.0.dist-info/RECORD,,
60
+ gamesentenceminer-2.9.2.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
61
+ gamesentenceminer-2.9.2.dist-info/METADATA,sha256=XVxZdTUhTDvmNprnr75Eqs1Cd-6QjFDQOjjn-CeLI0Y,7280
62
+ gamesentenceminer-2.9.2.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
63
+ gamesentenceminer-2.9.2.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
64
+ gamesentenceminer-2.9.2.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
65
+ gamesentenceminer-2.9.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.7.1)
2
+ Generator: setuptools (80.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5