From 89b80225c16a1ed26bd20596c7bb2287d04ad913 Mon Sep 17 00:00:00 2001 From: Raincloud Date: Mon, 1 Dec 2025 22:25:51 -0700 Subject: [PATCH] bulk repair mp4 --- .specstory/.gitignore | 2 + ...2-35Z-can-damaged-hevc-mp4s-be-repaired.md | 265 +++++++++--------- repair_mp4.bat | 1 + repair_mp4.py | 1 + 4 files changed, 141 insertions(+), 128 deletions(-) diff --git a/.specstory/.gitignore b/.specstory/.gitignore index 53b537f..d05a40e 100644 --- a/.specstory/.gitignore +++ b/.specstory/.gitignore @@ -1,2 +1,4 @@ # SpecStory explanation file /.what-is-this.md +# SpecStory project identity file +/.project.json diff --git a/.specstory/history/2025-10-08_02-35Z-can-damaged-hevc-mp4s-be-repaired.md b/.specstory/history/2025-10-08_02-35Z-can-damaged-hevc-mp4s-be-repaired.md index d5a4e36..66cc9e8 100644 --- a/.specstory/history/2025-10-08_02-35Z-can-damaged-hevc-mp4s-be-repaired.md +++ b/.specstory/history/2025-10-08_02-35Z-can-damaged-hevc-mp4s-be-repaired.md @@ -1,4 +1,4 @@ - + @@ -10,9 +10,10 @@ multiple damaged HEVC mp4s in input. can they be fixed? --- -_**Assistant (gpt-5)**_ +_**Agent (model claude-4.5-sonnet-thinking, mode Agent)**_ -
Thought ProcessThe user is asking about damaged HEVC mp4 files in the input directory. They want to know if they can be fixed. Let me first check what files are in the input directory to understand what we're working with. +
Thought Process +The user is asking about damaged HEVC mp4 files in the input directory. They want to know if they can be fixed. Let me first check what files are in the input directory to understand what we're working with. I should: 1. List the input directory to see what files are there @@ -27,17 +28,18 @@ I'll help you check the damaged HEVC MP4 files in your input directory and see w --- -Tool use: **list_dir** - +
- Listed directory undefined • **0** results + Tool use: **list_directory** • Listed directory undefined • **0** results No results found
+
--- -
Thought ProcessI can see there are many MP4 files in the input directory. The user says they are damaged HEVC MP4 files. Let me check one of the existing scripts to understand the structure, and then I can create a tool to analyze and potentially fix these files. +
Thought Process +I can see there are many MP4 files in the input directory. The user says they are damaged HEVC MP4 files. Let me check one of the existing scripts to understand the structure, and then I can create a tool to analyze and potentially fix these files. For damaged MP4 files, common fixes include: 1. Using ffmpeg to remux (copy streams without re-encoding) which can fix container issues @@ -52,13 +54,16 @@ I can see you have many DVR MP4 files. Let me check what kind of damage we're de --- -Tool use: **read_file** + +
Tool use: **read_file** • Read file: encode_dvr.py -Read file: encode_dvr.py +
+
--- -
Thought ProcessGood, I can see the structure. Now let me create a repair tool for damaged MP4 files. The best approaches for fixing damaged HEVC MP4s are: +
Thought Process +Good, I can see the structure. Now let me create a repair tool for damaged MP4 files. The best approaches for fixing damaged HEVC MP4s are: 1. Try to remux with ffmpeg (copy streams to a new container) 2. Use ffmpeg with error concealment to recover what's possible @@ -72,10 +77,9 @@ Now let me create a repair tool to analyze and fix the damaged MP4 files. I'll c --- -Tool use: **write** + - -
Edit file: repair_mp4.py +
Tool use: **code_edit** • Edit file: repair_mp4.py **Chunk 1** Lines added: 237, lines removed: 1 @@ -92,53 +96,53 @@ Lines added: 237, lines removed: 1 + + # ANSI color codes + class Colors: -+ PURPLE = '\033[95m' -+ BLUE = '\033[94m' -+ GREEN = '\033[92m' -+ YELLOW = '\033[93m' -+ RED = '\033[91m' -+ ENDC = '\033[0m' ++ PURPLE = '\033[95m' ++ BLUE = '\033[94m' ++ GREEN = '\033[92m' ++ YELLOW = '\033[93m' ++ RED = '\033[91m' ++ ENDC = '\033[0m' + + # Set up logging -+ log_dir = "logs" ++ 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') ++ 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}") ++ """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', -+ '-' ++ '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 ++ 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]}") ++ 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") ++ 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""" ++ """Get basic file info""" + cmd = [ -+ 'ffprobe', -+ '-v', 'error', -+ '-show_entries', 'format=duration,size:stream=codec_name,width,height', -+ '-of', 'json', ++ '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) @@ -148,120 +152,120 @@ Lines added: 237, lines removed: 1 + 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") ++ """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', ++ '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") ++ 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]}") ++ 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}") ++ 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") ++ """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', ++ '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") ++ 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]}") ++ 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}") ++ 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") ++ """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', ++ '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") ++ 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]}") ++ 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}") ++ 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""" ++ """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}" ++ 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}") ++ 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") ++ 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") ++ print(f"{Colors.GREEN}File appears healthy, no repair needed{Colors.ENDC}") ++ logging.info("File is healthy, skipping") + return + + # Try repair methods in sequence @@ -274,62 +278,63 @@ Lines added: 237, lines removed: 1 + 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}") ++ 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}") ++ 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") ++ 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") ++ 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}") ++ 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" ++ 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'))] ++ 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") ++ 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") ++ 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}") ++ 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}") ++ 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}") + ```
+ + --- -Tool use: **write** + - -
Edit file: repair_mp4.bat +
Tool use: **code_edit** • Edit file: repair_mp4.bat **Chunk 1** Lines added: 4, lines removed: 1 @@ -345,9 +350,12 @@ Lines added: 4, lines removed: 1
+ + --- -
Thought ProcessGreat! I've created the repair script. Now let me explain to the user what I've created and how it works. The script will: +
Thought Process +Great! I've created the repair script. Now let me explain to the user what I've created and how it works. The script will: 1. Check each MP4 file for errors using ffmpeg 2. Try multiple repair methods if issues are found @@ -406,9 +414,10 @@ _**User (2025-10-08 05:55Z)**_ --- -_**Assistant (gpt-5)**_ +_**Agent (model gpt-5, mode Agent)**_ -
Thought Process**Evaluating code logic** +
Thought Process +**Evaluating code logic** I'm seeing a problem in the log after method 1 succeeds—it's showing errors and saying the "Repaired file still has issues." In our code, when verification fails, it prints that "Repair completed but the file still has issues" and then returns. This means we aren't continuing to methods 2 and 3 if method 1 has an output but isn't verified as healthy. We should adjust the design to allow proceeding with subsequent methods, ensuring they’re all attempted if verification fails. I'll need to edit the repair_mp4.py file for this. **Improving video processing methods** diff --git a/repair_mp4.bat b/repair_mp4.bat index c43137c..4de7388 100644 --- a/repair_mp4.bat +++ b/repair_mp4.bat @@ -3,3 +3,4 @@ python repair_mp4.py pause + diff --git a/repair_mp4.py b/repair_mp4.py index f7c73c5..c03e790 100644 --- a/repair_mp4.py +++ b/repair_mp4.py @@ -236,3 +236,4 @@ if __name__ == "__main__": print(f"{Colors.GREEN}{'='*60}{Colors.ENDC}") +