import cv2 import numpy as np from PIL import Image from typing import List import os def extract_frames(video_path: str, num_frames: int = 10) -> List[Image.Image]: """ Extracts a fixed number of evenly spaced frames from a video file. Args: video_path: Path to the video file num_frames: Number of frames to extract Returns: List of PIL Images extracted from the video Raises: ValueError: If video cannot be opened or is invalid """ if not os.path.exists(video_path): raise FileNotFoundError(f"Video file not found: {video_path}") cap = cv2.VideoCapture(video_path) if not cap.isOpened(): raise ValueError(f"Could not open video file: {video_path}") try: total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) if total_frames <= 0: raise ValueError("Video contains no frames or duration cannot be determined") # If video has fewer frames than requested, take all available frames actual_num_frames = min(num_frames, total_frames) # Calculate frame indices to extract (evenly spaced) indices = np.linspace(0, total_frames - 1, actual_num_frames, dtype=int) frames = [] for idx in indices: # Set the frame position cap.set(cv2.CAP_PROP_POS_FRAMES, idx) ret, frame = cap.read() if ret: # OpenCV returns BGR, convert to RGB for PIL frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) pil_image = Image.fromarray(frame_rgb) frames.append(pil_image) else: print(f"[Warning] Failed to read frame {idx} from {video_path}") if not frames: raise ValueError("Failed to extract any valid frames from the video") return frames finally: cap.release()