video control & random start

This commit is contained in:
2025-09-25 16:30:31 +02:00
parent 8e3462aaf2
commit e0898c2349
3 changed files with 351 additions and 17 deletions

View File

@@ -0,0 +1,39 @@
[Unit]
Description=Raspberry Pi Video Player with Random Video Startup
Documentation=https://github.com/your-repo/ulivision-tv
After=network.target sound.target graphical-session.target
Wants=graphical-session.target
[Service]
Type=simple
User=root
Group=root
WorkingDirectory=/opt/video_player
ExecStart=/opt/video_player/venv/bin/python /opt/video_player/video_player.py --random
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
SyslogIdentifier=video-player-random
# Environment variables
Environment=DISPLAY=:0
Environment=XAUTHORITY=/home/pi/.Xauthority
Environment=PYTHONPATH=/opt/video_player
# Security settings
NoNewPrivileges=false
PrivateTmp=false
ProtectSystem=false
ProtectHome=false
# Resource limits
LimitNOFILE=65536
MemoryMax=512M
# GPIO access
SupplementaryGroups=gpio
[Install]
WantedBy=multi-user.target

115
video_control.py Executable file
View File

@@ -0,0 +1,115 @@
#!/usr/bin/env python3
"""
Video Control Script
Simple command-line interface for controlling the video player
"""
import os
import sys
import argparse
import subprocess
from pathlib import Path
def run_video_player(args):
"""Run the video player with specified arguments"""
script_dir = Path(__file__).parent
video_player_script = script_dir / "video_player.py"
if not video_player_script.exists():
print(f"Error: video_player.py not found at {video_player_script}")
return False
# Build command
cmd = ["sudo", "python3", str(video_player_script)]
if args.random:
cmd.append("--random")
elif args.channel is not None:
cmd.extend(["--channel", str(args.channel)])
elif args.index is not None:
cmd.extend(["--index", str(args.index)])
elif args.list_channels:
cmd.append("--list-channels")
elif args.list_videos:
cmd.append("--list-videos")
if args.config:
cmd.extend(["--config", args.config])
try:
print(f"Running: {' '.join(cmd)}")
subprocess.run(cmd, check=True)
return True
except subprocess.CalledProcessError as e:
print(f"Error running video player: {e}")
return False
except KeyboardInterrupt:
print("\nInterrupted by user")
return False
def main():
"""Main entry point"""
parser = argparse.ArgumentParser(
description="Video Control - Simple interface for video player",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python3 video_control.py --random # Play random video
python3 video_control.py --channel 5 # Play channel 5
python3 video_control.py --index 2 # Play video at index 2
python3 video_control.py --list-channels # List channels
python3 video_control.py --list-videos # List videos
"""
)
# Mode arguments (mutually exclusive)
mode_group = parser.add_mutually_exclusive_group()
mode_group.add_argument(
'--random',
action='store_true',
help='Play a random video'
)
mode_group.add_argument(
'--channel',
type=int,
metavar='N',
help='Play specific channel number'
)
mode_group.add_argument(
'--index',
type=int,
metavar='N',
help='Play video at specific index (0-based)'
)
mode_group.add_argument(
'--list-channels',
action='store_true',
help='List available channels and exit'
)
mode_group.add_argument(
'--list-videos',
action='store_true',
help='List available videos with indices and exit'
)
# Configuration arguments
parser.add_argument(
'--config',
type=str,
help='Path to configuration file'
)
args = parser.parse_args()
# Check if at least one mode is specified
if not any([args.random, args.channel is not None, args.index is not None,
args.list_channels, args.list_videos]):
parser.print_help()
return
# Run video player
success = run_video_player(args)
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()

View File

@@ -12,6 +12,8 @@ import logging
import threading import threading
import queue import queue
import subprocess import subprocess
import argparse
import random
from pathlib import Path from pathlib import Path
from typing import Dict, List, Optional, Tuple from typing import Dict, List, Optional, Tuple
import vlc import vlc
@@ -244,6 +246,49 @@ class VideoPlayer:
self.logger.error(f"Error playing channel {channel_number}: {e}") self.logger.error(f"Error playing channel {channel_number}: {e}")
return False return False
def play_random_video(self) -> bool:
"""Play a random video from the available channels"""
if not self.channels:
self.logger.error("No channels available for random playback")
return False
try:
# Get a random channel number
random_channel = random.choice(list(self.channels.keys()))
self.logger.info(f"Selected random channel: {random_channel}")
return self.play_channel(random_channel)
except Exception as e:
self.logger.error(f"Error playing random video: {e}")
return False
def play_video_by_index(self, index: int) -> bool:
"""Play video by folder index (0-based)"""
video_files = self.scan_video_folder()
if not video_files:
self.logger.error("No video files found")
return False
if index < 0 or index >= len(video_files):
self.logger.warning(f"Index {index} out of range (0-{len(video_files)-1})")
return False
try:
video_file = video_files[index]
media = self.vlc_instance.media_new(str(video_file))
self.vlc_player.set_media(media)
self.vlc_player.play()
self.current_channel = None # Not a channel-based playback
self.logger.info(f"Playing video by index {index}: {video_file.name}")
# Wait for media to start playing
time.sleep(0.5)
return True
except Exception as e:
self.logger.error(f"Error playing video by index {index}: {e}")
return False
def setup_gpio(self): def setup_gpio(self):
"""Setup GPIO for IR receiver""" """Setup GPIO for IR receiver"""
try: try:
@@ -413,9 +458,15 @@ class VideoPlayer:
self.logger.info("Power toggle - shutting down") self.logger.info("Power toggle - shutting down")
self.running = False self.running = False
def start(self): def start(self, startup_mode: str = "default", channel_number: int = None, video_index: int = None):
"""Start the video player""" """Start the video player with specified mode
self.logger.info("Starting video player...")
Args:
startup_mode: "default", "random", "channel", or "index"
channel_number: Channel number to play (for channel mode)
video_index: Video index to play (for index mode)
"""
self.logger.info(f"Starting video player in {startup_mode} mode...")
self.running = True self.running = True
# Initialize VLC # Initialize VLC
@@ -440,20 +491,32 @@ class VideoPlayer:
ir_thread = threading.Thread(target=self.process_ir_commands, daemon=True) ir_thread = threading.Thread(target=self.process_ir_commands, daemon=True)
ir_thread.start() ir_thread.start()
# Play default channel # Play based on startup mode
if self.default_channel in self.channels: success = False
self.play_channel(self.default_channel) if startup_mode == "random":
else: success = self.play_random_video()
# Play first available channel elif startup_mode == "channel" and channel_number is not None:
first_channel = min(self.channels.keys()) success = self.play_channel(channel_number)
self.play_channel(first_channel) elif startup_mode == "index" and video_index is not None:
success = self.play_video_by_index(video_index)
else: # default mode
if self.default_channel in self.channels:
success = self.play_channel(self.default_channel)
else:
# Play first available channel
first_channel = min(self.channels.keys())
success = self.play_channel(first_channel)
self.logger.info("Video player started successfully") if success:
return True self.logger.info("Video player started successfully")
else:
self.logger.error("Failed to start video playback")
return success
def run(self): def run(self, startup_mode: str = "default", channel_number: int = None, video_index: int = None):
"""Main run loop""" """Main run loop"""
if not self.start(): if not self.start(startup_mode, channel_number, video_index):
return return
try: try:
@@ -485,8 +548,97 @@ class VideoPlayer:
GPIO.cleanup() GPIO.cleanup()
self.logger.info("Cleanup complete") self.logger.info("Cleanup complete")
def parse_arguments():
"""Parse command line arguments"""
parser = argparse.ArgumentParser(
description="Raspberry Pi Video Player with IR Remote Control",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python3 video_player.py # Start with default channel
python3 video_player.py --random # Start with random video
python3 video_player.py --channel 5 # Start with channel 5
python3 video_player.py --index 2 # Start with video at index 2 (0-based)
python3 video_player.py --list-channels # List available channels
python3 video_player.py --list-videos # List available videos with indices
"""
)
# Startup mode arguments (mutually exclusive)
mode_group = parser.add_mutually_exclusive_group()
mode_group.add_argument(
'--random',
action='store_true',
help='Start with a random video'
)
mode_group.add_argument(
'--channel',
type=int,
metavar='N',
help='Start with specific channel number'
)
mode_group.add_argument(
'--index',
type=int,
metavar='N',
help='Start with video at specific index (0-based)'
)
# Information arguments
parser.add_argument(
'--list-channels',
action='store_true',
help='List available channels and exit'
)
parser.add_argument(
'--list-videos',
action='store_true',
help='List available videos with indices and exit'
)
# Configuration arguments
parser.add_argument(
'--config',
type=str,
default='config.json',
help='Path to configuration file (default: config.json)'
)
return parser.parse_args()
def list_channels(player):
"""List available channels"""
if not player.channels:
print("No channels available")
return
print("Available channels:")
print("-" * 50)
for channel_num in sorted(player.channels.keys()):
channel = player.channels[channel_num]
print(f"Channel {channel_num}: {channel['name']}")
print(f" Path: {channel['path']}")
print()
def list_videos(player):
"""List available videos with indices"""
video_files = player.scan_video_folder()
if not video_files:
print("No video files found")
return
print("Available videos (with indices):")
print("-" * 50)
for i, video_file in enumerate(video_files):
print(f"Index {i}: {video_file.name}")
print(f" Path: {video_file}")
print()
def main(): def main():
"""Main entry point""" """Main entry point"""
args = parse_arguments()
# Check if running as root (needed for GPIO access) # Check if running as root (needed for GPIO access)
if os.geteuid() != 0: if os.geteuid() != 0:
print("This script must be run as root for GPIO access") print("This script must be run as root for GPIO access")
@@ -502,9 +654,37 @@ def main():
except (psutil.NoSuchProcess, psutil.AccessDenied): except (psutil.NoSuchProcess, psutil.AccessDenied):
continue continue
# Create and run video player # Create video player
player = VideoPlayer() player = VideoPlayer(args.config)
player.run()
# Handle list commands
if args.list_channels:
# Load channels first
if not player.load_channel_mapping():
player.create_channels()
list_channels(player)
return
if args.list_videos:
list_videos(player)
return
# Determine startup mode and parameters
startup_mode = "default"
channel_number = None
video_index = None
if args.random:
startup_mode = "random"
elif args.channel is not None:
startup_mode = "channel"
channel_number = args.channel
elif args.index is not None:
startup_mode = "index"
video_index = args.index
# Run video player
player.run(startup_mode, channel_number, video_index)
if __name__ == "__main__": if __name__ == "__main__":
main() main()