Files
GigaMux/repair_mp4.py

239 lines
8.4 KiB
Python
Raw Normal View History

2025-10-28 22:19:22 -06:00
import os
import subprocess
from pathlib import Path
import json
import logging
from datetime import datetime
# ANSI color codes
class Colors:
PURPLE = '\033[95m'
BLUE = '\033[94m'
GREEN = '\033[92m'
YELLOW = '\033[93m'
RED = '\033[91m'
ENDC = '\033[0m'
# Set up logging
log_dir = "logs"
os.makedirs(log_dir, exist_ok=True)
log_file = os.path.join(log_dir, f"repair_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log")
logging.basicConfig(filename=log_file, level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
def check_file_health(input_file):
"""Check if MP4 file has issues using ffmpeg"""
print(f"\n{Colors.BLUE}Analyzing: {input_file}{Colors.ENDC}")
logging.info(f"Analyzing file: {input_file}")
cmd = [
'ffmpeg',
'-v', 'error',
'-i', str(input_file),
'-f', 'null',
'-'
]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.stderr:
print(f"{Colors.RED}Issues found:{Colors.ENDC}")
for line in result.stderr.split('\n')[:10]: # Show first 10 errors
if line.strip():
print(f" {Colors.YELLOW}{line}{Colors.ENDC}")
logging.warning(f"File has errors: {result.stderr[:500]}")
return False
else:
print(f"{Colors.GREEN}No errors detected{Colors.ENDC}")
logging.info("File appears healthy")
return True
def get_file_info(input_file):
"""Get basic file info"""
cmd = [
'ffprobe',
'-v', 'error',
'-show_entries', 'format=duration,size:stream=codec_name,width,height',
'-of', 'json',
input_file
]
result = subprocess.run(cmd, capture_output=True, text=True)
try:
return json.loads(result.stdout)
except:
return None
def repair_method_1_remux(input_file, output_file):
"""Method 1: Simple remux (copy streams to new container)"""
print(f"\n{Colors.BLUE}Trying Method 1: Remux (copy streams){Colors.ENDC}")
logging.info("Attempting repair method 1: Remux")
cmd = [
'ffmpeg',
'-v', 'warning',
'-i', str(input_file),
'-c', 'copy',
'-map', '0',
'-y',
str(output_file)
]
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
if result.returncode == 0 and Path(output_file).exists():
print(f"{Colors.GREEN}Method 1 successful!{Colors.ENDC}")
logging.info("Method 1 successful")
return True
else:
print(f"{Colors.RED}Method 1 failed{Colors.ENDC}")
logging.warning(f"Method 1 failed: {result.stderr[:200]}")
return False
except Exception as e:
print(f"{Colors.RED}Method 1 error: {e}{Colors.ENDC}")
logging.error(f"Method 1 error: {e}")
return False
def repair_method_2_error_concealment(input_file, output_file):
"""Method 2: Remux with error concealment"""
print(f"\n{Colors.BLUE}Trying Method 2: Error concealment{Colors.ENDC}")
logging.info("Attempting repair method 2: Error concealment")
cmd = [
'ffmpeg',
'-v', 'warning',
'-err_detect', 'ignore_err',
'-i', str(input_file),
'-c', 'copy',
'-map', '0',
'-fflags', '+genpts+igndts',
'-y',
str(output_file)
]
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
if result.returncode == 0 and Path(output_file).exists():
print(f"{Colors.GREEN}Method 2 successful!{Colors.ENDC}")
logging.info("Method 2 successful")
return True
else:
print(f"{Colors.RED}Method 2 failed{Colors.ENDC}")
logging.warning(f"Method 2 failed: {result.stderr[:200]}")
return False
except Exception as e:
print(f"{Colors.RED}Method 2 error: {e}{Colors.ENDC}")
logging.error(f"Method 2 error: {e}")
return False
def repair_method_3_force_keyframes(input_file, output_file):
"""Method 3: Force keyframe processing"""
print(f"\n{Colors.BLUE}Trying Method 3: Force keyframe processing{Colors.ENDC}")
logging.info("Attempting repair method 3: Force keyframe processing")
cmd = [
'ffmpeg',
'-v', 'warning',
'-fflags', '+discardcorrupt+genpts',
'-i', str(input_file),
'-c', 'copy',
'-map', '0',
'-avoid_negative_ts', 'make_zero',
'-y',
str(output_file)
]
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
if result.returncode == 0 and Path(output_file).exists():
print(f"{Colors.GREEN}Method 3 successful!{Colors.ENDC}")
logging.info("Method 3 successful")
return True
else:
print(f"{Colors.RED}Method 3 failed{Colors.ENDC}")
logging.warning(f"Method 3 failed: {result.stderr[:200]}")
return False
except Exception as e:
print(f"{Colors.RED}Method 3 error: {e}{Colors.ENDC}")
logging.error(f"Method 3 error: {e}")
return False
def repair_file(input_file, output_dir):
"""Attempt to repair a damaged MP4 file"""
input_path = Path(input_file)
output_path = Path(output_dir) / f"{input_path.stem}_repaired{input_path.suffix}"
print(f"\n{Colors.PURPLE}{'='*60}{Colors.ENDC}")
print(f"{Colors.PURPLE}Processing: {input_path.name}{Colors.ENDC}")
print(f"{Colors.PURPLE}{'='*60}{Colors.ENDC}")
# Skip if already repaired
if output_path.exists():
print(f"{Colors.YELLOW}Already repaired: {output_path.name}{Colors.ENDC}")
logging.info(f"Skipping {input_path} - already repaired")
return
# Check file health first
is_healthy = check_file_health(str(input_path))
if is_healthy:
print(f"{Colors.GREEN}File appears healthy, no repair needed{Colors.ENDC}")
logging.info("File is healthy, skipping")
return
# Try repair methods in sequence
methods = [
repair_method_1_remux,
repair_method_2_error_concealment,
repair_method_3_force_keyframes
]
for method in methods:
if method(str(input_path), str(output_path)):
# Verify the repaired file
print(f"\n{Colors.BLUE}Verifying repaired file...{Colors.ENDC}")
if check_file_health(str(output_path)):
print(f"{Colors.GREEN}✓ Repair successful and verified!{Colors.ENDC}")
logging.info(f"Successfully repaired: {output_path}")
# Show size comparison
original_size = input_path.stat().st_size / (1024*1024)
repaired_size = output_path.stat().st_size / (1024*1024)
print(f"Original: {original_size:.2f} MB → Repaired: {repaired_size:.2f} MB")
logging.info(f"Size: {original_size:.2f} MB → {repaired_size:.2f} MB")
return
else:
print(f"{Colors.YELLOW}Repair completed but file still has issues{Colors.ENDC}")
logging.warning("Repaired file still has issues")
return
print(f"{Colors.RED}All repair methods failed{Colors.ENDC}")
logging.error(f"Failed to repair: {input_path}")
if __name__ == "__main__":
input_dir = "input"
output_dir = "output"
os.makedirs(output_dir, exist_ok=True)
# Get list of MP4 files
files = [f for f in os.listdir(input_dir) if f.endswith(('.mp4', '.DVR.mp4'))]
total_files = len(files)
if total_files == 0:
print(f"{Colors.YELLOW}No MP4 files found in input directory{Colors.ENDC}")
logging.info("No files to process")
else:
print(f"\n{Colors.BLUE}Found {total_files} MP4 files to check{Colors.ENDC}")
logging.info(f"Found {total_files} files to process")
for i, file in enumerate(files, 1):
input_file = os.path.join(input_dir, file)
print(f"\n{Colors.BLUE}[{i}/{total_files}]{Colors.ENDC}")
repair_file(input_file, output_dir)
print(f"\n{Colors.GREEN}{'='*60}{Colors.ENDC}")
print(f"{Colors.GREEN}Repair process complete!{Colors.ENDC}")
print(f"{Colors.GREEN}Check the output directory for repaired files{Colors.ENDC}")
print(f"{Colors.GREEN}Log file: {log_file}{Colors.ENDC}")
print(f"{Colors.GREEN}{'='*60}{Colors.ENDC}")