Add configuration management module

This commit is contained in:
2025-12-09 16:37:00 +11:00
parent 065cc2407c
commit bab157e1d2

400
config.py Normal file
View File

@@ -0,0 +1,400 @@
# config.py - Enhanced with folder sequence support
"""
Enhanced Configuration with folder-based sequential playback
"""
import os
import json
import logging
from pathlib import Path
from datetime import datetime
class Config:
"""Enhanced configuration class with folder sequence support"""
# Directory paths
BASE_DIR = Path(__file__).parent
VIDEOS_DIR = BASE_DIR / "videos"
TRAILERS_DIR = VIDEOS_DIR / "trailers"
SPECIFIC_VIDEOS_DIR = VIDEOS_DIR / "specific"
GROUP_VIDEOS_DIR = VIDEOS_DIR / "groupvideos" # New folder for sequences
LOGS_DIR = BASE_DIR / "logs"
# Mapping files
KEY_MAPPING_FILE = VIDEOS_DIR / "key_mapping.txt"
FOLDER_MAPPING_FILE = VIDEOS_DIR / "folder_mapping.txt" # New mapping for folders
FOLDER_STATE_FILE = VIDEOS_DIR / "folder_state.json" # Track playback position
# Supported video formats
SUPPORTED_FORMATS = ['.mp4', '.avi', '.mov', '.mkv', '.wmv', '.flv', '.webm', '.m4v']
# Playback settings
MIN_PLAYBACK_TIME = 2.0
TRANSITION_DELAY = 0.5
SPECIFIC_VIDEO_MIN_PLAY_TIME = 5.0
# Video scaling settings
ENABLE_SCALING = True
MAINTAIN_ASPECT_RATIO = True
SCALE_FACTOR = 0
# Secondary monitor settings
PREFER_SECONDARY_MONITOR = True
SECONDARY_MONITOR_WIDTH = 1920
SECONDARY_MONITOR_HEIGHT = 1080
# Logging settings
LOG_LEVEL = logging.DEBUG
LOG_FORMAT = '%(asctime)s.%(msecs)03d [%(name)-15s] %(levelname)-8s: %(message)s'
LOG_DATE_FORMAT = '%H:%M:%S'
# Debug settings
DEBUG_LOG_MAX_LINES = 2000
DEBUG_UPDATE_INTERVAL = 100
# NFC settings
NFC_MIN_LENGTH = 4
NFC_MAX_LENGTH = 16
NFC_INPUT_TIMEOUT = 0.5
def __init__(self):
"""Initialize configuration with folder support"""
self.validate_settings()
self.setup_detailed_logging()
# Log system information
logger = logging.getLogger(__name__)
logger.info("=== SYSTEM CONFIGURATION ===")
logger.info(f"Base directory: {self.BASE_DIR}")
logger.info(f"Trailers directory: {self.TRAILERS_DIR}")
logger.info(f"Specific videos directory: {self.SPECIFIC_VIDEOS_DIR}")
logger.info(f"Group videos directory: {self.GROUP_VIDEOS_DIR}")
logger.info(f"Supported formats: {self.SUPPORTED_FORMATS}")
logger.info("=== CONFIGURATION COMPLETE ===")
def validate_settings(self):
"""Validate and create directories"""
directories = [
self.VIDEOS_DIR,
self.TRAILERS_DIR,
self.SPECIFIC_VIDEOS_DIR,
self.GROUP_VIDEOS_DIR,
self.LOGS_DIR
]
for directory in directories:
directory.mkdir(exist_ok=True, parents=True)
def setup_detailed_logging(self):
"""Setup comprehensive logging system"""
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
log_file = self.LOGS_DIR / f"video_player_{timestamp}.log"
# Clear any existing handlers
root_logger = logging.getLogger()
if root_logger.handlers:
for handler in root_logger.handlers:
root_logger.removeHandler(handler)
# Create file handler with explicit settings
file_handler = logging.FileHandler(log_file, encoding='utf-8', mode='w')
file_handler.setLevel(self.LOG_LEVEL)
file_handler.setFormatter(logging.Formatter(
fmt=self.LOG_FORMAT,
datefmt=self.LOG_DATE_FORMAT
))
# Create console handler
console_handler = logging.StreamHandler()
console_handler.setLevel(self.LOG_LEVEL)
console_handler.setFormatter(logging.Formatter(
fmt=self.LOG_FORMAT,
datefmt=self.LOG_DATE_FORMAT
))
# Configure root logger
root_logger.setLevel(self.LOG_LEVEL)
root_logger.addHandler(file_handler)
root_logger.addHandler(console_handler)
# Reduce noise from VLC
logging.getLogger('vlc').setLevel(logging.WARNING)
logging.getLogger('mpv').setLevel(logging.WARNING)
logger = logging.getLogger(__name__)
logger.info(f"Logging initialized - Log file: {log_file}")
logger.info(f"Log file location: {log_file.absolute()}")
def load_key_mapping(self):
"""Load NFC mappings for single files (legacy support)"""
logger = logging.getLogger(__name__)
key_map = {}
try:
if not self.KEY_MAPPING_FILE.exists():
logger.warning(f"Key mapping file not found: {self.KEY_MAPPING_FILE}")
self.create_example_mapping()
return {}
logger.info(f"Loading key mappings from: {self.KEY_MAPPING_FILE}")
with open(self.KEY_MAPPING_FILE, 'r', encoding='utf-8') as f:
for line_num, line in enumerate(f, 1):
line = line.strip()
if not line or line.startswith('#'):
continue
if ',' not in line:
continue
parts = line.split(',', 1)
if len(parts) != 2:
continue
nfc_id = parts[0].strip()
movie_name = parts[1].strip()
if not nfc_id or not movie_name:
continue
found_file = self.find_video_file(movie_name)
if found_file:
key_map[nfc_id] = str(found_file)
logger.debug(f"Mapped NFC {nfc_id} -> {found_file.name}")
else:
key_map[nfc_id] = f"Missing: {movie_name}"
logger.warning(f"Video file not found for '{movie_name}' (NFC: {nfc_id})")
logger.info(f"Loaded {len(key_map)} key mappings")
return key_map
except Exception as e:
logger.error(f"Error loading key mapping: {e}", exc_info=True)
return {}
def load_folder_mapping(self):
"""Load NFC mappings for folder sequences"""
logger = logging.getLogger(__name__)
folder_map = {}
try:
if not self.FOLDER_MAPPING_FILE.exists():
logger.info("Creating example folder mapping file")
self.create_example_folder_mapping()
return {}
logger.info(f"Loading folder mappings from: {self.FOLDER_MAPPING_FILE}")
with open(self.FOLDER_MAPPING_FILE, 'r', encoding='utf-8') as f:
for line_num, line in enumerate(f, 1):
line = line.strip()
if not line or line.startswith('#'):
continue
if ',' not in line:
continue
parts = line.split(',', 1)
if len(parts) != 2:
continue
nfc_id = parts[0].strip()
folder_name = parts[1].strip()
if not nfc_id or not folder_name:
continue
# Check if folder exists
folder_path = self.GROUP_VIDEOS_DIR / folder_name
if folder_path.exists() and folder_path.is_dir():
# Get all videos in folder, sorted
videos = self.get_sorted_videos_in_folder(folder_path)
if videos:
folder_map[nfc_id] = {
'folder_name': folder_name,
'folder_path': str(folder_path),
'videos': videos
}
logger.info(f"Mapped NFC {nfc_id} -> Folder '{folder_name}' ({len(videos)} videos)")
else:
logger.warning(f"No videos found in folder '{folder_name}' (NFC: {nfc_id})")
else:
logger.warning(f"Folder not found: '{folder_name}' (NFC: {nfc_id})")
logger.info(f"Loaded {len(folder_map)} folder mappings")
return folder_map
except Exception as e:
logger.error(f"Error loading folder mapping: {e}", exc_info=True)
return {}
def get_sorted_videos_in_folder(self, folder_path):
"""Get all videos in a folder, sorted naturally"""
videos = []
try:
for file_path in sorted(folder_path.iterdir()):
if file_path.is_file() and file_path.suffix.lower() in self.SUPPORTED_FORMATS:
videos.append(str(file_path))
except Exception as e:
logging.getLogger(__name__).error(f"Error reading folder {folder_path}: {e}")
return videos
def load_folder_state(self):
"""Load the current playback position for each folder"""
logger = logging.getLogger(__name__)
try:
if self.FOLDER_STATE_FILE.exists():
with open(self.FOLDER_STATE_FILE, 'r', encoding='utf-8') as f:
state = json.load(f)
logger.info(f"Loaded folder state: {len(state)} folders tracked")
return state
else:
logger.info("No folder state file found - starting fresh")
return {}
except Exception as e:
logger.error(f"Error loading folder state: {e}")
return {}
def save_folder_state(self, state):
"""Save the current playback position for each folder"""
logger = logging.getLogger(__name__)
try:
with open(self.FOLDER_STATE_FILE, 'w', encoding='utf-8') as f:
json.dump(state, f, indent=2)
logger.debug(f"Saved folder state: {len(state)} folders")
except Exception as e:
logger.error(f"Error saving folder state: {e}")
def reset_folder_state(self):
"""Reset all folder playback positions to the beginning"""
logger = logging.getLogger(__name__)
try:
if self.FOLDER_STATE_FILE.exists():
self.FOLDER_STATE_FILE.unlink()
logger.info("Folder state reset - all sequences will start from beginning")
return True
except Exception as e:
logger.error(f"Error resetting folder state: {e}")
return False
def find_video_file(self, movie_name):
"""Enhanced video file matching"""
for file_path in self.SPECIFIC_VIDEOS_DIR.iterdir():
if (file_path.is_file() and
file_path.suffix.lower() in self.SUPPORTED_FORMATS and
file_path.stem.lower() == movie_name.lower()):
return file_path
for file_path in self.SPECIFIC_VIDEOS_DIR.iterdir():
if (file_path.is_file() and
file_path.suffix.lower() in self.SUPPORTED_FORMATS and
movie_name.lower() in file_path.stem.lower()):
return file_path
return None
def create_example_mapping(self):
"""Create example key mapping file for single videos"""
try:
example_content = """# NFC Key Mapping File for Single Video Files
# Format: NFC_ID, Movie_Name
# These map to individual files in the 'specific' folder
#
# Examples:
# 12345678,avengers
# 87654321,spider-man
#
# For FOLDER sequences, use folder_mapping.txt instead!
"""
with open(self.KEY_MAPPING_FILE, 'w', encoding='utf-8') as f:
f.write(example_content)
except Exception as e:
logging.getLogger(__name__).error(f"Failed to create example mapping: {e}")
def create_example_folder_mapping(self):
"""Create example folder mapping file"""
logger = logging.getLogger(__name__)
try:
example_content = """# NFC Folder Mapping File for Sequential Video Playback
# Format: NFC_ID, Folder_Name
#
# Instructions:
# 1. Each line maps an NFC tag to a FOLDER of videos
# 2. Videos in the folder will play in sequential order
# 3. Each time you scan the tag, it plays the NEXT video in sequence
# 4. Folders must be in: videos/groupvideos/
#
# Example folder structure:
# videos/groupvideos/LOTR/video1.mp4
# videos/groupvideos/LOTR/video2.mp4
# videos/groupvideos/LOTR/video3.mp4
# videos/groupvideos/Avengers/video1.mp4
# videos/groupvideos/Avengers/video2.mp4
#
# Example mappings:
# 11111111,LOTR
# 22222222,Avengers
# 33333333,Jurassic
# 44444444,StarWars
#
# Tips:
# - Folder names are case-sensitive
# - Videos play in alphabetical order (name your files accordingly)
# - Use numbers/prefixes to control order: 01_intro.mp4, 02_main.mp4, etc.
# - Each scan advances to the next video, then loops back to start
#
# Your folder mappings below:
"""
with open(self.FOLDER_MAPPING_FILE, 'w', encoding='utf-8') as f:
f.write(example_content)
logger.info(f"Created example folder mapping file: {self.FOLDER_MAPPING_FILE}")
# Create example folder structure
example_folders = ['LOTR', 'Avengers', 'Jurassic']
for folder_name in example_folders:
folder_path = self.GROUP_VIDEOS_DIR / folder_name
folder_path.mkdir(exist_ok=True)
logger.info(f"Created example folder: {folder_path}")
except Exception as e:
logger.error(f"Failed to create example folder mapping: {e}")
def get_video_files(self, directory: Path):
"""Get video files with enhanced logging"""
logger = logging.getLogger(__name__)
if not directory.exists():
logger.warning(f"Directory does not exist: {directory}")
return []
video_files = []
for file_path in directory.iterdir():
if file_path.is_file() and file_path.suffix.lower() in self.SUPPORTED_FORMATS:
video_files.append(file_path)
return video_files
def get_display_settings(self):
"""Get display settings for video window"""
return {
'prefer_secondary': self.PREFER_SECONDARY_MONITOR,
'secondary_width': self.SECONDARY_MONITOR_WIDTH,
'secondary_height': self.SECONDARY_MONITOR_HEIGHT,
'enable_scaling': self.ENABLE_SCALING,
'maintain_aspect': self.MAINTAIN_ASPECT_RATIO,
'scale_factor': self.SCALE_FACTOR
}