from collections import deque import time NEXT = "NEXT" PREV = "PREV" FIRST = "FIRST" LAST = "LAST" NONE = "NONE" VALID_COMMANDS = {NEXT, PREV, FIRST, LAST, NONE} HISTORY_SIZE = 6 SWIPE_DELTA_THRESHOLD = 0.08 COOLDOWN_SECONDS = 0.6 FINGER_CONFIRM_FRAMES = 2 _x_history = deque(maxlen=HISTORY_SIZE) _last_command_time = 0.0 _first_hold_count = 0 _last_hold_count = 0 def _in_cooldown(now_ts: float) -> bool: return (now_ts - _last_command_time) < COOLDOWN_SECONDS def _register_command(command: str, now_ts: float) -> str: global _last_command_time _last_command_time = now_ts _x_history.clear() return command def _finger_only_command(fingers: dict) -> str: global _first_hold_count, _last_hold_count index = fingers.get("index", False) middle = fingers.get("middle", False) ring = fingers.get("ring", False) pinky = fingers.get("pinky", False) # Thumb is noisy in webcam/selfie views, so we ignore thumb for FIRST/LAST. only_index = index and (not middle) and (not ring) and (not pinky) only_pinky = pinky and (not index) and (not middle) and (not ring) if only_index: _first_hold_count += 1 _last_hold_count = 0 if _first_hold_count >= FINGER_CONFIRM_FRAMES: _first_hold_count = 0 return FIRST return NONE if only_pinky: _last_hold_count += 1 _first_hold_count = 0 if _last_hold_count >= FINGER_CONFIRM_FRAMES: _last_hold_count = 0 return LAST return NONE _first_hold_count = 0 _last_hold_count = 0 return NONE def infer_gesture_command(hand_data) -> str: """ Infer command from tracked hand data. Command priority: 1) Finger-only gestures (FIRST/LAST) 2) Swipe gestures (NEXT/PREV) 3) NONE """ now_ts = time.time() if _in_cooldown(now_ts): return NONE if not hand_data or not hand_data.get("hand_found", False): _x_history.clear() return NONE fingers = hand_data.get("fingers_extended", {}) finger_command = _finger_only_command(fingers) if finger_command != NONE: return _register_command(finger_command, now_ts) center_x = hand_data.get("hand_center_x") if center_x is None: return NONE _x_history.append(float(center_x)) if len(_x_history) < 3: return NONE delta_x = _x_history[-1] - _x_history[0] if delta_x >= SWIPE_DELTA_THRESHOLD: return _register_command(NEXT, now_ts) if delta_x <= -SWIPE_DELTA_THRESHOLD: return _register_command(PREV, now_ts) return NONE def is_valid_command(command: str) -> bool: return command in VALID_COMMANDS