399 lines
14 KiB
Python
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())
|