Compare commits

..

3 Commits

Author SHA1 Message Date
155b5c25d1 rm suspiciously small error 2026-01-21 01:20:49 -07:00
afdcc1c6fd todo 2026-01-18 11:35:01 -07:00
4db7819172 ffmpeg error handling 2026-01-18 11:12:40 -07:00
7 changed files with 5068 additions and 82 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -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.
---

View File

@@ -0,0 +1,135 @@
<!-- 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.
---

4
TODO.md Normal file
View 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

View File

@@ -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:

View File

@@ -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)

View File

@@ -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:
output = process.stderr.readline() try:
if output == '' and process.poll() is not None: output = process.stderr.readline()
if output == '' and process.poll() is not None:
break
if output:
text = output.strip()
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 break
if output: except Exception as e:
logging.info(f"FFmpeg: {output.strip()}") # Unexpected error
print(f"{Colors.PURPLE}FFmpeg: {output.strip()}{Colors.ENDC}") 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)