#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ ============================================================================= Skript pro ovládání "digitálního ducha" v zrcadle (Projekt Zrcadlo) ============================================================================= :Název skriptu: ghost_trigger.py :Autor: zrnek :Verze: 2.0.0 (Finální verze s IPC ovládáním) :Datum: 30. října 2025 :Popis: Tento skript běží na Raspberry Pi Zero 2 W a ovládá iluzi "ducha" pro Halloweenskou párty. Hlavní funkce: 1. Při startu analyzuje video soubory ve složce 'videos' a zjistí jejich délku. 2. Spustí přehrávač 'mpv' v režimu na pozadí, který zobrazuje černý obrázek ('black.png') a čeká na příkazy. 3. Monitoruje pohybový PIR senzor. 4. Při detekci pohybu: a. Okamžitě uzamkne senzor, aby se zabránilo opakovanému spuštění. b. Počká na krátké "zpoždění pro překvapení" (definované v config.yaml). c. Pošle 'mpv' přes 'socat' příkaz k plynulému přehrání náhodného videa. d. Po skončení videa pošle příkaz pro návrat k černé obrazovce. e. Udrží senzor zamčený po přesnou dobu trvání videa. f. Odemkne senzor a čeká na další pohyb. :Závislosti: - Python 3 - Knihovny: PyYAML (`python3-yaml`), gpiozero (`python3-gpiozero`) - Systémové nástroje: `mpv`, `socat`, `ffprobe` (z balíku `ffmpeg`) :Struktura projektu: /home/zrnek/duch/ ├── config/ │ └── config.yaml (Konfigurační soubor) ├── videos/ │ ├── video1.mp4 (Soubory s videi duchů) │ └── ... ├── black.png (Obrázek pro černé pozadí) └── ghost_trigger.py (Tento skript) :Spuštění: Skript je navržen pro automatické spuštění jako systémová služba prostřednictvím 'ghost-player.service'. """ import os import yaml import random import subprocess import time from gpiozero import MotionSensor from signal import pause # --- Načtení konfigurace --- CONFIG_PATH = os.path.join(os.path.dirname(__file__), "config/config.yaml") config = {} try: with open(CONFIG_PATH, 'r') as f: config = yaml.safe_load(f) except Exception: exit() PIR_PIN = config.get('pir_pin', 17) VIDEOS_DIR = os.path.join(os.path.dirname(__file__), config.get('videos_dir', 'videos')) BLACK_IMAGE_PATH = os.path.join(os.path.dirname(__file__), "black.png") VIDEO_EXTENSIONS = tuple(config.get('video_extensions', ['.mp4', '.mkv'])) MPV_SOCKET_PATH = "/tmp/mpvsocket" # <<< Načtení hodnot ze souboru config.yaml >>> SURPRISE_DELAY = config.get('surprise_delay', 1.5) SENSOR_LOCK_DURATION = config.get('sensor_lock_duration', 30) available_videos = [] is_playing = False # --- Funkce pro ovládání mpv --- def send_mpv_command(command_json): """Pošle JSON příkaz do běžícího mpv přes socat.""" full_command = f"echo '{command_json}' | socat - {MPV_SOCKET_PATH}" try: subprocess.run(full_command, shell=True, check=True, capture_output=True, text=True) except subprocess.CalledProcessError: pass # --- Hlavní logika --- def load_videos(): """Načte seznam video souborů.""" global available_videos try: files = os.listdir(VIDEOS_DIR) available_videos = [os.path.join(VIDEOS_DIR, f) for f in files if f.lower().endswith(VIDEO_EXTENSIONS)] except Exception: available_videos = [] def play_random_ghost(): """Zpracuje detekci pohybu, počká a přehraje video, pokud již jiné neběží.""" global is_playing if is_playing: print("Pohyb ignorován, video již běží.") return try: is_playing = True print(f"Pohyb detekován, čekám {SURPRISE_DELAY}s pro moment překvapení...") # <<< NOVINKA: Použití proměnné ze souboru config.yaml >>> time.sleep(SURPRISE_DELAY) print("Posílám příkaz do mpv...") load_videos() if not available_videos: return video_to_play = random.choice(available_videos) command1 = f'{{"command": ["loadfile", "{video_to_play}", "replace", "loop=no"]}}' send_mpv_command(command1) command2 = f'{{"command": ["loadfile", "{BLACK_IMAGE_PATH}", "append-play"]}}' send_mpv_command(command2) finally: print(f"Senzor bude znovu aktivní za {SENSOR_LOCK_DURATION}s.") # <<< NOVINKA: Použití proměnné ze souboru config.yaml >>> time.sleep(SENSOR_LOCK_DURATION) is_playing = False print("Senzor je opět aktivní.") # --- Hlavní spuštění --- os.system("pkill -f mpv") time.sleep(1) load_videos() subprocess.Popen([ "/usr/bin/mpv", "--no-audio", "--fullscreen", "--loop-file=inf", "--idle", f"--input-ipc-server={MPV_SOCKET_PATH}", BLACK_IMAGE_PATH ]) time.sleep(2) pir = MotionSensor(PIR_PIN) pir.when_motion = play_random_ghost print("Aplikace ducha běží, mpv je ovládáno na dálku. Čekám na pohyb...") try: pause() except KeyboardInterrupt: print("Ukončuji aplikaci.") os.system("pkill -f mpv")