lybic-guiagents 0.1.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.
Potentially problematic release.
This version of lybic-guiagents might be problematic. Click here for more details.
- desktop_env/__init__.py +1 -0
- desktop_env/actions.py +203 -0
- desktop_env/controllers/__init__.py +0 -0
- desktop_env/controllers/python.py +471 -0
- desktop_env/controllers/setup.py +882 -0
- desktop_env/desktop_env.py +509 -0
- desktop_env/evaluators/__init__.py +5 -0
- desktop_env/evaluators/getters/__init__.py +41 -0
- desktop_env/evaluators/getters/calc.py +15 -0
- desktop_env/evaluators/getters/chrome.py +1774 -0
- desktop_env/evaluators/getters/file.py +154 -0
- desktop_env/evaluators/getters/general.py +42 -0
- desktop_env/evaluators/getters/gimp.py +38 -0
- desktop_env/evaluators/getters/impress.py +126 -0
- desktop_env/evaluators/getters/info.py +24 -0
- desktop_env/evaluators/getters/misc.py +406 -0
- desktop_env/evaluators/getters/replay.py +20 -0
- desktop_env/evaluators/getters/vlc.py +86 -0
- desktop_env/evaluators/getters/vscode.py +35 -0
- desktop_env/evaluators/metrics/__init__.py +160 -0
- desktop_env/evaluators/metrics/basic_os.py +68 -0
- desktop_env/evaluators/metrics/chrome.py +493 -0
- desktop_env/evaluators/metrics/docs.py +1011 -0
- desktop_env/evaluators/metrics/general.py +665 -0
- desktop_env/evaluators/metrics/gimp.py +637 -0
- desktop_env/evaluators/metrics/libreoffice.py +28 -0
- desktop_env/evaluators/metrics/others.py +92 -0
- desktop_env/evaluators/metrics/pdf.py +31 -0
- desktop_env/evaluators/metrics/slides.py +957 -0
- desktop_env/evaluators/metrics/table.py +585 -0
- desktop_env/evaluators/metrics/thunderbird.py +176 -0
- desktop_env/evaluators/metrics/utils.py +719 -0
- desktop_env/evaluators/metrics/vlc.py +524 -0
- desktop_env/evaluators/metrics/vscode.py +283 -0
- desktop_env/providers/__init__.py +35 -0
- desktop_env/providers/aws/__init__.py +0 -0
- desktop_env/providers/aws/manager.py +278 -0
- desktop_env/providers/aws/provider.py +186 -0
- desktop_env/providers/aws/provider_with_proxy.py +315 -0
- desktop_env/providers/aws/proxy_pool.py +193 -0
- desktop_env/providers/azure/__init__.py +0 -0
- desktop_env/providers/azure/manager.py +87 -0
- desktop_env/providers/azure/provider.py +207 -0
- desktop_env/providers/base.py +97 -0
- desktop_env/providers/gcp/__init__.py +0 -0
- desktop_env/providers/gcp/manager.py +0 -0
- desktop_env/providers/gcp/provider.py +0 -0
- desktop_env/providers/virtualbox/__init__.py +0 -0
- desktop_env/providers/virtualbox/manager.py +463 -0
- desktop_env/providers/virtualbox/provider.py +124 -0
- desktop_env/providers/vmware/__init__.py +0 -0
- desktop_env/providers/vmware/manager.py +455 -0
- desktop_env/providers/vmware/provider.py +105 -0
- gui_agents/__init__.py +0 -0
- gui_agents/agents/Action.py +209 -0
- gui_agents/agents/__init__.py +0 -0
- gui_agents/agents/agent_s.py +832 -0
- gui_agents/agents/global_state.py +610 -0
- gui_agents/agents/grounding.py +651 -0
- gui_agents/agents/hardware_interface.py +129 -0
- gui_agents/agents/manager.py +568 -0
- gui_agents/agents/translator.py +132 -0
- gui_agents/agents/worker.py +355 -0
- gui_agents/cli_app.py +560 -0
- gui_agents/core/__init__.py +0 -0
- gui_agents/core/engine.py +1496 -0
- gui_agents/core/knowledge.py +449 -0
- gui_agents/core/mllm.py +555 -0
- gui_agents/tools/__init__.py +0 -0
- gui_agents/tools/tools.py +727 -0
- gui_agents/unit_test/__init__.py +0 -0
- gui_agents/unit_test/run_tests.py +65 -0
- gui_agents/unit_test/test_manager.py +330 -0
- gui_agents/unit_test/test_worker.py +269 -0
- gui_agents/utils/__init__.py +0 -0
- gui_agents/utils/analyze_display.py +301 -0
- gui_agents/utils/common_utils.py +263 -0
- gui_agents/utils/display_viewer.py +281 -0
- gui_agents/utils/embedding_manager.py +53 -0
- gui_agents/utils/image_axis_utils.py +27 -0
- lybic_guiagents-0.1.0.dist-info/METADATA +416 -0
- lybic_guiagents-0.1.0.dist-info/RECORD +85 -0
- lybic_guiagents-0.1.0.dist-info/WHEEL +5 -0
- lybic_guiagents-0.1.0.dist-info/licenses/LICENSE +201 -0
- lybic_guiagents-0.1.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import subprocess
|
|
4
|
+
from typing import Dict
|
|
5
|
+
from xml.etree import ElementTree
|
|
6
|
+
from urllib.parse import urlparse
|
|
7
|
+
|
|
8
|
+
import acoustid
|
|
9
|
+
import cv2
|
|
10
|
+
import imagehash
|
|
11
|
+
import librosa
|
|
12
|
+
import numpy as np
|
|
13
|
+
from PIL import Image
|
|
14
|
+
from fastdtw import fastdtw
|
|
15
|
+
from scipy.spatial.distance import cosine
|
|
16
|
+
from skimage.metrics import structural_similarity as ssim
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger("desktopenv.metrics.vlc")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def is_vlc_playing(actual_status_path: str, rule: Dict[str, str]) -> float:
|
|
22
|
+
"""
|
|
23
|
+
Checks if VLC is currently playing a file.
|
|
24
|
+
"""
|
|
25
|
+
with open(actual_status_path, 'rb') as file:
|
|
26
|
+
actual_status = file.read().decode('utf-8')
|
|
27
|
+
|
|
28
|
+
tree = ElementTree.fromstring(actual_status)
|
|
29
|
+
status = tree.find('state').text
|
|
30
|
+
logger.info(f"VLC Status: {status}")
|
|
31
|
+
if status == 'playing':
|
|
32
|
+
if rule['type'] == 'file_name':
|
|
33
|
+
# Try multiple possible paths for file information in VLC XML
|
|
34
|
+
file_paths = [
|
|
35
|
+
'information/category[@name="meta"]/info[@name="filename"]',
|
|
36
|
+
'information/category[@name="meta"]/info[@name="title"]',
|
|
37
|
+
'information/category[@name="meta"]/info[@name="uri"]',
|
|
38
|
+
'information/category[@name="meta"]/info[@name="location"]',
|
|
39
|
+
'information/category[@name="meta"]/info[@name="name"]'
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
file_info = None
|
|
43
|
+
for path in file_paths:
|
|
44
|
+
element = tree.find(path)
|
|
45
|
+
if element is not None and element.text:
|
|
46
|
+
file_info = element.text
|
|
47
|
+
break
|
|
48
|
+
|
|
49
|
+
if file_info:
|
|
50
|
+
expected_filename = rule['file_name']
|
|
51
|
+
|
|
52
|
+
# Method 1: Direct filename match (most precise)
|
|
53
|
+
actual_basename = os.path.basename(file_info)
|
|
54
|
+
if actual_basename == expected_filename:
|
|
55
|
+
return 1
|
|
56
|
+
|
|
57
|
+
# Method 2: Endswith match (for backward compatibility)
|
|
58
|
+
if file_info.endswith(expected_filename):
|
|
59
|
+
return 1
|
|
60
|
+
|
|
61
|
+
# Method 3: For paths, check if expected filename is in the path
|
|
62
|
+
if expected_filename in file_info:
|
|
63
|
+
# Additional check to avoid false positives
|
|
64
|
+
# Make sure it's actually the filename, not just part of a path
|
|
65
|
+
if file_info.endswith('/' + expected_filename) or file_info.endswith('\\' + expected_filename):
|
|
66
|
+
return 1
|
|
67
|
+
|
|
68
|
+
logger.warning(f"File name mismatch - Expected: {expected_filename}, Found: {file_info}")
|
|
69
|
+
return 0
|
|
70
|
+
else:
|
|
71
|
+
logger.warning(f"Could not find file information in VLC status XML for rule: {rule}")
|
|
72
|
+
return 0
|
|
73
|
+
elif rule['type'] == 'url':
|
|
74
|
+
# Try multiple possible paths for URL information in VLC XML
|
|
75
|
+
url_paths = [
|
|
76
|
+
'information/category[@name="meta"]/info[@name="url"]',
|
|
77
|
+
'information/category[@name="meta"]/info[@name="URI"]',
|
|
78
|
+
'information/category[@name="meta"]/info[@name="location"]',
|
|
79
|
+
'information/category[@name="meta"]/info[@name="title"]', # Sometimes URL is in title for streams
|
|
80
|
+
'information/category[@name="meta"]/info[@name="filename"]', # Sometimes URL is in filename for streams
|
|
81
|
+
'information/category[@name="Stream 0"]/info[@name="Codec"]', # Try stream info
|
|
82
|
+
'information/category[@name="Stream 0"]/info[@name="Type"]',
|
|
83
|
+
'information/category[@name="Stream 0"]/info[@name="Language"]'
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
file_info = None
|
|
87
|
+
logger.debug(f"Looking for URL: {rule['url']}")
|
|
88
|
+
|
|
89
|
+
for path in url_paths:
|
|
90
|
+
element = tree.find(path)
|
|
91
|
+
if element is not None and element.text:
|
|
92
|
+
file_info = element.text
|
|
93
|
+
logger.debug(f"Found URL info at '{path}': {file_info}")
|
|
94
|
+
break
|
|
95
|
+
|
|
96
|
+
if file_info:
|
|
97
|
+
# For URL comparison, check if the rule URL is contained in the file_info
|
|
98
|
+
# This handles cases where VLC might show a longer or modified URL
|
|
99
|
+
expected_url = rule['url']
|
|
100
|
+
|
|
101
|
+
# Method 1: Direct URL match
|
|
102
|
+
if expected_url in file_info or file_info.endswith(expected_url):
|
|
103
|
+
return 1
|
|
104
|
+
|
|
105
|
+
# Method 2: For HLS streams, VLC often shows just the filename instead of full URL
|
|
106
|
+
# Check if the file_info matches the filename part of the expected URL
|
|
107
|
+
try:
|
|
108
|
+
expected_parsed = urlparse(expected_url)
|
|
109
|
+
expected_filename = os.path.basename(expected_parsed.path)
|
|
110
|
+
|
|
111
|
+
# If VLC shows just the filename (common for HLS streams)
|
|
112
|
+
if file_info == expected_filename:
|
|
113
|
+
logger.info(f"URL filename match - Expected URL: {expected_url}, VLC shows filename: {file_info}")
|
|
114
|
+
return 1
|
|
115
|
+
|
|
116
|
+
# Method 3: Check if both are URLs from the same domain and similar path
|
|
117
|
+
if '://' in file_info: # file_info is also a URL
|
|
118
|
+
actual_parsed = urlparse(file_info)
|
|
119
|
+
# Same domain and similar path structure
|
|
120
|
+
if (expected_parsed.netloc == actual_parsed.netloc and
|
|
121
|
+
expected_parsed.path in actual_parsed.path):
|
|
122
|
+
return 1
|
|
123
|
+
except Exception as e:
|
|
124
|
+
logger.debug(f"URL parsing error: {e}")
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
logger.warning(f"URL mismatch - Expected: {expected_url}, Found: {file_info}")
|
|
128
|
+
return 0
|
|
129
|
+
else:
|
|
130
|
+
logger.warning(f"Could not find URL information in VLC status XML for rule: {rule}")
|
|
131
|
+
return 0
|
|
132
|
+
else:
|
|
133
|
+
logger.error(f"Unknown type: {rule['type']}")
|
|
134
|
+
return 0
|
|
135
|
+
else:
|
|
136
|
+
return 0
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
# fixme: part of this function can be moved to getters
|
|
140
|
+
def is_vlc_recordings_folder(actual_config_path: str, rule: Dict[str, str]) -> float:
|
|
141
|
+
"""
|
|
142
|
+
Checks if VLC's recording folder is set to the expected value.
|
|
143
|
+
"""
|
|
144
|
+
with open(actual_config_path, 'rb') as file:
|
|
145
|
+
config_file = file.read().decode('utf-8')
|
|
146
|
+
|
|
147
|
+
expected_recording_file_path = rule['recording_file_path']
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
for line in config_file.split("\n"):
|
|
151
|
+
# Skip comments and empty lines
|
|
152
|
+
if line.startswith('#') or not line.strip():
|
|
153
|
+
continue
|
|
154
|
+
# Check if the line contains the recording path setting
|
|
155
|
+
if 'input-record-path' in line:
|
|
156
|
+
# Extract the value of the recording path and remove surrounding whitespace
|
|
157
|
+
current_path = line.split('=')[-1].strip()
|
|
158
|
+
# Compare with the Desktop path
|
|
159
|
+
if current_path == expected_recording_file_path:
|
|
160
|
+
return 1
|
|
161
|
+
else:
|
|
162
|
+
return 0
|
|
163
|
+
# The configuration key was not found in the file
|
|
164
|
+
return 0
|
|
165
|
+
except FileNotFoundError:
|
|
166
|
+
logger.error("VLC configuration file not found.")
|
|
167
|
+
return 0
|
|
168
|
+
except Exception as e:
|
|
169
|
+
logger.error(f"An error occurred: {e}")
|
|
170
|
+
return 0
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def is_vlc_fullscreen(actual_window_size, screen_size):
|
|
174
|
+
if screen_size is None or actual_window_size is None:
|
|
175
|
+
# if the screen size is not available, means that the window is not fullscreen
|
|
176
|
+
return 0
|
|
177
|
+
|
|
178
|
+
if actual_window_size['width'] == screen_size['width'] and actual_window_size['height'] == screen_size['height']:
|
|
179
|
+
return 1
|
|
180
|
+
else:
|
|
181
|
+
return 0
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def compare_images(image1_path, image2_path, **options):
|
|
185
|
+
# You would call this function with the paths to the two images you want to compare:
|
|
186
|
+
# score = compare_images('path_to_image1', 'path_to_image2')
|
|
187
|
+
# print("Similarity score:", score)
|
|
188
|
+
|
|
189
|
+
if not image1_path or not image2_path:
|
|
190
|
+
return 0
|
|
191
|
+
|
|
192
|
+
base_score = options.get("reference_base_result", None)
|
|
193
|
+
|
|
194
|
+
# Open the images and convert to grayscale
|
|
195
|
+
image1 = Image.open(image1_path).convert('L')
|
|
196
|
+
image2 = Image.open(image2_path).convert('L')
|
|
197
|
+
|
|
198
|
+
# Resize images to the smaller one's size for comparison
|
|
199
|
+
image1_size = image1.size
|
|
200
|
+
image2_size = image2.size
|
|
201
|
+
new_size = min(image1_size, image2_size)
|
|
202
|
+
|
|
203
|
+
image1 = image1.resize(new_size, Image.Resampling.LANCZOS)
|
|
204
|
+
image2 = image2.resize(new_size, Image.Resampling.LANCZOS)
|
|
205
|
+
|
|
206
|
+
# Convert images to numpy arrays
|
|
207
|
+
image1_array = np.array(image1)
|
|
208
|
+
image2_array = np.array(image2)
|
|
209
|
+
|
|
210
|
+
# Calculate SSIM between two images
|
|
211
|
+
similarity_index = ssim(image1_array, image2_array)
|
|
212
|
+
|
|
213
|
+
epsilon = 0.01
|
|
214
|
+
if base_score is not None:
|
|
215
|
+
if similarity_index >= base_score + epsilon:
|
|
216
|
+
return (similarity_index - base_score) / (1 - base_score)
|
|
217
|
+
else:
|
|
218
|
+
return 0
|
|
219
|
+
else:
|
|
220
|
+
return similarity_index
|
|
221
|
+
|
|
222
|
+
def compare_audios(audio_path_1, audio_path_2):
|
|
223
|
+
"""
|
|
224
|
+
Compare two audio files and return a similarity score in the range [0, 1].
|
|
225
|
+
audio_path_1, audio_path_2: paths to the audio files to compare
|
|
226
|
+
"""
|
|
227
|
+
# similarity = compare_audios_simple('path_to_audio1.mp3', 'path_to_audio2.mp3')
|
|
228
|
+
# print(f'Similarity Score: {similarity}')
|
|
229
|
+
|
|
230
|
+
if not audio_path_1 or not audio_path_2:
|
|
231
|
+
return 0
|
|
232
|
+
|
|
233
|
+
y1, y2 = None, None
|
|
234
|
+
try:
|
|
235
|
+
y1, sr1 = librosa.load(audio_path_1)
|
|
236
|
+
except Exception:
|
|
237
|
+
logger.warning(f"Could not load audio from {os.path.basename(audio_path_1)}. It might be empty or corrupt.")
|
|
238
|
+
|
|
239
|
+
try:
|
|
240
|
+
y2, sr2 = librosa.load(audio_path_2)
|
|
241
|
+
except Exception:
|
|
242
|
+
logger.warning(f"Could not load audio from {os.path.basename(audio_path_2)}. It might be empty or corrupt.")
|
|
243
|
+
|
|
244
|
+
# Handle cases where one or both audio files are empty or corrupt.
|
|
245
|
+
is_y1_bad = (y1 is None) or (y1.shape[0] == 0)
|
|
246
|
+
is_y2_bad = (y2 is None) or (y2.shape[0] == 0)
|
|
247
|
+
|
|
248
|
+
if is_y1_bad and is_y2_bad:
|
|
249
|
+
logger.info("Both audio files are empty or corrupt. Considering them perfectly similar.")
|
|
250
|
+
return 1.0
|
|
251
|
+
|
|
252
|
+
if is_y1_bad or is_y2_bad:
|
|
253
|
+
logger.warning(f"One audio file is empty/corrupt, the other is not. Similarity is 0.")
|
|
254
|
+
return 0.0
|
|
255
|
+
|
|
256
|
+
try:
|
|
257
|
+
logger.info(f"Audio 1 ({os.path.basename(audio_path_1)}): sr={sr1}, len={len(y1)}")
|
|
258
|
+
logger.info(f"Audio 2 ({os.path.basename(audio_path_2)}): sr={sr2}, len={len(y2)}")
|
|
259
|
+
|
|
260
|
+
# Extract MFCC features
|
|
261
|
+
mfcc1 = librosa.feature.mfcc(y=y1, sr=sr1)
|
|
262
|
+
mfcc2 = librosa.feature.mfcc(y=y2, sr=sr2)
|
|
263
|
+
except Exception as e:
|
|
264
|
+
logger.error(f"Error during MFCC extraction: {e}")
|
|
265
|
+
return 0.0
|
|
266
|
+
|
|
267
|
+
# Normalize the MFCC features
|
|
268
|
+
mfcc1 = librosa.util.normalize(mfcc1, axis=1)
|
|
269
|
+
mfcc2 = librosa.util.normalize(mfcc2, axis=1)
|
|
270
|
+
logger.info(f"MFCCs normalized.")
|
|
271
|
+
|
|
272
|
+
# Define a lambda function to compute cosine distance
|
|
273
|
+
dist_func = lambda x, y: cosine(x, y)
|
|
274
|
+
|
|
275
|
+
# Use the DTW algorithm to find the best alignment path
|
|
276
|
+
distance, path = fastdtw(mfcc1.T, mfcc2.T, dist=dist_func)
|
|
277
|
+
logger.info(f"DTW distance: {distance:.4f}, Path length: {len(path)}")
|
|
278
|
+
|
|
279
|
+
# Normalize the DTW distance by the length of the alignment path.
|
|
280
|
+
if len(path) == 0:
|
|
281
|
+
normalized_distance = np.inf
|
|
282
|
+
else:
|
|
283
|
+
normalized_distance = distance / len(path)
|
|
284
|
+
logger.info(f"Normalized DTW distance: {normalized_distance:.4f}")
|
|
285
|
+
|
|
286
|
+
# Convert the normalized distance to a similarity score using an exponential decay function.
|
|
287
|
+
similarity = np.exp(-normalized_distance)
|
|
288
|
+
|
|
289
|
+
return similarity
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def compare_audios_by_dl_model(audio_path_1, audio_path_2):
|
|
293
|
+
pass
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def compare_videos(video_path1, video_path2, max_frames_to_check=100, threshold=5):
|
|
297
|
+
# Open both video files
|
|
298
|
+
cap1 = cv2.VideoCapture(video_path1)
|
|
299
|
+
cap2 = cv2.VideoCapture(video_path2)
|
|
300
|
+
|
|
301
|
+
frames_checked = 0
|
|
302
|
+
mismatch_count = 0
|
|
303
|
+
|
|
304
|
+
while frames_checked < max_frames_to_check:
|
|
305
|
+
# Read frames from both videos
|
|
306
|
+
ret1, frame1 = cap1.read()
|
|
307
|
+
ret2, frame2 = cap2.read()
|
|
308
|
+
|
|
309
|
+
# If a video ends, then check if both ended to confirm they are of the same length
|
|
310
|
+
if not ret1 or not ret2:
|
|
311
|
+
return 1. if ret1 == ret2 else 0. # return float only
|
|
312
|
+
|
|
313
|
+
# Convert frames to PIL Images
|
|
314
|
+
frame1 = Image.fromarray(cv2.cvtColor(frame1, cv2.COLOR_BGR2RGB))
|
|
315
|
+
frame2 = Image.fromarray(cv2.cvtColor(frame2, cv2.COLOR_BGR2RGB))
|
|
316
|
+
|
|
317
|
+
# Compute the perceptual hash for each frame
|
|
318
|
+
hash1 = imagehash.phash(frame1)
|
|
319
|
+
hash2 = imagehash.phash(frame2)
|
|
320
|
+
|
|
321
|
+
# Increment the frames checked
|
|
322
|
+
frames_checked += 1
|
|
323
|
+
|
|
324
|
+
# Compute the difference in the hashes
|
|
325
|
+
if hash1 - hash2 > threshold:
|
|
326
|
+
mismatch_count += 1
|
|
327
|
+
# If there's a significant difference, the frames are not the same
|
|
328
|
+
if mismatch_count > threshold:
|
|
329
|
+
return 0.
|
|
330
|
+
|
|
331
|
+
# If we reach here, the content appears to be the same
|
|
332
|
+
return 1.
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def check_qt_bgcone(actual_config_path, rule):
|
|
336
|
+
with open(actual_config_path, 'rb') as file:
|
|
337
|
+
config_file = file.read().decode('utf-8')
|
|
338
|
+
|
|
339
|
+
expected_qt_bgcone = rule['expected_qt_bgcone']
|
|
340
|
+
if isinstance(expected_qt_bgcone, int):
|
|
341
|
+
expected_qt_bgcone = str(expected_qt_bgcone)
|
|
342
|
+
|
|
343
|
+
try:
|
|
344
|
+
# The default value of qt_bgcone is 1, which means it is enabled
|
|
345
|
+
qt_bgcone = "1"
|
|
346
|
+
for line in config_file.split("\n"):
|
|
347
|
+
# Check if the line contains the recording path setting
|
|
348
|
+
if 'qt-bgcone=' in line:
|
|
349
|
+
# Extract the value of the recording path and remove surrounding whitespace
|
|
350
|
+
qt_bgcone = line.split('=')[-1].strip()
|
|
351
|
+
# The configuration key was not found in the file
|
|
352
|
+
|
|
353
|
+
if qt_bgcone == expected_qt_bgcone:
|
|
354
|
+
return 1
|
|
355
|
+
else:
|
|
356
|
+
return 0
|
|
357
|
+
except FileNotFoundError:
|
|
358
|
+
logger.error("VLC configuration file not found.")
|
|
359
|
+
return 0
|
|
360
|
+
except Exception as e:
|
|
361
|
+
logger.error(f"An error occurred: {e}")
|
|
362
|
+
return 0
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def check_qt_max_volume(actual_config_path, rule):
|
|
366
|
+
with open(actual_config_path, 'rb') as file:
|
|
367
|
+
config_file = file.read().decode('utf-8')
|
|
368
|
+
|
|
369
|
+
expected_qt_max_volume = rule['expected_qt_max_volume']
|
|
370
|
+
if isinstance(expected_qt_max_volume, int):
|
|
371
|
+
expected_qt_max_volume = str(expected_qt_max_volume)
|
|
372
|
+
|
|
373
|
+
try:
|
|
374
|
+
qt_max_volume = "125"
|
|
375
|
+
for line in config_file.split("\n"):
|
|
376
|
+
if 'qt-max-volume=' in line:
|
|
377
|
+
qt_max_volume = line.split('=')[-1].strip()
|
|
378
|
+
# The configuration key was not found in the file
|
|
379
|
+
|
|
380
|
+
if qt_max_volume == expected_qt_max_volume:
|
|
381
|
+
return 1
|
|
382
|
+
else:
|
|
383
|
+
return 0
|
|
384
|
+
except FileNotFoundError:
|
|
385
|
+
logger.error("VLC configuration file not found.")
|
|
386
|
+
return 0
|
|
387
|
+
except Exception as e:
|
|
388
|
+
logger.error(f"An error occurred: {e}")
|
|
389
|
+
return 0
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def check_qt_minimal_view(actual_config_path, rule):
|
|
393
|
+
with open(actual_config_path, 'rb') as file:
|
|
394
|
+
config_file = file.read().decode('utf-8')
|
|
395
|
+
|
|
396
|
+
expected_qt_minimal_view = rule['expected_qt_minimal_view']
|
|
397
|
+
if isinstance(expected_qt_minimal_view, int):
|
|
398
|
+
expected_qt_minimal_view = str(expected_qt_minimal_view)
|
|
399
|
+
|
|
400
|
+
try:
|
|
401
|
+
qt_minimal_view = "0"
|
|
402
|
+
for line in config_file.split("\n"):
|
|
403
|
+
if 'qt-minimal-view=' in line:
|
|
404
|
+
qt_minimal_view = line.split('=')[-1].strip()
|
|
405
|
+
|
|
406
|
+
if qt_minimal_view == expected_qt_minimal_view:
|
|
407
|
+
return 1
|
|
408
|
+
else:
|
|
409
|
+
return 0
|
|
410
|
+
except FileNotFoundError:
|
|
411
|
+
logger.error("VLC configuration file not found.")
|
|
412
|
+
return 0
|
|
413
|
+
except Exception as e:
|
|
414
|
+
logger.error(f"An error occurred: {e}")
|
|
415
|
+
return 0
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def check_qt_slider_colours(actual_config_path, rule):
|
|
419
|
+
with open(actual_config_path, 'rb') as file:
|
|
420
|
+
config_file = file.read().decode('utf-8')
|
|
421
|
+
|
|
422
|
+
try:
|
|
423
|
+
qt_slider_colours = "153;210;153;20;210;20;255;199;15;245;39;29"
|
|
424
|
+
for line in config_file.split("\n"):
|
|
425
|
+
if 'qt-slider-colours' in line:
|
|
426
|
+
qt_slider_colours = line.split('=')[-1].strip()
|
|
427
|
+
# The configuration key was not found in the file
|
|
428
|
+
|
|
429
|
+
if rule['type'] == 'match':
|
|
430
|
+
expected_qt_slider_colours = rule['expected_qt_slider_colours']
|
|
431
|
+
if qt_slider_colours == expected_qt_slider_colours:
|
|
432
|
+
return 1
|
|
433
|
+
else:
|
|
434
|
+
return 0
|
|
435
|
+
elif rule['type'] == 'blackish':
|
|
436
|
+
def is_color_blackish(rgb_values, threshold=100):
|
|
437
|
+
# decide if the color is blackish
|
|
438
|
+
return all(value < threshold for value in rgb_values)
|
|
439
|
+
|
|
440
|
+
def parse_qt_slider_colours(colours_string):
|
|
441
|
+
# parse the string of colours into a list of RGB tuples
|
|
442
|
+
values = [int(x) for x in colours_string.split(';')]
|
|
443
|
+
colors = list(zip(values[0::3], values[1::3], values[2::3]))
|
|
444
|
+
return colors
|
|
445
|
+
|
|
446
|
+
colors = parse_qt_slider_colours(qt_slider_colours)
|
|
447
|
+
|
|
448
|
+
# check if all colors are blackish
|
|
449
|
+
for color in colors:
|
|
450
|
+
if is_color_blackish(color):
|
|
451
|
+
pass
|
|
452
|
+
else:
|
|
453
|
+
return 0
|
|
454
|
+
return 1
|
|
455
|
+
|
|
456
|
+
except FileNotFoundError:
|
|
457
|
+
logger.error("VLC configuration file not found.")
|
|
458
|
+
return 0
|
|
459
|
+
except Exception as e:
|
|
460
|
+
logger.error(f"An error occurred: {e}")
|
|
461
|
+
return 0
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def check_global_key_play_pause(actual_config_path, rule):
|
|
465
|
+
"""
|
|
466
|
+
# Play/Pause (str)
|
|
467
|
+
#global-key-play-pause=
|
|
468
|
+
|
|
469
|
+
# Play/Pause (str)
|
|
470
|
+
#key-play-pause=Space
|
|
471
|
+
"""
|
|
472
|
+
with open(actual_config_path, 'rb') as file:
|
|
473
|
+
config_file = file.read().decode('utf-8')
|
|
474
|
+
|
|
475
|
+
expected_global_key_play_pause = rule['expected_global_key_play_pause']
|
|
476
|
+
|
|
477
|
+
if isinstance(expected_global_key_play_pause, int):
|
|
478
|
+
expected_global_key_play_pause = str(expected_global_key_play_pause)
|
|
479
|
+
|
|
480
|
+
try:
|
|
481
|
+
global_key_play_pause = "0"
|
|
482
|
+
for line in config_file.split("\n"):
|
|
483
|
+
# Check if the line contains the recording path setting
|
|
484
|
+
if 'global-key-play-pause=' in line:
|
|
485
|
+
global_key_play_pause = "0" if line.split('=')[-1].strip() == "" else "1"
|
|
486
|
+
|
|
487
|
+
if global_key_play_pause == expected_global_key_play_pause:
|
|
488
|
+
return 1
|
|
489
|
+
else:
|
|
490
|
+
return 0
|
|
491
|
+
except FileNotFoundError:
|
|
492
|
+
logger.error("VLC configuration file not found.")
|
|
493
|
+
return 0
|
|
494
|
+
except Exception as e:
|
|
495
|
+
logger.error(f"An error occurred: {e}")
|
|
496
|
+
return 0
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
def check_one_instance_when_started_from_file(actual_config_path, rule):
|
|
500
|
+
with open(actual_config_path, 'rb') as file:
|
|
501
|
+
config_file = file.read().decode('utf-8')
|
|
502
|
+
|
|
503
|
+
expected_one_instance_when_started_from_file = rule['expected_one_instance_when_started_from_file']
|
|
504
|
+
|
|
505
|
+
if isinstance(expected_one_instance_when_started_from_file, int):
|
|
506
|
+
expected_one_instance_when_started_from_file = str(expected_one_instance_when_started_from_file)
|
|
507
|
+
|
|
508
|
+
try:
|
|
509
|
+
one_instance_when_started_from_file = "1"
|
|
510
|
+
for line in config_file.split("\n"):
|
|
511
|
+
# Check if the line contains the recording path setting
|
|
512
|
+
if 'one-instance-when-started-from-file=' in line:
|
|
513
|
+
one_instance_when_started_from_file = line.split('=')[-1].strip()
|
|
514
|
+
|
|
515
|
+
if one_instance_when_started_from_file == expected_one_instance_when_started_from_file:
|
|
516
|
+
return 1
|
|
517
|
+
else:
|
|
518
|
+
return 0
|
|
519
|
+
except FileNotFoundError:
|
|
520
|
+
logger.error("VLC configuration file not found.")
|
|
521
|
+
return 0
|
|
522
|
+
except Exception as e:
|
|
523
|
+
logger.error(f"An error occurred: {e}")
|
|
524
|
+
return 0
|