#!/usr/bin/python3 import time from datetime import datetime from pathlib import Path import numpy as np from picamera2 import Picamera2 from picamera2.encoders import H264Encoder from picamera2.outputs import CircularOutput2, PyavOutput VIDEO_DIR = Path.home() / "videos" VIDEO_DIR.mkdir(parents=True, exist_ok=True) MAIN_SIZE = (1920, 1080) LORES_SIZE = (320, 240) BITRATE = 15_000_000 FRAMERATE = 30 # Seconds retained before motion is detected. PREBUFFER_SECONDS = 5 # Stop recording this many seconds after the last detected movement. STOP_DELAY_SECONDS = 15 # Process roughly five motion-detection frames per second. DETECTION_INTERVAL = 0.20 # Increase this if IR grain causes false detections. MOTION_THRESHOLD = 8.0 picam2 = Picamera2() configuration = picam2.create_video_configuration( main={ "size": MAIN_SIZE, "format": "YUV420", }, lores={ "size": LORES_SIZE, "format": "YUV420", }, controls={ "FrameRate": FRAMERATE, }, ) picam2.configure(configuration) encoder = H264Encoder( bitrate=BITRATE, repeat=True, ) output = CircularOutput2( buffer_duration_ms=PREBUFFER_SECONDS * 1000 ) picam2.start_recording(encoder, output) width, height = LORES_SIZE previous = None recording = False last_motion_time = 0.0 print("Motion detection active.") print(f"Videos: {VIDEO_DIR}") print("Press Ctrl+C to stop.") try: while True: # YUV420 begins with the full-resolution grayscale Y plane. current = picam2.capture_array("lores")[:height, :width] if previous is not None: # Cast before subtraction to avoid unsigned-byte wraparound. difference = current.astype(np.int16) - previous.astype(np.int16) mse = float(np.square(difference).mean()) if mse > MOTION_THRESHOLD: last_motion_time = time.monotonic() if not recording: timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") filename = VIDEO_DIR / f"motion_{timestamp}.mp4" output.open_output(PyavOutput(str(filename))) recording = True print(f"Motion detected ({mse:.1f}): {filename.name}") elif ( recording and time.monotonic() - last_motion_time > STOP_DELAY_SECONDS ): output.close_output() recording = False print("Motion ended: recording closed.") previous = current time.sleep(DETECTION_INTERVAL) except KeyboardInterrupt: print("\nStopping...") finally: if recording: output.close_output() picam2.stop_recording() print("Stopped cleanly.")