Compare commits
8 Commits
d52514c7b4
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 708a65031a | |||
| 155b5c25d1 | |||
| afdcc1c6fd | |||
| 4db7819172 | |||
| ed30865e4f | |||
| 7cf24df9ae | |||
| 979c97b7f6 | |||
| c540a5fb04 |
4641
.specstory/history/2026-01-17_02-53Z-python-error-logging-and-i-o.md
Normal file
4641
.specstory/history/2026-01-17_02-53Z-python-error-logging-and-i-o.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,26 @@
|
|||||||
|
<!-- Generated by SpecStory, Markdown v2.1.0 -->
|
||||||
|
|
||||||
|
<!-- cursor Session a72eb019-4b76-4b1a-aa9c-82a980c5b71d (2026-01-18 18:26Z) -->
|
||||||
|
|
||||||
|
# Video encoding time calculation (2026-01-18 18:26Z)
|
||||||
|
|
||||||
|
_**User**_
|
||||||
|
|
||||||
|
at 2.26x speed, how long to encode 4:03:33 video?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_**Agent (model default, mode Agent)**_
|
||||||
|
|
||||||
|
|
||||||
|
At 2.26x speed, encoding a 4:03:33 video takes about 1:47:47.
|
||||||
|
|
||||||
|
**Calculation:**
|
||||||
|
- Video duration: 4:03:33 = 14,613 seconds
|
||||||
|
- Encoding time: 14,613 ÷ 2.26 ≈ 6,467 seconds
|
||||||
|
- Converted: **1 hour, 47 minutes, 47 seconds** (1:47:47)
|
||||||
|
|
||||||
|
Roughly 1 hour 48 minutes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
<!-- Generated by SpecStory, Markdown v2.1.0 -->
|
||||||
|
|
||||||
|
<!-- cursor Session e8fe7835-9078-49eb-97e5-a9fff3e0d0fa (2026-01-21 08:11Z) -->
|
||||||
|
|
||||||
|
# Check_files.py small output error handling (2026-01-21 08:11Z)
|
||||||
|
|
||||||
|
_**User**_
|
||||||
|
|
||||||
|
@check_files.py remove suspiciously small output as an error
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_**Agent (model default, mode Agent)**_
|
||||||
|
|
||||||
|
|
||||||
|
Reviewing `check_files.py` to understand its current behavior.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<tool-use data-tool-type="read" data-tool-name="read_file_v2">
|
||||||
|
<details><summary>Tool use: **read_file_v2** • Read file: R:\Videos\mux\check_files.py</summary>
|
||||||
|
|
||||||
|
</details>
|
||||||
|
</tool-use>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
Updating the code to delete suspiciously small output files and treat it as an error.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<tool-use data-tool-type="write" data-tool-name="edit_file_v2">
|
||||||
|
|
||||||
|
<details><summary>Tool use: **edit_file_v2** • Edit file: R:\Videos\mux\check_files.py</summary>
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
</tool-use>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
Updated `check_files.py` to delete suspiciously small output files and treat it as an error. Changes:
|
||||||
|
|
||||||
|
1. **Deletes the file** when output is less than 10% of input size
|
||||||
|
2. **Changes color from yellow to red** to indicate an error
|
||||||
|
3. **Logs the deletion** for tracking
|
||||||
|
4. **Handles deletion errors** if the file can't be removed
|
||||||
|
|
||||||
|
When a suspiciously small output file is detected, it's automatically deleted and reported as an error in red.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_**User**_
|
||||||
|
|
||||||
|
NO. remove the error, don't remove any files.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_**Agent (model default, mode Agent)**_
|
||||||
|
|
||||||
|
|
||||||
|
Removing the suspiciously small output check so it's no longer flagged as an error.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<tool-use data-tool-type="read" data-tool-name="read_file_v2">
|
||||||
|
<details><summary>Tool use: **read_file_v2** • Read file: R:\Videos\mux\check_files.py</summary>
|
||||||
|
|
||||||
|
</details>
|
||||||
|
</tool-use>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
Removing the suspiciously small output check from the error detection logic.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<tool-use data-tool-type="write" data-tool-name="edit_file_v2">
|
||||||
|
|
||||||
|
<details><summary>Tool use: **edit_file_v2** • Edit file: R:\Videos\mux\check_files.py</summary>
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
</tool-use>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<tool-use data-tool-type="write" data-tool-name="edit_file_v2">
|
||||||
|
|
||||||
|
<details><summary>Tool use: **edit_file_v2** • Edit file: R:\Videos\mux\check_files.py</summary>
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
</tool-use>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<tool-use data-tool-type="write" data-tool-name="edit_file_v2">
|
||||||
|
|
||||||
|
<details><summary>Tool use: **edit_file_v2** • Edit file: R:\Videos\mux\check_files.py</summary>
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
</tool-use>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<tool-use data-tool-type="write" data-tool-name="edit_file_v2">
|
||||||
|
|
||||||
|
<details><summary>Tool use: **edit_file_v2** • Edit file: R:\Videos\mux\check_files.py</summary>
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
</tool-use>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
Removed the suspiciously small output check from the error detection. The script will:
|
||||||
|
|
||||||
|
1. No longer check if output files are suspiciously small
|
||||||
|
2. No longer flag this as an error
|
||||||
|
3. No longer count it in the issue summary
|
||||||
|
|
||||||
|
The `format_file_size` function was also removed since it's no longer needed. The script now only checks for duration mismatches, missing video streams, resolution mismatches, and audio stream issues.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_**User**_
|
||||||
|
|
||||||
|
though now I think of it, add a user prompt to delete all detected problem files./
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_**Agent (model default, mode Agent)**_
|
||||||
|
|
||||||
|
|
||||||
|
Adding a user prompt to delete all detected problem files. Reviewing the current file structure:
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<tool-use data-tool-type="read" data-tool-name="read_file_v2">
|
||||||
|
<details><summary>Tool use: **read_file_v2** • Read file: R:\Videos\mux\check_files.py</summary>
|
||||||
|
|
||||||
|
</details>
|
||||||
|
</tool-use>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
Adding a user prompt to delete all detected problem files after the summary:
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<tool-use data-tool-type="write" data-tool-name="edit_file_v2">
|
||||||
|
|
||||||
|
<details><summary>Tool use: **edit_file_v2** • Edit file: R:\Videos\mux\check_files.py</summary>
|
||||||
|
|
||||||
|
Status: **loading**
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
</tool-use>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
BIN
0gameplay.lnk
Normal file
BIN
0gameplay.lnk
Normal file
Binary file not shown.
4
TODO.md
Normal file
4
TODO.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
* [ ] Overhaul: integrate basic functionality of Flamenco into UI
|
||||||
|
* [ ] Error handling
|
||||||
|
* [ ] Log errors
|
||||||
|
* [ ] Mark task as errored; the output will be deleted and tried again
|
||||||
@@ -41,13 +41,6 @@ def get_file_info(file_path):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return None, f"Error running ffprobe: {str(e)}"
|
return None, f"Error running ffprobe: {str(e)}"
|
||||||
|
|
||||||
def format_file_size(size_bytes):
|
|
||||||
size_mb = size_bytes / (1024 * 1024)
|
|
||||||
if size_mb >= 1024: # If size is 1GB or larger
|
|
||||||
size_gb = size_mb / 1024
|
|
||||||
return f"{size_gb:.2f} GB"
|
|
||||||
return f"{size_mb:.2f} MB"
|
|
||||||
|
|
||||||
def check_file(input_path, output_path):
|
def check_file(input_path, output_path):
|
||||||
try:
|
try:
|
||||||
input_info, input_error = get_file_info(input_path)
|
input_info, input_error = get_file_info(input_path)
|
||||||
@@ -93,13 +86,6 @@ def check_file(input_path, output_path):
|
|||||||
if in_audio['channels'] != out_audio['channels']:
|
if in_audio['channels'] != out_audio['channels']:
|
||||||
return f"Audio channel mismatch in stream {i}: Input={in_audio['channels']}, Output={out_audio['channels']}"
|
return f"Audio channel mismatch in stream {i}: Input={in_audio['channels']}, Output={out_audio['channels']}"
|
||||||
|
|
||||||
# Check file size
|
|
||||||
input_size = int(input_info['format']['size'])
|
|
||||||
output_size = int(output_info['format']['size'])
|
|
||||||
|
|
||||||
if output_size < input_size * 0.1: # Output less than 10% of input size
|
|
||||||
return f"{Colors.YELLOW}Suspiciously small output: Input={format_file_size(input_size)}, Output={format_file_size(output_size)}{Colors.ENDC}"
|
|
||||||
|
|
||||||
return None # No issues found
|
return None # No issues found
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -121,7 +107,6 @@ def main():
|
|||||||
"Resolution mismatch": 0,
|
"Resolution mismatch": 0,
|
||||||
"Audio stream count mismatch": 0,
|
"Audio stream count mismatch": 0,
|
||||||
"Audio channel mismatch": 0,
|
"Audio channel mismatch": 0,
|
||||||
"Suspiciously small output": 0,
|
|
||||||
"Other errors": 0
|
"Other errors": 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,8 +246,6 @@ def main():
|
|||||||
issue_counts["Audio stream count mismatch"] += 1
|
issue_counts["Audio stream count mismatch"] += 1
|
||||||
elif "Audio channel mismatch" in issue:
|
elif "Audio channel mismatch" in issue:
|
||||||
issue_counts["Audio channel mismatch"] += 1
|
issue_counts["Audio channel mismatch"] += 1
|
||||||
elif "Suspiciously small output" in issue:
|
|
||||||
issue_counts["Suspiciously small output"] += 1
|
|
||||||
else:
|
else:
|
||||||
issue_counts["Other errors"] += 1
|
issue_counts["Other errors"] += 1
|
||||||
else:
|
else:
|
||||||
@@ -281,6 +264,38 @@ def main():
|
|||||||
for file, issue in issues_found:
|
for file, issue in issues_found:
|
||||||
print(f"\n{file}:")
|
print(f"\n{file}:")
|
||||||
print(f"{Colors.RED} {issue}{Colors.ENDC}")
|
print(f"{Colors.RED} {issue}{Colors.ENDC}")
|
||||||
|
|
||||||
|
# Prompt to delete problem files
|
||||||
|
print(f"\n{Colors.YELLOW}Files with issues detected.{Colors.ENDC}")
|
||||||
|
response = input("Delete all problematic output files? (yes/no): ").strip().lower()
|
||||||
|
|
||||||
|
if response in ['yes', 'y']:
|
||||||
|
deleted_count = 0
|
||||||
|
failed_count = 0
|
||||||
|
|
||||||
|
for file, issue in issues_found:
|
||||||
|
# Skip "Missing from output" - nothing to delete
|
||||||
|
if issue == "Missing from output":
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Delete output file for all other issues
|
||||||
|
output_path = os.path.join(output_dir, file)
|
||||||
|
if os.path.exists(output_path):
|
||||||
|
try:
|
||||||
|
os.remove(output_path)
|
||||||
|
deleted_count += 1
|
||||||
|
logging.info(f"Deleted problematic output file: {output_path} (Issue: {issue})")
|
||||||
|
print(f"{Colors.GREEN}Deleted: {file}{Colors.ENDC}")
|
||||||
|
except Exception as e:
|
||||||
|
failed_count += 1
|
||||||
|
logging.error(f"Failed to delete {output_path}: {str(e)}")
|
||||||
|
print(f"{Colors.RED}Failed to delete {file}: {str(e)}{Colors.ENDC}")
|
||||||
|
|
||||||
|
print(f"\n{Colors.GREEN}Deleted {deleted_count} file(s){Colors.ENDC}")
|
||||||
|
if failed_count > 0:
|
||||||
|
print(f"{Colors.RED}Failed to delete {failed_count} file(s){Colors.ENDC}")
|
||||||
|
else:
|
||||||
|
print("Deletion cancelled.")
|
||||||
else:
|
else:
|
||||||
print(f"{Colors.GREEN}No issues found in any files!{Colors.ENDC}")
|
print(f"{Colors.GREEN}No issues found in any files!{Colors.ENDC}")
|
||||||
print("\nIssue counts:")
|
print("\nIssue counts:")
|
||||||
|
|||||||
163
encode_VOD.py
163
encode_VOD.py
@@ -27,18 +27,52 @@ def get_gpu_selection():
|
|||||||
return gpu
|
return gpu
|
||||||
print(f"{Colors.RED}Invalid selection. Please try again.{Colors.ENDC}")
|
print(f"{Colors.RED}Invalid selection. Please try again.{Colors.ENDC}")
|
||||||
|
|
||||||
|
# Custom file handler that silently handles I/O errors (for network shares)
|
||||||
|
class SafeFileHandler(logging.FileHandler):
|
||||||
|
"""File handler that silently handles I/O errors during flush"""
|
||||||
|
def flush(self):
|
||||||
|
"""Override flush to silently handle I/O errors"""
|
||||||
|
try:
|
||||||
|
super().flush()
|
||||||
|
except (OSError, IOError):
|
||||||
|
# Silently ignore I/O errors (network share issues)
|
||||||
|
pass
|
||||||
|
except Exception:
|
||||||
|
# Silently ignore all other errors during flush
|
||||||
|
pass
|
||||||
|
|
||||||
|
def emit(self, record):
|
||||||
|
"""Override emit to handle errors gracefully"""
|
||||||
|
try:
|
||||||
|
super().emit(record)
|
||||||
|
except (OSError, IOError):
|
||||||
|
# Silently ignore I/O errors - we'll fall back to console output
|
||||||
|
self.handleError(record)
|
||||||
|
except Exception:
|
||||||
|
# Handle other errors
|
||||||
|
self.handleError(record)
|
||||||
|
|
||||||
|
def handleError(self, record):
|
||||||
|
"""Override to prevent error messages from being printed"""
|
||||||
|
# Don't print "--- Logging error ---" messages
|
||||||
|
pass
|
||||||
|
|
||||||
# Set up logging
|
# Set up logging
|
||||||
log_dir = "logs"
|
log_dir = "logs"
|
||||||
os.makedirs(log_dir, exist_ok=True)
|
os.makedirs(log_dir, exist_ok=True)
|
||||||
log_file = os.path.join(log_dir, f"encode_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log")
|
log_file = os.path.join(log_dir, f"encode_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log")
|
||||||
|
|
||||||
# Configure logging with Windows-safe settings
|
# Configure logging with custom handler that handles network share errors
|
||||||
logging.basicConfig(
|
handler = SafeFileHandler(log_file, mode='w', encoding='utf-8')
|
||||||
filename=log_file,
|
handler.setLevel(logging.INFO)
|
||||||
level=logging.INFO,
|
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
|
||||||
format='%(asctime)s - %(levelname)s - %(message)s',
|
handler.setFormatter(formatter)
|
||||||
filemode='w' # Explicitly set write mode
|
|
||||||
)
|
logger = logging.getLogger()
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
logger.addHandler(handler)
|
||||||
|
# Remove default handlers to avoid duplicate output
|
||||||
|
logger.handlers = [handler]
|
||||||
|
|
||||||
def get_file_info(input_file):
|
def get_file_info(input_file):
|
||||||
cmd = [
|
cmd = [
|
||||||
@@ -75,6 +109,58 @@ def format_size(size_bytes):
|
|||||||
size_bytes /= 1024
|
size_bytes /= 1024
|
||||||
return f"{size_bytes:.2f} TB"
|
return f"{size_bytes:.2f} TB"
|
||||||
|
|
||||||
|
def safe_log_info(message, print_msg=None):
|
||||||
|
"""Safely log info message, ensuring console output even if logging fails"""
|
||||||
|
try:
|
||||||
|
logging.info(message)
|
||||||
|
except (OSError, IOError) as e:
|
||||||
|
# Logging failed (likely network share issue) - print to console
|
||||||
|
if print_msg is None:
|
||||||
|
print(f"{Colors.YELLOW}[Logging failed: {e}] {message}{Colors.ENDC}")
|
||||||
|
else:
|
||||||
|
print(print_msg)
|
||||||
|
print(f"{Colors.YELLOW}[Logging failed: {e}]{Colors.ENDC}")
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
# Other logging errors
|
||||||
|
if print_msg is None:
|
||||||
|
print(f"{Colors.YELLOW}[Logging error: {e}] {message}{Colors.ENDC}")
|
||||||
|
else:
|
||||||
|
print(print_msg)
|
||||||
|
print(f"{Colors.YELLOW}[Logging error: {e}]{Colors.ENDC}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Always print to console if message provided
|
||||||
|
if print_msg is not None:
|
||||||
|
print(print_msg)
|
||||||
|
|
||||||
|
def safe_log_error(message, print_msg=None):
|
||||||
|
"""Safely log error message, ensuring console output even if logging fails"""
|
||||||
|
try:
|
||||||
|
logging.error(message)
|
||||||
|
except (OSError, IOError) as e:
|
||||||
|
# Logging failed (likely network share issue) - print to console
|
||||||
|
if print_msg is None:
|
||||||
|
print(f"{Colors.RED}[Logging failed: {e}] {message}{Colors.ENDC}")
|
||||||
|
else:
|
||||||
|
print(print_msg)
|
||||||
|
print(f"{Colors.RED}[Logging failed: {e}]{Colors.ENDC}")
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
# Other logging errors
|
||||||
|
if print_msg is None:
|
||||||
|
print(f"{Colors.RED}[Logging error: {e}] {message}{Colors.ENDC}")
|
||||||
|
else:
|
||||||
|
print(print_msg)
|
||||||
|
print(f"{Colors.RED}[Logging error: {e}]{Colors.ENDC}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Always print to console if message provided
|
||||||
|
if print_msg is not None:
|
||||||
|
print(print_msg)
|
||||||
|
else:
|
||||||
|
print(f"{Colors.RED}{message}{Colors.ENDC}")
|
||||||
|
|
||||||
def encode_dvr(input_file, output_dir, gpu):
|
def encode_dvr(input_file, output_dir, gpu):
|
||||||
input_path = Path(input_file)
|
input_path = Path(input_file)
|
||||||
output_path = Path(output_dir) / f"{input_path.stem}{input_path.suffix}"
|
output_path = Path(output_dir) / f"{input_path.stem}{input_path.suffix}"
|
||||||
@@ -84,9 +170,9 @@ def encode_dvr(input_file, output_dir, gpu):
|
|||||||
input_size = int(file_info['format']['size'])
|
input_size = int(file_info['format']['size'])
|
||||||
duration = float(file_info['format']['duration'])
|
duration = float(file_info['format']['duration'])
|
||||||
|
|
||||||
logging.info(f"Processing file: {input_path}")
|
safe_log_info(f"Processing file: {input_path}")
|
||||||
logging.info(f"Input size: {format_size(input_size)}")
|
safe_log_info(f"Input size: {format_size(input_size)}")
|
||||||
logging.info(f"Duration: {duration:.2f} seconds")
|
safe_log_info(f"Duration: {duration:.2f} seconds")
|
||||||
|
|
||||||
print(f"\n{Colors.BLUE}Processing file: {input_path}{Colors.ENDC}")
|
print(f"\n{Colors.BLUE}Processing file: {input_path}{Colors.ENDC}")
|
||||||
print(f"Input size: {format_size(input_size)}")
|
print(f"Input size: {format_size(input_size)}")
|
||||||
@@ -95,21 +181,21 @@ def encode_dvr(input_file, output_dir, gpu):
|
|||||||
# Log stream information
|
# Log stream information
|
||||||
for i, stream in enumerate(file_info.get('streams', [])):
|
for i, stream in enumerate(file_info.get('streams', [])):
|
||||||
stream_type = 'Video' if stream.get('codec_name', '').startswith('h') else 'Audio'
|
stream_type = 'Video' if stream.get('codec_name', '').startswith('h') else 'Audio'
|
||||||
logging.info(f"Stream {i} ({stream_type}):")
|
safe_log_info(f"Stream {i} ({stream_type}):")
|
||||||
for key, value in stream.items():
|
for key, value in stream.items():
|
||||||
logging.info(f" {key}: {value}")
|
safe_log_info(f" {key}: {value}")
|
||||||
|
|
||||||
# Skip if output file already exists
|
# Skip if output file already exists
|
||||||
if output_path.exists():
|
if output_path.exists():
|
||||||
output_size = output_path.stat().st_size
|
output_size = output_path.stat().st_size
|
||||||
logging.info(f"Skipping {input_path} - output already exists: {output_path}")
|
safe_log_info(f"Skipping {input_path} - output already exists: {output_path}")
|
||||||
logging.info(f"Output size: {format_size(output_size)}")
|
safe_log_info(f"Output size: {format_size(output_size)}")
|
||||||
print(f"{Colors.YELLOW}Skipping {input_path} - output already exists{Colors.ENDC}")
|
print(f"{Colors.YELLOW}Skipping {input_path} - output already exists{Colors.ENDC}")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Get audio labels
|
# Get audio labels
|
||||||
audio_labels = get_audio_labels(str(input_path))
|
audio_labels = get_audio_labels(str(input_path))
|
||||||
logging.info(f"Audio labels: {audio_labels}")
|
safe_log_info(f"Audio labels: {audio_labels}")
|
||||||
|
|
||||||
# FFmpeg command with NVIDIA HEVC encoder and maximum quality
|
# FFmpeg command with NVIDIA HEVC encoder and maximum quality
|
||||||
cmd = [
|
cmd = [
|
||||||
@@ -156,13 +242,16 @@ def encode_dvr(input_file, output_dir, gpu):
|
|||||||
text = line.strip()
|
text = line.strip()
|
||||||
try:
|
try:
|
||||||
if text.startswith('frame=') or ' fps=' in text:
|
if text.startswith('frame=') or ' fps=' in text:
|
||||||
logging.info(f"Progress: {text}")
|
safe_log_info(f"Progress: {text}", f"{Colors.PURPLE}Progress: {text}{Colors.ENDC}")
|
||||||
print(f"{Colors.PURPLE}Progress: {text}{Colors.ENDC}")
|
|
||||||
else:
|
else:
|
||||||
logging.info(f"FFmpeg: {text}")
|
safe_log_info(f"FFmpeg: {text}", f"{Colors.GREEN}FFmpeg: {text}{Colors.ENDC}")
|
||||||
print(f"{Colors.GREEN}FFmpeg: {text}{Colors.ENDC}")
|
except (OSError, IOError) as e:
|
||||||
except (OSError, IOError):
|
# I/O error reading from pipe - log it
|
||||||
pass
|
safe_log_error(f"I/O error reading FFmpeg output: {e}")
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
# Unexpected error
|
||||||
|
safe_log_error(f"Unexpected error processing FFmpeg output: {e}")
|
||||||
|
|
||||||
process.wait()
|
process.wait()
|
||||||
|
|
||||||
@@ -172,23 +261,27 @@ def encode_dvr(input_file, output_dir, gpu):
|
|||||||
output_size = int(output_info['format']['size'])
|
output_size = int(output_info['format']['size'])
|
||||||
compression_ratio = input_size / output_size if output_size > 0 else 0
|
compression_ratio = input_size / output_size if output_size > 0 else 0
|
||||||
|
|
||||||
logging.info(f"Successfully encoded: {output_path}")
|
safe_log_info(f"Successfully encoded: {output_path}", f"{Colors.GREEN}Successfully encoded: {output_path}{Colors.ENDC}")
|
||||||
logging.info(f"Output size: {format_size(output_size)}")
|
safe_log_info(f"Output size: {format_size(output_size)}")
|
||||||
logging.info(f"Compression ratio: {compression_ratio:.2f}x")
|
safe_log_info(f"Compression ratio: {compression_ratio:.2f}x", f"Compression ratio: {compression_ratio:.2f}x")
|
||||||
print(f"{Colors.GREEN}Successfully encoded: {output_path}{Colors.ENDC}")
|
|
||||||
print(f"Compression ratio: {compression_ratio:.2f}x")
|
|
||||||
else:
|
else:
|
||||||
logging.error(f"FFmpeg process failed with return code {process.returncode}")
|
# Convert Windows error code to signed integer if needed
|
||||||
print(f"{Colors.RED}FFmpeg process failed with return code {process.returncode}{Colors.ENDC}")
|
return_code = process.returncode
|
||||||
|
if return_code > 2147483647: # If it's a large unsigned int, convert to signed
|
||||||
|
return_code = return_code - 4294967296
|
||||||
|
safe_log_error(f"FFmpeg process failed with return code {return_code}",
|
||||||
|
f"{Colors.RED}FFmpeg process failed with return code {return_code}{Colors.ENDC}")
|
||||||
|
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
logging.error(f"Error encoding {input_path}: {e}")
|
safe_log_error(f"Error encoding {input_path}: {e}", f"{Colors.RED}Error encoding {input_path}: {e}{Colors.ENDC}")
|
||||||
print(f"{Colors.RED}Error encoding {input_path}: {e}{Colors.ENDC}")
|
except Exception as e:
|
||||||
|
safe_log_error(f"Unexpected error encoding {input_path}: {type(e).__name__}: {e}",
|
||||||
|
f"{Colors.RED}Unexpected error encoding {input_path}: {e}{Colors.ENDC}")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Get GPU selection
|
# Get GPU selection
|
||||||
gpu = get_gpu_selection()
|
gpu = get_gpu_selection()
|
||||||
logging.info(f"Selected GPU: {gpu}")
|
safe_log_info(f"Selected GPU: {gpu}")
|
||||||
|
|
||||||
input_dir = "input"
|
input_dir = "input"
|
||||||
output_dir = "output"
|
output_dir = "output"
|
||||||
@@ -199,14 +292,12 @@ if __name__ == "__main__":
|
|||||||
total_files = len(files)
|
total_files = len(files)
|
||||||
|
|
||||||
if total_files == 0:
|
if total_files == 0:
|
||||||
logging.info("No files to process in input directory")
|
safe_log_info("No files to process in input directory", f"{Colors.YELLOW}No files to process in input directory{Colors.ENDC}")
|
||||||
print(f"{Colors.YELLOW}No files to process in input directory{Colors.ENDC}")
|
|
||||||
else:
|
else:
|
||||||
logging.info(f"Found {total_files} files to process")
|
safe_log_info(f"Found {total_files} files to process", f"{Colors.BLUE}Found {total_files} files to process{Colors.ENDC}")
|
||||||
print(f"{Colors.BLUE}Found {total_files} files to process{Colors.ENDC}")
|
|
||||||
|
|
||||||
for i, file in enumerate(files, 1):
|
for i, file in enumerate(files, 1):
|
||||||
input_file = os.path.join(input_dir, file)
|
input_file = os.path.join(input_dir, file)
|
||||||
logging.info(f"Processing file {i}/{total_files}: {file}")
|
safe_log_info(f"Processing file {i}/{total_files}: {file}")
|
||||||
print(f"\n{Colors.BLUE}Processing file {i}/{total_files}: {file}{Colors.ENDC}")
|
print(f"\n{Colors.BLUE}Processing file {i}/{total_files}: {file}{Colors.ENDC}")
|
||||||
encode_dvr(input_file, output_dir, gpu)
|
encode_dvr(input_file, output_dir, gpu)
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
@echo off
|
|
||||||
python encode_dvr.py
|
|
||||||
pause
|
|
||||||
209
encode_dvr.py
209
encode_dvr.py
@@ -1,209 +0,0 @@
|
|||||||
import os
|
|
||||||
import subprocess
|
|
||||||
from pathlib import Path
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
from datetime import datetime
|
|
||||||
import shutil
|
|
||||||
|
|
||||||
# ANSI color codes
|
|
||||||
class Colors:
|
|
||||||
PURPLE = '\033[95m'
|
|
||||||
BLUE = '\033[94m'
|
|
||||||
GREEN = '\033[92m'
|
|
||||||
YELLOW = '\033[93m'
|
|
||||||
RED = '\033[91m'
|
|
||||||
ENDC = '\033[0m'
|
|
||||||
|
|
||||||
def get_gpu_selection():
|
|
||||||
while True:
|
|
||||||
print(f"\n{Colors.BLUE}Select GPU slot:{Colors.ENDC}")
|
|
||||||
print("0 - First GPU")
|
|
||||||
print("1 - Second GPU")
|
|
||||||
print("2 - Third GPU")
|
|
||||||
gpu = input(f"{Colors.YELLOW}Enter GPU number (0-2):{Colors.ENDC} ").strip()
|
|
||||||
if gpu in ['0', '1', '2']:
|
|
||||||
return gpu
|
|
||||||
print(f"{Colors.RED}Invalid selection. Please try again.{Colors.ENDC}")
|
|
||||||
|
|
||||||
# Set up logging
|
|
||||||
log_dir = "logs"
|
|
||||||
os.makedirs(log_dir, exist_ok=True)
|
|
||||||
log_file = os.path.join(log_dir, f"encode_{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 get_file_info(input_file):
|
|
||||||
cmd = [
|
|
||||||
'ffprobe',
|
|
||||||
'-v', 'error',
|
|
||||||
'-show_entries', 'format=duration,size:stream=codec_name,width,height,r_frame_rate,channels,channel_layout',
|
|
||||||
'-of', 'json',
|
|
||||||
input_file
|
|
||||||
]
|
|
||||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
||||||
return json.loads(result.stdout)
|
|
||||||
|
|
||||||
def get_audio_labels(input_file):
|
|
||||||
cmd = [
|
|
||||||
'ffprobe',
|
|
||||||
'-v', 'error',
|
|
||||||
'-select_streams', 'a',
|
|
||||||
'-show_entries', 'stream=index:stream_tags=title',
|
|
||||||
'-of', 'json',
|
|
||||||
input_file
|
|
||||||
]
|
|
||||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
||||||
info = json.loads(result.stdout)
|
|
||||||
labels = []
|
|
||||||
for stream in info.get('streams', []):
|
|
||||||
title = stream.get('tags', {}).get('title', None)
|
|
||||||
labels.append(title)
|
|
||||||
return labels
|
|
||||||
|
|
||||||
def format_size(size_bytes):
|
|
||||||
for unit in ['B', 'KB', 'MB', 'GB']:
|
|
||||||
if size_bytes < 1024:
|
|
||||||
return f"{size_bytes:.2f} {unit}"
|
|
||||||
size_bytes /= 1024
|
|
||||||
return f"{size_bytes:.2f} TB"
|
|
||||||
|
|
||||||
def encode_dvr(input_file, output_dir, gpu):
|
|
||||||
input_path = Path(input_file)
|
|
||||||
output_path = Path(output_dir) / f"{input_path.stem}{input_path.suffix}"
|
|
||||||
|
|
||||||
# Get file info for logging
|
|
||||||
file_info = get_file_info(str(input_path))
|
|
||||||
input_size = int(file_info['format']['size'])
|
|
||||||
duration = float(file_info['format']['duration'])
|
|
||||||
|
|
||||||
logging.info(f"Processing file: {input_path}")
|
|
||||||
logging.info(f"Input size: {format_size(input_size)}")
|
|
||||||
logging.info(f"Duration: {duration:.2f} seconds")
|
|
||||||
|
|
||||||
print(f"\n{Colors.BLUE}Processing file: {input_path}{Colors.ENDC}")
|
|
||||||
print(f"Input size: {format_size(input_size)}")
|
|
||||||
print(f"Duration: {duration:.2f} seconds")
|
|
||||||
|
|
||||||
# Log stream information
|
|
||||||
for i, stream in enumerate(file_info.get('streams', [])):
|
|
||||||
stream_type = 'Video' if stream.get('codec_name', '').startswith('h') else 'Audio'
|
|
||||||
logging.info(f"Stream {i} ({stream_type}):")
|
|
||||||
for key, value in stream.items():
|
|
||||||
logging.info(f" {key}: {value}")
|
|
||||||
|
|
||||||
# Skip if output file already exists
|
|
||||||
if output_path.exists():
|
|
||||||
output_size = output_path.stat().st_size
|
|
||||||
logging.info(f"Skipping {input_path} - output already exists: {output_path}")
|
|
||||||
logging.info(f"Output size: {format_size(output_size)}")
|
|
||||||
print(f"{Colors.YELLOW}Skipping {input_path} - output already exists{Colors.ENDC}")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Get audio labels
|
|
||||||
audio_labels = get_audio_labels(str(input_path))
|
|
||||||
logging.info(f"Audio labels: {audio_labels}")
|
|
||||||
|
|
||||||
# FFmpeg command with NVIDIA HEVC encoder and maximum quality
|
|
||||||
cmd = [
|
|
||||||
'ffmpeg',
|
|
||||||
'-v', 'verbose',
|
|
||||||
'-progress', 'pipe:1', # Force progress output to stdout
|
|
||||||
'-stats', # Show encoding statistics
|
|
||||||
'-stats_period', '0.5', # Update stats every 0.5 seconds
|
|
||||||
'-i', str(input_path),
|
|
||||||
'-c:v', 'hevc_nvenc',
|
|
||||||
'-gpu', gpu,
|
|
||||||
'-preset', 'p7',
|
|
||||||
'-tune', 'hq',
|
|
||||||
'-rc', 'vbr', # Variable bitrate mode
|
|
||||||
'-rc-lookahead', '32', # Increase lookahead for better VBR decisions
|
|
||||||
'-spatial-aq', '1', # Enable spatial AQ for better quality
|
|
||||||
'-aq-strength', '15', # Maximum allowed AQ strength
|
|
||||||
'-cq', '35',
|
|
||||||
'-b:v', '0', # Let VBR determine bitrate
|
|
||||||
'-c:a', 'copy',
|
|
||||||
'-map', '0',
|
|
||||||
]
|
|
||||||
# Add metadata for each audio stream if label exists
|
|
||||||
for idx, label in enumerate(audio_labels):
|
|
||||||
if label:
|
|
||||||
cmd += [f'-metadata:s:a:{idx}', f'title={label}']
|
|
||||||
cmd.append(str(output_path))
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Run FFmpeg with timeout
|
|
||||||
process = subprocess.Popen(
|
|
||||||
cmd,
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.PIPE,
|
|
||||||
universal_newlines=True
|
|
||||||
)
|
|
||||||
|
|
||||||
# Set timeout to 5 minutes
|
|
||||||
timeout = 300
|
|
||||||
start_time = datetime.now()
|
|
||||||
|
|
||||||
# Log FFmpeg output in real-time
|
|
||||||
while True:
|
|
||||||
# Check for timeout
|
|
||||||
if (datetime.now() - start_time).total_seconds() > timeout:
|
|
||||||
process.kill()
|
|
||||||
raise TimeoutError(f"FFmpeg process timed out after {timeout} seconds")
|
|
||||||
|
|
||||||
output = process.stderr.readline()
|
|
||||||
if output == '' and process.poll() is not None:
|
|
||||||
break
|
|
||||||
if output:
|
|
||||||
logging.info(f"FFmpeg: {output.strip()}")
|
|
||||||
print(f"{Colors.PURPLE}FFmpeg: {output.strip()}{Colors.ENDC}")
|
|
||||||
|
|
||||||
if process.returncode == 0:
|
|
||||||
# Get output file info
|
|
||||||
output_info = get_file_info(str(output_path))
|
|
||||||
output_size = int(output_info['format']['size'])
|
|
||||||
compression_ratio = input_size / output_size if output_size > 0 else 0
|
|
||||||
|
|
||||||
logging.info(f"Successfully encoded: {output_path}")
|
|
||||||
logging.info(f"Output size: {format_size(output_size)}")
|
|
||||||
logging.info(f"Compression ratio: {compression_ratio:.2f}x")
|
|
||||||
print(f"{Colors.GREEN}Successfully encoded: {output_path}{Colors.ENDC}")
|
|
||||||
print(f"Compression ratio: {compression_ratio:.2f}x")
|
|
||||||
else:
|
|
||||||
logging.error(f"FFmpeg process failed with return code {process.returncode}")
|
|
||||||
print(f"{Colors.RED}FFmpeg process failed with return code {process.returncode}{Colors.ENDC}")
|
|
||||||
|
|
||||||
except TimeoutError as e:
|
|
||||||
logging.error(f"Timeout error: {e}")
|
|
||||||
print(f"{Colors.RED}Timeout error: {e}{Colors.ENDC}")
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
logging.error(f"Error encoding {input_path}: {e}")
|
|
||||||
print(f"{Colors.RED}Error encoding {input_path}: {e}{Colors.ENDC}")
|
|
||||||
except Exception as e:
|
|
||||||
logging.error(f"Unexpected error: {e}")
|
|
||||||
print(f"{Colors.RED}Unexpected error: {e}{Colors.ENDC}")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
# Get GPU selection
|
|
||||||
gpu = get_gpu_selection()
|
|
||||||
logging.info(f"Selected GPU: {gpu}")
|
|
||||||
|
|
||||||
input_dir = "input"
|
|
||||||
output_dir = "output"
|
|
||||||
os.makedirs(output_dir, exist_ok=True)
|
|
||||||
|
|
||||||
# Get list of files to process
|
|
||||||
files = [f for f in os.listdir(input_dir) if f.endswith(('.mp4', '.DVR.mp4'))]
|
|
||||||
total_files = len(files)
|
|
||||||
|
|
||||||
if total_files == 0:
|
|
||||||
logging.info("No files to process in input directory")
|
|
||||||
print(f"{Colors.YELLOW}No files to process in input directory{Colors.ENDC}")
|
|
||||||
else:
|
|
||||||
logging.info(f"Found {total_files} files to process")
|
|
||||||
print(f"{Colors.BLUE}Found {total_files} files to process{Colors.ENDC}")
|
|
||||||
|
|
||||||
for i, file in enumerate(files, 1):
|
|
||||||
input_file = os.path.join(input_dir, file)
|
|
||||||
logging.info(f"Processing file {i}/{total_files}: {file}")
|
|
||||||
print(f"\n{Colors.BLUE}Processing file {i}/{total_files}: {file}{Colors.ENDC}")
|
|
||||||
encode_dvr(input_file, output_dir, gpu)
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
@echo off
|
|
||||||
python encode_dvr_23.py
|
|
||||||
pause
|
|
||||||
191
encode_dvr_23.py
191
encode_dvr_23.py
@@ -1,191 +0,0 @@
|
|||||||
import os
|
|
||||||
import subprocess
|
|
||||||
from pathlib import Path
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
from datetime import datetime
|
|
||||||
import shutil
|
|
||||||
|
|
||||||
# ANSI color codes
|
|
||||||
class Colors:
|
|
||||||
PURPLE = '\033[95m'
|
|
||||||
BLUE = '\033[94m'
|
|
||||||
GREEN = '\033[92m'
|
|
||||||
YELLOW = '\033[93m'
|
|
||||||
RED = '\033[91m'
|
|
||||||
ENDC = '\033[0m'
|
|
||||||
|
|
||||||
def get_gpu_selection():
|
|
||||||
while True:
|
|
||||||
print(f"\n{Colors.BLUE}Select GPU slot:{Colors.ENDC}")
|
|
||||||
print("0 - First GPU")
|
|
||||||
print("1 - Second GPU")
|
|
||||||
print("2 - Third GPU")
|
|
||||||
gpu = input(f"{Colors.YELLOW}Enter GPU number (0-2):{Colors.ENDC} ").strip()
|
|
||||||
if gpu in ['0', '1', '2']:
|
|
||||||
return gpu
|
|
||||||
print(f"{Colors.RED}Invalid selection. Please try again.{Colors.ENDC}")
|
|
||||||
|
|
||||||
# Set up logging
|
|
||||||
log_dir = "logs"
|
|
||||||
os.makedirs(log_dir, exist_ok=True)
|
|
||||||
log_file = os.path.join(log_dir, f"encode_{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 get_file_info(input_file):
|
|
||||||
cmd = [
|
|
||||||
'ffprobe',
|
|
||||||
'-v', 'error',
|
|
||||||
'-show_entries', 'format=duration,size:stream=codec_name,width,height,r_frame_rate,channels,channel_layout',
|
|
||||||
'-of', 'json',
|
|
||||||
input_file
|
|
||||||
]
|
|
||||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
||||||
return json.loads(result.stdout)
|
|
||||||
|
|
||||||
def get_audio_labels(input_file):
|
|
||||||
cmd = [
|
|
||||||
'ffprobe',
|
|
||||||
'-v', 'error',
|
|
||||||
'-select_streams', 'a',
|
|
||||||
'-show_entries', 'stream=index:stream_tags=title',
|
|
||||||
'-of', 'json',
|
|
||||||
input_file
|
|
||||||
]
|
|
||||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
||||||
info = json.loads(result.stdout)
|
|
||||||
labels = []
|
|
||||||
for stream in info.get('streams', []):
|
|
||||||
title = stream.get('tags', {}).get('title', None)
|
|
||||||
labels.append(title)
|
|
||||||
return labels
|
|
||||||
|
|
||||||
def format_size(size_bytes):
|
|
||||||
for unit in ['B', 'KB', 'MB', 'GB']:
|
|
||||||
if size_bytes < 1024:
|
|
||||||
return f"{size_bytes:.2f} {unit}"
|
|
||||||
size_bytes /= 1024
|
|
||||||
return f"{size_bytes:.2f} TB"
|
|
||||||
|
|
||||||
def encode_dvr(input_file, output_dir, gpu):
|
|
||||||
input_path = Path(input_file)
|
|
||||||
output_path = Path(output_dir) / f"{input_path.stem}{input_path.suffix}"
|
|
||||||
|
|
||||||
# Get file info for logging
|
|
||||||
file_info = get_file_info(str(input_path))
|
|
||||||
input_size = int(file_info['format']['size'])
|
|
||||||
duration = float(file_info['format']['duration'])
|
|
||||||
|
|
||||||
logging.info(f"Processing file: {input_path}")
|
|
||||||
logging.info(f"Input size: {format_size(input_size)}")
|
|
||||||
logging.info(f"Duration: {duration:.2f} seconds")
|
|
||||||
|
|
||||||
print(f"\n{Colors.BLUE}Processing file: {input_path}{Colors.ENDC}")
|
|
||||||
print(f"Input size: {format_size(input_size)}")
|
|
||||||
print(f"Duration: {duration:.2f} seconds")
|
|
||||||
|
|
||||||
# Log stream information
|
|
||||||
for i, stream in enumerate(file_info.get('streams', [])):
|
|
||||||
stream_type = 'Video' if stream.get('codec_name', '').startswith('h') else 'Audio'
|
|
||||||
logging.info(f"Stream {i} ({stream_type}):")
|
|
||||||
for key, value in stream.items():
|
|
||||||
logging.info(f" {key}: {value}")
|
|
||||||
|
|
||||||
# Skip if output file already exists
|
|
||||||
if output_path.exists():
|
|
||||||
output_size = output_path.stat().st_size
|
|
||||||
logging.info(f"Skipping {input_path} - output already exists: {output_path}")
|
|
||||||
logging.info(f"Output size: {format_size(output_size)}")
|
|
||||||
print(f"{Colors.YELLOW}Skipping {input_path} - output already exists{Colors.ENDC}")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Get audio labels
|
|
||||||
audio_labels = get_audio_labels(str(input_path))
|
|
||||||
logging.info(f"Audio labels: {audio_labels}")
|
|
||||||
|
|
||||||
# FFmpeg command with NVIDIA HEVC encoder and maximum quality
|
|
||||||
cmd = [
|
|
||||||
'ffmpeg',
|
|
||||||
'-v', 'verbose', # Enable verbose output
|
|
||||||
'-i', str(input_path),
|
|
||||||
'-c:v', 'hevc_nvenc',
|
|
||||||
'-gpu', gpu,
|
|
||||||
'-preset', 'p7',
|
|
||||||
'-tune', 'hq',
|
|
||||||
'-rc', 'vbr', # Variable bitrate mode
|
|
||||||
'-rc-lookahead', '32', # Increase lookahead for better VBR decisions
|
|
||||||
'-spatial-aq', '1', # Enable spatial AQ for better quality
|
|
||||||
'-aq-strength', '15', # Maximum allowed AQ strength
|
|
||||||
'-cq', '23',
|
|
||||||
'-b:v', '0', # Let VBR determine bitrate
|
|
||||||
'-c:a', 'copy',
|
|
||||||
'-map', '0',
|
|
||||||
]
|
|
||||||
# Add metadata for each audio stream if label exists
|
|
||||||
for idx, label in enumerate(audio_labels):
|
|
||||||
if label:
|
|
||||||
cmd += [f'-metadata:s:a:{idx}', f'title={label}']
|
|
||||||
cmd.append(str(output_path))
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Run FFmpeg and capture output
|
|
||||||
process = subprocess.Popen(
|
|
||||||
cmd,
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.PIPE,
|
|
||||||
universal_newlines=True
|
|
||||||
)
|
|
||||||
|
|
||||||
# Log FFmpeg output in real-time
|
|
||||||
while True:
|
|
||||||
output = process.stderr.readline()
|
|
||||||
if output == '' and process.poll() is not None:
|
|
||||||
break
|
|
||||||
if output:
|
|
||||||
logging.info(f"FFmpeg: {output.strip()}")
|
|
||||||
print(f"{Colors.PURPLE}FFmpeg: {output.strip()}{Colors.ENDC}")
|
|
||||||
|
|
||||||
if process.returncode == 0:
|
|
||||||
# Get output file info
|
|
||||||
output_info = get_file_info(str(output_path))
|
|
||||||
output_size = int(output_info['format']['size'])
|
|
||||||
compression_ratio = input_size / output_size if output_size > 0 else 0
|
|
||||||
|
|
||||||
logging.info(f"Successfully encoded: {output_path}")
|
|
||||||
logging.info(f"Output size: {format_size(output_size)}")
|
|
||||||
logging.info(f"Compression ratio: {compression_ratio:.2f}x")
|
|
||||||
print(f"{Colors.GREEN}Successfully encoded: {output_path}{Colors.ENDC}")
|
|
||||||
print(f"Compression ratio: {compression_ratio:.2f}x")
|
|
||||||
else:
|
|
||||||
logging.error(f"FFmpeg process failed with return code {process.returncode}")
|
|
||||||
print(f"{Colors.RED}FFmpeg process failed with return code {process.returncode}{Colors.ENDC}")
|
|
||||||
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
logging.error(f"Error encoding {input_path}: {e}")
|
|
||||||
print(f"{Colors.RED}Error encoding {input_path}: {e}{Colors.ENDC}")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
# Get GPU selection
|
|
||||||
gpu = get_gpu_selection()
|
|
||||||
logging.info(f"Selected GPU: {gpu}")
|
|
||||||
|
|
||||||
input_dir = "input"
|
|
||||||
output_dir = "output"
|
|
||||||
os.makedirs(output_dir, exist_ok=True)
|
|
||||||
|
|
||||||
# Get list of files to process
|
|
||||||
files = [f for f in os.listdir(input_dir) if f.endswith(('.mp4', '.DVR.mp4'))]
|
|
||||||
total_files = len(files)
|
|
||||||
|
|
||||||
if total_files == 0:
|
|
||||||
logging.info("No files to process in input directory")
|
|
||||||
print(f"{Colors.YELLOW}No files to process in input directory{Colors.ENDC}")
|
|
||||||
else:
|
|
||||||
logging.info(f"Found {total_files} files to process")
|
|
||||||
print(f"{Colors.BLUE}Found {total_files} files to process{Colors.ENDC}")
|
|
||||||
|
|
||||||
for i, file in enumerate(files, 1):
|
|
||||||
input_file = os.path.join(input_dir, file)
|
|
||||||
logging.info(f"Processing file {i}/{total_files}: {file}")
|
|
||||||
print(f"\n{Colors.BLUE}Processing file {i}/{total_files}: {file}{Colors.ENDC}")
|
|
||||||
encode_dvr(input_file, output_dir, gpu)
|
|
||||||
@@ -26,11 +26,52 @@ def get_gpu_selection():
|
|||||||
return gpu
|
return gpu
|
||||||
print(f"{Colors.RED}Invalid selection. Please try again.{Colors.ENDC}")
|
print(f"{Colors.RED}Invalid selection. Please try again.{Colors.ENDC}")
|
||||||
|
|
||||||
|
# Custom file handler that silently handles I/O errors (for network shares)
|
||||||
|
class SafeFileHandler(logging.FileHandler):
|
||||||
|
"""File handler that silently handles I/O errors during flush"""
|
||||||
|
def flush(self):
|
||||||
|
"""Override flush to silently handle I/O errors"""
|
||||||
|
try:
|
||||||
|
super().flush()
|
||||||
|
except (OSError, IOError):
|
||||||
|
# Silently ignore I/O errors (network share issues)
|
||||||
|
pass
|
||||||
|
except Exception:
|
||||||
|
# Silently ignore all other errors during flush
|
||||||
|
pass
|
||||||
|
|
||||||
|
def emit(self, record):
|
||||||
|
"""Override emit to handle errors gracefully"""
|
||||||
|
try:
|
||||||
|
super().emit(record)
|
||||||
|
except (OSError, IOError):
|
||||||
|
# Silently ignore I/O errors - we'll fall back to console output
|
||||||
|
self.handleError(record)
|
||||||
|
except Exception:
|
||||||
|
# Handle other errors
|
||||||
|
self.handleError(record)
|
||||||
|
|
||||||
|
def handleError(self, record):
|
||||||
|
"""Override to prevent error messages from being printed"""
|
||||||
|
# Don't print "--- Logging error ---" messages
|
||||||
|
pass
|
||||||
|
|
||||||
# Set up logging
|
# Set up logging
|
||||||
log_dir = "logs"
|
log_dir = "logs"
|
||||||
os.makedirs(log_dir, exist_ok=True)
|
os.makedirs(log_dir, exist_ok=True)
|
||||||
log_file = os.path.join(log_dir, f"encode_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log")
|
log_file = os.path.join(log_dir, f"encode_{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')
|
|
||||||
|
# Configure logging with custom handler that handles network share errors
|
||||||
|
handler = SafeFileHandler(log_file, mode='w', encoding='utf-8')
|
||||||
|
handler.setLevel(logging.INFO)
|
||||||
|
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
|
||||||
|
handler.setFormatter(formatter)
|
||||||
|
|
||||||
|
logger = logging.getLogger()
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
logger.addHandler(handler)
|
||||||
|
# Remove default handlers to avoid duplicate output
|
||||||
|
logger.handlers = [handler]
|
||||||
|
|
||||||
def get_file_info(input_file):
|
def get_file_info(input_file):
|
||||||
cmd = [
|
cmd = [
|
||||||
@@ -67,6 +108,58 @@ def format_size(size_bytes):
|
|||||||
size_bytes /= 1024
|
size_bytes /= 1024
|
||||||
return f"{size_bytes:.2f} TB"
|
return f"{size_bytes:.2f} TB"
|
||||||
|
|
||||||
|
def safe_log_info(message, print_msg=None):
|
||||||
|
"""Safely log info message, ensuring console output even if logging fails"""
|
||||||
|
try:
|
||||||
|
logging.info(message)
|
||||||
|
except (OSError, IOError) as e:
|
||||||
|
# Logging failed (likely network share issue) - print to console
|
||||||
|
if print_msg is None:
|
||||||
|
print(f"{Colors.YELLOW}[Logging failed: {e}] {message}{Colors.ENDC}")
|
||||||
|
else:
|
||||||
|
print(print_msg)
|
||||||
|
print(f"{Colors.YELLOW}[Logging failed: {e}]{Colors.ENDC}")
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
# Other logging errors
|
||||||
|
if print_msg is None:
|
||||||
|
print(f"{Colors.YELLOW}[Logging error: {e}] {message}{Colors.ENDC}")
|
||||||
|
else:
|
||||||
|
print(print_msg)
|
||||||
|
print(f"{Colors.YELLOW}[Logging error: {e}]{Colors.ENDC}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Always print to console if message provided
|
||||||
|
if print_msg is not None:
|
||||||
|
print(print_msg)
|
||||||
|
|
||||||
|
def safe_log_error(message, print_msg=None):
|
||||||
|
"""Safely log error message, ensuring console output even if logging fails"""
|
||||||
|
try:
|
||||||
|
logging.error(message)
|
||||||
|
except (OSError, IOError) as e:
|
||||||
|
# Logging failed (likely network share issue) - print to console
|
||||||
|
if print_msg is None:
|
||||||
|
print(f"{Colors.RED}[Logging failed: {e}] {message}{Colors.ENDC}")
|
||||||
|
else:
|
||||||
|
print(print_msg)
|
||||||
|
print(f"{Colors.RED}[Logging failed: {e}]{Colors.ENDC}")
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
# Other logging errors
|
||||||
|
if print_msg is None:
|
||||||
|
print(f"{Colors.RED}[Logging error: {e}] {message}{Colors.ENDC}")
|
||||||
|
else:
|
||||||
|
print(print_msg)
|
||||||
|
print(f"{Colors.RED}[Logging error: {e}]{Colors.ENDC}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Always print to console if message provided
|
||||||
|
if print_msg is not None:
|
||||||
|
print(print_msg)
|
||||||
|
else:
|
||||||
|
print(f"{Colors.RED}{message}{Colors.ENDC}")
|
||||||
|
|
||||||
def encode_dvr(input_file, output_dir, gpu):
|
def encode_dvr(input_file, output_dir, gpu):
|
||||||
input_path = Path(input_file)
|
input_path = Path(input_file)
|
||||||
output_path = Path(output_dir) / f"{input_path.stem}{input_path.suffix}"
|
output_path = Path(output_dir) / f"{input_path.stem}{input_path.suffix}"
|
||||||
@@ -76,9 +169,9 @@ def encode_dvr(input_file, output_dir, gpu):
|
|||||||
input_size = int(file_info['format']['size'])
|
input_size = int(file_info['format']['size'])
|
||||||
duration = float(file_info['format']['duration'])
|
duration = float(file_info['format']['duration'])
|
||||||
|
|
||||||
logging.info(f"Processing file: {input_path}")
|
safe_log_info(f"Processing file: {input_path}")
|
||||||
logging.info(f"Input size: {format_size(input_size)}")
|
safe_log_info(f"Input size: {format_size(input_size)}")
|
||||||
logging.info(f"Duration: {duration:.2f} seconds")
|
safe_log_info(f"Duration: {duration:.2f} seconds")
|
||||||
|
|
||||||
print(f"\n{Colors.BLUE}Processing file: {input_path}{Colors.ENDC}")
|
print(f"\n{Colors.BLUE}Processing file: {input_path}{Colors.ENDC}")
|
||||||
print(f"Input size: {format_size(input_size)}")
|
print(f"Input size: {format_size(input_size)}")
|
||||||
@@ -87,21 +180,21 @@ def encode_dvr(input_file, output_dir, gpu):
|
|||||||
# Log stream information
|
# Log stream information
|
||||||
for i, stream in enumerate(file_info.get('streams', [])):
|
for i, stream in enumerate(file_info.get('streams', [])):
|
||||||
stream_type = 'Video' if stream.get('codec_name', '').startswith('h') else 'Audio'
|
stream_type = 'Video' if stream.get('codec_name', '').startswith('h') else 'Audio'
|
||||||
logging.info(f"Stream {i} ({stream_type}):")
|
safe_log_info(f"Stream {i} ({stream_type}):")
|
||||||
for key, value in stream.items():
|
for key, value in stream.items():
|
||||||
logging.info(f" {key}: {value}")
|
safe_log_info(f" {key}: {value}")
|
||||||
|
|
||||||
# Skip if output file already exists
|
# Skip if output file already exists
|
||||||
if output_path.exists():
|
if output_path.exists():
|
||||||
output_size = output_path.stat().st_size
|
output_size = output_path.stat().st_size
|
||||||
logging.info(f"Skipping {input_path} - output already exists: {output_path}")
|
safe_log_info(f"Skipping {input_path} - output already exists: {output_path}")
|
||||||
logging.info(f"Output size: {format_size(output_size)}")
|
safe_log_info(f"Output size: {format_size(output_size)}")
|
||||||
print(f"{Colors.YELLOW}Skipping {input_path} - output already exists{Colors.ENDC}")
|
print(f"{Colors.YELLOW}Skipping {input_path} - output already exists{Colors.ENDC}")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Get audio labels
|
# Get audio labels
|
||||||
audio_labels = get_audio_labels(str(input_path))
|
audio_labels = get_audio_labels(str(input_path))
|
||||||
logging.info(f"Audio labels: {audio_labels}")
|
safe_log_info(f"Audio labels: {audio_labels}")
|
||||||
|
|
||||||
# FFmpeg command with NVIDIA HEVC encoder and maximum quality
|
# FFmpeg command with NVIDIA HEVC encoder and maximum quality
|
||||||
cmd = [
|
cmd = [
|
||||||
@@ -138,12 +231,23 @@ def encode_dvr(input_file, output_dir, gpu):
|
|||||||
|
|
||||||
# Log FFmpeg output in real-time
|
# Log FFmpeg output in real-time
|
||||||
while True:
|
while True:
|
||||||
|
try:
|
||||||
output = process.stderr.readline()
|
output = process.stderr.readline()
|
||||||
if output == '' and process.poll() is not None:
|
if output == '' and process.poll() is not None:
|
||||||
break
|
break
|
||||||
if output:
|
if output:
|
||||||
logging.info(f"FFmpeg: {output.strip()}")
|
text = output.strip()
|
||||||
print(f"{Colors.PURPLE}FFmpeg: {output.strip()}{Colors.ENDC}")
|
if text.startswith('frame=') or ' fps=' in text:
|
||||||
|
safe_log_info(f"Progress: {text}", f"{Colors.PURPLE}Progress: {text}{Colors.ENDC}")
|
||||||
|
else:
|
||||||
|
safe_log_info(f"FFmpeg: {text}", f"{Colors.GREEN}FFmpeg: {text}{Colors.ENDC}")
|
||||||
|
except (OSError, IOError) as e:
|
||||||
|
# I/O error reading from pipe - log it
|
||||||
|
safe_log_error(f"I/O error reading FFmpeg output: {e}")
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
# Unexpected error
|
||||||
|
safe_log_error(f"Unexpected error processing FFmpeg output: {e}")
|
||||||
|
|
||||||
if process.returncode == 0:
|
if process.returncode == 0:
|
||||||
# Get output file info
|
# Get output file info
|
||||||
@@ -151,23 +255,27 @@ def encode_dvr(input_file, output_dir, gpu):
|
|||||||
output_size = int(output_info['format']['size'])
|
output_size = int(output_info['format']['size'])
|
||||||
compression_ratio = input_size / output_size if output_size > 0 else 0
|
compression_ratio = input_size / output_size if output_size > 0 else 0
|
||||||
|
|
||||||
logging.info(f"Successfully encoded: {output_path}")
|
safe_log_info(f"Successfully encoded: {output_path}", f"{Colors.GREEN}Successfully encoded: {output_path}{Colors.ENDC}")
|
||||||
logging.info(f"Output size: {format_size(output_size)}")
|
safe_log_info(f"Output size: {format_size(output_size)}")
|
||||||
logging.info(f"Compression ratio: {compression_ratio:.2f}x")
|
safe_log_info(f"Compression ratio: {compression_ratio:.2f}x", f"Compression ratio: {compression_ratio:.2f}x")
|
||||||
print(f"{Colors.GREEN}Successfully encoded: {output_path}{Colors.ENDC}")
|
|
||||||
print(f"Compression ratio: {compression_ratio:.2f}x")
|
|
||||||
else:
|
else:
|
||||||
logging.error(f"FFmpeg process failed with return code {process.returncode}")
|
# Convert Windows error code to signed integer if needed
|
||||||
print(f"{Colors.RED}FFmpeg process failed with return code {process.returncode}{Colors.ENDC}")
|
return_code = process.returncode
|
||||||
|
if return_code > 2147483647: # If it's a large unsigned int, convert to signed
|
||||||
|
return_code = return_code - 4294967296
|
||||||
|
safe_log_error(f"FFmpeg process failed with return code {return_code}",
|
||||||
|
f"{Colors.RED}FFmpeg process failed with return code {return_code}{Colors.ENDC}")
|
||||||
|
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
logging.error(f"Error encoding {input_path}: {e}")
|
safe_log_error(f"Error encoding {input_path}: {e}", f"{Colors.RED}Error encoding {input_path}: {e}{Colors.ENDC}")
|
||||||
print(f"{Colors.RED}Error encoding {input_path}: {e}{Colors.ENDC}")
|
except Exception as e:
|
||||||
|
safe_log_error(f"Unexpected error encoding {input_path}: {type(e).__name__}: {e}",
|
||||||
|
f"{Colors.RED}Unexpected error encoding {input_path}: {e}{Colors.ENDC}")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Get GPU selection
|
# Get GPU selection
|
||||||
gpu = get_gpu_selection()
|
gpu = get_gpu_selection()
|
||||||
logging.info(f"Selected GPU: {gpu}")
|
safe_log_info(f"Selected GPU: {gpu}")
|
||||||
|
|
||||||
input_dir = "input"
|
input_dir = "input"
|
||||||
output_dir = "output"
|
output_dir = "output"
|
||||||
@@ -178,14 +286,12 @@ if __name__ == "__main__":
|
|||||||
total_files = len(files)
|
total_files = len(files)
|
||||||
|
|
||||||
if total_files == 0:
|
if total_files == 0:
|
||||||
logging.info("No files to process in input directory")
|
safe_log_info("No files to process in input directory", f"{Colors.YELLOW}No files to process in input directory{Colors.ENDC}")
|
||||||
print(f"{Colors.YELLOW}No files to process in input directory{Colors.ENDC}")
|
|
||||||
else:
|
else:
|
||||||
logging.info(f"Found {total_files} files to process")
|
safe_log_info(f"Found {total_files} files to process", f"{Colors.BLUE}Found {total_files} files to process{Colors.ENDC}")
|
||||||
print(f"{Colors.BLUE}Found {total_files} files to process{Colors.ENDC}")
|
|
||||||
|
|
||||||
for i, file in enumerate(files, 1):
|
for i, file in enumerate(files, 1):
|
||||||
input_file = os.path.join(input_dir, file)
|
input_file = os.path.join(input_dir, file)
|
||||||
logging.info(f"Processing file {i}/{total_files}: {file}")
|
safe_log_info(f"Processing file {i}/{total_files}: {file}")
|
||||||
print(f"\n{Colors.BLUE}Processing file {i}/{total_files}: {file}{Colors.ENDC}")
|
print(f"\n{Colors.BLUE}Processing file {i}/{total_files}: {file}{Colors.ENDC}")
|
||||||
encode_dvr(input_file, output_dir, gpu)
|
encode_dvr(input_file, output_dir, gpu)
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
@echo off
|
|
||||||
python encode_dvr_lossless.py
|
|
||||||
pause
|
|
||||||
@@ -1,191 +0,0 @@
|
|||||||
import os
|
|
||||||
import subprocess
|
|
||||||
from pathlib import Path
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
from datetime import datetime
|
|
||||||
import shutil
|
|
||||||
|
|
||||||
# ANSI color codes
|
|
||||||
class Colors:
|
|
||||||
PURPLE = '\033[95m'
|
|
||||||
BLUE = '\033[94m'
|
|
||||||
GREEN = '\033[92m'
|
|
||||||
YELLOW = '\033[93m'
|
|
||||||
RED = '\033[91m'
|
|
||||||
ENDC = '\033[0m'
|
|
||||||
|
|
||||||
def get_gpu_selection():
|
|
||||||
while True:
|
|
||||||
print(f"\n{Colors.BLUE}Select GPU slot:{Colors.ENDC}")
|
|
||||||
print("0 - First GPU")
|
|
||||||
print("1 - Second GPU")
|
|
||||||
print("2 - Third GPU")
|
|
||||||
gpu = input(f"{Colors.YELLOW}Enter GPU number (0-2):{Colors.ENDC} ").strip()
|
|
||||||
if gpu in ['0', '1', '2']:
|
|
||||||
return gpu
|
|
||||||
print(f"{Colors.RED}Invalid selection. Please try again.{Colors.ENDC}")
|
|
||||||
|
|
||||||
# Set up logging
|
|
||||||
log_dir = "logs"
|
|
||||||
os.makedirs(log_dir, exist_ok=True)
|
|
||||||
log_file = os.path.join(log_dir, f"encode_{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 get_file_info(input_file):
|
|
||||||
cmd = [
|
|
||||||
'ffprobe',
|
|
||||||
'-v', 'error',
|
|
||||||
'-show_entries', 'format=duration,size:stream=codec_name,width,height,r_frame_rate,channels,channel_layout',
|
|
||||||
'-of', 'json',
|
|
||||||
input_file
|
|
||||||
]
|
|
||||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
||||||
return json.loads(result.stdout)
|
|
||||||
|
|
||||||
def get_audio_labels(input_file):
|
|
||||||
cmd = [
|
|
||||||
'ffprobe',
|
|
||||||
'-v', 'error',
|
|
||||||
'-select_streams', 'a',
|
|
||||||
'-show_entries', 'stream=index:stream_tags=title',
|
|
||||||
'-of', 'json',
|
|
||||||
input_file
|
|
||||||
]
|
|
||||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
||||||
info = json.loads(result.stdout)
|
|
||||||
labels = []
|
|
||||||
for stream in info.get('streams', []):
|
|
||||||
title = stream.get('tags', {}).get('title', None)
|
|
||||||
labels.append(title)
|
|
||||||
return labels
|
|
||||||
|
|
||||||
def format_size(size_bytes):
|
|
||||||
for unit in ['B', 'KB', 'MB', 'GB']:
|
|
||||||
if size_bytes < 1024:
|
|
||||||
return f"{size_bytes:.2f} {unit}"
|
|
||||||
size_bytes /= 1024
|
|
||||||
return f"{size_bytes:.2f} TB"
|
|
||||||
|
|
||||||
def encode_dvr(input_file, output_dir, gpu):
|
|
||||||
input_path = Path(input_file)
|
|
||||||
output_path = Path(output_dir) / f"{input_path.stem}{input_path.suffix}"
|
|
||||||
|
|
||||||
# Get file info for logging
|
|
||||||
file_info = get_file_info(str(input_path))
|
|
||||||
input_size = int(file_info['format']['size'])
|
|
||||||
duration = float(file_info['format']['duration'])
|
|
||||||
|
|
||||||
logging.info(f"Processing file: {input_path}")
|
|
||||||
logging.info(f"Input size: {format_size(input_size)}")
|
|
||||||
logging.info(f"Duration: {duration:.2f} seconds")
|
|
||||||
|
|
||||||
print(f"\n{Colors.BLUE}Processing file: {input_path}{Colors.ENDC}")
|
|
||||||
print(f"Input size: {format_size(input_size)}")
|
|
||||||
print(f"Duration: {duration:.2f} seconds")
|
|
||||||
|
|
||||||
# Log stream information
|
|
||||||
for i, stream in enumerate(file_info.get('streams', [])):
|
|
||||||
stream_type = 'Video' if stream.get('codec_name', '').startswith('h') else 'Audio'
|
|
||||||
logging.info(f"Stream {i} ({stream_type}):")
|
|
||||||
for key, value in stream.items():
|
|
||||||
logging.info(f" {key}: {value}")
|
|
||||||
|
|
||||||
# Skip if output file already exists
|
|
||||||
if output_path.exists():
|
|
||||||
output_size = output_path.stat().st_size
|
|
||||||
logging.info(f"Skipping {input_path} - output already exists: {output_path}")
|
|
||||||
logging.info(f"Output size: {format_size(output_size)}")
|
|
||||||
print(f"{Colors.YELLOW}Skipping {input_path} - output already exists{Colors.ENDC}")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Get audio labels
|
|
||||||
audio_labels = get_audio_labels(str(input_path))
|
|
||||||
logging.info(f"Audio labels: {audio_labels}")
|
|
||||||
|
|
||||||
# FFmpeg command with NVIDIA HEVC encoder and maximum quality
|
|
||||||
cmd = [
|
|
||||||
'ffmpeg',
|
|
||||||
'-v', 'verbose', # Enable verbose output
|
|
||||||
'-i', str(input_path),
|
|
||||||
'-c:v', 'hevc_nvenc',
|
|
||||||
'-gpu', gpu,
|
|
||||||
'-preset', 'p7',
|
|
||||||
'-tune', 'hq',
|
|
||||||
'-rc', 'vbr', # Variable bitrate mode
|
|
||||||
'-rc-lookahead', '32', # Increase lookahead for better VBR decisions
|
|
||||||
'-spatial-aq', '1', # Enable spatial AQ for better quality
|
|
||||||
'-aq-strength', '15', # Maximum allowed AQ strength
|
|
||||||
'-cq', '0',
|
|
||||||
'-b:v', '0', # Let VBR determine bitrate
|
|
||||||
'-c:a', 'copy',
|
|
||||||
'-map', '0',
|
|
||||||
]
|
|
||||||
# Add metadata for each audio stream if label exists
|
|
||||||
for idx, label in enumerate(audio_labels):
|
|
||||||
if label:
|
|
||||||
cmd += [f'-metadata:s:a:{idx}', f'title={label}']
|
|
||||||
cmd.append(str(output_path))
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Run FFmpeg and capture output
|
|
||||||
process = subprocess.Popen(
|
|
||||||
cmd,
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.PIPE,
|
|
||||||
universal_newlines=True
|
|
||||||
)
|
|
||||||
|
|
||||||
# Log FFmpeg output in real-time
|
|
||||||
while True:
|
|
||||||
output = process.stderr.readline()
|
|
||||||
if output == '' and process.poll() is not None:
|
|
||||||
break
|
|
||||||
if output:
|
|
||||||
logging.info(f"FFmpeg: {output.strip()}")
|
|
||||||
print(f"{Colors.PURPLE}FFmpeg: {output.strip()}{Colors.ENDC}")
|
|
||||||
|
|
||||||
if process.returncode == 0:
|
|
||||||
# Get output file info
|
|
||||||
output_info = get_file_info(str(output_path))
|
|
||||||
output_size = int(output_info['format']['size'])
|
|
||||||
compression_ratio = input_size / output_size if output_size > 0 else 0
|
|
||||||
|
|
||||||
logging.info(f"Successfully encoded: {output_path}")
|
|
||||||
logging.info(f"Output size: {format_size(output_size)}")
|
|
||||||
logging.info(f"Compression ratio: {compression_ratio:.2f}x")
|
|
||||||
print(f"{Colors.GREEN}Successfully encoded: {output_path}{Colors.ENDC}")
|
|
||||||
print(f"Compression ratio: {compression_ratio:.2f}x")
|
|
||||||
else:
|
|
||||||
logging.error(f"FFmpeg process failed with return code {process.returncode}")
|
|
||||||
print(f"{Colors.RED}FFmpeg process failed with return code {process.returncode}{Colors.ENDC}")
|
|
||||||
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
logging.error(f"Error encoding {input_path}: {e}")
|
|
||||||
print(f"{Colors.RED}Error encoding {input_path}: {e}{Colors.ENDC}")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
# Get GPU selection
|
|
||||||
gpu = get_gpu_selection()
|
|
||||||
logging.info(f"Selected GPU: {gpu}")
|
|
||||||
|
|
||||||
input_dir = "input"
|
|
||||||
output_dir = "output"
|
|
||||||
os.makedirs(output_dir, exist_ok=True)
|
|
||||||
|
|
||||||
# Get list of files to process
|
|
||||||
files = [f for f in os.listdir(input_dir) if f.endswith(('.mp4', '.DVR.mp4'))]
|
|
||||||
total_files = len(files)
|
|
||||||
|
|
||||||
if total_files == 0:
|
|
||||||
logging.info("No files to process in input directory")
|
|
||||||
print(f"{Colors.YELLOW}No files to process in input directory{Colors.ENDC}")
|
|
||||||
else:
|
|
||||||
logging.info(f"Found {total_files} files to process")
|
|
||||||
print(f"{Colors.BLUE}Found {total_files} files to process{Colors.ENDC}")
|
|
||||||
|
|
||||||
for i, file in enumerate(files, 1):
|
|
||||||
input_file = os.path.join(input_dir, file)
|
|
||||||
logging.info(f"Processing file {i}/{total_files}: {file}")
|
|
||||||
print(f"\n{Colors.BLUE}Processing file {i}/{total_files}: {file}{Colors.ENDC}")
|
|
||||||
encode_dvr(input_file, output_dir, gpu)
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
@echo off
|
|
||||||
python encode_dvr_lossless_maxrate.py
|
|
||||||
pause
|
|
||||||
@@ -1,192 +0,0 @@
|
|||||||
import os
|
|
||||||
import subprocess
|
|
||||||
from pathlib import Path
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
from datetime import datetime
|
|
||||||
import shutil
|
|
||||||
|
|
||||||
# ANSI color codes
|
|
||||||
class Colors:
|
|
||||||
PURPLE = '\033[95m'
|
|
||||||
BLUE = '\033[94m'
|
|
||||||
GREEN = '\033[92m'
|
|
||||||
YELLOW = '\033[93m'
|
|
||||||
RED = '\033[91m'
|
|
||||||
ENDC = '\033[0m'
|
|
||||||
|
|
||||||
def get_gpu_selection():
|
|
||||||
while True:
|
|
||||||
print(f"\n{Colors.BLUE}Select GPU slot:{Colors.ENDC}")
|
|
||||||
print("0 - First GPU")
|
|
||||||
print("1 - Second GPU")
|
|
||||||
print("2 - Third GPU")
|
|
||||||
gpu = input(f"{Colors.YELLOW}Enter GPU number (0-2):{Colors.ENDC} ").strip()
|
|
||||||
if gpu in ['0', '1', '2']:
|
|
||||||
return gpu
|
|
||||||
print(f"{Colors.RED}Invalid selection. Please try again.{Colors.ENDC}")
|
|
||||||
|
|
||||||
# Set up logging
|
|
||||||
log_dir = "logs"
|
|
||||||
os.makedirs(log_dir, exist_ok=True)
|
|
||||||
log_file = os.path.join(log_dir, f"encode_{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 get_file_info(input_file):
|
|
||||||
cmd = [
|
|
||||||
'ffprobe',
|
|
||||||
'-v', 'error',
|
|
||||||
'-show_entries', 'format=duration,size:stream=codec_name,width,height,r_frame_rate,channels,channel_layout',
|
|
||||||
'-of', 'json',
|
|
||||||
input_file
|
|
||||||
]
|
|
||||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
||||||
return json.loads(result.stdout)
|
|
||||||
|
|
||||||
def get_audio_labels(input_file):
|
|
||||||
cmd = [
|
|
||||||
'ffprobe',
|
|
||||||
'-v', 'error',
|
|
||||||
'-select_streams', 'a',
|
|
||||||
'-show_entries', 'stream=index:stream_tags=title',
|
|
||||||
'-of', 'json',
|
|
||||||
input_file
|
|
||||||
]
|
|
||||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
||||||
info = json.loads(result.stdout)
|
|
||||||
labels = []
|
|
||||||
for stream in info.get('streams', []):
|
|
||||||
title = stream.get('tags', {}).get('title', None)
|
|
||||||
labels.append(title)
|
|
||||||
return labels
|
|
||||||
|
|
||||||
def format_size(size_bytes):
|
|
||||||
for unit in ['B', 'KB', 'MB', 'GB']:
|
|
||||||
if size_bytes < 1024:
|
|
||||||
return f"{size_bytes:.2f} {unit}"
|
|
||||||
size_bytes /= 1024
|
|
||||||
return f"{size_bytes:.2f} TB"
|
|
||||||
|
|
||||||
def encode_dvr(input_file, output_dir, gpu):
|
|
||||||
input_path = Path(input_file)
|
|
||||||
output_path = Path(output_dir) / f"{input_path.stem}{input_path.suffix}"
|
|
||||||
|
|
||||||
# Get file info for logging
|
|
||||||
file_info = get_file_info(str(input_path))
|
|
||||||
input_size = int(file_info['format']['size'])
|
|
||||||
duration = float(file_info['format']['duration'])
|
|
||||||
|
|
||||||
logging.info(f"Processing file: {input_path}")
|
|
||||||
logging.info(f"Input size: {format_size(input_size)}")
|
|
||||||
logging.info(f"Duration: {duration:.2f} seconds")
|
|
||||||
|
|
||||||
print(f"\n{Colors.BLUE}Processing file: {input_path}{Colors.ENDC}")
|
|
||||||
print(f"Input size: {format_size(input_size)}")
|
|
||||||
print(f"Duration: {duration:.2f} seconds")
|
|
||||||
|
|
||||||
# Log stream information
|
|
||||||
for i, stream in enumerate(file_info.get('streams', [])):
|
|
||||||
stream_type = 'Video' if stream.get('codec_name', '').startswith('h') else 'Audio'
|
|
||||||
logging.info(f"Stream {i} ({stream_type}):")
|
|
||||||
for key, value in stream.items():
|
|
||||||
logging.info(f" {key}: {value}")
|
|
||||||
|
|
||||||
# Skip if output file already exists
|
|
||||||
if output_path.exists():
|
|
||||||
output_size = output_path.stat().st_size
|
|
||||||
logging.info(f"Skipping {input_path} - output already exists: {output_path}")
|
|
||||||
logging.info(f"Output size: {format_size(output_size)}")
|
|
||||||
print(f"{Colors.YELLOW}Skipping {input_path} - output already exists{Colors.ENDC}")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Get audio labels
|
|
||||||
audio_labels = get_audio_labels(str(input_path))
|
|
||||||
logging.info(f"Audio labels: {audio_labels}")
|
|
||||||
|
|
||||||
# FFmpeg command with NVIDIA HEVC encoder and maximum quality
|
|
||||||
cmd = [
|
|
||||||
'ffmpeg',
|
|
||||||
'-v', 'verbose',
|
|
||||||
'-i', str(input_path),
|
|
||||||
'-c:v', 'hevc_nvenc',
|
|
||||||
'-gpu', gpu,
|
|
||||||
'-preset', 'p7',
|
|
||||||
'-tune', 'hq',
|
|
||||||
'-rc', 'vbr',
|
|
||||||
'-rc-lookahead', '32',
|
|
||||||
'-spatial-aq', '1',
|
|
||||||
'-aq-strength', '15',
|
|
||||||
'-cq', '0',
|
|
||||||
'-b:v', '0',
|
|
||||||
'-maxrate', '5500k',
|
|
||||||
'-c:a', 'copy',
|
|
||||||
'-map', '0',
|
|
||||||
]
|
|
||||||
# Add metadata for each audio stream if label exists
|
|
||||||
for idx, label in enumerate(audio_labels):
|
|
||||||
if label:
|
|
||||||
cmd += [f'-metadata:s:a:{idx}', f'title={label}']
|
|
||||||
cmd.append(str(output_path))
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Run FFmpeg and capture output
|
|
||||||
process = subprocess.Popen(
|
|
||||||
cmd,
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.PIPE,
|
|
||||||
universal_newlines=True
|
|
||||||
)
|
|
||||||
|
|
||||||
# Log FFmpeg output in real-time
|
|
||||||
while True:
|
|
||||||
output = process.stderr.readline()
|
|
||||||
if output == '' and process.poll() is not None:
|
|
||||||
break
|
|
||||||
if output:
|
|
||||||
logging.info(f"FFmpeg: {output.strip()}")
|
|
||||||
print(f"{Colors.PURPLE}FFmpeg: {output.strip()}{Colors.ENDC}")
|
|
||||||
|
|
||||||
if process.returncode == 0:
|
|
||||||
# Get output file info
|
|
||||||
output_info = get_file_info(str(output_path))
|
|
||||||
output_size = int(output_info['format']['size'])
|
|
||||||
compression_ratio = input_size / output_size if output_size > 0 else 0
|
|
||||||
|
|
||||||
logging.info(f"Successfully encoded: {output_path}")
|
|
||||||
logging.info(f"Output size: {format_size(output_size)}")
|
|
||||||
logging.info(f"Compression ratio: {compression_ratio:.2f}x")
|
|
||||||
print(f"{Colors.GREEN}Successfully encoded: {output_path}{Colors.ENDC}")
|
|
||||||
print(f"Compression ratio: {compression_ratio:.2f}x")
|
|
||||||
else:
|
|
||||||
logging.error(f"FFmpeg process failed with return code {process.returncode}")
|
|
||||||
print(f"{Colors.RED}FFmpeg process failed with return code {process.returncode}{Colors.ENDC}")
|
|
||||||
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
logging.error(f"Error encoding {input_path}: {e}")
|
|
||||||
print(f"{Colors.RED}Error encoding {input_path}: {e}{Colors.ENDC}")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
# Get GPU selection
|
|
||||||
gpu = get_gpu_selection()
|
|
||||||
logging.info(f"Selected GPU: {gpu}")
|
|
||||||
|
|
||||||
input_dir = "input"
|
|
||||||
output_dir = "output"
|
|
||||||
os.makedirs(output_dir, exist_ok=True)
|
|
||||||
|
|
||||||
# Get list of files to process
|
|
||||||
files = [f for f in os.listdir(input_dir) if f.endswith(('.mp4', '.DVR.mp4'))]
|
|
||||||
total_files = len(files)
|
|
||||||
|
|
||||||
if total_files == 0:
|
|
||||||
logging.info("No files to process in input directory")
|
|
||||||
print(f"{Colors.YELLOW}No files to process in input directory{Colors.ENDC}")
|
|
||||||
else:
|
|
||||||
logging.info(f"Found {total_files} files to process")
|
|
||||||
print(f"{Colors.BLUE}Found {total_files} files to process{Colors.ENDC}")
|
|
||||||
|
|
||||||
for i, file in enumerate(files, 1):
|
|
||||||
input_file = os.path.join(input_dir, file)
|
|
||||||
logging.info(f"Processing file {i}/{total_files}: {file}")
|
|
||||||
print(f"\n{Colors.BLUE}Processing file {i}/{total_files}: {file}{Colors.ENDC}")
|
|
||||||
encode_dvr(input_file, output_dir, gpu)
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
@echo off
|
|
||||||
python encode_dvr_lossless_vp9.py
|
|
||||||
pause
|
|
||||||
@@ -1,191 +0,0 @@
|
|||||||
import os
|
|
||||||
import subprocess
|
|
||||||
from pathlib import Path
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
from datetime import datetime
|
|
||||||
import shutil
|
|
||||||
|
|
||||||
# ANSI color codes
|
|
||||||
class Colors:
|
|
||||||
PURPLE = '\033[95m'
|
|
||||||
BLUE = '\033[94m'
|
|
||||||
GREEN = '\033[92m'
|
|
||||||
YELLOW = '\033[93m'
|
|
||||||
RED = '\033[91m'
|
|
||||||
ENDC = '\033[0m'
|
|
||||||
|
|
||||||
def get_gpu_selection():
|
|
||||||
while True:
|
|
||||||
print(f"\n{Colors.BLUE}Select GPU slot:{Colors.ENDC}")
|
|
||||||
print("0 - First GPU")
|
|
||||||
print("1 - Second GPU")
|
|
||||||
print("2 - Third GPU")
|
|
||||||
gpu = input(f"{Colors.YELLOW}Enter GPU number (0-2):{Colors.ENDC} ").strip()
|
|
||||||
if gpu in ['0', '1', '2']:
|
|
||||||
return gpu
|
|
||||||
print(f"{Colors.RED}Invalid selection. Please try again.{Colors.ENDC}")
|
|
||||||
|
|
||||||
# Set up logging
|
|
||||||
log_dir = "logs"
|
|
||||||
os.makedirs(log_dir, exist_ok=True)
|
|
||||||
log_file = os.path.join(log_dir, f"encode_{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 get_file_info(input_file):
|
|
||||||
cmd = [
|
|
||||||
'ffprobe',
|
|
||||||
'-v', 'error',
|
|
||||||
'-show_entries', 'format=duration,size:stream=codec_name,width,height,r_frame_rate,channels,channel_layout',
|
|
||||||
'-of', 'json',
|
|
||||||
input_file
|
|
||||||
]
|
|
||||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
||||||
return json.loads(result.stdout)
|
|
||||||
|
|
||||||
def get_audio_labels(input_file):
|
|
||||||
cmd = [
|
|
||||||
'ffprobe',
|
|
||||||
'-v', 'error',
|
|
||||||
'-select_streams', 'a',
|
|
||||||
'-show_entries', 'stream=index:stream_tags=title',
|
|
||||||
'-of', 'json',
|
|
||||||
input_file
|
|
||||||
]
|
|
||||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
||||||
info = json.loads(result.stdout)
|
|
||||||
labels = []
|
|
||||||
for stream in info.get('streams', []):
|
|
||||||
title = stream.get('tags', {}).get('title', None)
|
|
||||||
labels.append(title)
|
|
||||||
return labels
|
|
||||||
|
|
||||||
def format_size(size_bytes):
|
|
||||||
for unit in ['B', 'KB', 'MB', 'GB']:
|
|
||||||
if size_bytes < 1024:
|
|
||||||
return f"{size_bytes:.2f} {unit}"
|
|
||||||
size_bytes /= 1024
|
|
||||||
return f"{size_bytes:.2f} TB"
|
|
||||||
|
|
||||||
def encode_dvr(input_file, output_dir, gpu):
|
|
||||||
input_path = Path(input_file)
|
|
||||||
output_path = Path(output_dir) / f"{input_path.stem}{input_path.suffix}"
|
|
||||||
|
|
||||||
# Get file info for logging
|
|
||||||
file_info = get_file_info(str(input_path))
|
|
||||||
input_size = int(file_info['format']['size'])
|
|
||||||
duration = float(file_info['format']['duration'])
|
|
||||||
|
|
||||||
logging.info(f"Processing file: {input_path}")
|
|
||||||
logging.info(f"Input size: {format_size(input_size)}")
|
|
||||||
logging.info(f"Duration: {duration:.2f} seconds")
|
|
||||||
|
|
||||||
print(f"\n{Colors.BLUE}Processing file: {input_path}{Colors.ENDC}")
|
|
||||||
print(f"Input size: {format_size(input_size)}")
|
|
||||||
print(f"Duration: {duration:.2f} seconds")
|
|
||||||
|
|
||||||
# Log stream information
|
|
||||||
for i, stream in enumerate(file_info.get('streams', [])):
|
|
||||||
stream_type = 'Video' if stream.get('codec_name', '').startswith('h') else 'Audio'
|
|
||||||
logging.info(f"Stream {i} ({stream_type}):")
|
|
||||||
for key, value in stream.items():
|
|
||||||
logging.info(f" {key}: {value}")
|
|
||||||
|
|
||||||
# Skip if output file already exists
|
|
||||||
if output_path.exists():
|
|
||||||
output_size = output_path.stat().st_size
|
|
||||||
logging.info(f"Skipping {input_path} - output already exists: {output_path}")
|
|
||||||
logging.info(f"Output size: {format_size(output_size)}")
|
|
||||||
print(f"{Colors.YELLOW}Skipping {input_path} - output already exists{Colors.ENDC}")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Get audio labels
|
|
||||||
audio_labels = get_audio_labels(str(input_path))
|
|
||||||
logging.info(f"Audio labels: {audio_labels}")
|
|
||||||
|
|
||||||
# FFmpeg command for high-quality VP9 encoding
|
|
||||||
cmd = [
|
|
||||||
'ffmpeg',
|
|
||||||
'-v', 'verbose',
|
|
||||||
'-i', str(input_path),
|
|
||||||
'-c:v', 'libvpx-vp9',
|
|
||||||
'-crf', '0', # Extremely high quality (0=lossless, 5-10=extremely high)
|
|
||||||
'-b:v', '10000k',
|
|
||||||
'-maxrate', '10000k',
|
|
||||||
'-cpu-used', '0', # Slowest encoding for highest quality
|
|
||||||
'-row-mt', '1',
|
|
||||||
'-threads', '0',
|
|
||||||
'-tile-columns', '2', # Better quality for 1080p
|
|
||||||
'-frame-parallel', '1', # Parallel frame processing
|
|
||||||
'-lag-in-frames', '25', # More frames for better compression
|
|
||||||
'-c:a', 'copy',
|
|
||||||
'-map', '0',
|
|
||||||
]
|
|
||||||
# Add metadata for each audio stream if label exists
|
|
||||||
for idx, label in enumerate(audio_labels):
|
|
||||||
if label:
|
|
||||||
cmd += [f'-metadata:s:a:{idx}', f'title={label}']
|
|
||||||
cmd.append(str(output_path))
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Run FFmpeg and capture output
|
|
||||||
process = subprocess.Popen(
|
|
||||||
cmd,
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.PIPE,
|
|
||||||
universal_newlines=True
|
|
||||||
)
|
|
||||||
|
|
||||||
# Log FFmpeg output in real-time
|
|
||||||
while True:
|
|
||||||
output = process.stderr.readline()
|
|
||||||
if output == '' and process.poll() is not None:
|
|
||||||
break
|
|
||||||
if output:
|
|
||||||
logging.info(f"FFmpeg: {output.strip()}")
|
|
||||||
print(f"{Colors.PURPLE}FFmpeg: {output.strip()}{Colors.ENDC}")
|
|
||||||
|
|
||||||
if process.returncode == 0:
|
|
||||||
# Get output file info
|
|
||||||
output_info = get_file_info(str(output_path))
|
|
||||||
output_size = int(output_info['format']['size'])
|
|
||||||
compression_ratio = input_size / output_size if output_size > 0 else 0
|
|
||||||
|
|
||||||
logging.info(f"Successfully encoded: {output_path}")
|
|
||||||
logging.info(f"Output size: {format_size(output_size)}")
|
|
||||||
logging.info(f"Compression ratio: {compression_ratio:.2f}x")
|
|
||||||
print(f"{Colors.GREEN}Successfully encoded: {output_path}{Colors.ENDC}")
|
|
||||||
print(f"Compression ratio: {compression_ratio:.2f}x")
|
|
||||||
else:
|
|
||||||
logging.error(f"FFmpeg process failed with return code {process.returncode}")
|
|
||||||
print(f"{Colors.RED}FFmpeg process failed with return code {process.returncode}{Colors.ENDC}")
|
|
||||||
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
logging.error(f"Error encoding {input_path}: {e}")
|
|
||||||
print(f"{Colors.RED}Error encoding {input_path}: {e}{Colors.ENDC}")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
# Get GPU selection
|
|
||||||
gpu = get_gpu_selection()
|
|
||||||
logging.info(f"Selected GPU: {gpu}")
|
|
||||||
|
|
||||||
input_dir = "input"
|
|
||||||
output_dir = "output"
|
|
||||||
os.makedirs(output_dir, exist_ok=True)
|
|
||||||
|
|
||||||
# Get list of files to process
|
|
||||||
files = [f for f in os.listdir(input_dir) if f.endswith(('.mp4', '.DVR.mp4'))]
|
|
||||||
total_files = len(files)
|
|
||||||
|
|
||||||
if total_files == 0:
|
|
||||||
logging.info("No files to process in input directory")
|
|
||||||
print(f"{Colors.YELLOW}No files to process in input directory{Colors.ENDC}")
|
|
||||||
else:
|
|
||||||
logging.info(f"Found {total_files} files to process")
|
|
||||||
print(f"{Colors.BLUE}Found {total_files} files to process{Colors.ENDC}")
|
|
||||||
|
|
||||||
for i, file in enumerate(files, 1):
|
|
||||||
input_file = os.path.join(input_dir, file)
|
|
||||||
logging.info(f"Processing file {i}/{total_files}: {file}")
|
|
||||||
print(f"\n{Colors.BLUE}Processing file {i}/{total_files}: {file}{Colors.ENDC}")
|
|
||||||
encode_dvr(input_file, output_dir, gpu)
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
@echo off
|
|
||||||
python encode_vtubrec2500.py
|
|
||||||
pause
|
|
||||||
@@ -1,193 +0,0 @@
|
|||||||
import os
|
|
||||||
import subprocess
|
|
||||||
from pathlib import Path
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
from datetime import datetime
|
|
||||||
import shutil
|
|
||||||
|
|
||||||
# ANSI color codes
|
|
||||||
class Colors:
|
|
||||||
PURPLE = '\033[95m'
|
|
||||||
BLUE = '\033[94m'
|
|
||||||
GREEN = '\033[92m'
|
|
||||||
YELLOW = '\033[93m'
|
|
||||||
RED = '\033[91m'
|
|
||||||
ENDC = '\033[0m'
|
|
||||||
|
|
||||||
def get_gpu_selection():
|
|
||||||
while True:
|
|
||||||
print(f"\n{Colors.BLUE}Select GPU slot:{Colors.ENDC}")
|
|
||||||
print("0 - First GPU")
|
|
||||||
print("1 - Second GPU")
|
|
||||||
print("2 - Third GPU")
|
|
||||||
gpu = input(f"{Colors.YELLOW}Enter GPU number (0-2):{Colors.ENDC} ").strip()
|
|
||||||
if gpu in ['0', '1', '2']:
|
|
||||||
return gpu
|
|
||||||
print(f"{Colors.RED}Invalid selection. Please try again.{Colors.ENDC}")
|
|
||||||
|
|
||||||
# Set up logging
|
|
||||||
log_dir = "logs"
|
|
||||||
os.makedirs(log_dir, exist_ok=True)
|
|
||||||
log_file = os.path.join(log_dir, f"encode_{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 get_file_info(input_file):
|
|
||||||
cmd = [
|
|
||||||
'ffprobe',
|
|
||||||
'-v', 'error',
|
|
||||||
'-show_entries', 'format=duration,size:stream=codec_name,width,height,r_frame_rate,channels,channel_layout',
|
|
||||||
'-of', 'json',
|
|
||||||
input_file
|
|
||||||
]
|
|
||||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
||||||
return json.loads(result.stdout)
|
|
||||||
|
|
||||||
def get_audio_labels(input_file):
|
|
||||||
cmd = [
|
|
||||||
'ffprobe',
|
|
||||||
'-v', 'error',
|
|
||||||
'-select_streams', 'a',
|
|
||||||
'-show_entries', 'stream=index:stream_tags=title',
|
|
||||||
'-of', 'json',
|
|
||||||
input_file
|
|
||||||
]
|
|
||||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
||||||
info = json.loads(result.stdout)
|
|
||||||
labels = []
|
|
||||||
for stream in info.get('streams', []):
|
|
||||||
title = stream.get('tags', {}).get('title', None)
|
|
||||||
labels.append(title)
|
|
||||||
return labels
|
|
||||||
|
|
||||||
def format_size(size_bytes):
|
|
||||||
for unit in ['B', 'KB', 'MB', 'GB']:
|
|
||||||
if size_bytes < 1024:
|
|
||||||
return f"{size_bytes:.2f} {unit}"
|
|
||||||
size_bytes /= 1024
|
|
||||||
return f"{size_bytes:.2f} TB"
|
|
||||||
|
|
||||||
def encode_dvr(input_file, output_dir, gpu):
|
|
||||||
input_path = Path(input_file)
|
|
||||||
output_path = Path(output_dir) / f"{input_path.stem}{input_path.suffix}"
|
|
||||||
|
|
||||||
# Get file info for logging
|
|
||||||
file_info = get_file_info(str(input_path))
|
|
||||||
input_size = int(file_info['format']['size'])
|
|
||||||
duration = float(file_info['format']['duration'])
|
|
||||||
|
|
||||||
logging.info(f"Processing file: {input_path}")
|
|
||||||
logging.info(f"Input size: {format_size(input_size)}")
|
|
||||||
logging.info(f"Duration: {duration:.2f} seconds")
|
|
||||||
|
|
||||||
print(f"\n{Colors.BLUE}Processing file: {input_path}{Colors.ENDC}")
|
|
||||||
print(f"Input size: {format_size(input_size)}")
|
|
||||||
print(f"Duration: {duration:.2f} seconds")
|
|
||||||
|
|
||||||
# Log stream information
|
|
||||||
for i, stream in enumerate(file_info.get('streams', [])):
|
|
||||||
stream_type = 'Video' if stream.get('codec_name', '').startswith('h') else 'Audio'
|
|
||||||
logging.info(f"Stream {i} ({stream_type}):")
|
|
||||||
for key, value in stream.items():
|
|
||||||
logging.info(f" {key}: {value}")
|
|
||||||
|
|
||||||
# Skip if output file already exists
|
|
||||||
if output_path.exists():
|
|
||||||
output_size = output_path.stat().st_size
|
|
||||||
logging.info(f"Skipping {input_path} - output already exists: {output_path}")
|
|
||||||
logging.info(f"Output size: {format_size(output_size)}")
|
|
||||||
print(f"{Colors.YELLOW}Skipping {input_path} - output already exists{Colors.ENDC}")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Get audio labels
|
|
||||||
audio_labels = get_audio_labels(str(input_path))
|
|
||||||
logging.info(f"Audio labels: {audio_labels}")
|
|
||||||
|
|
||||||
# FFmpeg command with NVIDIA HEVC encoder and maximum quality
|
|
||||||
cmd = [
|
|
||||||
'ffmpeg',
|
|
||||||
'-v', 'verbose',
|
|
||||||
'-i', str(input_path),
|
|
||||||
'-c:v', 'hevc_nvenc',
|
|
||||||
'-gpu', gpu,
|
|
||||||
'-preset', 'p7',
|
|
||||||
'-tune', 'hq',
|
|
||||||
'-rc', 'vbr',
|
|
||||||
'-rc-lookahead', '32',
|
|
||||||
'-spatial-aq', '1',
|
|
||||||
'-aq-strength', '15',
|
|
||||||
'-cq', '26',
|
|
||||||
'-b:v', '2500k',
|
|
||||||
'-maxrate', '2500k',
|
|
||||||
'-bufsize', '5000k',
|
|
||||||
'-c:a', 'copy',
|
|
||||||
'-map', '0',
|
|
||||||
]
|
|
||||||
# Add metadata for each audio stream if label exists
|
|
||||||
for idx, label in enumerate(audio_labels):
|
|
||||||
if label:
|
|
||||||
cmd += [f'-metadata:s:a:{idx}', f'title={label}']
|
|
||||||
cmd.append(str(output_path))
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Run FFmpeg and capture output
|
|
||||||
process = subprocess.Popen(
|
|
||||||
cmd,
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.PIPE,
|
|
||||||
universal_newlines=True
|
|
||||||
)
|
|
||||||
|
|
||||||
# Log FFmpeg output in real-time
|
|
||||||
while True:
|
|
||||||
output = process.stderr.readline()
|
|
||||||
if output == '' and process.poll() is not None:
|
|
||||||
break
|
|
||||||
if output:
|
|
||||||
logging.info(f"FFmpeg: {output.strip()}")
|
|
||||||
print(f"{Colors.PURPLE}FFmpeg: {output.strip()}{Colors.ENDC}")
|
|
||||||
|
|
||||||
if process.returncode == 0:
|
|
||||||
# Get output file info
|
|
||||||
output_info = get_file_info(str(output_path))
|
|
||||||
output_size = int(output_info['format']['size'])
|
|
||||||
compression_ratio = input_size / output_size if output_size > 0 else 0
|
|
||||||
|
|
||||||
logging.info(f"Successfully encoded: {output_path}")
|
|
||||||
logging.info(f"Output size: {format_size(output_size)}")
|
|
||||||
logging.info(f"Compression ratio: {compression_ratio:.2f}x")
|
|
||||||
print(f"{Colors.GREEN}Successfully encoded: {output_path}{Colors.ENDC}")
|
|
||||||
print(f"Compression ratio: {compression_ratio:.2f}x")
|
|
||||||
else:
|
|
||||||
logging.error(f"FFmpeg process failed with return code {process.returncode}")
|
|
||||||
print(f"{Colors.RED}FFmpeg process failed with return code {process.returncode}{Colors.ENDC}")
|
|
||||||
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
logging.error(f"Error encoding {input_path}: {e}")
|
|
||||||
print(f"{Colors.RED}Error encoding {input_path}: {e}{Colors.ENDC}")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
# Get GPU selection
|
|
||||||
gpu = get_gpu_selection()
|
|
||||||
logging.info(f"Selected GPU: {gpu}")
|
|
||||||
|
|
||||||
input_dir = "input"
|
|
||||||
output_dir = "output"
|
|
||||||
os.makedirs(output_dir, exist_ok=True)
|
|
||||||
|
|
||||||
# Get list of files to process
|
|
||||||
files = [f for f in os.listdir(input_dir) if f.endswith(('.mp4', '.DVR.mp4'))]
|
|
||||||
total_files = len(files)
|
|
||||||
|
|
||||||
if total_files == 0:
|
|
||||||
logging.info("No files to process in input directory")
|
|
||||||
print(f"{Colors.YELLOW}No files to process in input directory{Colors.ENDC}")
|
|
||||||
else:
|
|
||||||
logging.info(f"Found {total_files} files to process")
|
|
||||||
print(f"{Colors.BLUE}Found {total_files} files to process{Colors.ENDC}")
|
|
||||||
|
|
||||||
for i, file in enumerate(files, 1):
|
|
||||||
input_file = os.path.join(input_dir, file)
|
|
||||||
logging.info(f"Processing file {i}/{total_files}: {file}")
|
|
||||||
print(f"\n{Colors.BLUE}Processing file {i}/{total_files}: {file}{Colors.ENDC}")
|
|
||||||
encode_dvr(input_file, output_dir, gpu)
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
@echo off
|
|
||||||
python encode_vtubrec5k.py
|
|
||||||
pause
|
|
||||||
@@ -1,194 +0,0 @@
|
|||||||
import os
|
|
||||||
import subprocess
|
|
||||||
from pathlib import Path
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
from datetime import datetime
|
|
||||||
import shutil
|
|
||||||
|
|
||||||
# ANSI color codes
|
|
||||||
class Colors:
|
|
||||||
PURPLE = '\033[95m'
|
|
||||||
BLUE = '\033[94m'
|
|
||||||
GREEN = '\033[92m'
|
|
||||||
YELLOW = '\033[93m'
|
|
||||||
RED = '\033[91m'
|
|
||||||
ENDC = '\033[0m'
|
|
||||||
|
|
||||||
def get_gpu_selection():
|
|
||||||
while True:
|
|
||||||
print(f"\n{Colors.BLUE}Select GPU slot:{Colors.ENDC}")
|
|
||||||
print("0 - First GPU")
|
|
||||||
print("1 - Second GPU")
|
|
||||||
print("2 - Third GPU")
|
|
||||||
gpu = input(f"{Colors.YELLOW}Enter GPU number (0-2):{Colors.ENDC} ").strip()
|
|
||||||
if gpu in ['0', '1', '2']:
|
|
||||||
return gpu
|
|
||||||
print(f"{Colors.RED}Invalid selection. Please try again.{Colors.ENDC}")
|
|
||||||
|
|
||||||
# Set up logging
|
|
||||||
log_dir = "logs"
|
|
||||||
os.makedirs(log_dir, exist_ok=True)
|
|
||||||
log_file = os.path.join(log_dir, f"encode_{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 get_file_info(input_file):
|
|
||||||
cmd = [
|
|
||||||
'ffprobe',
|
|
||||||
'-v', 'error',
|
|
||||||
'-show_entries', 'format=duration,size:stream=codec_name,width,height,r_frame_rate,channels,channel_layout',
|
|
||||||
'-of', 'json',
|
|
||||||
input_file
|
|
||||||
]
|
|
||||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
||||||
return json.loads(result.stdout)
|
|
||||||
|
|
||||||
def get_audio_labels(input_file):
|
|
||||||
cmd = [
|
|
||||||
'ffprobe',
|
|
||||||
'-v', 'error',
|
|
||||||
'-select_streams', 'a',
|
|
||||||
'-show_entries', 'stream=index:stream_tags=title',
|
|
||||||
'-of', 'json',
|
|
||||||
input_file
|
|
||||||
]
|
|
||||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
||||||
info = json.loads(result.stdout)
|
|
||||||
labels = []
|
|
||||||
for stream in info.get('streams', []):
|
|
||||||
title = stream.get('tags', {}).get('title', None)
|
|
||||||
labels.append(title)
|
|
||||||
return labels
|
|
||||||
|
|
||||||
def format_size(size_bytes):
|
|
||||||
for unit in ['B', 'KB', 'MB', 'GB']:
|
|
||||||
if size_bytes < 1024:
|
|
||||||
return f"{size_bytes:.2f} {unit}"
|
|
||||||
size_bytes /= 1024
|
|
||||||
return f"{size_bytes:.2f} TB"
|
|
||||||
|
|
||||||
def encode_dvr(input_file, output_dir, gpu):
|
|
||||||
input_path = Path(input_file)
|
|
||||||
output_path = Path(output_dir) / f"{input_path.stem}{input_path.suffix}"
|
|
||||||
|
|
||||||
# Get file info for logging
|
|
||||||
file_info = get_file_info(str(input_path))
|
|
||||||
input_size = int(file_info['format']['size'])
|
|
||||||
duration = float(file_info['format']['duration'])
|
|
||||||
|
|
||||||
logging.info(f"Processing file: {input_path}")
|
|
||||||
logging.info(f"Input size: {format_size(input_size)}")
|
|
||||||
logging.info(f"Duration: {duration:.2f} seconds")
|
|
||||||
|
|
||||||
print(f"\n{Colors.BLUE}Processing file: {input_path}{Colors.ENDC}")
|
|
||||||
print(f"Input size: {format_size(input_size)}")
|
|
||||||
print(f"Duration: {duration:.2f} seconds")
|
|
||||||
|
|
||||||
# Log stream information
|
|
||||||
for i, stream in enumerate(file_info.get('streams', [])):
|
|
||||||
stream_type = 'Video' if stream.get('codec_name', '').startswith('h') else 'Audio'
|
|
||||||
logging.info(f"Stream {i} ({stream_type}):")
|
|
||||||
for key, value in stream.items():
|
|
||||||
logging.info(f" {key}: {value}")
|
|
||||||
|
|
||||||
# Skip if output file already exists
|
|
||||||
if output_path.exists():
|
|
||||||
output_size = output_path.stat().st_size
|
|
||||||
logging.info(f"Skipping {input_path} - output already exists: {output_path}")
|
|
||||||
logging.info(f"Output size: {format_size(output_size)}")
|
|
||||||
print(f"{Colors.YELLOW}Skipping {input_path} - output already exists{Colors.ENDC}")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Get audio labels
|
|
||||||
audio_labels = get_audio_labels(str(input_path))
|
|
||||||
logging.info(f"Audio labels: {audio_labels}")
|
|
||||||
|
|
||||||
# FFmpeg command with NVIDIA HEVC encoder and maximum quality
|
|
||||||
cmd = [
|
|
||||||
'ffmpeg',
|
|
||||||
'-v', 'verbose',
|
|
||||||
'-i', str(input_path),
|
|
||||||
'-c:v', 'hevc_nvenc',
|
|
||||||
'-gpu', gpu,
|
|
||||||
'-preset', 'p7',
|
|
||||||
'-tune', 'hq',
|
|
||||||
'-rc', 'vbr',
|
|
||||||
'-rc-lookahead', '32',
|
|
||||||
'-spatial-aq', '1',
|
|
||||||
'-aq-strength', '15',
|
|
||||||
'-cq', '23',
|
|
||||||
'-b:v', '5000k',
|
|
||||||
'-maxrate', '5000k',
|
|
||||||
'-bufsize', '10000k',
|
|
||||||
'-b:a', '192k',
|
|
||||||
'-c:a', 'copy',
|
|
||||||
'-map', '0',
|
|
||||||
]
|
|
||||||
# Add metadata for each audio stream if label exists
|
|
||||||
for idx, label in enumerate(audio_labels):
|
|
||||||
if label:
|
|
||||||
cmd += [f'-metadata:s:a:{idx}', f'title={label}']
|
|
||||||
cmd.append(str(output_path))
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Run FFmpeg and capture output
|
|
||||||
process = subprocess.Popen(
|
|
||||||
cmd,
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.PIPE,
|
|
||||||
universal_newlines=True
|
|
||||||
)
|
|
||||||
|
|
||||||
# Log FFmpeg output in real-time
|
|
||||||
while True:
|
|
||||||
output = process.stderr.readline()
|
|
||||||
if output == '' and process.poll() is not None:
|
|
||||||
break
|
|
||||||
if output:
|
|
||||||
logging.info(f"FFmpeg: {output.strip()}")
|
|
||||||
print(f"{Colors.PURPLE}FFmpeg: {output.strip()}{Colors.ENDC}")
|
|
||||||
|
|
||||||
if process.returncode == 0:
|
|
||||||
# Get output file info
|
|
||||||
output_info = get_file_info(str(output_path))
|
|
||||||
output_size = int(output_info['format']['size'])
|
|
||||||
compression_ratio = input_size / output_size if output_size > 0 else 0
|
|
||||||
|
|
||||||
logging.info(f"Successfully encoded: {output_path}")
|
|
||||||
logging.info(f"Output size: {format_size(output_size)}")
|
|
||||||
logging.info(f"Compression ratio: {compression_ratio:.2f}x")
|
|
||||||
print(f"{Colors.GREEN}Successfully encoded: {output_path}{Colors.ENDC}")
|
|
||||||
print(f"Compression ratio: {compression_ratio:.2f}x")
|
|
||||||
else:
|
|
||||||
logging.error(f"FFmpeg process failed with return code {process.returncode}")
|
|
||||||
print(f"{Colors.RED}FFmpeg process failed with return code {process.returncode}{Colors.ENDC}")
|
|
||||||
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
logging.error(f"Error encoding {input_path}: {e}")
|
|
||||||
print(f"{Colors.RED}Error encoding {input_path}: {e}{Colors.ENDC}")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
# Get GPU selection
|
|
||||||
gpu = get_gpu_selection()
|
|
||||||
logging.info(f"Selected GPU: {gpu}")
|
|
||||||
|
|
||||||
input_dir = "input"
|
|
||||||
output_dir = "output"
|
|
||||||
os.makedirs(output_dir, exist_ok=True)
|
|
||||||
|
|
||||||
# Get list of files to process
|
|
||||||
files = [f for f in os.listdir(input_dir) if f.endswith(('.mp4', '.DVR.mp4'))]
|
|
||||||
total_files = len(files)
|
|
||||||
|
|
||||||
if total_files == 0:
|
|
||||||
logging.info("No files to process in input directory")
|
|
||||||
print(f"{Colors.YELLOW}No files to process in input directory{Colors.ENDC}")
|
|
||||||
else:
|
|
||||||
logging.info(f"Found {total_files} files to process")
|
|
||||||
print(f"{Colors.BLUE}Found {total_files} files to process{Colors.ENDC}")
|
|
||||||
|
|
||||||
for i, file in enumerate(files, 1):
|
|
||||||
input_file = os.path.join(input_dir, file)
|
|
||||||
logging.info(f"Processing file {i}/{total_files}: {file}")
|
|
||||||
print(f"\n{Colors.BLUE}Processing file {i}/{total_files}: {file}{Colors.ENDC}")
|
|
||||||
encode_dvr(input_file, output_dir, gpu)
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
@echo off
|
|
||||||
python repair_mp4.py
|
|
||||||
pause
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
239
repair_mp4.py
239
repair_mp4.py
@@ -1,239 +0,0 @@
|
|||||||
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}")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user