-
Notifications
You must be signed in to change notification settings - Fork 139
/
Copy pathrecord_screen.py
325 lines (273 loc) · 14.6 KB
/
record_screen.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
import datetime
# https://steam.oxxostudio.tw/category/python/library/threading.html
import os
import subprocess
import sys
import threading
import time
from os import getpid
import mss
import numpy as np
from windrecorder import file_utils, ocr_manager, record, record_wintitle, state, utils
from windrecorder.config import config
from windrecorder.exceptions import LockExistsException
from windrecorder.llm import cache_day_tags_in_idle_routine
from windrecorder.lock import FileLock
from windrecorder.logger import get_logger
logger = get_logger(__name__)
if config.img_embed_module_install:
try:
from windrecorder import img_embed_manager
except ModuleNotFoundError:
config.set_and_save_config("img_embed_module_install", False)
pass # TODO log here
# 全局状态变量
monitor_idle_minutes = 0
last_screenshot_array = None
idle_maintain_time_gap = datetime.timedelta(minutes=40) # 与上次闲时维护至少相隔
idle_maintaining_in_process = False # 维护中的锁
last_idle_maintain_time = datetime.datetime.now()
try:
# 读取之前闲时维护的时间
with open(config.last_idle_maintain_file_path, "r", encoding="utf-8") as f:
time_read = f.read()
last_idle_maintain_time = datetime.datetime.strptime(time_read, "%Y-%m-%d_%H-%M-%S")
except (FileNotFoundError, ValueError):
file_utils.ensure_dir("cache")
with open(config.last_idle_maintain_file_path, "w", encoding="utf-8") as f:
f.write(last_idle_maintain_time.strftime("%Y-%m-%d_%H-%M-%S"))
def assert_ffmpeg():
try:
subprocess.run([config.ffmpeg_path, "-version"])
except FileNotFoundError:
logger.error("Error: ffmpeg is not installed! Please ensure ffmpeg is in the PATH.")
sys.exit(1)
# 闲时维护的操作流程
def idle_maintain_process_main():
global idle_maintaining_in_process
idle_maintaining_in_process = True
try:
logger.info("start idle maintain processing")
threading.Thread(target=ocr_manager.ocr_manager_main, daemon=True).start()
# 图像语义嵌入
if config.enable_img_embed_search and config.img_embed_module_install:
try:
img_emb_lock = FileLock(config.img_emb_lock_path, str(getpid()), timeout_s=30 * 60)
with img_emb_lock:
img_embed_manager.all_videofile_do_img_embedding_routine(
video_queue_batch=config.batch_size_embed_video_in_idle
)
except LockExistsException:
with open(config.tray_lock_path, encoding="utf-8") as f:
check_pid = int(f.read())
img_emb_is_running = utils.is_process_running(check_pid, compare_process_name="python.exe")
if img_emb_is_running:
logger.warning("another img embedding indexing is running.")
else:
try:
os.remove(config.tray_lock_path)
except FileNotFoundError:
pass
# 将缓存截图转换为视频
condition_convert_screenshots_to_vid = True
if config.convert_screenshots_to_vid_energy_saving_mode != 0 and not utils.is_power_plugged_in():
condition_convert_screenshots_to_vid = False
if condition_convert_screenshots_to_vid:
record.index_cache_screenshots_dir_process()
# 清理过期与已转换为视频的截图缓存文件夹
record.clean_cache_screenshots_dir_process()
# 清理过时视频
ocr_manager.remove_outdated_videofiles(video_queue_batch=config.batch_size_remove_video_in_idle)
# 压缩过期视频
record.compress_outdated_videofiles(video_queue_batch=config.batch_size_compress_video_in_idle)
# 清理缓存文件夹
record.try_empty_cache_dir_in_idle_routine()
# 统计webui footer info
state.make_webui_footer_state_data_cache(ask_from="idle")
# 生成 AI tags
if config.enable_ai_extract_tag and config.enable_ai_extract_tag_in_idle:
cache_day_tags_in_idle_routine()
# 生成随机词表
# wordcloud.generate_all_word_lexicon_by_month()
except Exception as e:
logger.error(f"Error on idle maintain: {e}")
finally:
idle_maintaining_in_process = False
# 录制完成后索引单个刚录制好的视频文件
def index_video_data(video_saved_dir, vid_file_name):
logger.info("- Windrecorder -\n---Indexing OCR data\n---")
full_path = os.path.join(video_saved_dir, vid_file_name)
if os.path.exists(full_path):
try:
logger.info(f"--{full_path} existed. Start ocr processing.")
ocr_manager.ocr_process_single_video(video_saved_dir, vid_file_name, config.iframe_dir)
except LockExistsException:
logger.warning(f"--{full_path} ocr is already in process.")
# 持续录制屏幕的主要线程
def continuously_record_screen():
global last_idle_maintain_time
while True:
# 主循环过程
if (
monitor_idle_minutes > config.screentime_not_change_to_pause_record
or utils.is_str_contain_list_word(record_wintitle.get_current_wintitle(), config.exclude_words)
or utils.is_screen_locked()
):
logger.info("Windrecorder: Screen content not updated, stop recording.")
subprocess.run("color 60", shell=True) # 设定背景色为不活动
# 算算是否该进入维护了(与上次维护时间相比)
timegap_between_last_idle_maintain = datetime.datetime.now() - last_idle_maintain_time
if timegap_between_last_idle_maintain > idle_maintain_time_gap and not idle_maintaining_in_process: # 超时且无锁情况下
logger.info(
f"Windrecorder: It is separated by {timegap_between_last_idle_maintain} from the last maintenance, enter idle maintenance."
)
thread_idle_maintain = threading.Thread(target=idle_maintain_process_main, daemon=True)
thread_idle_maintain.start()
# 更新维护时间
last_idle_maintain_time = datetime.datetime.now() # 本次闲时维护时间
with open(config.last_idle_maintain_file_path, "w", encoding="utf-8") as f:
f.write(last_idle_maintain_time.strftime("%Y-%m-%d_%H-%M-%S"))
time.sleep(10)
else:
subprocess.run("color 2f", shell=True) # 设定背景色为活动
if config.record_mode == "ffmpeg":
video_saved_dir, video_out_name = record.record_screen_via_ffmpeg() # 使用 ffmpeg 录制屏幕
# 自动索引策略
if config.OCR_index_strategy == 1:
logger.info(f"Windrecorder: Starting Indexing video data: '{video_out_name}'")
thread_index_video_data = threading.Thread(
target=index_video_data,
args=(
video_saved_dir,
video_out_name,
),
daemon=True,
)
thread_index_video_data.start()
elif config.record_mode == "screenshot_array":
saved_dir_filepath = record.record_screen_via_screenshot_process() # 使用连续截图录制屏幕
# convert to video startegy instantly
condition_convert_screenshots_to_vid = False
if config.convert_screenshots_to_vid_energy_saving_mode == 0:
condition_convert_screenshots_to_vid = True
elif config.convert_screenshots_to_vid_energy_saving_mode == 1:
if utils.is_power_plugged_in():
condition_convert_screenshots_to_vid = True
if condition_convert_screenshots_to_vid:
thread_convert_screenshots_into_video = threading.Thread(
target=record.convert_screenshots_dir_into_video_process,
args=(saved_dir_filepath,),
daemon=True,
)
thread_convert_screenshots_into_video.start()
time.sleep(2)
# 每隔一段截图对比是否屏幕内容缺少变化
def monitor_compare_screenshot():
with mss.mss() as sct:
while True:
global monitor_idle_minutes
global last_screenshot_array
if utils.is_screen_locked() or not utils.is_system_awake():
logger.info("Windrecorder: Screen locked / System not awaked")
monitor_idle_minutes += 0.5
else:
try:
while True:
similarity = None
screenshot_array = []
if config.multi_display_record_strategy == "single" and config.record_single_display_index < len(
sct.monitors
):
screenshot = sct.grab(sct.monitors[config.record_single_display_index])
logger.debug(f"{sct.monitors[config.record_single_display_index]=}")
screenshot_array.append(np.array(screenshot))
else:
for monitor in sct.monitors[1:]:
screenshot = sct.grab(monitor)
logger.debug(f"{monitor=}")
screenshot_array.append(np.array(screenshot))
if last_screenshot_array is not None:
similarity = []
for last_screen, now_screen in zip(last_screenshot_array, screenshot_array):
similarity.append(ocr_manager.compare_image_similarity_np(last_screen, now_screen))
if all(sim > 0.90 for sim in similarity): # 对比检测阈值
monitor_idle_minutes += 0.5
else:
monitor_idle_minutes = 0
last_screenshot_array = screenshot_array.copy()
logger.info(f"monitor_idle_minutes:{monitor_idle_minutes}, similarity:{similarity}")
time.sleep(30)
except Exception as e:
logger.warning(f"Error occurred:{str(e)}")
if "batchDistance" in str(e): # 如果是抓不到画面导致出错,可以认为是进入了休眠等情况
monitor_idle_minutes += 0.5
else:
monitor_idle_minutes = 0
time.sleep(5)
# 定时记录前台窗口标题页名
def record_active_window_title():
while True:
if not utils.is_screen_locked() or utils.is_system_awake():
record_wintitle.record_wintitle_now()
time.sleep(2)
def main():
subprocess.run("color 60", shell=True) # 设定背景色为不活动
assert_ffmpeg()
while True:
try:
recording_lock = FileLock(config.record_lock_path, str(getpid()), timeout_s=None)
except LockExistsException:
if record.is_recording():
logger.error("Windrecorder: Another screen record service is running.")
sys.exit(1)
else:
try:
os.remove(config.record_lock_path)
except FileNotFoundError:
pass
continue
with recording_lock:
logger.info(f"Windrecorder: config.OCR_index_strategy: {config.OCR_index_strategy}")
if config.OCR_index_strategy == 1:
# 维护之前退出没留下的视频(如果有)
threading.Thread(target=ocr_manager.ocr_manager_main, daemon=True).start()
# 将未转换为视频的截图缓存进行转换
condition_convert_screenshots_to_vid = False
if config.convert_screenshots_to_vid_energy_saving_mode == 0:
condition_convert_screenshots_to_vid = True
elif config.convert_screenshots_to_vid_energy_saving_mode == 1:
if utils.is_power_plugged_in():
condition_convert_screenshots_to_vid = True
if condition_convert_screenshots_to_vid:
threading.Thread(target=record.index_cache_screenshots_dir_process, daemon=True).start()
# 清理已转换为视频的截图缓存
threading.Thread(target=record.clean_cache_screenshots_dir_process, daemon=True).start()
# 屏幕内容多长时间不变则暂停录制
logger.info(f"{config.screentime_not_change_to_pause_record=}")
thread_monitor_compare_screenshot: threading.Thread | None = None
if config.screentime_not_change_to_pause_record > 0: # 是否使用屏幕不变检测
thread_monitor_compare_screenshot = threading.Thread(target=monitor_compare_screenshot, daemon=True)
thread_monitor_compare_screenshot.start()
# 录屏的线程
thread_continuously_record_screen = threading.Thread(target=continuously_record_screen, daemon=True)
thread_continuously_record_screen.start()
# 记录前台窗体标题的进程:
thread_record_active_window_title = threading.Thread(target=record_active_window_title, daemon=True)
thread_record_active_window_title.start()
while True:
# 如果屏幕重复画面检测线程意外出错,重启它
if thread_monitor_compare_screenshot is not None and not thread_monitor_compare_screenshot.is_alive():
thread_monitor_compare_screenshot = threading.Thread(target=monitor_compare_screenshot, daemon=True)
thread_monitor_compare_screenshot.start()
# 如果屏幕录制线程意外出错,重启它
if not thread_continuously_record_screen.is_alive():
thread_continuously_record_screen = threading.Thread(target=continuously_record_screen, daemon=True)
thread_continuously_record_screen.start()
# 如果记录窗体标题进程出错,重启它
if config.record_mode == "ffmpeg":
if not thread_record_active_window_title.is_alive():
thread_record_active_window_title = threading.Thread(target=record_active_window_title, daemon=True)
thread_record_active_window_title.start()
time.sleep(30)
if __name__ == "__main__":
main()