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 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()
|
||||||
|
|||||||
Reference in New Issue
Block a user