autoglm-gui 0.4.11__py3-none-any.whl → 0.4.12__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.
- AutoGLM_GUI/__init__.py +8 -0
- AutoGLM_GUI/__main__.py +29 -34
- AutoGLM_GUI/adb_plus/__init__.py +6 -0
- AutoGLM_GUI/adb_plus/device.py +50 -0
- AutoGLM_GUI/adb_plus/ip.py +78 -0
- AutoGLM_GUI/adb_plus/serial.py +35 -0
- AutoGLM_GUI/api/__init__.py +8 -0
- AutoGLM_GUI/api/agents.py +76 -67
- AutoGLM_GUI/api/devices.py +96 -6
- AutoGLM_GUI/api/media.py +12 -235
- AutoGLM_GUI/config_manager.py +538 -97
- AutoGLM_GUI/exceptions.py +7 -0
- AutoGLM_GUI/platform_utils.py +19 -0
- AutoGLM_GUI/schemas.py +35 -2
- AutoGLM_GUI/scrcpy_protocol.py +46 -0
- AutoGLM_GUI/scrcpy_stream.py +192 -307
- AutoGLM_GUI/server.py +7 -2
- AutoGLM_GUI/socketio_server.py +125 -0
- AutoGLM_GUI/static/assets/{about-wSo3UgQ-.js → about-kgOkkOWe.js} +1 -1
- AutoGLM_GUI/static/assets/chat-CZV3RByK.js +149 -0
- AutoGLM_GUI/static/assets/{index-B5u1xtK1.js → index-BPYHsweG.js} +1 -1
- AutoGLM_GUI/static/assets/index-Beu9cbSy.css +1 -0
- AutoGLM_GUI/static/assets/index-DfI_Z1Cx.js +10 -0
- AutoGLM_GUI/static/assets/worker-D6BRitjy.js +1 -0
- AutoGLM_GUI/static/index.html +2 -2
- {autoglm_gui-0.4.11.dist-info → autoglm_gui-0.4.12.dist-info}/METADATA +2 -1
- autoglm_gui-0.4.12.dist-info/RECORD +56 -0
- AutoGLM_GUI/resources/apks/ADBKeyBoard.LICENSE.txt +0 -339
- AutoGLM_GUI/resources/apks/ADBKeyBoard.README.txt +0 -1
- AutoGLM_GUI/resources/apks/ADBKeyboard.apk +0 -0
- AutoGLM_GUI/static/assets/chat-BcY2K0yj.js +0 -25
- AutoGLM_GUI/static/assets/index-CHrYo3Qj.css +0 -1
- AutoGLM_GUI/static/assets/index-D5BALRbT.js +0 -10
- autoglm_gui-0.4.11.dist-info/RECORD +0 -52
- {autoglm_gui-0.4.11.dist-info → autoglm_gui-0.4.12.dist-info}/WHEEL +0 -0
- {autoglm_gui-0.4.11.dist-info → autoglm_gui-0.4.12.dist-info}/entry_points.txt +0 -0
- {autoglm_gui-0.4.11.dist-info → autoglm_gui-0.4.12.dist-info}/licenses/LICENSE +0 -0
AutoGLM_GUI/api/media.py
CHANGED
|
@@ -1,52 +1,28 @@
|
|
|
1
|
-
"""Media routes: screenshot
|
|
1
|
+
"""Media routes: screenshot and stream reset."""
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import os
|
|
5
|
-
from pathlib import Path
|
|
3
|
+
from __future__ import annotations
|
|
6
4
|
|
|
7
|
-
from fastapi import APIRouter
|
|
5
|
+
from fastapi import APIRouter
|
|
8
6
|
|
|
9
7
|
from AutoGLM_GUI.adb_plus import capture_screenshot
|
|
10
8
|
from AutoGLM_GUI.logger import logger
|
|
11
9
|
from AutoGLM_GUI.schemas import ScreenshotRequest, ScreenshotResponse
|
|
12
|
-
from AutoGLM_GUI.
|
|
13
|
-
from AutoGLM_GUI.state import scrcpy_locks, scrcpy_streamers
|
|
10
|
+
from AutoGLM_GUI.socketio_server import stop_streamers
|
|
14
11
|
|
|
15
12
|
router = APIRouter()
|
|
16
13
|
|
|
17
|
-
# Debug configuration: Set DEBUG_SAVE_VIDEO_STREAM=1 to save streams to debug_streams/
|
|
18
|
-
DEBUG_SAVE_STREAM = os.getenv("DEBUG_SAVE_VIDEO_STREAM", "0") == "1"
|
|
19
|
-
|
|
20
14
|
|
|
21
15
|
@router.post("/api/video/reset")
|
|
22
16
|
async def reset_video_stream(device_id: str | None = None) -> dict:
|
|
23
|
-
"""Reset
|
|
17
|
+
"""Reset active scrcpy streams (Socket.IO)."""
|
|
18
|
+
stop_streamers(device_id=device_id)
|
|
24
19
|
if device_id:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
logger.info(f"Streamer reset for device {device_id}")
|
|
32
|
-
return {
|
|
33
|
-
"success": True,
|
|
34
|
-
"message": f"Video stream reset for device {device_id}",
|
|
35
|
-
}
|
|
36
|
-
return {
|
|
37
|
-
"success": True,
|
|
38
|
-
"message": f"No active video stream for device {device_id}",
|
|
39
|
-
}
|
|
40
|
-
return {"success": True, "message": f"No video stream for device {device_id}"}
|
|
41
|
-
|
|
42
|
-
device_ids = list(scrcpy_streamers.keys())
|
|
43
|
-
for dev_id in device_ids:
|
|
44
|
-
if dev_id in scrcpy_locks:
|
|
45
|
-
async with scrcpy_locks[dev_id]:
|
|
46
|
-
if dev_id in scrcpy_streamers:
|
|
47
|
-
scrcpy_streamers[dev_id].stop()
|
|
48
|
-
del scrcpy_streamers[dev_id]
|
|
49
|
-
logger.info("All streamers reset")
|
|
20
|
+
logger.info("Video stream reset for device %s", device_id)
|
|
21
|
+
return {
|
|
22
|
+
"success": True,
|
|
23
|
+
"message": f"Video stream reset for device {device_id}",
|
|
24
|
+
}
|
|
25
|
+
logger.info("All video streams reset")
|
|
50
26
|
return {"success": True, "message": "All video streams reset"}
|
|
51
27
|
|
|
52
28
|
|
|
@@ -71,202 +47,3 @@ def take_screenshot(request: ScreenshotRequest) -> ScreenshotResponse:
|
|
|
71
47
|
is_sensitive=False,
|
|
72
48
|
error=str(e),
|
|
73
49
|
)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
@router.websocket("/api/video/stream")
|
|
77
|
-
async def video_stream_ws(
|
|
78
|
-
websocket: WebSocket,
|
|
79
|
-
device_id: str | None = None,
|
|
80
|
-
):
|
|
81
|
-
"""Stream real-time H.264 video from scrcpy server via WebSocket(多设备支持)."""
|
|
82
|
-
await websocket.accept()
|
|
83
|
-
|
|
84
|
-
if not device_id:
|
|
85
|
-
await websocket.send_json({"error": "device_id is required"})
|
|
86
|
-
return
|
|
87
|
-
|
|
88
|
-
logger.info(f"WebSocket connection for device {device_id}")
|
|
89
|
-
|
|
90
|
-
# Debug: Save stream to file for analysis (controlled by DEBUG_SAVE_VIDEO_STREAM env var)
|
|
91
|
-
debug_file = None
|
|
92
|
-
if DEBUG_SAVE_STREAM:
|
|
93
|
-
debug_dir = Path("debug_streams")
|
|
94
|
-
debug_dir.mkdir(exist_ok=True)
|
|
95
|
-
debug_file_path = (
|
|
96
|
-
debug_dir / f"{device_id}_{int(__import__('time').time())}.h264"
|
|
97
|
-
)
|
|
98
|
-
debug_file = open(debug_file_path, "wb")
|
|
99
|
-
logger.debug(f"DEBUG: Saving stream to {debug_file_path}")
|
|
100
|
-
|
|
101
|
-
if device_id not in scrcpy_locks:
|
|
102
|
-
scrcpy_locks[device_id] = asyncio.Lock()
|
|
103
|
-
|
|
104
|
-
async with scrcpy_locks[device_id]:
|
|
105
|
-
if device_id not in scrcpy_streamers:
|
|
106
|
-
logger.info(f"Creating streamer for device {device_id}")
|
|
107
|
-
scrcpy_streamers[device_id] = ScrcpyStreamer(
|
|
108
|
-
device_id=device_id, max_size=1280, bit_rate=4_000_000
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
try:
|
|
112
|
-
logger.info(f"Starting scrcpy server for device {device_id}")
|
|
113
|
-
await scrcpy_streamers[device_id].start()
|
|
114
|
-
logger.info(f"Scrcpy server started for device {device_id}")
|
|
115
|
-
|
|
116
|
-
# Read NAL units until we have SPS, PPS, and IDR
|
|
117
|
-
streamer = scrcpy_streamers[device_id]
|
|
118
|
-
|
|
119
|
-
logger.debug("Reading NAL units for initialization...")
|
|
120
|
-
for attempt in range(20): # Max 20 NAL units for initialization
|
|
121
|
-
try:
|
|
122
|
-
nal_unit = await streamer.read_nal_unit(auto_cache=True)
|
|
123
|
-
nal_type = nal_unit[4] & 0x1F if len(nal_unit) > 4 else -1
|
|
124
|
-
nal_type_names = {5: "IDR", 7: "SPS", 8: "PPS"}
|
|
125
|
-
logger.debug(
|
|
126
|
-
f"Read NAL unit: type={nal_type_names.get(nal_type, nal_type)}, size={len(nal_unit)} bytes"
|
|
127
|
-
)
|
|
128
|
-
|
|
129
|
-
# Check if we have all required parameter sets
|
|
130
|
-
if (
|
|
131
|
-
streamer.cached_sps
|
|
132
|
-
and streamer.cached_pps
|
|
133
|
-
and streamer.cached_idr
|
|
134
|
-
):
|
|
135
|
-
logger.debug(
|
|
136
|
-
f"✓ Initialization complete: SPS={len(streamer.cached_sps)}B, PPS={len(streamer.cached_pps)}B, IDR={len(streamer.cached_idr)}B"
|
|
137
|
-
)
|
|
138
|
-
break
|
|
139
|
-
except Exception as e:
|
|
140
|
-
logger.warning(f"Failed to read NAL unit: {e}")
|
|
141
|
-
await asyncio.sleep(0.5)
|
|
142
|
-
continue
|
|
143
|
-
|
|
144
|
-
# Get initialization data (SPS + PPS + IDR)
|
|
145
|
-
init_data = streamer.get_initialization_data()
|
|
146
|
-
if not init_data:
|
|
147
|
-
raise RuntimeError(
|
|
148
|
-
"Failed to get initialization data (missing SPS/PPS/IDR)"
|
|
149
|
-
)
|
|
150
|
-
|
|
151
|
-
# Send initialization data as ONE message (SPS+PPS+IDR combined)
|
|
152
|
-
await websocket.send_bytes(init_data)
|
|
153
|
-
logger.debug(
|
|
154
|
-
f"✓ Sent initialization data to first client: {len(init_data)} bytes total"
|
|
155
|
-
)
|
|
156
|
-
|
|
157
|
-
# Debug: Save to file
|
|
158
|
-
if debug_file:
|
|
159
|
-
debug_file.write(init_data)
|
|
160
|
-
debug_file.flush()
|
|
161
|
-
|
|
162
|
-
except Exception as e:
|
|
163
|
-
logger.exception(f"Failed to start streamer: {e}")
|
|
164
|
-
scrcpy_streamers[device_id].stop()
|
|
165
|
-
del scrcpy_streamers[device_id]
|
|
166
|
-
try:
|
|
167
|
-
await websocket.send_json({"error": str(e)})
|
|
168
|
-
except Exception:
|
|
169
|
-
pass
|
|
170
|
-
return
|
|
171
|
-
else:
|
|
172
|
-
logger.info(f"Reusing streamer for device {device_id}")
|
|
173
|
-
|
|
174
|
-
streamer = scrcpy_streamers[device_id]
|
|
175
|
-
# CRITICAL: Send complete initialization data (SPS+PPS+IDR)
|
|
176
|
-
# Without IDR frame, decoder cannot start and will show black screen
|
|
177
|
-
|
|
178
|
-
# Wait for initialization data to be ready (max 5 seconds)
|
|
179
|
-
init_data = None
|
|
180
|
-
for attempt in range(10):
|
|
181
|
-
init_data = streamer.get_initialization_data()
|
|
182
|
-
if init_data:
|
|
183
|
-
break
|
|
184
|
-
logger.debug(
|
|
185
|
-
f"Waiting for initialization data (attempt {attempt + 1}/10)..."
|
|
186
|
-
)
|
|
187
|
-
await asyncio.sleep(0.5)
|
|
188
|
-
|
|
189
|
-
if init_data:
|
|
190
|
-
# Log what we're sending
|
|
191
|
-
logger.debug(
|
|
192
|
-
f"✓ Sending cached initialization data for device {device_id}:"
|
|
193
|
-
)
|
|
194
|
-
logger.debug(
|
|
195
|
-
f" - SPS: {len(streamer.cached_sps) if streamer.cached_sps else 0}B"
|
|
196
|
-
)
|
|
197
|
-
logger.debug(
|
|
198
|
-
f" - PPS: {len(streamer.cached_pps) if streamer.cached_pps else 0}B"
|
|
199
|
-
)
|
|
200
|
-
logger.debug(
|
|
201
|
-
f" - IDR: {len(streamer.cached_idr) if streamer.cached_idr else 0}B"
|
|
202
|
-
)
|
|
203
|
-
logger.debug(f" - Total: {len(init_data)} bytes")
|
|
204
|
-
|
|
205
|
-
await websocket.send_bytes(init_data)
|
|
206
|
-
logger.debug("✓ Initialization data sent successfully")
|
|
207
|
-
|
|
208
|
-
# Debug: Save to file
|
|
209
|
-
if debug_file:
|
|
210
|
-
debug_file.write(init_data)
|
|
211
|
-
debug_file.flush()
|
|
212
|
-
else:
|
|
213
|
-
error_msg = f"Initialization data not ready for device {device_id} after 5 seconds"
|
|
214
|
-
logger.error(f"ERROR: {error_msg}")
|
|
215
|
-
try:
|
|
216
|
-
await websocket.send_json({"error": error_msg})
|
|
217
|
-
except Exception:
|
|
218
|
-
pass
|
|
219
|
-
return
|
|
220
|
-
|
|
221
|
-
streamer = scrcpy_streamers[device_id]
|
|
222
|
-
|
|
223
|
-
stream_failed = False
|
|
224
|
-
try:
|
|
225
|
-
nal_count = 0
|
|
226
|
-
while True:
|
|
227
|
-
try:
|
|
228
|
-
# Read one complete NAL unit
|
|
229
|
-
# Each WebSocket message = one complete NAL unit (clear semantic boundary)
|
|
230
|
-
nal_unit = await streamer.read_nal_unit(auto_cache=True)
|
|
231
|
-
await websocket.send_bytes(nal_unit)
|
|
232
|
-
|
|
233
|
-
# Debug: Save to file
|
|
234
|
-
if debug_file:
|
|
235
|
-
debug_file.write(nal_unit)
|
|
236
|
-
debug_file.flush()
|
|
237
|
-
|
|
238
|
-
nal_count += 1
|
|
239
|
-
if nal_count % 100 == 0:
|
|
240
|
-
logger.debug(f"Device {device_id}: Sent {nal_count} NAL units")
|
|
241
|
-
except ConnectionError as e:
|
|
242
|
-
logger.warning(f"Device {device_id}: Connection error: {e}")
|
|
243
|
-
stream_failed = True
|
|
244
|
-
try:
|
|
245
|
-
await websocket.send_json({"error": f"Stream error: {str(e)}"})
|
|
246
|
-
except Exception:
|
|
247
|
-
pass
|
|
248
|
-
break
|
|
249
|
-
|
|
250
|
-
except WebSocketDisconnect:
|
|
251
|
-
logger.info(f"Device {device_id}: Client disconnected")
|
|
252
|
-
except Exception as e:
|
|
253
|
-
logger.exception(f"Device {device_id}: Error: {e}")
|
|
254
|
-
stream_failed = True
|
|
255
|
-
try:
|
|
256
|
-
await websocket.send_json({"error": str(e)})
|
|
257
|
-
except Exception:
|
|
258
|
-
pass
|
|
259
|
-
|
|
260
|
-
if stream_failed:
|
|
261
|
-
async with scrcpy_locks[device_id]:
|
|
262
|
-
if device_id in scrcpy_streamers:
|
|
263
|
-
logger.info(f"Resetting streamer for device {device_id}")
|
|
264
|
-
scrcpy_streamers[device_id].stop()
|
|
265
|
-
del scrcpy_streamers[device_id]
|
|
266
|
-
|
|
267
|
-
# Debug: Close file
|
|
268
|
-
if debug_file:
|
|
269
|
-
debug_file.close()
|
|
270
|
-
logger.debug("DEBUG: Closed debug file")
|
|
271
|
-
|
|
272
|
-
logger.info(f"Device {device_id}: Stream ended")
|