Add configuration management module
This commit is contained in:
400
config.py
Normal file
400
config.py
Normal 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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user