Buckets:
| """Record the simulator's fixed RGB camera and a synchronized activity timeline.""" | |
| import json | |
| from pathlib import Path | |
| import shutil | |
| import subprocess | |
| class CameraRecorder: | |
| def __init__(self, path, simulation_time, fps=30): | |
| self.path = Path(path).resolve() | |
| self.path.parent.mkdir(parents=True, exist_ok=True) | |
| self.fps = int(fps) | |
| if not 1 <= self.fps <= 60: | |
| raise ValueError("Recording frame rate must be between 1 and 60") | |
| self.start_time = float(simulation_time) | |
| self.next_time = self.start_time | |
| self.frames = 0 | |
| self.events = [] | |
| executable = shutil.which("ffmpeg") | |
| if not executable: | |
| raise RuntimeError("ffmpeg is required for camera recording") | |
| self.log = self.path.with_suffix(".ffmpeg.log").open("w") | |
| self.process = subprocess.Popen([ | |
| executable, "-hide_banner", "-loglevel", "warning", "-y", | |
| "-f", "rawvideo", "-pixel_format", "rgb24", "-video_size", "960x720", | |
| "-framerate", str(self.fps), "-i", "pipe:0", "-an", | |
| "-c:v", "libx264", "-preset", "veryfast", "-crf", "18", | |
| "-pix_fmt", "yuv420p", "-movflags", "+faststart", str(self.path), | |
| ], stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=self.log) | |
| def event(self, simulation_time, kind, label, **extra): | |
| self.events.append({"time_seconds": max(0., float(simulation_time) - self.start_time), | |
| "type": kind, "label": label, **extra}) | |
| def due(self, simulation_time): | |
| return float(simulation_time) + 1e-8 >= self.next_time | |
| def write(self, rgb, simulation_time): | |
| frame = rgb.tobytes() | |
| while self.due(simulation_time): | |
| self.process.stdin.write(frame) | |
| self.frames += 1 | |
| self.next_time = self.start_time + self.frames / self.fps | |
| def close(self): | |
| self.process.stdin.close() | |
| code = self.process.wait(timeout=30) | |
| self.log.close() | |
| if code: | |
| raise RuntimeError(f"Video encoder failed; see {self.path.with_suffix('.ffmpeg.log')}") | |
| result = {"video": str(self.path), "fps": self.fps, "frames": self.frames, | |
| "duration_seconds": self.frames / self.fps, "events": self.events, | |
| "source": "MuJoCo front RGB camera; continuous frames from the actual run"} | |
| timeline = self.path.parent / "timeline.json" | |
| timeline.write_text(json.dumps(result, indent=2)) | |
| return {"video": str(self.path), "timeline": str(timeline), | |
| "frames": self.frames, "duration_seconds": result["duration_seconds"]} | |
Xet Storage Details
- Size:
- 2.66 kB
- Xet hash:
- d926f27b34bbc29c9206d44a0a87fe21c950057998b5ba08471f5f1bb6de3a6a
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.