Files
moonlight-drive-in/validate_videos.py
jessikitty 05a8bd237b Upload files to "/"
Add core Python modules
2025-12-09 16:49:13 +11:00

399 lines
14 KiB
Python

# validate_videos.py
"""
Video Collection Validator
Checks NFC mappings against actual video files and provides detailed reports
"""
import sys
from pathlib import Path
from datetime import datetime
import logging
# Setup basic logging
logging.basicConfig(
level=logging.INFO,
format='%(levelname)s: %(message)s'
)
class VideoValidator:
def __init__(self):
self.base_dir = Path(__file__).parent
self.videos_dir = self.base_dir / "videos"
self.trailers_dir = self.videos_dir / "trailers"
self.specific_dir = self.videos_dir / "specific"
self.mapping_file = self.videos_dir / "key_mapping.txt"
self.supported_formats = ['.mp4', '.avi', '.mov', '.mkv', '.wmv', '.flv', '.webm', '.m4v']
self.results = {
'valid_mappings': [],
'missing_files': [],
'unmapped_videos': [],
'trailer_count': 0,
'specific_count': 0
}
def print_header(self):
"""Print validation header"""
print("\n" + "="*70)
print("VIDEO COLLECTION VALIDATOR")
print("="*70)
print(f"Validation Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print("="*70 + "\n")
def check_directories(self):
"""Check if required directories exist"""
print("Checking directories...")
print("-" * 70)
all_exist = True
dirs = [
("Videos", self.videos_dir),
("Trailers", self.trailers_dir),
("Specific Videos", self.specific_dir)
]
for name, path in dirs:
if path.exists():
print(f"{name} directory found: {path}")
else:
print(f"{name} directory MISSING: {path}")
all_exist = False
print()
return all_exist
def scan_video_files(self, directory, category):
"""Scan a directory for video files"""
if not directory.exists():
return []
videos = []
for file_path in directory.iterdir():
if file_path.is_file() and file_path.suffix.lower() in self.supported_formats:
size_mb = file_path.stat().st_size / (1024 * 1024)
videos.append({
'path': file_path,
'name': file_path.name,
'stem': file_path.stem,
'size_mb': size_mb
})
return videos
def load_nfc_mappings(self):
"""Load and parse NFC mappings"""
if not self.mapping_file.exists():
print(f"✗ Key mapping file not found: {self.mapping_file}")
return {}
mappings = {}
line_num = 0
try:
with open(self.mapping_file, 'r', encoding='utf-8') as f:
for line in f:
line_num += 1
line = line.strip()
# Skip comments and empty lines
if not line or line.startswith('#'):
continue
if ',' not in line:
print(f"⚠ Warning: Invalid format at line {line_num}: {line}")
continue
parts = line.split(',', 1)
if len(parts) != 2:
continue
nfc_id = parts[0].strip()
movie_name = parts[1].strip()
if nfc_id and movie_name:
mappings[nfc_id] = movie_name
except Exception as e:
print(f"✗ Error reading mapping file: {e}")
return {}
return mappings
def find_video_match(self, movie_name, video_files):
"""Find matching video file for a movie name"""
movie_lower = movie_name.lower()
# Strategy 1: Exact match (stem)
for video in video_files:
if video['stem'].lower() == movie_lower:
return video, 'exact'
# Strategy 2: Movie name in filename
for video in video_files:
if movie_lower in video['stem'].lower():
return video, 'substring'
# Strategy 3: Filename in movie name (reverse check)
for video in video_files:
if video['stem'].lower() in movie_lower:
return video, 'reverse'
return None, None
def validate_mappings(self):
"""Validate NFC mappings against video files"""
print("Validating NFC mappings...")
print("-" * 70)
# Load mappings
mappings = self.load_nfc_mappings()
if not mappings:
print("✗ No valid mappings found in key_mapping.txt")
print(" Create mappings in format: NFC_ID,Movie_Name")
print()
return
print(f"Found {len(mappings)} NFC mappings to validate")
print()
# Scan specific video files
specific_videos = self.scan_video_files(self.specific_dir, "specific")
self.results['specific_count'] = len(specific_videos)
print(f"Found {len(specific_videos)} video files in specific folder")
print()
# Check each mapping
print("Checking mappings:")
print()
for nfc_id, movie_name in mappings.items():
match, match_type = self.find_video_match(movie_name, specific_videos)
if match:
self.results['valid_mappings'].append({
'nfc_id': nfc_id,
'movie_name': movie_name,
'file': match['name'],
'size_mb': match['size_mb'],
'match_type': match_type
})
print(f"{nfc_id}'{movie_name}'")
print(f" Matched: {match['name']} ({match['size_mb']:.1f} MB) [{match_type}]")
else:
self.results['missing_files'].append({
'nfc_id': nfc_id,
'movie_name': movie_name
})
print(f"{nfc_id}'{movie_name}'")
print(f" ERROR: No matching video file found!")
print()
def check_unmapped_videos(self):
"""Find videos that don't have NFC mappings"""
print("Checking for unmapped videos...")
print("-" * 70)
mappings = self.load_nfc_mappings()
specific_videos = self.scan_video_files(self.specific_dir, "specific")
# Get list of mapped movie names (lowercase for comparison)
mapped_names = set(name.lower() for name in mappings.values())
unmapped = []
for video in specific_videos:
# Check if this video matches any mapping
video_matched = False
for movie_name in mappings.values():
if (movie_name.lower() in video['stem'].lower() or
video['stem'].lower() in movie_name.lower()):
video_matched = True
break
if not video_matched:
unmapped.append(video)
self.results['unmapped_videos'].append(video)
if unmapped:
print(f"Found {len(unmapped)} unmapped videos:")
print()
for video in unmapped:
print(f"{video['name']} ({video['size_mb']:.1f} MB)")
print()
print("Consider adding NFC mappings for these videos!")
else:
print("✓ All specific videos have NFC mappings")
print()
def check_trailers(self):
"""Check trailer video collection"""
print("Checking trailer collection...")
print("-" * 70)
trailers = self.scan_video_files(self.trailers_dir, "trailers")
self.results['trailer_count'] = len(trailers)
if len(trailers) == 0:
print("✗ No trailer videos found!")
print(" Add trailer videos to: " + str(self.trailers_dir))
elif len(trailers) < 5:
print(f"⚠ Only {len(trailers)} trailers found")
print(" Consider adding more trailers for variety")
print()
for trailer in trailers:
print(f"{trailer['name']} ({trailer['size_mb']:.1f} MB)")
else:
print(f"✓ Found {len(trailers)} trailer videos")
total_size = sum(t['size_mb'] for t in trailers)
print(f" Total size: {total_size:.1f} MB")
print()
print("Sample trailers:")
for trailer in trailers[:5]:
print(f"{trailer['name']} ({trailer['size_mb']:.1f} MB)")
if len(trailers) > 5:
print(f" ... and {len(trailers) - 5} more")
print()
def print_summary(self):
"""Print validation summary"""
print("\n" + "="*70)
print("VALIDATION SUMMARY")
print("="*70)
valid_count = len(self.results['valid_mappings'])
missing_count = len(self.results['missing_files'])
unmapped_count = len(self.results['unmapped_videos'])
trailer_count = self.results['trailer_count']
specific_count = self.results['specific_count']
print(f"\nTrailer Videos: {trailer_count}")
print(f"Specific Videos: {specific_count}")
print(f"Valid NFC Mappings: {valid_count}")
print(f"Missing Files: {missing_count}")
print(f"Unmapped Videos: {unmapped_count}")
# Overall status
print("\n" + "-"*70)
if missing_count == 0 and trailer_count > 0:
print("✓ VALIDATION PASSED - System ready!")
elif missing_count == 0 and trailer_count == 0:
print("⚠ WARNING - No trailer videos found")
else:
print("✗ VALIDATION FAILED - Fix missing files")
print("-"*70)
# Recommendations
print("\nRECOMMENDATIONS:")
if missing_count > 0:
print(f" • Fix {missing_count} missing video file(s)")
if trailer_count == 0:
print(" • Add trailer videos to trailers folder")
elif trailer_count < 5:
print(f" • Add more trailers (currently only {trailer_count})")
if unmapped_count > 0:
print(f" • Add NFC mappings for {unmapped_count} unmapped video(s)")
if missing_count == 0 and trailer_count >= 5 and unmapped_count == 0:
print(" • Everything looks good! Your system is ready to use.")
print()
def save_report(self):
"""Save detailed report to file"""
report_file = self.base_dir / f"validation_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"
try:
with open(report_file, 'w', encoding='utf-8') as f:
f.write("VIDEO COLLECTION VALIDATION REPORT\n")
f.write("="*70 + "\n")
f.write(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write("="*70 + "\n\n")
f.write("VALID MAPPINGS:\n")
f.write("-"*70 + "\n")
for item in self.results['valid_mappings']:
f.write(f"NFC: {item['nfc_id']}\n")
f.write(f" Mapping: '{item['movie_name']}'\n")
f.write(f" File: {item['file']}\n")
f.write(f" Size: {item['size_mb']:.1f} MB\n")
f.write(f" Match: {item['match_type']}\n\n")
if self.results['missing_files']:
f.write("\nMISSING FILES:\n")
f.write("-"*70 + "\n")
for item in self.results['missing_files']:
f.write(f"NFC: {item['nfc_id']}\n")
f.write(f" Looking for: '{item['movie_name']}'\n")
f.write(f" Status: FILE NOT FOUND\n\n")
if self.results['unmapped_videos']:
f.write("\nUNMAPPED VIDEOS:\n")
f.write("-"*70 + "\n")
for video in self.results['unmapped_videos']:
f.write(f"File: {video['name']}\n")
f.write(f" Size: {video['size_mb']:.1f} MB\n")
f.write(f" Status: No NFC mapping\n\n")
print(f"Detailed report saved to: {report_file}")
except Exception as e:
print(f"Warning: Could not save report file: {e}")
def run(self):
"""Run complete validation"""
self.print_header()
# Check directories
if not self.check_directories():
print("\n✗ Required directories are missing!")
print(" Please run setup or create the required directories.")
return 1
# Check trailers
self.check_trailers()
# Validate mappings
self.validate_mappings()
# Check unmapped videos
self.check_unmapped_videos()
# Print summary
self.print_summary()
# Save report
self.save_report()
# Return exit code
if len(self.results['missing_files']) > 0:
return 1
return 0
def main():
"""Main entry point"""
try:
validator = VideoValidator()
exit_code = validator.run()
print("\n" + "="*70)
print("Press any key to exit...")
input()
return exit_code
except KeyboardInterrupt:
print("\n\nValidation cancelled by user")
return 1
except Exception as e:
print(f"\n✗ Validation error: {e}")
logging.exception("Validation failed")
return 1
if __name__ == "__main__":
sys.exit(main())