| 120 | |
| 121 | |
| 122 | def annotate(video_path: Path) -> None: |
| 123 | if not video_path.exists(): |
| 124 | print(f"ERROR: video not found at {video_path}", file=sys.stderr) |
| 125 | sys.exit(1) |
| 126 | |
| 127 | cap = cv2.VideoCapture(str(video_path)) |
| 128 | if not cap.isOpened(): |
| 129 | print(f"ERROR: failed to open {video_path}", file=sys.stderr) |
| 130 | sys.exit(1) |
| 131 | |
| 132 | total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) |
| 133 | fps = cap.get(cv2.CAP_PROP_FPS) or 30.0 |
| 134 | |
| 135 | gt_path = video_path.with_suffix(video_path.suffix + ".gt.json") |
| 136 | existing = load_gt(gt_path) |
| 137 | strokes = list(existing.get("strokes", [])) |
| 138 | bounces = list(existing.get("bounces", [])) |
| 139 | history: list[tuple[str, int]] = [ |
| 140 | ("stroke", i) for i in range(len(strokes)) |
| 141 | ] + [("bounce", i) for i in range(len(bounces))] |
| 142 | if strokes or bounces: |
| 143 | print(f"Loaded {len(strokes)} strokes, {len(bounces)} bounces from {gt_path}") |
| 144 | |
| 145 | frame_idx = 0 |
| 146 | current_player = 1 |
| 147 | playing = False |
| 148 | last_event = "" |
| 149 | window = "annotate" |
| 150 | cv2.namedWindow(window, cv2.WINDOW_NORMAL) |
| 151 | cv2.resizeWindow(window, 1280, 720) |
| 152 | |
| 153 | cached_frame_idx = -1 |
| 154 | cached_frame = None |
| 155 | |
| 156 | while True: |
| 157 | if frame_idx != cached_frame_idx: |
| 158 | cap.set(cv2.CAP_PROP_POS_FRAMES, frame_idx) |
| 159 | ret, frame = cap.read() |
| 160 | if not ret: |
| 161 | frame_idx = max(0, min(frame_idx, total - 1)) |
| 162 | continue |
| 163 | cached_frame_idx = frame_idx |
| 164 | cached_frame = frame |
| 165 | |
| 166 | display = cached_frame.copy() |
| 167 | draw_hud(display, frame_idx, total, current_player, last_event, strokes, bounces) |
| 168 | cv2.imshow(window, display) |
| 169 | |
| 170 | wait_ms = max(1, int(1000 / fps)) if playing else 0 |
| 171 | key = cv2.waitKey(wait_ms) |
| 172 | |
| 173 | if playing: |
| 174 | if frame_idx >= total - 1: |
| 175 | playing = False |
| 176 | else: |
| 177 | frame_idx += 1 |
| 178 | |
| 179 | if key == -1: |