-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmonitor_utils.py
More file actions
204 lines (165 loc) · 6.82 KB
/
monitor_utils.py
File metadata and controls
204 lines (165 loc) · 6.82 KB
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
#!/usr/bin/env python3
"""
Monitor detection and resolution utilities
"""
import subprocess
import re
from typing import List, Tuple, Dict, Optional
class Monitor:
def __init__(self, name: str, width: int, height: int, x: int = 0, y: int = 0, is_primary: bool = False):
self.name = name
self.width = width
self.height = height
self.x = x
self.y = y
self.is_primary = is_primary
@property
def resolution(self) -> str:
return f"{self.width}x{self.height}"
@property
def aspect_ratio(self) -> float:
return self.width / self.height
@property
def aspect_ratio_string(self) -> str:
ratio = self.aspect_ratio
if abs(ratio - 16/9) < 0.1:
return "16:9"
elif abs(ratio - 21/9) < 0.1:
return "21:9"
elif abs(ratio - 32/9) < 0.1:
return "32:9"
elif abs(ratio - 4/3) < 0.1:
return "4:3"
else:
return f"{ratio:.2f}:1"
def __str__(self):
primary = " (Primary)" if self.is_primary else ""
return f"{self.name}: {self.resolution} ({self.aspect_ratio_string}){primary}"
class MonitorDetector:
def __init__(self):
self.monitors: List[Monitor] = []
self.detect_monitors()
def detect_monitors(self):
"""Detect connected monitors using xrandr"""
self.monitors = []
try:
result = subprocess.run(['xrandr', '--query'], capture_output=True, text=True)
if result.returncode != 0:
# Fallback to basic detection
self._detect_fallback()
return
output = result.stdout
# Parse xrandr output
for line in output.split('\n'):
if ' connected' in line:
self._parse_monitor_line(line)
except FileNotFoundError:
# xrandr not available, use fallback
self._detect_fallback()
def _parse_monitor_line(self, line: str):
"""Parse a single monitor line from xrandr output"""
parts = line.split()
if len(parts) < 3:
return
monitor_name = parts[0]
is_primary = 'primary' in line
# Look for resolution pattern like "1920x1080+0+0"
resolution_match = re.search(r'(\d+)x(\d+)\+(\d+)\+(\d+)', line)
if resolution_match:
width = int(resolution_match.group(1))
height = int(resolution_match.group(2))
x = int(resolution_match.group(3))
y = int(resolution_match.group(4))
monitor = Monitor(monitor_name, width, height, x, y, is_primary)
self.monitors.append(monitor)
def _detect_fallback(self):
"""Fallback monitor detection using xdpyinfo"""
try:
result = subprocess.run(['xdpyinfo'], capture_output=True, text=True)
if result.returncode == 0:
output = result.stdout
# Look for dimensions
match = re.search(r'dimensions:\s+(\d+)x(\d+)', output)
if match:
width = int(match.group(1))
height = int(match.group(2))
monitor = Monitor("Screen", width, height, 0, 0, True)
self.monitors.append(monitor)
return
except FileNotFoundError:
pass
# Ultimate fallback - common resolutions
monitor = Monitor("Default", 1920, 1080, 0, 0, True)
self.monitors.append(monitor)
def get_primary_monitor(self) -> Optional[Monitor]:
"""Get the primary monitor"""
for monitor in self.monitors:
if monitor.is_primary:
return monitor
# If no primary found, return first monitor
return self.monitors[0] if self.monitors else None
def get_total_screen_size(self) -> Tuple[int, int]:
"""Get total screen dimensions across all monitors"""
if not self.monitors:
return (1920, 1080)
max_x = max(monitor.x + monitor.width for monitor in self.monitors)
max_y = max(monitor.y + monitor.height for monitor in self.monitors)
return (max_x, max_y)
def calculate_scaled_dimensions(self, monitor: Monitor, target_height: int) -> Tuple[int, int]:
"""Calculate scaled dimensions maintaining aspect ratio"""
aspect_ratio = monitor.aspect_ratio
width = int(target_height * aspect_ratio)
# Ensure even numbers (required by some codecs)
if width % 2 != 0:
width += 1
if target_height % 2 != 0:
target_height += 1
return (width, target_height)
def estimate_file_size(self, monitor: Monitor, target_height: int, fps: int, duration_seconds: int = 60, format_type: str = 'webm') -> str:
"""Estimate file size for given settings"""
width, height = self.calculate_scaled_dimensions(monitor, target_height)
# Rough estimates based on typical encoding
if format_type == 'webm':
# VP9 encoding
if target_height <= 480:
bitrate_mbps = 0.5
elif target_height <= 720:
bitrate_mbps = 1.0
else:
bitrate_mbps = 2.0
else: # mp4
# H.264 encoding
if target_height <= 480:
bitrate_mbps = 1.0
elif target_height <= 720:
bitrate_mbps = 2.5
else:
bitrate_mbps = 5.0
# Calculate file size
file_size_mb = (bitrate_mbps * duration_seconds) / 8 # Convert bits to bytes
if file_size_mb < 1:
return f"{file_size_mb * 1024:.0f} KB"
elif file_size_mb < 1024:
return f"{file_size_mb:.1f} MB"
else:
return f"{file_size_mb / 1024:.1f} GB"
def main():
"""Test monitor detection"""
detector = MonitorDetector()
print("🖥️ Detected Monitors:")
print("=" * 40)
for i, monitor in enumerate(detector.monitors, 1):
print(f"{i}. {monitor}")
# Show scaled dimensions
for height in [480, 720, 1080]:
width, h = detector.calculate_scaled_dimensions(monitor, height)
size_estimate = detector.estimate_file_size(monitor, height, 30, 60)
print(f" {height}p: {width}x{h} (~{size_estimate}/min)")
print()
primary = detector.get_primary_monitor()
if primary:
print(f"Primary monitor: {primary}")
total_w, total_h = detector.get_total_screen_size()
print(f"Total screen area: {total_w}x{total_h}")
if __name__ == "__main__":
main()