video control & random start
This commit is contained in:
39
video-player-random.service
Normal file
39
video-player-random.service
Normal 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
115
video_control.py
Executable 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()
|
||||
214
video_player.py
214
video_player.py
@@ -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)
|
||||
else:
|
||||
# Play first available channel
|
||||
first_channel = min(self.channels.keys())
|
||||
self.play_channel(first_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)
|
||||
|
||||
self.logger.info("Video player started successfully")
|
||||
return True
|
||||
if success:
|
||||
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"""
|
||||
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()
|
||||
|
||||
Reference in New Issue
Block a user