feat(plugins): add source-aware evidence and recording timelines
This commit is contained in:
1 parent
72ff342605
commit
ef7af9988e
61 files changed
+3847
-108
No files matched your search
@@ -546,10 +546,17 @@ class UnifiedMobileController:
|
||||
output_dir = session.local_video_path.parent
|
||||
new_video_path = output_dir / f"recording_{session.android_segment_index:03d}.mkv"
|
||||
process = await self._spawn_scrcpy(
|
||||
build_scrcpy_record_command("scrcpy", session.device_id, new_video_path)
|
||||
self._archive_command(session.device_id, new_video_path, session.recording_codec)
|
||||
)
|
||||
spawned_at = time.time()
|
||||
first_frame_at = await await_scrcpy_first_frame(process, spawned_at)
|
||||
try:
|
||||
first_frame_at = await await_scrcpy_first_frame(
|
||||
process, spawned_at, require_marker=True
|
||||
)
|
||||
except (OSError, TimeoutError) as exc:
|
||||
await self._stop_scrcpy(process)
|
||||
self._record_recording_failure(session, f"录屏未取得首帧:{exc}")
|
||||
return False
|
||||
if process.returncode is not None:
|
||||
stderr = await process.stderr.read()
|
||||
logger.error(f"Failed to start scrcpy segment: {stderr.decode(errors='replace')}")
|
||||
@@ -599,6 +606,21 @@ class UnifiedMobileController:
|
||||
except Exception as e:
|
||||
logger.error(f"Error in recording supervisor for {device_id}: {e}")
|
||||
|
||||
def _archive_command(self, device_id, path, codec):
|
||||
from artemis.plugins.recording import recording_config
|
||||
|
||||
config = recording_config(self.ctx)
|
||||
bitrate = config.h265_kbps if codec == "h265" else config.h264_kbps
|
||||
return build_scrcpy_record_command(
|
||||
"scrcpy",
|
||||
device_id,
|
||||
path,
|
||||
f"{bitrate}K",
|
||||
max_long_edge=config.max_long_edge,
|
||||
fps=config.fps,
|
||||
codec=codec,
|
||||
)
|
||||
|
||||
async def start_video_recording(
|
||||
self,
|
||||
output_dir: Path | None = None,
|
||||
@@ -623,6 +645,7 @@ class UnifiedMobileController:
|
||||
message=f"Recording already in progress for device {device_id}",
|
||||
)
|
||||
|
||||
session = None
|
||||
try:
|
||||
if not output_dir:
|
||||
output_dir = Path(
|
||||
@@ -658,7 +681,16 @@ class UnifiedMobileController:
|
||||
session.capture_width, session.capture_height = display_state[1:]
|
||||
|
||||
# Start scrcpy in background
|
||||
cmd = build_scrcpy_record_command("scrcpy", device_id, local_video_path)
|
||||
from artemis.plugins.recording import recording_config
|
||||
|
||||
archive_config = recording_config(self.ctx)
|
||||
session.recording_codec = (
|
||||
"h265"
|
||||
if archive_config.codec == "h265"
|
||||
or (archive_config.codec == "auto" and archive_config.hevc_playback)
|
||||
else "h264"
|
||||
)
|
||||
cmd = self._archive_command(device_id, local_video_path, session.recording_codec)
|
||||
|
||||
process = await self._spawn_scrcpy(cmd)
|
||||
spawned_at = time.time()
|
||||
@@ -671,7 +703,26 @@ class UnifiedMobileController:
|
||||
# The file's t=0 is the first captured frame, about a second after
|
||||
# spawn. Anchor the recording timeline there: segment offsets and
|
||||
# analyzer clip trimming all treat session.start_time as that t=0.
|
||||
first_frame_at = await await_scrcpy_first_frame(process, spawned_at)
|
||||
try:
|
||||
first_frame_at = await await_scrcpy_first_frame(
|
||||
process, spawned_at, require_marker=True
|
||||
)
|
||||
except (OSError, TimeoutError):
|
||||
await self._stop_scrcpy(process)
|
||||
if session.recording_codec != "h265":
|
||||
raise
|
||||
first_frame_at = spawned_at
|
||||
if process.returncode is not None and session.recording_codec == "h265":
|
||||
logger.warning("设备 H.265 录制不可用,回退到 H.264 留档")
|
||||
session.recording_codec = "h264"
|
||||
process = await self._spawn_scrcpy(
|
||||
self._archive_command(device_id, local_video_path, "h264")
|
||||
)
|
||||
session.process = process
|
||||
spawned_at = time.time()
|
||||
first_frame_at = await await_scrcpy_first_frame(
|
||||
process, spawned_at, require_marker=True
|
||||
)
|
||||
if process.returncode is not None:
|
||||
stderr = await process.stderr.read()
|
||||
err_msg = stderr.decode()
|
||||
@@ -722,8 +773,23 @@ class UnifiedMobileController:
|
||||
source_revision=f"{session.video_id}:{session.generation}:active",
|
||||
)
|
||||
|
||||
except asyncio.CancelledError:
|
||||
if session and session.process:
|
||||
await self._stop_scrcpy(session.process)
|
||||
remove_active_session(device_id)
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to start scrcpy recording: {e}")
|
||||
if session and session.process:
|
||||
await self._stop_scrcpy(session.process)
|
||||
if self.ctx and self.ctx.data_engine:
|
||||
self.ctx.data_engine.record_video_start(
|
||||
video_id=session.video_id,
|
||||
device_id=device_id,
|
||||
local_video_path=session.local_video_path,
|
||||
start_time=session.start_time,
|
||||
)
|
||||
self._record_recording_failure(session, f"录屏启动失败:{e}")
|
||||
remove_active_session(device_id)
|
||||
return VideoRecordingResult(
|
||||
success=False,
|
||||
@@ -835,6 +901,12 @@ class UnifiedMobileController:
|
||||
output_dir, mp4_paths, self._segment_session_offsets(session, mp4_paths)
|
||||
)
|
||||
|
||||
if not manifest_path:
|
||||
message = "录像文件缺少有效时长或画面尺寸,已保留文件供排查"
|
||||
self._record_recording_failure(session, message)
|
||||
remove_active_session(device_id)
|
||||
return VideoRecordingResult(success=False, message=message)
|
||||
|
||||
remove_active_session(device_id)
|
||||
|
||||
# Persist update to local database if Data Engine is active
|
||||
|
||||
Reference in new issue
Block a user