GameSentenceMiner 2.1.2.post1__py3-none-any.whl → 2.2.0__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-2.1.2.post1.dist-info → GameSentenceMiner-2.2.0.dist-info}/METADATA +2 -6
- GameSentenceMiner-2.2.0.dist-info/RECORD +24 -0
- GameSentenceMiner-2.2.0.dist-info/entry_points.txt +2 -0
- GameSentenceMiner-2.2.0.dist-info/top_level.txt +1 -0
- {GameSentenceMiner → src}/anki.py +6 -9
- {GameSentenceMiner → src}/config_gui.py +3 -4
- {GameSentenceMiner → src}/configuration.py +4 -4
- src/downloader/Untitled_json.py +472 -0
- src/downloader/download_tools.py +149 -0
- {GameSentenceMiner → src}/ffmpeg.py +14 -11
- {GameSentenceMiner → src}/gametext.py +4 -4
- {GameSentenceMiner → src}/gsm.py +81 -51
- {GameSentenceMiner → src}/obs.py +40 -7
- {GameSentenceMiner → src}/package_updater.py +1 -1
- {GameSentenceMiner → src}/util.py +3 -0
- src/vad/__init__.py +0 -0
- {GameSentenceMiner → src}/vad/silero_trim.py +2 -2
- {GameSentenceMiner → src}/vad/vosk_helper.py +2 -2
- {GameSentenceMiner → src}/vad/whisper_helper.py +2 -2
- GameSentenceMiner-2.1.2.post1.dist-info/RECORD +0 -21
- GameSentenceMiner-2.1.2.post1.dist-info/entry_points.txt +0 -2
- GameSentenceMiner-2.1.2.post1.dist-info/top_level.txt +0 -1
- {GameSentenceMiner-2.1.2.post1.dist-info → GameSentenceMiner-2.2.0.dist-info}/WHEEL +0 -0
- {GameSentenceMiner → src}/__init__.py +0 -0
- {GameSentenceMiner/vad → src/downloader}/__init__.py +0 -0
- {GameSentenceMiner → src}/model.py +0 -0
- {GameSentenceMiner → src}/notification.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: GameSentenceMiner
|
3
|
-
Version: 2.
|
3
|
+
Version: 2.2.0
|
4
4
|
Summary: A tool for mining sentences from games.
|
5
5
|
Author-email: Beangate <bpwhelan95@gmail.com>
|
6
6
|
License: MIT License
|
@@ -12,7 +12,6 @@ Classifier: Operating System :: OS Independent
|
|
12
12
|
Requires-Python: >=3.11
|
13
13
|
Description-Content-Type: text/markdown
|
14
14
|
Requires-Dist: requests~=2.32.3
|
15
|
-
Requires-Dist: anki~=24.6.3
|
16
15
|
Requires-Dist: watchdog~=5.0.2
|
17
16
|
Requires-Dist: DateTime~=5.5
|
18
17
|
Requires-Dist: pyperclip~=1.9.0
|
@@ -25,14 +24,11 @@ Requires-Dist: obs-websocket-py~=1.0
|
|
25
24
|
Requires-Dist: plyer~=2.1.0
|
26
25
|
Requires-Dist: keyboard~=0.13.5
|
27
26
|
Requires-Dist: websockets~=13.0.1
|
28
|
-
Requires-Dist: obsws_python~=1.7.0
|
29
|
-
Requires-Dist: numpy~=2.0.2
|
30
27
|
Requires-Dist: stable-ts~=2.17.5
|
31
|
-
Requires-Dist: openai-whisper
|
32
28
|
Requires-Dist: silero-vad~=5.1.2
|
33
29
|
Requires-Dist: ttkbootstrap~=1.10.1
|
34
30
|
Requires-Dist: dataclasses_json~=0.6.7
|
35
|
-
Requires-Dist:
|
31
|
+
Requires-Dist: numpy
|
36
32
|
Requires-Dist: pystray
|
37
33
|
Requires-Dist: pywin32
|
38
34
|
|
@@ -0,0 +1,24 @@
|
|
1
|
+
src/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
src/anki.py,sha256=wf1_iUVHrEnzNtK2uL8FJBtAwihiwO_WkmgvqLExf5Q,9089
|
3
|
+
src/config_gui.py,sha256=oY397FO5WYHluPNOtfed3Eakw3TMpVStCaoEU60jw00,48963
|
4
|
+
src/configuration.py,sha256=QVVe8_Pkiyxahgbabf0dpBeY1oIktypvOzBvlwyLEhI,14035
|
5
|
+
src/ffmpeg.py,sha256=9WtP_ajm-nGWPRbuPMJAdnybsKyuAq8Uc_npK4NqGJE,10633
|
6
|
+
src/gametext.py,sha256=3HN1imfcmecfGstqDjfKBZJL02tJfrZsZBTphXLVGh8,4010
|
7
|
+
src/gsm.py,sha256=ip4NuMHwAgK_lC9-Y1fUGXYj_SAf-laNPY_nSsmLRYQ,16246
|
8
|
+
src/model.py,sha256=oh8VVT8T1UKekbmP6MGNgQ8jIuQ_7Rg4GPzDCn2kJo8,1999
|
9
|
+
src/notification.py,sha256=sWgIIXhaB9WV1K_oQGf5-IR6q3dakae_QS-RuIvbcEs,1939
|
10
|
+
src/obs.py,sha256=6XIsIRntr63cdpfNS2CanUWr3KyMHoQ8PxfKLZzSkVY,6495
|
11
|
+
src/package_updater.py,sha256=Yel-KWBHr5kgIJpHM1A1w5tcx_gDqrLrK0pqXRhinlI,1472
|
12
|
+
src/util.py,sha256=eTMD5oqsDwem5C7GUG6-x3AQ884a1WLaJ1VVJgeHmA4,4491
|
13
|
+
src/downloader/Untitled_json.py,sha256=RUUl2bbbCpUDUUS0fP0tdvf5FngZ7ILdA_J5TFYAXUQ,15272
|
14
|
+
src/downloader/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
|
+
src/downloader/download_tools.py,sha256=m1UV8EvWJdNn2cz11BCUI2mth46RhbVdVHXYYC318V8,6378
|
16
|
+
src/vad/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
17
|
+
src/vad/silero_trim.py,sha256=rcHzSNjAm_liiKBWsdywpIC1oUrM00I7kxmomXCQ-Io,1521
|
18
|
+
src/vad/vosk_helper.py,sha256=4oaI7uesINu1sO7PA0-M4qSOmiEneRRN4Ae8ztmYCBE,5798
|
19
|
+
src/vad/whisper_helper.py,sha256=N8-FZMIjLb5CiFeyY8pz050v7RguU39B6tnvsPTbSmc,3383
|
20
|
+
GameSentenceMiner-2.2.0.dist-info/METADATA,sha256=LZH1a7NVyGPQTuHhed4yTGLtYV0K2nw6OV2epwDRa2Q,13408
|
21
|
+
GameSentenceMiner-2.2.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
22
|
+
GameSentenceMiner-2.2.0.dist-info/entry_points.txt,sha256=hzWrryQzGBdqbVqzUIBbVZvvh7MBTUYvWiIs95QrfHE,51
|
23
|
+
GameSentenceMiner-2.2.0.dist-info/top_level.txt,sha256=74rtVfumQlgAPzR5_2CgYN24MB0XARCg0t-gzk6gTrM,4
|
24
|
+
GameSentenceMiner-2.2.0.dist-info/RECORD,,
|
@@ -0,0 +1 @@
|
|
1
|
+
src
|
@@ -6,15 +6,12 @@ import urllib.request
|
|
6
6
|
|
7
7
|
import requests as req
|
8
8
|
|
9
|
-
from
|
10
|
-
|
11
|
-
from . import
|
12
|
-
from . import
|
13
|
-
|
14
|
-
from .
|
15
|
-
from .configuration import get_config
|
16
|
-
from .gametext import get_last_two_sentences
|
17
|
-
from .obs import get_current_game
|
9
|
+
from src import obs, util, notification, ffmpeg
|
10
|
+
|
11
|
+
from src.configuration import *
|
12
|
+
from src.configuration import get_config
|
13
|
+
from src.gametext import get_last_two_sentences
|
14
|
+
from src.obs import get_current_game
|
18
15
|
|
19
16
|
audio_in_anki = None
|
20
17
|
screenshot_in_anki = None
|
@@ -3,10 +3,9 @@ from tkinter import filedialog, messagebox, simpledialog
|
|
3
3
|
|
4
4
|
import ttkbootstrap as ttk
|
5
5
|
|
6
|
-
from .package_updater import check_for_updates, get_latest_version, update, get_current_version
|
7
|
-
from
|
8
|
-
from . import
|
9
|
-
from .configuration import *
|
6
|
+
from src.package_updater import check_for_updates, get_latest_version, update, get_current_version
|
7
|
+
from src import obs, configuration
|
8
|
+
from src.configuration import *
|
10
9
|
|
11
10
|
settings_saved = False
|
12
11
|
on_save = []
|
@@ -46,9 +46,9 @@ class General:
|
|
46
46
|
@dataclass_json
|
47
47
|
@dataclass
|
48
48
|
class Paths:
|
49
|
-
folder_to_watch: str = expanduser("~/Videos/
|
50
|
-
audio_destination: str = expanduser("~/Videos/
|
51
|
-
screenshot_destination: str = expanduser("~/Videos/
|
49
|
+
folder_to_watch: str = expanduser("~/Videos/GSM")
|
50
|
+
audio_destination: str = expanduser("~/Videos/GSM/Audio/")
|
51
|
+
screenshot_destination: str = expanduser("~/Videos/GSM/SS/")
|
52
52
|
remove_video: bool = True
|
53
53
|
remove_audio: bool = False
|
54
54
|
remove_screenshot: bool = False
|
@@ -369,7 +369,7 @@ console_handler = logging.StreamHandler()
|
|
369
369
|
console_handler.setLevel(logging.INFO)
|
370
370
|
|
371
371
|
# Create rotating file handler with level DEBUG
|
372
|
-
file_handler = RotatingFileHandler(get_log_path(), maxBytes=
|
372
|
+
file_handler = RotatingFileHandler(get_log_path(), maxBytes=10 * 1024 * 1024, backupCount=0, encoding='utf-8')
|
373
373
|
file_handler.setLevel(logging.DEBUG)
|
374
374
|
|
375
375
|
# Create a formatter
|
@@ -0,0 +1,472 @@
|
|
1
|
+
scenes="""{
|
2
|
+
"DesktopAudioDevice1": {
|
3
|
+
"prev_ver": 520093697,
|
4
|
+
"name": "Desktop Audio",
|
5
|
+
"uuid": "8587cf13-6937-4e51-a1df-ed1ebb87d537",
|
6
|
+
"id": "wasapi_output_capture",
|
7
|
+
"versioned_id": "wasapi_output_capture",
|
8
|
+
"settings": {
|
9
|
+
"device_id": "default"
|
10
|
+
},
|
11
|
+
"mixers": 255,
|
12
|
+
"sync": 0,
|
13
|
+
"flags": 0,
|
14
|
+
"volume": 1.0,
|
15
|
+
"balance": 0.5,
|
16
|
+
"enabled": true,
|
17
|
+
"muted": true,
|
18
|
+
"push-to-mute": false,
|
19
|
+
"push-to-mute-delay": 0,
|
20
|
+
"push-to-talk": false,
|
21
|
+
"push-to-talk-delay": 0,
|
22
|
+
"hotkeys": {
|
23
|
+
"libobs.mute": [],
|
24
|
+
"libobs.unmute": [],
|
25
|
+
"libobs.push-to-mute": [],
|
26
|
+
"libobs.push-to-talk": []
|
27
|
+
},
|
28
|
+
"deinterlace_mode": 0,
|
29
|
+
"deinterlace_field_order": 0,
|
30
|
+
"monitoring_type": 0,
|
31
|
+
"private_settings": {}
|
32
|
+
},
|
33
|
+
"AuxAudioDevice1": {
|
34
|
+
"prev_ver": 520093697,
|
35
|
+
"name": "Mic/Aux",
|
36
|
+
"uuid": "cbadac0a-b67a-46c9-9ff5-e649bff42549",
|
37
|
+
"id": "wasapi_input_capture",
|
38
|
+
"versioned_id": "wasapi_input_capture",
|
39
|
+
"settings": {
|
40
|
+
"device_id": "default"
|
41
|
+
},
|
42
|
+
"mixers": 255,
|
43
|
+
"sync": 0,
|
44
|
+
"flags": 0,
|
45
|
+
"volume": 1.0,
|
46
|
+
"balance": 0.5,
|
47
|
+
"enabled": true,
|
48
|
+
"muted": true,
|
49
|
+
"push-to-mute": false,
|
50
|
+
"push-to-mute-delay": 0,
|
51
|
+
"push-to-talk": false,
|
52
|
+
"push-to-talk-delay": 0,
|
53
|
+
"hotkeys": {
|
54
|
+
"libobs.mute": [],
|
55
|
+
"libobs.unmute": [],
|
56
|
+
"libobs.push-to-mute": [],
|
57
|
+
"libobs.push-to-talk": []
|
58
|
+
},
|
59
|
+
"deinterlace_mode": 0,
|
60
|
+
"deinterlace_field_order": 0,
|
61
|
+
"monitoring_type": 0,
|
62
|
+
"private_settings": {}
|
63
|
+
},
|
64
|
+
"current_scene": "Yuzu",
|
65
|
+
"current_program_scene": "Yuzu",
|
66
|
+
"scene_order": [
|
67
|
+
{
|
68
|
+
"name": "Example Game Capture"
|
69
|
+
},
|
70
|
+
{
|
71
|
+
"name": "Example Window Capture"
|
72
|
+
},
|
73
|
+
{
|
74
|
+
"name": "Yuzu"
|
75
|
+
}
|
76
|
+
],
|
77
|
+
"name": "Untitled",
|
78
|
+
"sources": [
|
79
|
+
{
|
80
|
+
"prev_ver": 520093697,
|
81
|
+
"name": "Game Capture",
|
82
|
+
"uuid": "3060072f-58ed-4c34-8bdc-a1e66507e99f",
|
83
|
+
"id": "game_capture",
|
84
|
+
"versioned_id": "game_capture",
|
85
|
+
"settings": {
|
86
|
+
"capture_audio": true
|
87
|
+
},
|
88
|
+
"mixers": 255,
|
89
|
+
"sync": 0,
|
90
|
+
"flags": 0,
|
91
|
+
"volume": 1.0,
|
92
|
+
"balance": 0.5,
|
93
|
+
"enabled": true,
|
94
|
+
"muted": false,
|
95
|
+
"push-to-mute": false,
|
96
|
+
"push-to-mute-delay": 0,
|
97
|
+
"push-to-talk": false,
|
98
|
+
"push-to-talk-delay": 0,
|
99
|
+
"hotkeys": {
|
100
|
+
"libobs.mute": [],
|
101
|
+
"libobs.unmute": [],
|
102
|
+
"libobs.push-to-mute": [],
|
103
|
+
"libobs.push-to-talk": [],
|
104
|
+
"hotkey_start": [],
|
105
|
+
"hotkey_stop": []
|
106
|
+
},
|
107
|
+
"deinterlace_mode": 0,
|
108
|
+
"deinterlace_field_order": 0,
|
109
|
+
"monitoring_type": 0,
|
110
|
+
"private_settings": {}
|
111
|
+
},
|
112
|
+
{
|
113
|
+
"prev_ver": 520093697,
|
114
|
+
"name": "Example Game Capture",
|
115
|
+
"uuid": "13da3321-7d49-4a97-b4c5-b1f0912c6440",
|
116
|
+
"id": "scene",
|
117
|
+
"versioned_id": "scene",
|
118
|
+
"settings": {
|
119
|
+
"id_counter": 1,
|
120
|
+
"custom_size": false,
|
121
|
+
"items": [
|
122
|
+
{
|
123
|
+
"name": "Game Capture",
|
124
|
+
"source_uuid": "3060072f-58ed-4c34-8bdc-a1e66507e99f",
|
125
|
+
"visible": true,
|
126
|
+
"locked": false,
|
127
|
+
"rot": 0.0,
|
128
|
+
"scale_ref": {
|
129
|
+
"x": 1920.0,
|
130
|
+
"y": 1080.0
|
131
|
+
},
|
132
|
+
"align": 5,
|
133
|
+
"bounds_type": 0,
|
134
|
+
"bounds_align": 0,
|
135
|
+
"bounds_crop": false,
|
136
|
+
"crop_left": 0,
|
137
|
+
"crop_top": 0,
|
138
|
+
"crop_right": 0,
|
139
|
+
"crop_bottom": 0,
|
140
|
+
"id": 1,
|
141
|
+
"group_item_backup": false,
|
142
|
+
"pos": {
|
143
|
+
"x": 0.0,
|
144
|
+
"y": 0.0
|
145
|
+
},
|
146
|
+
"pos_rel": {
|
147
|
+
"x": -1.7777777910232544,
|
148
|
+
"y": -1.0
|
149
|
+
},
|
150
|
+
"scale": {
|
151
|
+
"x": 1.0,
|
152
|
+
"y": 1.0
|
153
|
+
},
|
154
|
+
"scale_rel": {
|
155
|
+
"x": 1.0,
|
156
|
+
"y": 1.0
|
157
|
+
},
|
158
|
+
"bounds": {
|
159
|
+
"x": 0.0,
|
160
|
+
"y": 0.0
|
161
|
+
},
|
162
|
+
"bounds_rel": {
|
163
|
+
"x": 0.0,
|
164
|
+
"y": 0.0
|
165
|
+
},
|
166
|
+
"scale_filter": "disable",
|
167
|
+
"blend_method": "default",
|
168
|
+
"blend_type": "normal",
|
169
|
+
"show_transition": {
|
170
|
+
"duration": 0
|
171
|
+
},
|
172
|
+
"hide_transition": {
|
173
|
+
"duration": 0
|
174
|
+
},
|
175
|
+
"private_settings": {}
|
176
|
+
}
|
177
|
+
]
|
178
|
+
},
|
179
|
+
"mixers": 0,
|
180
|
+
"sync": 0,
|
181
|
+
"flags": 0,
|
182
|
+
"volume": 1.0,
|
183
|
+
"balance": 0.5,
|
184
|
+
"enabled": true,
|
185
|
+
"muted": false,
|
186
|
+
"push-to-mute": false,
|
187
|
+
"push-to-mute-delay": 0,
|
188
|
+
"push-to-talk": false,
|
189
|
+
"push-to-talk-delay": 0,
|
190
|
+
"hotkeys": {
|
191
|
+
"OBSBasic.SelectScene": [],
|
192
|
+
"libobs.show_scene_item.1": [],
|
193
|
+
"libobs.hide_scene_item.1": []
|
194
|
+
},
|
195
|
+
"deinterlace_mode": 0,
|
196
|
+
"deinterlace_field_order": 0,
|
197
|
+
"monitoring_type": 0,
|
198
|
+
"private_settings": {}
|
199
|
+
},
|
200
|
+
{
|
201
|
+
"prev_ver": 520093697,
|
202
|
+
"name": "Example Window Capture",
|
203
|
+
"uuid": "313ace86-d326-4bc4-bb70-69a64cd3b995",
|
204
|
+
"id": "scene",
|
205
|
+
"versioned_id": "scene",
|
206
|
+
"settings": {
|
207
|
+
"id_counter": 2,
|
208
|
+
"custom_size": false,
|
209
|
+
"items": [
|
210
|
+
{
|
211
|
+
"name": "Window Capture",
|
212
|
+
"source_uuid": "35e6ccb1-1559-4f0a-a26f-f3d4faa4acb4",
|
213
|
+
"visible": true,
|
214
|
+
"locked": false,
|
215
|
+
"rot": 0.0,
|
216
|
+
"scale_ref": {
|
217
|
+
"x": 1920.0,
|
218
|
+
"y": 1080.0
|
219
|
+
},
|
220
|
+
"align": 5,
|
221
|
+
"bounds_type": 0,
|
222
|
+
"bounds_align": 0,
|
223
|
+
"bounds_crop": false,
|
224
|
+
"crop_left": 0,
|
225
|
+
"crop_top": 0,
|
226
|
+
"crop_right": 0,
|
227
|
+
"crop_bottom": 0,
|
228
|
+
"id": 2,
|
229
|
+
"group_item_backup": false,
|
230
|
+
"pos": {
|
231
|
+
"x": 0.0,
|
232
|
+
"y": 0.0
|
233
|
+
},
|
234
|
+
"pos_rel": {
|
235
|
+
"x": -1.7777777910232544,
|
236
|
+
"y": -1.0
|
237
|
+
},
|
238
|
+
"scale": {
|
239
|
+
"x": 1.0,
|
240
|
+
"y": 1.0
|
241
|
+
},
|
242
|
+
"scale_rel": {
|
243
|
+
"x": 1.0,
|
244
|
+
"y": 1.0
|
245
|
+
},
|
246
|
+
"bounds": {
|
247
|
+
"x": 0.0,
|
248
|
+
"y": 0.0
|
249
|
+
},
|
250
|
+
"bounds_rel": {
|
251
|
+
"x": 0.0,
|
252
|
+
"y": 0.0
|
253
|
+
},
|
254
|
+
"scale_filter": "disable",
|
255
|
+
"blend_method": "default",
|
256
|
+
"blend_type": "normal",
|
257
|
+
"show_transition": {
|
258
|
+
"duration": 0
|
259
|
+
},
|
260
|
+
"hide_transition": {
|
261
|
+
"duration": 0
|
262
|
+
},
|
263
|
+
"private_settings": {}
|
264
|
+
}
|
265
|
+
]
|
266
|
+
},
|
267
|
+
"mixers": 0,
|
268
|
+
"sync": 0,
|
269
|
+
"flags": 0,
|
270
|
+
"volume": 1.0,
|
271
|
+
"balance": 0.5,
|
272
|
+
"enabled": true,
|
273
|
+
"muted": false,
|
274
|
+
"push-to-mute": false,
|
275
|
+
"push-to-mute-delay": 0,
|
276
|
+
"push-to-talk": false,
|
277
|
+
"push-to-talk-delay": 0,
|
278
|
+
"hotkeys": {
|
279
|
+
"OBSBasic.SelectScene": [],
|
280
|
+
"libobs.show_scene_item.2": [],
|
281
|
+
"libobs.hide_scene_item.2": []
|
282
|
+
},
|
283
|
+
"deinterlace_mode": 0,
|
284
|
+
"deinterlace_field_order": 0,
|
285
|
+
"monitoring_type": 0,
|
286
|
+
"private_settings": {}
|
287
|
+
},
|
288
|
+
{
|
289
|
+
"prev_ver": 520093697,
|
290
|
+
"name": "Yuzu",
|
291
|
+
"uuid": "68d18b0a-7b81-4dd6-8a29-5bd882f9b8d8",
|
292
|
+
"id": "scene",
|
293
|
+
"versioned_id": "scene",
|
294
|
+
"settings": {
|
295
|
+
"id_counter": 2,
|
296
|
+
"custom_size": false,
|
297
|
+
"items": [
|
298
|
+
{
|
299
|
+
"name": "Window Capture",
|
300
|
+
"source_uuid": "35e6ccb1-1559-4f0a-a26f-f3d4faa4acb4",
|
301
|
+
"visible": true,
|
302
|
+
"locked": false,
|
303
|
+
"rot": 0.0,
|
304
|
+
"scale_ref": {
|
305
|
+
"x": 1920.0,
|
306
|
+
"y": 1080.0
|
307
|
+
},
|
308
|
+
"align": 5,
|
309
|
+
"bounds_type": 0,
|
310
|
+
"bounds_align": 0,
|
311
|
+
"bounds_crop": false,
|
312
|
+
"crop_left": 0,
|
313
|
+
"crop_top": 0,
|
314
|
+
"crop_right": 0,
|
315
|
+
"crop_bottom": 0,
|
316
|
+
"id": 2,
|
317
|
+
"group_item_backup": false,
|
318
|
+
"pos": {
|
319
|
+
"x": 0.0,
|
320
|
+
"y": 0.0
|
321
|
+
},
|
322
|
+
"pos_rel": {
|
323
|
+
"x": -1.7777777910232544,
|
324
|
+
"y": -1.0
|
325
|
+
},
|
326
|
+
"scale": {
|
327
|
+
"x": 1.0,
|
328
|
+
"y": 1.0
|
329
|
+
},
|
330
|
+
"scale_rel": {
|
331
|
+
"x": 1.0,
|
332
|
+
"y": 1.0
|
333
|
+
},
|
334
|
+
"bounds": {
|
335
|
+
"x": 0.0,
|
336
|
+
"y": 0.0
|
337
|
+
},
|
338
|
+
"bounds_rel": {
|
339
|
+
"x": 0.0,
|
340
|
+
"y": 0.0
|
341
|
+
},
|
342
|
+
"scale_filter": "disable",
|
343
|
+
"blend_method": "default",
|
344
|
+
"blend_type": "normal",
|
345
|
+
"show_transition": {
|
346
|
+
"duration": 0
|
347
|
+
},
|
348
|
+
"hide_transition": {
|
349
|
+
"duration": 0
|
350
|
+
},
|
351
|
+
"private_settings": {}
|
352
|
+
}
|
353
|
+
]
|
354
|
+
},
|
355
|
+
"mixers": 0,
|
356
|
+
"sync": 0,
|
357
|
+
"flags": 0,
|
358
|
+
"volume": 1.0,
|
359
|
+
"balance": 0.5,
|
360
|
+
"enabled": true,
|
361
|
+
"muted": false,
|
362
|
+
"push-to-mute": false,
|
363
|
+
"push-to-mute-delay": 0,
|
364
|
+
"push-to-talk": false,
|
365
|
+
"push-to-talk-delay": 0,
|
366
|
+
"hotkeys": {
|
367
|
+
"OBSBasic.SelectScene": [],
|
368
|
+
"libobs.show_scene_item.2": [],
|
369
|
+
"libobs.hide_scene_item.2": []
|
370
|
+
},
|
371
|
+
"deinterlace_mode": 0,
|
372
|
+
"deinterlace_field_order": 0,
|
373
|
+
"monitoring_type": 0,
|
374
|
+
"private_settings": {}
|
375
|
+
},
|
376
|
+
{
|
377
|
+
"prev_ver": 520093697,
|
378
|
+
"name": "Window Capture",
|
379
|
+
"uuid": "35e6ccb1-1559-4f0a-a26f-f3d4faa4acb4",
|
380
|
+
"id": "window_capture",
|
381
|
+
"versioned_id": "window_capture",
|
382
|
+
"settings": {
|
383
|
+
"capture_audio": true
|
384
|
+
},
|
385
|
+
"mixers": 255,
|
386
|
+
"sync": 0,
|
387
|
+
"flags": 0,
|
388
|
+
"volume": 1.0,
|
389
|
+
"balance": 0.5,
|
390
|
+
"enabled": true,
|
391
|
+
"muted": false,
|
392
|
+
"push-to-mute": false,
|
393
|
+
"push-to-mute-delay": 0,
|
394
|
+
"push-to-talk": false,
|
395
|
+
"push-to-talk-delay": 0,
|
396
|
+
"hotkeys": {
|
397
|
+
"libobs.mute": [],
|
398
|
+
"libobs.unmute": [],
|
399
|
+
"libobs.push-to-mute": [],
|
400
|
+
"libobs.push-to-talk": []
|
401
|
+
},
|
402
|
+
"deinterlace_mode": 0,
|
403
|
+
"deinterlace_field_order": 0,
|
404
|
+
"monitoring_type": 0,
|
405
|
+
"private_settings": {}
|
406
|
+
}
|
407
|
+
],
|
408
|
+
"groups": [],
|
409
|
+
"quick_transitions": [
|
410
|
+
{
|
411
|
+
"name": "Cut",
|
412
|
+
"duration": 300,
|
413
|
+
"hotkeys": [],
|
414
|
+
"id": 1,
|
415
|
+
"fade_to_black": false
|
416
|
+
},
|
417
|
+
{
|
418
|
+
"name": "Fade",
|
419
|
+
"duration": 300,
|
420
|
+
"hotkeys": [],
|
421
|
+
"id": 2,
|
422
|
+
"fade_to_black": false
|
423
|
+
},
|
424
|
+
{
|
425
|
+
"name": "Fade",
|
426
|
+
"duration": 300,
|
427
|
+
"hotkeys": [],
|
428
|
+
"id": 3,
|
429
|
+
"fade_to_black": true
|
430
|
+
}
|
431
|
+
],
|
432
|
+
"transitions": [],
|
433
|
+
"saved_projectors": [],
|
434
|
+
"current_transition": "Fade",
|
435
|
+
"transition_duration": 300,
|
436
|
+
"preview_locked": false,
|
437
|
+
"scaling_enabled": false,
|
438
|
+
"scaling_level": 0,
|
439
|
+
"scaling_off_x": 0.0,
|
440
|
+
"scaling_off_y": 0.0,
|
441
|
+
"virtual-camera": {
|
442
|
+
"type2": 3
|
443
|
+
},
|
444
|
+
"modules": {
|
445
|
+
"scripts-tool": [],
|
446
|
+
"output-timer": {
|
447
|
+
"streamTimerHours": 0,
|
448
|
+
"streamTimerMinutes": 0,
|
449
|
+
"streamTimerSeconds": 30,
|
450
|
+
"recordTimerHours": 0,
|
451
|
+
"recordTimerMinutes": 0,
|
452
|
+
"recordTimerSeconds": 30,
|
453
|
+
"autoStartStreamTimer": false,
|
454
|
+
"autoStartRecordTimer": false,
|
455
|
+
"pauseRecordTimer": true
|
456
|
+
},
|
457
|
+
"auto-scene-switcher": {
|
458
|
+
"interval": 300,
|
459
|
+
"non_matching_scene": "",
|
460
|
+
"switch_if_not_matching": false,
|
461
|
+
"active": false,
|
462
|
+
"switches": []
|
463
|
+
},
|
464
|
+
"captions": {
|
465
|
+
"source": "",
|
466
|
+
"enabled": false,
|
467
|
+
"lang_id": 1033,
|
468
|
+
"provider": "mssapi"
|
469
|
+
}
|
470
|
+
},
|
471
|
+
"version": 2
|
472
|
+
}"""
|
@@ -0,0 +1,149 @@
|
|
1
|
+
import json
|
2
|
+
import os
|
3
|
+
import secrets
|
4
|
+
import shutil
|
5
|
+
import urllib.request
|
6
|
+
import platform
|
7
|
+
import zipfile
|
8
|
+
|
9
|
+
from src.downloader.Untitled_json import scenes
|
10
|
+
from src.configuration import get_app_directory, logger
|
11
|
+
|
12
|
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
13
|
+
|
14
|
+
def copy_obs_settings(src, dest):
|
15
|
+
|
16
|
+
if os.path.exists(src):
|
17
|
+
user_input = input(f"Existng OBS install located. Do you want to copy OBS settings from {src} to {dest}? (y/n): ").strip().lower() or "y"
|
18
|
+
if user_input in ['y', 'yes', '1']:
|
19
|
+
logger.info(f"Copying OBS settings from {src} to {dest}...")
|
20
|
+
shutil.copytree(src, dest, dirs_exist_ok=True)
|
21
|
+
logger.info("OBS settings copied successfully.")
|
22
|
+
return True
|
23
|
+
else:
|
24
|
+
logger.info("Not copying settings!")
|
25
|
+
return False
|
26
|
+
logger.warning(f"OBS settings directory {src} does not exist. Skipping copy.")
|
27
|
+
return False
|
28
|
+
|
29
|
+
|
30
|
+
def download_obs_if_needed():
|
31
|
+
obs_path = os.path.join(get_app_directory(), 'obs-studio')
|
32
|
+
if os.path.exists(obs_path):
|
33
|
+
logger.debug(f"OBS already installed at {obs_path}.")
|
34
|
+
return
|
35
|
+
|
36
|
+
os.makedirs(obs_path, exist_ok=True)
|
37
|
+
latest_release_url = "https://api.github.com/repos/obsproject/obs-studio/releases/latest"
|
38
|
+
with urllib.request.urlopen(latest_release_url) as response:
|
39
|
+
latest_release = json.load(response)
|
40
|
+
obs_url = {
|
41
|
+
"Windows": next(asset['browser_download_url'] for asset in latest_release['assets'] if
|
42
|
+
asset['name'].endswith('Windows.zip')),
|
43
|
+
"Linux": next(asset['browser_download_url'] for asset in latest_release['assets'] if
|
44
|
+
asset['name'].endswith('Ubuntu-24.04-x86_64.deb')),
|
45
|
+
"Darwin": next(asset['browser_download_url'] for asset in latest_release['assets'] if
|
46
|
+
asset['name'].endswith('macOS-Intel.dmg'))
|
47
|
+
}.get(platform.system(), None)
|
48
|
+
|
49
|
+
if obs_url is None:
|
50
|
+
logger.error("Unsupported OS. Please install OBS manually.")
|
51
|
+
return
|
52
|
+
|
53
|
+
download_dir = os.path.join(get_app_directory(), "downloads")
|
54
|
+
os.makedirs(download_dir, exist_ok=True)
|
55
|
+
obs_installer = os.path.join(download_dir, "OBS.zip")
|
56
|
+
|
57
|
+
if os.path.exists(obs_installer):
|
58
|
+
logger.debug("OBS installer already exists. Skipping download.")
|
59
|
+
else:
|
60
|
+
logger.info(f"Downloading OBS from {obs_url}...")
|
61
|
+
urllib.request.urlretrieve(obs_url, obs_installer)
|
62
|
+
|
63
|
+
if platform.system() == "Windows":
|
64
|
+
|
65
|
+
|
66
|
+
logger.info(f"OBS downloaded. Extracting to {obs_path}...")
|
67
|
+
with zipfile.ZipFile(obs_installer, 'r') as zip_ref:
|
68
|
+
zip_ref.extractall(obs_path)
|
69
|
+
open(os.path.join(obs_path, "portable_mode"), 'a').close()
|
70
|
+
websocket_config_path = os.path.join(obs_path, 'config', 'obs-studio')
|
71
|
+
if not copy_obs_settings(os.path.join(os.getenv('APPDATA'), 'obs-studio'), websocket_config_path):
|
72
|
+
websocket_config_path = os.path.join(obs_path, 'config', 'obs-studio', 'plugin_config', 'obs-websocket')
|
73
|
+
os.makedirs(websocket_config_path, exist_ok=True)
|
74
|
+
|
75
|
+
websocket_config = {
|
76
|
+
"alerts_enabled": False,
|
77
|
+
"auth_required": False,
|
78
|
+
"first_load": False,
|
79
|
+
"server_enabled": True,
|
80
|
+
"server_password": secrets.token_urlsafe(16),
|
81
|
+
"server_port": 7274
|
82
|
+
}
|
83
|
+
with open(os.path.join(websocket_config_path, 'config.json'), 'w') as config_file:
|
84
|
+
json.dump(websocket_config, config_file, indent=4)
|
85
|
+
basic_ini_path = os.path.join(obs_path, 'config', 'obs-studio', 'basic', 'profiles', 'Untitled')
|
86
|
+
os.makedirs(basic_ini_path, exist_ok=True)
|
87
|
+
with open(os.path.join(basic_ini_path, 'basic.ini'), 'w') as basic_ini_file:
|
88
|
+
basic_ini_file.write(
|
89
|
+
"[SimpleOutput]\n"
|
90
|
+
f"FilePath={os.path.expanduser('~')}/Videos/GSM\n"
|
91
|
+
"RecRB=true\n"
|
92
|
+
"RecRBTime=60\n"
|
93
|
+
"RecRBSize=512\n"
|
94
|
+
"RecRBPrefix=GSM\n"
|
95
|
+
"RecAudioEncoder=opus\n"
|
96
|
+
)
|
97
|
+
scene_json_path = os.path.join(obs_path, 'config', 'obs-studio', 'basic', 'scenes')
|
98
|
+
os.makedirs(scene_json_path, exist_ok=True)
|
99
|
+
with open(os.path.join(scene_json_path, 'Untitled.json'), 'w') as scene_file:
|
100
|
+
scene_file.write(scenes)
|
101
|
+
logger.info(f"OBS extracted to {obs_path}.")
|
102
|
+
else:
|
103
|
+
logger.error(f"Please install OBS manually from {obs_installer}")
|
104
|
+
|
105
|
+
def download_ffmpeg_if_needed():
|
106
|
+
ffmpeg_path = os.path.join(get_app_directory(), 'ffmpeg')
|
107
|
+
|
108
|
+
if os.path.exists(ffmpeg_path):
|
109
|
+
logger.debug(f"FFmpeg already installed at {ffmpeg_path}.")
|
110
|
+
return
|
111
|
+
|
112
|
+
os.makedirs(ffmpeg_path, exist_ok=True)
|
113
|
+
|
114
|
+
ffmpeg_url = {
|
115
|
+
"Windows": "https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip",
|
116
|
+
"Linux": "https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz",
|
117
|
+
"Darwin": "https://evermeet.cx/ffmpeg/ffmpeg.zip"
|
118
|
+
}.get(platform.system(), None)
|
119
|
+
|
120
|
+
if ffmpeg_url is None:
|
121
|
+
logger.error("Unsupported OS. Please install FFmpeg manually.")
|
122
|
+
return
|
123
|
+
|
124
|
+
download_dir = os.path.join(get_app_directory(), "downloads")
|
125
|
+
os.makedirs(download_dir, exist_ok=True)
|
126
|
+
ffmpeg_archive = os.path.join(download_dir, "ffmpeg.zip")
|
127
|
+
|
128
|
+
if os.path.exists(ffmpeg_archive):
|
129
|
+
logger.debug("FFmpeg archive already exists. Skipping download.")
|
130
|
+
else:
|
131
|
+
logger.info(f"Downloading FFmpeg from {ffmpeg_url}...")
|
132
|
+
urllib.request.urlretrieve(ffmpeg_url, ffmpeg_archive)
|
133
|
+
logger.info(f"FFmpeg downloaded. Extracting to {ffmpeg_path}...")
|
134
|
+
with zipfile.ZipFile(ffmpeg_archive, 'r') as zip_ref:
|
135
|
+
for member in zip_ref.namelist():
|
136
|
+
filename = os.path.basename(member)
|
137
|
+
if filename: # Skip directories
|
138
|
+
source = zip_ref.open(member)
|
139
|
+
target = open(os.path.join(ffmpeg_path, filename), "wb")
|
140
|
+
with source, target:
|
141
|
+
shutil.copyfileobj(source, target)
|
142
|
+
logger.info(f"FFmpeg extracted to {ffmpeg_path}.")
|
143
|
+
def main():
|
144
|
+
# Run dependency checks
|
145
|
+
download_obs_if_needed()
|
146
|
+
download_ffmpeg_if_needed()
|
147
|
+
|
148
|
+
if __name__ == "__main__":
|
149
|
+
main()
|
@@ -1,19 +1,21 @@
|
|
1
|
+
import subprocess
|
1
2
|
import tempfile
|
2
3
|
import time
|
3
4
|
|
4
|
-
from
|
5
|
-
from . import
|
6
|
-
from . import
|
7
|
-
from .configuration import *
|
8
|
-
from .util import *
|
5
|
+
from src import obs, util, configuration
|
6
|
+
from src.configuration import *
|
7
|
+
from src.util import *
|
9
8
|
|
10
|
-
|
9
|
+
def get_ffmpeg_path():
|
10
|
+
return os.path.join(get_app_directory(), "ffmpeg", "ffmpeg.exe") if util.is_windows() else "ffmpeg"
|
11
|
+
|
12
|
+
ffmpeg_base_command_list = [get_ffmpeg_path(), "-hide_banner", "-loglevel", "error", '-nostdin']
|
11
13
|
|
12
14
|
|
13
15
|
def get_screenshot(video_file, time_from_end):
|
14
16
|
time_from_end_to_capture = -time_from_end if time_from_end else -1
|
15
|
-
output_image = make_unique_file_name(
|
16
|
-
get_config().paths.screenshot_destination
|
17
|
+
output_image = make_unique_file_name(os.path.join(
|
18
|
+
get_config().paths.screenshot_destination, f"{obs.get_current_game(sanitize=True)}.{get_config().screenshot.extension}"))
|
17
19
|
# FFmpeg command to extract the last frame of the video
|
18
20
|
ffmpeg_command = ffmpeg_base_command_list + [
|
19
21
|
"-sseof", f"{time_from_end_to_capture}", # Seek to 1 second before the end of the video
|
@@ -59,7 +61,7 @@ def get_screenshot_time(video_path, line_time):
|
|
59
61
|
|
60
62
|
def process_image(image_file):
|
61
63
|
output_image = make_unique_file_name(
|
62
|
-
get_config().paths.screenshot_destination
|
64
|
+
os.path.join(get_config().paths.screenshot_destination, f"{obs.get_current_game(sanitize=True)}.{get_config().screenshot.extension}"))
|
63
65
|
|
64
66
|
# FFmpeg command to process the input image
|
65
67
|
ffmpeg_command = ffmpeg_base_command_list + [
|
@@ -112,7 +114,7 @@ def get_audio_codec(video_path):
|
|
112
114
|
|
113
115
|
def get_audio_and_trim(video_path, line_time, next_line_time):
|
114
116
|
supported_formats = {
|
115
|
-
'opus': '
|
117
|
+
'opus': 'libopus',
|
116
118
|
'mp3': 'libmp3lame',
|
117
119
|
'ogg': 'libvorbis',
|
118
120
|
'aac': 'aac',
|
@@ -139,7 +141,7 @@ def get_audio_and_trim(video_path, line_time, next_line_time):
|
|
139
141
|
|
140
142
|
# FFmpeg command to extract OR re-encode the audio
|
141
143
|
# command = f"{ffmpeg_base_command} -i \"{video_path}\" -map 0:a {codec_command} \"{untrimmed_audio}\""
|
142
|
-
|
144
|
+
logger.debug("Doing initial audio extraction")
|
143
145
|
logger.debug(" ".join(command))
|
144
146
|
|
145
147
|
subprocess.run(command)
|
@@ -295,3 +297,4 @@ def is_video_big_enough(file_path, min_size_kb=250):
|
|
295
297
|
except Exception as e:
|
296
298
|
logger.error(f"Error: {e}")
|
297
299
|
return False
|
300
|
+
|
@@ -7,10 +7,10 @@ from datetime import datetime
|
|
7
7
|
import pyperclip
|
8
8
|
import websockets
|
9
9
|
|
10
|
-
from
|
11
|
-
from .configuration import *
|
12
|
-
from .configuration import get_config, logger
|
13
|
-
from .util import remove_html_tags
|
10
|
+
from src import util
|
11
|
+
from src.configuration import *
|
12
|
+
from src.configuration import get_config, logger
|
13
|
+
from src.util import remove_html_tags
|
14
14
|
from difflib import SequenceMatcher
|
15
15
|
|
16
16
|
|
{GameSentenceMiner → src}/gsm.py
RENAMED
@@ -1,8 +1,8 @@
|
|
1
|
-
import shutil
|
2
1
|
import signal
|
2
|
+
import subprocess
|
3
3
|
import sys
|
4
|
-
import tempfile
|
5
4
|
import time
|
5
|
+
from subprocess import Popen
|
6
6
|
|
7
7
|
import keyboard
|
8
8
|
import psutil
|
@@ -12,21 +12,23 @@ from pystray import Icon, Menu, MenuItem
|
|
12
12
|
from watchdog.events import FileSystemEventHandler
|
13
13
|
from watchdog.observers import Observer
|
14
14
|
|
15
|
-
from
|
16
|
-
from
|
17
|
-
from
|
18
|
-
from
|
19
|
-
from
|
20
|
-
from
|
21
|
-
from
|
22
|
-
from
|
23
|
-
from .
|
24
|
-
from .
|
25
|
-
from .
|
26
|
-
from .
|
27
|
-
from .
|
28
|
-
|
29
|
-
|
15
|
+
from src import anki
|
16
|
+
from src import config_gui
|
17
|
+
from src import configuration
|
18
|
+
from src import ffmpeg
|
19
|
+
from src import gametext
|
20
|
+
from src import notification
|
21
|
+
from src import obs
|
22
|
+
from src import util
|
23
|
+
from src.downloader.download_tools import download_obs_if_needed, download_ffmpeg_if_needed
|
24
|
+
from src.vad import vosk_helper, silero_trim, whisper_helper
|
25
|
+
from src.configuration import *
|
26
|
+
from src.ffmpeg import get_audio_and_trim
|
27
|
+
from src.gametext import get_line_timing
|
28
|
+
from src.util import *
|
29
|
+
|
30
|
+
obs_process: Popen
|
31
|
+
procs_to_close = []
|
30
32
|
settings_window: config_gui.ConfigApp = None
|
31
33
|
obs_paused = False
|
32
34
|
icon: Icon
|
@@ -35,7 +37,7 @@ menu: Menu
|
|
35
37
|
|
36
38
|
class VideoToAudioHandler(FileSystemEventHandler):
|
37
39
|
def on_created(self, event):
|
38
|
-
if event.is_directory or "Replay" not in event.src_path:
|
40
|
+
if event.is_directory or ("Replay" not in event.src_path and "GSM" not in event.src_path):
|
39
41
|
return
|
40
42
|
if event.src_path.endswith(".mkv") or event.src_path.endswith(".mp4"): # Adjust based on your OBS output format
|
41
43
|
logger.info(f"MKV {event.src_path} FOUND, RUNNING LOGIC")
|
@@ -46,14 +48,19 @@ class VideoToAudioHandler(FileSystemEventHandler):
|
|
46
48
|
def convert_to_audio(video_path):
|
47
49
|
try:
|
48
50
|
with util.lock:
|
51
|
+
if os.path.exists(video_path) and os.access(video_path, os.R_OK):
|
52
|
+
logger.debug(f"Video found and is readable: {video_path}")
|
53
|
+
|
49
54
|
if get_config().obs.minimum_replay_size and not ffmpeg.is_video_big_enough(video_path,
|
50
55
|
get_config().obs.minimum_replay_size):
|
56
|
+
logger.debug("Checking if video is big enough")
|
51
57
|
notification.send_check_obs_notification(reason="Video may be empty, check scene in OBS.")
|
52
58
|
logger.error(
|
53
59
|
f"Video was unusually small, potentially empty! Check OBS for Correct Scene Settings! Path: {video_path}")
|
54
60
|
return
|
55
61
|
util.use_previous_audio = True
|
56
62
|
last_note = None
|
63
|
+
logger.debug("Attempting to get last anki card")
|
57
64
|
if get_config().anki.update_anki:
|
58
65
|
last_note = anki.get_last_anki_card()
|
59
66
|
if get_config().features.backfill_audio:
|
@@ -70,6 +77,7 @@ class VideoToAudioHandler(FileSystemEventHandler):
|
|
70
77
|
tango = last_note['fields'][get_config().anki.word_field]['value'] if last_note else ''
|
71
78
|
|
72
79
|
if get_config().anki.sentence_audio_field:
|
80
|
+
logger.debug("Attempting to get audio from video")
|
73
81
|
final_audio_output, should_update_audio, vad_trimmed_audio = VideoToAudioHandler.get_audio(
|
74
82
|
line_time,
|
75
83
|
next_line_time,
|
@@ -95,7 +103,7 @@ class VideoToAudioHandler(FileSystemEventHandler):
|
|
95
103
|
print(f)
|
96
104
|
print("Something went wrong with processing, anki card not updated")
|
97
105
|
except Exception as e:
|
98
|
-
logger.error(f"Some error was hit catching to allow further work to be done: {e}")
|
106
|
+
logger.error(f"Some error was hit catching to allow further work to be done: {e}", exc_info=1)
|
99
107
|
if get_config().paths.remove_video and os.path.exists(video_path):
|
100
108
|
os.remove(video_path) # Optionally remove the video after conversion
|
101
109
|
if get_config().paths.remove_audio and os.path.exists(vad_trimmed_audio):
|
@@ -106,8 +114,7 @@ class VideoToAudioHandler(FileSystemEventHandler):
|
|
106
114
|
trimmed_audio = get_audio_and_trim(video_path, line_time, next_line_time)
|
107
115
|
vad_trimmed_audio = make_unique_file_name(
|
108
116
|
f"{os.path.abspath(configuration.get_temporary_directory())}/{obs.get_current_game(sanitize=True)}.{get_config().audio.extension}")
|
109
|
-
final_audio_output = make_unique_file_name(
|
110
|
-
f"{get_config().paths.audio_destination}{obs.get_current_game(sanitize=True)}.{get_config().audio.extension}")
|
117
|
+
final_audio_output = make_unique_file_name(os.path.join(get_config().paths.audio_destination, f"{obs.get_current_game(sanitize=True)}.{get_config().audio.extension}"))
|
111
118
|
should_update_audio = True
|
112
119
|
if get_config().vad.do_vad_postprocessing:
|
113
120
|
match get_config().vad.selected_vad_model:
|
@@ -139,26 +146,33 @@ class VideoToAudioHandler(FileSystemEventHandler):
|
|
139
146
|
|
140
147
|
|
141
148
|
def initialize(reloading=False):
|
149
|
+
global obs_process
|
142
150
|
if not reloading:
|
151
|
+
if is_windows():
|
152
|
+
download_obs_if_needed()
|
153
|
+
download_ffmpeg_if_needed()
|
143
154
|
if get_config().obs.enabled:
|
155
|
+
obs_process = obs.start_obs()
|
144
156
|
obs.connect_to_obs(start_replay=True)
|
145
157
|
anki.start_monitoring_anki()
|
146
|
-
if get_config().general.open_config_on_startup:
|
147
|
-
proc = subprocess.Popen([sys.executable, "config_gui.py"])
|
148
|
-
config_pids.append(proc.pid)
|
149
158
|
gametext.start_text_monitor()
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
os.mkdir(get_config().paths.screenshot_destination)
|
154
|
-
if not os.path.exists(get_config().paths.audio_destination):
|
155
|
-
os.mkdir(get_config().paths.audio_destination)
|
159
|
+
os.makedirs(get_config().paths.folder_to_watch, exist_ok=True)
|
160
|
+
os.makedirs(get_config().paths.screenshot_destination, exist_ok=True)
|
161
|
+
os.makedirs(get_config().paths.audio_destination, exist_ok=True)
|
156
162
|
if get_config().vad.do_vad_postprocessing:
|
157
163
|
if VOSK in (get_config().vad.backup_vad_model, get_config().vad.selected_vad_model):
|
158
164
|
vosk_helper.get_vosk_model()
|
159
165
|
if WHISPER in (get_config().vad.backup_vad_model, get_config().vad.selected_vad_model):
|
160
166
|
whisper_helper.initialize_whisper_model()
|
161
167
|
|
168
|
+
def initial_checks():
|
169
|
+
try:
|
170
|
+
subprocess.run(ffmpeg.ffmpeg_base_command_list)
|
171
|
+
logger.debug("FFMPEG is installed and accessible.")
|
172
|
+
except FileNotFoundError:
|
173
|
+
logger.error("FFmpeg not found, please install it and add it to your PATH.")
|
174
|
+
raise
|
175
|
+
|
162
176
|
|
163
177
|
def register_hotkeys():
|
164
178
|
keyboard.add_hotkey(get_config().hotkeys.reset_line, gametext.reset_line_hotkey_pressed)
|
@@ -216,7 +230,7 @@ def open_settings():
|
|
216
230
|
def open_log():
|
217
231
|
"""Function to handle opening log."""
|
218
232
|
"""Open log file with the default application."""
|
219
|
-
log_file_path =
|
233
|
+
log_file_path = get_log_path()
|
220
234
|
if not os.path.exists(log_file_path):
|
221
235
|
print("Log file not found!")
|
222
236
|
return
|
@@ -241,17 +255,9 @@ def exit_program(icon, item):
|
|
241
255
|
|
242
256
|
def play_pause(icon, item):
|
243
257
|
global obs_paused, menu
|
244
|
-
|
245
|
-
obs.start_replay_buffer()
|
246
|
-
else:
|
247
|
-
obs.stop_replay_buffer()
|
248
|
-
|
249
|
-
obs_paused = not obs_paused
|
258
|
+
obs.toggle_replay_buffer()
|
250
259
|
update_icon()
|
251
260
|
|
252
|
-
def get_obs_icon_text():
|
253
|
-
return "Pause OBS" if obs_paused else "Resume OBS"
|
254
|
-
|
255
261
|
|
256
262
|
def update_icon():
|
257
263
|
global menu, icon
|
@@ -264,7 +270,8 @@ def update_icon():
|
|
264
270
|
menu = Menu(
|
265
271
|
MenuItem("Open Settings", open_settings),
|
266
272
|
MenuItem("Open Log", open_log),
|
267
|
-
MenuItem(
|
273
|
+
MenuItem("Toggle Replay Buffer", play_pause),
|
274
|
+
MenuItem("Restart OBS", restart_obs),
|
268
275
|
MenuItem("Switch Profile", profile_menu),
|
269
276
|
MenuItem("Exit", exit_program)
|
270
277
|
)
|
@@ -295,7 +302,8 @@ def run_tray():
|
|
295
302
|
menu = Menu(
|
296
303
|
MenuItem("Open Settings", open_settings),
|
297
304
|
MenuItem("Open Log", open_log),
|
298
|
-
MenuItem(
|
305
|
+
MenuItem("Toggle Replay Buffer", play_pause),
|
306
|
+
MenuItem("Restart OBS", restart_obs),
|
299
307
|
MenuItem("Switch Profile", profile_menu),
|
300
308
|
MenuItem("Exit", exit_program)
|
301
309
|
)
|
@@ -303,24 +311,43 @@ def run_tray():
|
|
303
311
|
icon = Icon("TrayApp", create_image(), "Game Sentence Miner", menu)
|
304
312
|
icon.run()
|
305
313
|
|
314
|
+
def close_obs():
|
315
|
+
if obs_process:
|
316
|
+
logger.info("Closing OBS")
|
317
|
+
obs_process.terminate()
|
318
|
+
obs_process.wait()
|
319
|
+
|
320
|
+
def restart_obs():
|
321
|
+
global obs_process
|
322
|
+
close_obs()
|
323
|
+
time.sleep(2)
|
324
|
+
obs_process = obs.start_obs()
|
325
|
+
obs.connect_to_obs(start_replay=True)
|
306
326
|
|
307
327
|
def cleanup():
|
308
328
|
logger.info("Performing cleanup...")
|
309
329
|
util.keep_running = False
|
310
330
|
|
311
|
-
|
331
|
+
if get_config().obs.enabled:
|
332
|
+
if get_config().obs.start_buffer:
|
333
|
+
obs.stop_replay_buffer()
|
334
|
+
obs.disconnect_from_obs()
|
335
|
+
close_obs()
|
336
|
+
|
337
|
+
proc: Popen
|
338
|
+
for proc in procs_to_close:
|
312
339
|
try:
|
313
|
-
|
314
|
-
|
340
|
+
logger.info(f"Terminating process {proc.args[0]}")
|
341
|
+
proc.terminate()
|
342
|
+
proc.wait() # Wait for OBS to fully close
|
343
|
+
logger.info(f"Process {proc.args[0]} terminated.")
|
315
344
|
except psutil.NoSuchProcess:
|
316
|
-
logger.info("
|
345
|
+
logger.info("PID already closed.")
|
317
346
|
except Exception as e:
|
318
|
-
|
347
|
+
proc.kill()
|
348
|
+
logger.error(f"Error terminating process {proc}: {e}")
|
349
|
+
|
319
350
|
|
320
|
-
if get_config().obs.enabled:
|
321
|
-
if get_config().obs.start_buffer:
|
322
|
-
obs.stop_replay_buffer()
|
323
|
-
obs.disconnect_from_obs()
|
324
351
|
settings_window.window.destroy()
|
325
352
|
logger.info("Cleanup complete.")
|
326
353
|
|
@@ -340,6 +367,7 @@ def main(reloading=False, do_config_input=True):
|
|
340
367
|
global settings_window
|
341
368
|
logger.info("Script started.")
|
342
369
|
initialize(reloading)
|
370
|
+
initial_checks()
|
343
371
|
event_handler = VideoToAudioHandler()
|
344
372
|
observer = Observer()
|
345
373
|
observer.schedule(event_handler, get_config().paths.folder_to_watch, recursive=False)
|
@@ -360,6 +388,8 @@ def main(reloading=False, do_config_input=True):
|
|
360
388
|
settings_window = config_gui.ConfigApp()
|
361
389
|
if get_config().general.check_for_update_on_startup:
|
362
390
|
settings_window.window.after(0, settings_window.check_update)
|
391
|
+
if get_config().general.open_config_on_startup:
|
392
|
+
settings_window.window.after(0, settings_window.show)
|
363
393
|
settings_window.add_save_hook(update_icon)
|
364
394
|
settings_window.window.mainloop()
|
365
395
|
except KeyboardInterrupt:
|
{GameSentenceMiner → src}/obs.py
RENAMED
@@ -1,18 +1,37 @@
|
|
1
|
+
import subprocess
|
1
2
|
import time
|
2
|
-
from sys import platform
|
3
3
|
|
4
4
|
from obswebsocket import obsws, requests
|
5
5
|
|
6
|
-
from
|
7
|
-
from . import
|
8
|
-
from .
|
9
|
-
from .model import *
|
6
|
+
from src import util, configuration
|
7
|
+
from src.configuration import *
|
8
|
+
from src.model import *
|
10
9
|
|
11
10
|
client: obsws = None
|
12
11
|
|
13
12
|
# REFERENCE: https://github.com/obsproject/obs-websocket/blob/master/docs/generated/protocol.md
|
14
13
|
|
15
14
|
|
15
|
+
def get_obs_path():
|
16
|
+
return os.path.join(configuration.get_app_directory(), 'obs-studio/bin/64bit/obs64.exe')
|
17
|
+
|
18
|
+
def start_obs():
|
19
|
+
obs_path = get_obs_path()
|
20
|
+
if not os.path.exists(obs_path):
|
21
|
+
logger.error(f"OBS not found at {obs_path}. Please install OBS.")
|
22
|
+
return None
|
23
|
+
|
24
|
+
try:
|
25
|
+
# process = subprocess.Popen([obs_path], cwd=os.path.dirname(obs_path))
|
26
|
+
# process = subprocess.Popen([obs_path, '--minimize-to-tray'], cwd=os.path.dirname(obs_path))
|
27
|
+
process = subprocess.Popen([obs_path, '--disable-shutdown-check'], cwd=os.path.dirname(obs_path))
|
28
|
+
logger.info("OBS launched")
|
29
|
+
return process
|
30
|
+
except Exception as e:
|
31
|
+
logger.error(f"Error launching OBS: {e}")
|
32
|
+
return None
|
33
|
+
|
34
|
+
|
16
35
|
def get_obs_websocket_config_values():
|
17
36
|
if platform == "win32":
|
18
37
|
config_path = os.path.expanduser(r"~\AppData\Roaming\obs-studio\plugin_config\obs-websocket\config.json")
|
@@ -89,6 +108,14 @@ def disconnect_from_obs():
|
|
89
108
|
logger.info("Disconnected from OBS WebSocket.")
|
90
109
|
|
91
110
|
|
111
|
+
def toggle_replay_buffer():
|
112
|
+
try:
|
113
|
+
client.call(requests.ToggleReplayBuffer())
|
114
|
+
print("Replay buffer Toggled.")
|
115
|
+
except Exception as e:
|
116
|
+
print(f"Error toggling buffer: {e}")
|
117
|
+
|
118
|
+
|
92
119
|
# Start replay buffer
|
93
120
|
def start_replay_buffer():
|
94
121
|
try:
|
@@ -110,7 +137,12 @@ def stop_replay_buffer():
|
|
110
137
|
# Save the current replay buffer
|
111
138
|
def save_replay_buffer():
|
112
139
|
try:
|
113
|
-
client.call(requests.
|
140
|
+
replay_buffer_started = client.call(requests.GetReplayBufferStatus()).datain['outputActive']
|
141
|
+
if replay_buffer_started:
|
142
|
+
client.call(requests.SaveReplayBuffer())
|
143
|
+
logger.info("Replay buffer saved.")
|
144
|
+
else:
|
145
|
+
logger.error("Replay Buffer is not active, could not save Replay Buffer!")
|
114
146
|
except Exception as e:
|
115
147
|
print(f"Error saving replay buffer: {e}")
|
116
148
|
|
@@ -138,7 +170,8 @@ def get_source_from_scene(scene_name):
|
|
138
170
|
|
139
171
|
def get_screenshot():
|
140
172
|
try:
|
141
|
-
screenshot = util.make_unique_file_name(os.path.abspath(
|
173
|
+
screenshot = util.make_unique_file_name(os.path.abspath(
|
174
|
+
configuration.get_temporary_directory()) + '/screenshot.png')
|
142
175
|
update_current_game()
|
143
176
|
current_source = get_source_from_scene(get_current_game())
|
144
177
|
current_source_name = current_source.sourceName
|
@@ -5,7 +5,7 @@ import sys
|
|
5
5
|
import pyperclip
|
6
6
|
import requests
|
7
7
|
|
8
|
-
from .configuration import logger, get_app_directory
|
8
|
+
from src.configuration import logger, get_app_directory
|
9
9
|
|
10
10
|
PACKAGE_NAME = "GameSentenceMiner"
|
11
11
|
VERSION_FILE_PATH = os.path.join(get_app_directory(), 'version.txt')
|
@@ -123,6 +123,9 @@ def run_agent_and_hook(pname, agent_script):
|
|
123
123
|
def is_linux():
|
124
124
|
return platform == 'linux'
|
125
125
|
|
126
|
+
def is_windows():
|
127
|
+
return platform == 'win32'
|
128
|
+
|
126
129
|
# def run_command(command, shell=False, input=None, capture_output=False, timeout=None, check=False, **kwargs):
|
127
130
|
# # Use shell=True if the OS is Linux, otherwise shell=False
|
128
131
|
# if is_linux():
|
src/vad/__init__.py
ADDED
File without changes
|
@@ -2,8 +2,8 @@ import tempfile
|
|
2
2
|
|
3
3
|
from silero_vad import load_silero_vad, read_audio, get_speech_timestamps
|
4
4
|
|
5
|
-
from
|
6
|
-
from
|
5
|
+
from src import configuration, ffmpeg
|
6
|
+
from src.configuration import *
|
7
7
|
|
8
8
|
# Silero VAD setup
|
9
9
|
vad_model = load_silero_vad()
|
@@ -7,8 +7,8 @@ import requests
|
|
7
7
|
import soundfile as sf
|
8
8
|
import vosk
|
9
9
|
|
10
|
-
from
|
11
|
-
from
|
10
|
+
from src import configuration, ffmpeg
|
11
|
+
from src.configuration import *
|
12
12
|
|
13
13
|
ffmpeg_base_command_list = ["ffmpeg", "-hide_banner", "-loglevel", "error"]
|
14
14
|
vosk.SetLogLevel(-1)
|
@@ -4,8 +4,8 @@ import warnings
|
|
4
4
|
import stable_whisper as whisper
|
5
5
|
from stable_whisper import WhisperResult
|
6
6
|
|
7
|
-
from
|
8
|
-
from
|
7
|
+
from src import configuration, ffmpeg
|
8
|
+
from src.configuration import *
|
9
9
|
|
10
10
|
ffmpeg_base_command_list = ["ffmpeg", "-hide_banner", "-loglevel", "error"]
|
11
11
|
whisper_model = None
|
@@ -1,21 +0,0 @@
|
|
1
|
-
GameSentenceMiner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
GameSentenceMiner/anki.py,sha256=3UT6K5PxzJMDoiXyOULrkeCoJ0KMq7JvBw6XhcLCirE,9114
|
3
|
-
GameSentenceMiner/config_gui.py,sha256=R16wJtFxPcqIixnKmxy3iIlpEIqFJwKmxXS7AoZVO0Y,48968
|
4
|
-
GameSentenceMiner/configuration.py,sha256=CSg67qIzEdj_w-dS-NEls9syyTeJD3rfPrwSZLomFcU,14029
|
5
|
-
GameSentenceMiner/ffmpeg.py,sha256=hdKimzkpAKsE-17qEAQg4uHy4-TtdFywYx48Skn9cPs,10418
|
6
|
-
GameSentenceMiner/gametext.py,sha256=GpR9P8h3GmmKH46Dw13kJPx66n3jGjFCiV8Fcrqn9E8,3999
|
7
|
-
GameSentenceMiner/gsm.py,sha256=GqMNcb8w117clJfFiNLhDbZ8eOFQTyVhejrjHvsBJXo,15072
|
8
|
-
GameSentenceMiner/model.py,sha256=oh8VVT8T1UKekbmP6MGNgQ8jIuQ_7Rg4GPzDCn2kJo8,1999
|
9
|
-
GameSentenceMiner/notification.py,sha256=sWgIIXhaB9WV1K_oQGf5-IR6q3dakae_QS-RuIvbcEs,1939
|
10
|
-
GameSentenceMiner/obs.py,sha256=gutnRk30jIxuvrpEosR9jw2h_tll36wgtAZkUF-vWDY,5256
|
11
|
-
GameSentenceMiner/package_updater.py,sha256=aexojWH6JcDt01ZcxZN5bHdt_TFxWgPKC4w-Pvjp_dQ,1469
|
12
|
-
GameSentenceMiner/util.py,sha256=OYg0j_rT9F7v3aJRwWnHvdWMYyxGlimrvw7U2C9ifeY,4441
|
13
|
-
GameSentenceMiner/vad/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
14
|
-
GameSentenceMiner/vad/silero_trim.py,sha256=r7bZYEj-NUXGKgD2UIhLrbTPyq0rau97qGtrMZcRK4A,1517
|
15
|
-
GameSentenceMiner/vad/vosk_helper.py,sha256=lWmlGMhmg_0QoWeCHrXwz9wDKPqY37BckHCekGVtJUI,5794
|
16
|
-
GameSentenceMiner/vad/whisper_helper.py,sha256=9kmPeSs6jRcKSVxYY-vtyTcqVUDORR4q1nl8fqxHHn4,3379
|
17
|
-
GameSentenceMiner-2.1.2.post1.dist-info/METADATA,sha256=w3DyH8RZX7bSR3VmuPNz9zCyCLIGsLWL0WuiilXIHVU,13541
|
18
|
-
GameSentenceMiner-2.1.2.post1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
19
|
-
GameSentenceMiner-2.1.2.post1.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
|
20
|
-
GameSentenceMiner-2.1.2.post1.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
|
21
|
-
GameSentenceMiner-2.1.2.post1.dist-info/RECORD,,
|
@@ -1 +0,0 @@
|
|
1
|
-
GameSentenceMiner
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|