Files
rpi-tulivision/setup.py
2025-09-25 14:36:17 +02:00

675 lines
23 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Video Player Setup and Configuration Script
Interactive setup for configuring the video player system
"""
import os
import sys
import json
import time
import subprocess
from pathlib import Path
from typing import Dict, List, Optional
import logging
class VideoPlayerSetup:
"""Interactive setup for Video Player system"""
def __init__(self):
self.config_dir = Path("/etc/video_player")
self.install_dir = Path("/opt/video_player")
self.video_folder = Path("/home/pi/Videos")
self.logger = self.setup_logging()
def setup_logging(self):
"""Setup logging for setup script"""
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
return logging.getLogger(__name__)
def print_header(self, title: str):
"""Print formatted header"""
print("\n" + "="*60)
print(f" {title}")
print("="*60)
def print_section(self, title: str):
"""Print formatted section header"""
print(f"\n--- {title} ---")
def get_user_input(self, prompt: str, default: str = None, required: bool = True) -> str:
"""Get user input with optional default value"""
if default:
full_prompt = f"{prompt} [{default}]: "
else:
full_prompt = f"{prompt}: "
while True:
response = input(full_prompt).strip()
if response:
return response
elif default:
return default
elif not required:
return ""
else:
print("This field is required. Please enter a value.")
def get_yes_no(self, prompt: str, default: bool = True) -> bool:
"""Get yes/no input from user"""
default_text = "Y/n" if default else "y/N"
response = input(f"{prompt} [{default_text}]: ").strip().lower()
if not response:
return default
return response in ['y', 'yes', '1', 'true']
def get_number(self, prompt: str, min_val: int = None, max_val: int = None, default: int = None) -> int:
"""Get number input from user with validation"""
while True:
try:
if default is not None:
response = input(f"{prompt} [{default}]: ").strip()
if not response:
return default
else:
response = input(f"{prompt}: ").strip()
number = int(response)
if min_val is not None and number < min_val:
print(f"Value must be at least {min_val}")
continue
if max_val is not None and number > max_val:
print(f"Value must be at most {max_val}")
continue
return number
except ValueError:
print("Please enter a valid number.")
def check_system_requirements(self) -> bool:
"""Check if system meets requirements"""
self.print_section("System Requirements Check")
# Check if running on Raspberry Pi
try:
with open('/proc/cpuinfo', 'r') as f:
cpuinfo = f.read()
if 'Raspberry Pi' not in cpuinfo:
print("⚠️ Warning: This system may not be a Raspberry Pi")
if not self.get_yes_no("Continue anyway?", False):
return False
except:
print("⚠️ Warning: Could not read CPU info")
# Check if running as root
if os.geteuid() != 0:
print("❌ Error: This script must be run as root (use sudo)")
return False
# Check required directories
required_dirs = [self.config_dir, self.install_dir]
for directory in required_dirs:
if not directory.exists():
print(f"❌ Error: Required directory not found: {directory}")
return False
# Check required files
required_files = [
self.install_dir / "video_player.py",
self.install_dir / "ir_remote.py",
self.install_dir / "config_manager.py"
]
for file_path in required_files:
if not file_path.exists():
print(f"❌ Error: Required file not found: {file_path}")
return False
print("✅ System requirements check passed")
return True
def configure_video_settings(self) -> Dict:
"""Configure video playback settings"""
self.print_section("Video Playback Configuration")
config = {}
# Video folder
print(f"Current video folder: {self.video_folder}")
if not self.get_yes_no("Use default video folder?", True):
config['video_folder'] = self.get_user_input("Enter video folder path")
else:
config['video_folder'] = str(self.video_folder)
# Default channel
config['default_channel'] = self.get_number(
"Default channel number to start on",
min_val=1,
default=1
)
# Auto play
config['auto_play'] = self.get_yes_no("Auto-play video on startup?", True)
# Display settings
config['fullscreen'] = self.get_yes_no("Start in fullscreen mode?", True)
if not config['fullscreen']:
config['window_width'] = self.get_number(
"Window width",
min_val=640,
default=1920
)
config['window_height'] = self.get_number(
"Window height",
min_val=480,
default=1080
)
config['hide_cursor'] = self.get_yes_no("Hide mouse cursor?", True)
return config
def configure_audio_settings(self) -> Dict:
"""Configure audio settings"""
self.print_section("Audio Configuration")
config = {}
# Audio device
config['audio_device'] = self.get_user_input(
"Audio device (default for system default)",
default="default"
)
# Volume
config['volume'] = self.get_number(
"Default volume (0-100)",
min_val=0,
max_val=100,
default=50
)
# Mute on start
config['mute_on_start'] = self.get_yes_no("Start muted?", False)
return config
def configure_channel_settings(self) -> Dict:
"""Configure channel system settings"""
self.print_section("Channel System Configuration")
config = {}
# Channel timeout
config['channel_timeout'] = self.get_number(
"Channel display timeout (seconds)",
min_val=1,
max_val=10,
default=3
)
# Multi-digit timeout
config['multi_digit_timeout'] = self.get_number(
"Multi-digit channel input timeout (seconds)",
min_val=1,
max_val=5,
default=1
)
# Channel assignment method
print("Channel assignment methods:")
print("1. alphabetical - Sort videos alphabetically")
print("2. manual - Manual channel assignment")
print("3. custom - Custom order from file")
method_choice = self.get_number(
"Choose assignment method (1-3)",
min_val=1,
max_val=3,
default=1
)
methods = {1: "alphabetical", 2: "manual", 3: "custom"}
config['channel_assignment_method'] = methods[method_choice]
return config
def configure_ir_settings(self) -> Dict:
"""Configure IR remote settings"""
self.print_section("IR Remote Configuration")
config = {}
# GPIO pin
config['ir_pin'] = self.get_number(
"GPIO pin for IR receiver (1-40)",
min_val=1,
max_val=40,
default=18
)
# IR protocols
print("Supported IR protocols:")
print("1. NEC (most common)")
print("2. RC5")
print("3. Both NEC and RC5")
protocol_choice = self.get_number(
"Choose IR protocol support (1-3)",
min_val=1,
max_val=3,
default=1
)
protocols = {
1: ["NEC"],
2: ["RC5"],
3: ["NEC", "RC5"]
}
config['ir_protocols'] = protocols[protocol_choice]
# IR repeat delay
config['ir_repeat_delay'] = self.get_number(
"IR repeat delay (seconds)",
min_val=1,
max_val=10,
default=1
) / 10.0 # Convert to decimal
return config
def configure_vlc_settings(self) -> Dict:
"""Configure VLC player settings"""
self.print_section("VLC Player Configuration")
config = {}
# VLC options
vlc_options = [
"--fullscreen",
"--no-video-title-show",
"--no-audio-display",
"--no-osd",
"--quiet"
]
print("Default VLC options:")
for i, option in enumerate(vlc_options, 1):
print(f"{i}. {option}")
if self.get_yes_no("Use default VLC options?", True):
config['vlc_options'] = vlc_options
else:
print("Enter custom VLC options (one per line, empty line to finish):")
custom_options = []
while True:
option = input("VLC option: ").strip()
if not option:
break
custom_options.append(option)
config['vlc_options'] = custom_options if custom_options else vlc_options
return config
def configure_logging_settings(self) -> Dict:
"""Configure logging settings"""
self.print_section("Logging Configuration")
config = {}
# Log level
print("Log levels:")
print("1. DEBUG - Detailed information")
print("2. INFO - General information")
print("3. WARNING - Warning messages")
print("4. ERROR - Error messages only")
level_choice = self.get_number(
"Choose log level (1-4)",
min_val=1,
max_val=4,
default=2
)
levels = {1: "DEBUG", 2: "INFO", 3: "WARNING", 4: "ERROR"}
config['log_level'] = levels[level_choice]
# Log file
config['log_file'] = self.get_user_input(
"Log file path",
default="/var/log/video_player.log"
)
# Log rotation
config['log_max_size'] = self.get_number(
"Maximum log file size (MB)",
min_val=1,
max_val=100,
default=10
) * 1024 * 1024 # Convert to bytes
config['log_backup_count'] = self.get_number(
"Number of log backup files",
min_val=1,
max_val=10,
default=5
)
return config
def configure_system_settings(self) -> Dict:
"""Configure system settings"""
self.print_section("System Configuration")
config = {}
# Check interval
config['check_interval'] = self.get_number(
"System check interval (seconds)",
min_val=1,
max_val=10,
default=1
)
# Restart on crash
config['restart_on_crash'] = self.get_yes_no("Restart on crash?", True)
if config['restart_on_crash']:
config['max_restart_attempts'] = self.get_number(
"Maximum restart attempts",
min_val=1,
max_val=10,
default=3
)
return config
def scan_video_files(self) -> List[Dict]:
"""Scan for video files in the configured folder"""
self.print_section("Video File Scanning")
video_folder = Path(self.config.get('video_folder', self.video_folder))
video_extensions = {'.mp4', '.avi', '.mkv', '.mov', '.wmv', '.flv', '.webm', '.m4v'}
if not video_folder.exists():
print(f"❌ Video folder does not exist: {video_folder}")
return []
video_files = []
for file_path in video_folder.rglob('*'):
if file_path.is_file() and file_path.suffix.lower() in video_extensions:
video_files.append({
'path': str(file_path),
'name': file_path.stem,
'size': file_path.stat().st_size
})
video_files.sort(key=lambda x: x['name'])
print(f"Found {len(video_files)} video files:")
for i, video in enumerate(video_files, 1):
size_mb = video['size'] / (1024 * 1024)
print(f"{i:2d}. {video['name']} ({size_mb:.1f} MB)")
return video_files
def create_channel_mapping(self, video_files: List[Dict]) -> Dict:
"""Create channel mapping from video files"""
self.print_section("Channel Mapping")
channels = {}
assignment_method = self.config.get('channel_assignment_method', 'alphabetical')
if assignment_method == 'alphabetical':
# Auto-assign channels alphabetically
for i, video in enumerate(video_files, 1):
channels[i] = {
'number': i,
'name': video['name'],
'path': video['path'],
'description': f"Auto-assigned channel {i}",
'category': 'general',
'enabled': True,
'priority': 0
}
elif assignment_method == 'manual':
# Manual channel assignment
print("Manual channel assignment:")
for video in video_files:
print(f"\nVideo: {video['name']}")
channel_num = self.get_number(
f"Assign to channel number (0 to skip)",
min_val=0,
max_val=999
)
if channel_num > 0:
channels[channel_num] = {
'number': channel_num,
'name': video['name'],
'path': video['path'],
'description': f"Manually assigned channel {channel_num}",
'category': 'general',
'enabled': True,
'priority': 0
}
else: # custom
# Load from existing file or create new
channels_file = self.config_dir / "channels.json"
if channels_file.exists():
with open(channels_file, 'r') as f:
existing_channels = json.load(f)
for channel_num, channel_info in existing_channels.items():
channels[int(channel_num)] = channel_info
print("Loaded existing channel mapping")
else:
print("No existing channel mapping found, using alphabetical assignment")
for i, video in enumerate(video_files, 1):
channels[i] = {
'number': i,
'name': video['name'],
'path': video['path'],
'description': f"Auto-assigned channel {i}",
'category': 'general',
'enabled': True,
'priority': 0
}
print(f"\nCreated {len(channels)} channels")
return channels
def save_configuration(self):
"""Save all configuration to files"""
self.print_section("Saving Configuration")
try:
# Save main configuration
main_config_file = self.config_dir / "config.json"
with open(main_config_file, 'w') as f:
json.dump(self.config, f, indent=2)
print(f"✅ Main configuration saved to {main_config_file}")
# Save channel configuration
if hasattr(self, 'channels'):
channels_file = self.config_dir / "channels.json"
channels_data = {str(k): v for k, v in self.channels.items()}
with open(channels_file, 'w') as f:
json.dump(channels_data, f, indent=2)
print(f"✅ Channel configuration saved to {channels_file}")
# Set proper permissions
os.chmod(main_config_file, 0o644)
if hasattr(self, 'channels'):
os.chmod(channels_file, 0o644)
print("✅ Configuration saved successfully")
except Exception as e:
print(f"❌ Error saving configuration: {e}")
return False
return True
def test_configuration(self) -> bool:
"""Test the configuration"""
self.print_section("Configuration Test")
try:
# Test video folder access
video_folder = Path(self.config.get('video_folder', self.video_folder))
if not video_folder.exists():
print(f"❌ Video folder does not exist: {video_folder}")
return False
print(f"✅ Video folder accessible: {video_folder}")
# Test GPIO pin
ir_pin = self.config.get('ir_pin', 18)
if not (1 <= ir_pin <= 40):
print(f"❌ Invalid GPIO pin: {ir_pin}")
return False
print(f"✅ GPIO pin valid: {ir_pin}")
# Test VLC
try:
result = subprocess.run(['vlc', '--version'],
capture_output=True, text=True, timeout=5)
if result.returncode == 0:
print("✅ VLC media player available")
else:
print("⚠️ VLC media player may not be properly installed")
except:
print("⚠️ Could not test VLC media player")
# Test Python modules
try:
import vlc
print("✅ python-vlc module available")
except ImportError:
print("❌ python-vlc module not available")
return False
try:
import RPi.GPIO as GPIO
print("✅ RPi.GPIO module available")
except ImportError:
print("❌ RPi.GPIO module not available")
return False
print("✅ Configuration test passed")
return True
except Exception as e:
print(f"❌ Configuration test failed: {e}")
return False
def run_setup(self):
"""Run the complete setup process"""
self.print_header("Video Player Setup")
print("Welcome to the Video Player setup wizard!")
print("This will help you configure the video player system.")
if not self.get_yes_no("Continue with setup?", True):
print("Setup cancelled")
return False
# Check system requirements
if not self.check_system_requirements():
print("❌ System requirements not met")
return False
# Collect configuration
self.config = {}
# Video settings
video_config = self.configure_video_settings()
self.config.update(video_config)
# Audio settings
audio_config = self.configure_audio_settings()
self.config.update(audio_config)
# Channel settings
channel_config = self.configure_channel_settings()
self.config.update(channel_config)
# IR settings
ir_config = self.configure_ir_settings()
self.config.update(ir_config)
# VLC settings
vlc_config = self.configure_vlc_settings()
self.config.update(vlc_config)
# Logging settings
logging_config = self.configure_logging_settings()
self.config.update(logging_config)
# System settings
system_config = self.configure_system_settings()
self.config.update(system_config)
# Scan video files
video_files = self.scan_video_files()
if video_files:
# Create channel mapping
self.channels = self.create_channel_mapping(video_files)
else:
print("⚠️ No video files found. You can add them later.")
self.channels = {}
# Test configuration
if not self.test_configuration():
print("❌ Configuration test failed")
if not self.get_yes_no("Continue anyway?", False):
return False
# Save configuration
if not self.save_configuration():
print("❌ Failed to save configuration")
return False
# Final summary
self.print_section("Setup Complete")
print("✅ Video Player setup completed successfully!")
print(f"📁 Video folder: {self.config.get('video_folder')}")
print(f"📺 Default channel: {self.config.get('default_channel')}")
print(f"🔧 IR pin: {self.config.get('ir_pin')}")
print(f"📊 Channels configured: {len(self.channels)}")
print(f"📝 Log level: {self.config.get('log_level')}")
print("\nNext steps:")
print("1. Add video files to your video folder")
print("2. Configure IR remote codes if needed")
print("3. Start the service: video-player-start")
print("4. Check status: video-player-status")
return True
def main():
"""Main entry point"""
if len(sys.argv) > 1 and sys.argv[1] == '--help':
print("Video Player Setup Script")
print("Usage: python3 setup.py")
print("This script will guide you through configuring the video player system.")
return
setup = VideoPlayerSetup()
success = setup.run_setup()
if success:
print("\n🎉 Setup completed successfully!")
sys.exit(0)
else:
print("\n❌ Setup failed!")
sys.exit(1)
if __name__ == "__main__":
main()