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