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 queue
import subprocess
import argparse
import random
from pathlib import Path
from typing import Dict, List, Optional, Tuple
import vlc
@@ -244,6 +246,49 @@ class VideoPlayer:
self.logger.error(f"Error playing channel {channel_number}: {e}")
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):
"""Setup GPIO for IR receiver"""
try:
@@ -413,9 +458,15 @@ class VideoPlayer:
self.logger.info("Power toggle - shutting down")
self.running = False
def start(self):
"""Start the video player"""
self.logger.info("Starting video player...")
def start(self, startup_mode: str = "default", channel_number: int = None, video_index: int = None):
"""Start the video player with specified mode
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
# Initialize VLC
@@ -440,20 +491,32 @@ class VideoPlayer:
ir_thread = threading.Thread(target=self.process_ir_commands, daemon=True)
ir_thread.start()
# Play default channel
if self.default_channel in self.channels:
self.play_channel(self.default_channel)
# Play based on startup mode
success = False
if startup_mode == "random":
success = self.play_random_video()
elif startup_mode == "channel" and channel_number is not None:
success = self.play_channel(channel_number)
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)
if success:
self.logger.info("Video player started successfully")
else:
# Play first available channel
first_channel = min(self.channels.keys())
self.play_channel(first_channel)
self.logger.error("Failed to start video playback")
self.logger.info("Video player started successfully")
return True
return success
def run(self):
def run(self, startup_mode: str = "default", channel_number: int = None, video_index: int = None):
"""Main run loop"""
if not self.start():
if not self.start(startup_mode, channel_number, video_index):
return
try:
@@ -485,8 +548,97 @@ class VideoPlayer:
GPIO.cleanup()
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():
"""Main entry point"""
args = parse_arguments()
# Check if running as root (needed for GPIO access)
if os.geteuid() != 0:
print("This script must be run as root for GPIO access")
@@ -502,9 +654,37 @@ def main():
except (psutil.NoSuchProcess, psutil.AccessDenied):
continue
# Create and run video player
player = VideoPlayer()
player.run()
# Create video player
player = VideoPlayer(args.config)
# 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__":
main()