# 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 }