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.

Files changed (85) hide show
  1. desktop_env/__init__.py +1 -0
  2. desktop_env/actions.py +203 -0
  3. desktop_env/controllers/__init__.py +0 -0
  4. desktop_env/controllers/python.py +471 -0
  5. desktop_env/controllers/setup.py +882 -0
  6. desktop_env/desktop_env.py +509 -0
  7. desktop_env/evaluators/__init__.py +5 -0
  8. desktop_env/evaluators/getters/__init__.py +41 -0
  9. desktop_env/evaluators/getters/calc.py +15 -0
  10. desktop_env/evaluators/getters/chrome.py +1774 -0
  11. desktop_env/evaluators/getters/file.py +154 -0
  12. desktop_env/evaluators/getters/general.py +42 -0
  13. desktop_env/evaluators/getters/gimp.py +38 -0
  14. desktop_env/evaluators/getters/impress.py +126 -0
  15. desktop_env/evaluators/getters/info.py +24 -0
  16. desktop_env/evaluators/getters/misc.py +406 -0
  17. desktop_env/evaluators/getters/replay.py +20 -0
  18. desktop_env/evaluators/getters/vlc.py +86 -0
  19. desktop_env/evaluators/getters/vscode.py +35 -0
  20. desktop_env/evaluators/metrics/__init__.py +160 -0
  21. desktop_env/evaluators/metrics/basic_os.py +68 -0
  22. desktop_env/evaluators/metrics/chrome.py +493 -0
  23. desktop_env/evaluators/metrics/docs.py +1011 -0
  24. desktop_env/evaluators/metrics/general.py +665 -0
  25. desktop_env/evaluators/metrics/gimp.py +637 -0
  26. desktop_env/evaluators/metrics/libreoffice.py +28 -0
  27. desktop_env/evaluators/metrics/others.py +92 -0
  28. desktop_env/evaluators/metrics/pdf.py +31 -0
  29. desktop_env/evaluators/metrics/slides.py +957 -0
  30. desktop_env/evaluators/metrics/table.py +585 -0
  31. desktop_env/evaluators/metrics/thunderbird.py +176 -0
  32. desktop_env/evaluators/metrics/utils.py +719 -0
  33. desktop_env/evaluators/metrics/vlc.py +524 -0
  34. desktop_env/evaluators/metrics/vscode.py +283 -0
  35. desktop_env/providers/__init__.py +35 -0
  36. desktop_env/providers/aws/__init__.py +0 -0
  37. desktop_env/providers/aws/manager.py +278 -0
  38. desktop_env/providers/aws/provider.py +186 -0
  39. desktop_env/providers/aws/provider_with_proxy.py +315 -0
  40. desktop_env/providers/aws/proxy_pool.py +193 -0
  41. desktop_env/providers/azure/__init__.py +0 -0
  42. desktop_env/providers/azure/manager.py +87 -0
  43. desktop_env/providers/azure/provider.py +207 -0
  44. desktop_env/providers/base.py +97 -0
  45. desktop_env/providers/gcp/__init__.py +0 -0
  46. desktop_env/providers/gcp/manager.py +0 -0
  47. desktop_env/providers/gcp/provider.py +0 -0
  48. desktop_env/providers/virtualbox/__init__.py +0 -0
  49. desktop_env/providers/virtualbox/manager.py +463 -0
  50. desktop_env/providers/virtualbox/provider.py +124 -0
  51. desktop_env/providers/vmware/__init__.py +0 -0
  52. desktop_env/providers/vmware/manager.py +455 -0
  53. desktop_env/providers/vmware/provider.py +105 -0
  54. gui_agents/__init__.py +0 -0
  55. gui_agents/agents/Action.py +209 -0
  56. gui_agents/agents/__init__.py +0 -0
  57. gui_agents/agents/agent_s.py +832 -0
  58. gui_agents/agents/global_state.py +610 -0
  59. gui_agents/agents/grounding.py +651 -0
  60. gui_agents/agents/hardware_interface.py +129 -0
  61. gui_agents/agents/manager.py +568 -0
  62. gui_agents/agents/translator.py +132 -0
  63. gui_agents/agents/worker.py +355 -0
  64. gui_agents/cli_app.py +560 -0
  65. gui_agents/core/__init__.py +0 -0
  66. gui_agents/core/engine.py +1496 -0
  67. gui_agents/core/knowledge.py +449 -0
  68. gui_agents/core/mllm.py +555 -0
  69. gui_agents/tools/__init__.py +0 -0
  70. gui_agents/tools/tools.py +727 -0
  71. gui_agents/unit_test/__init__.py +0 -0
  72. gui_agents/unit_test/run_tests.py +65 -0
  73. gui_agents/unit_test/test_manager.py +330 -0
  74. gui_agents/unit_test/test_worker.py +269 -0
  75. gui_agents/utils/__init__.py +0 -0
  76. gui_agents/utils/analyze_display.py +301 -0
  77. gui_agents/utils/common_utils.py +263 -0
  78. gui_agents/utils/display_viewer.py +281 -0
  79. gui_agents/utils/embedding_manager.py +53 -0
  80. gui_agents/utils/image_axis_utils.py +27 -0
  81. lybic_guiagents-0.1.0.dist-info/METADATA +416 -0
  82. lybic_guiagents-0.1.0.dist-info/RECORD +85 -0
  83. lybic_guiagents-0.1.0.dist-info/WHEEL +5 -0
  84. lybic_guiagents-0.1.0.dist-info/licenses/LICENSE +201 -0
  85. 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