typedefs and robustness updates
Adds type definitions to function arguments and return values Adds error handling for ffmpeg calls Correctly continue on error file
This commit is contained in:
@@ -13,7 +13,7 @@ video_extensions: list[str] = ["mp4", "mkv", "avi", "wmv", "mov", "m4v", "ts", "
|
|||||||
|
|
||||||
total_files: int = 0
|
total_files: int = 0
|
||||||
|
|
||||||
def setup_logging(verbose):
|
def setup_logging(verbose: bool) -> None:
|
||||||
"""Configure logging to both file and stdout."""
|
"""Configure logging to both file and stdout."""
|
||||||
log_level = logging.DEBUG if verbose else logging.INFO
|
log_level = logging.DEBUG if verbose else logging.INFO
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ def setup_logging(verbose):
|
|||||||
root_logger.addHandler(console_handler)
|
root_logger.addHandler(console_handler)
|
||||||
root_logger.addHandler(file_handler)
|
root_logger.addHandler(file_handler)
|
||||||
|
|
||||||
def ffmpeg_command(input, output, nice=20):
|
def ffmpeg_command(input: pathlib.Path, output: pathlib.Path, nice: int = 20) -> list[str]:
|
||||||
return [
|
return [
|
||||||
"nice", "-n", str(nice),
|
"nice", "-n", str(nice),
|
||||||
"ffmpeg",
|
"ffmpeg",
|
||||||
@@ -54,7 +54,7 @@ def ffmpeg_command(input, output, nice=20):
|
|||||||
str(output)
|
str(output)
|
||||||
]
|
]
|
||||||
|
|
||||||
def clean_directory(directory_path):
|
def clean_directory(directory_path: pathlib.Path) -> None:
|
||||||
"""Yeet all files and directories from a directory."""
|
"""Yeet all files and directories from a directory."""
|
||||||
for item in directory_path.iterdir():
|
for item in directory_path.iterdir():
|
||||||
if item.is_dir():
|
if item.is_dir():
|
||||||
@@ -62,10 +62,10 @@ def clean_directory(directory_path):
|
|||||||
else:
|
else:
|
||||||
item.unlink() # Yeet the file
|
item.unlink() # Yeet the file
|
||||||
|
|
||||||
def is_video(path):
|
def is_video(path: pathlib.Path) -> bool:
|
||||||
return path.suffix.lower()[1:] in video_extensions
|
return path.suffix.lower()[1:] in video_extensions
|
||||||
|
|
||||||
def get_video_duration(input_file):
|
def get_video_duration(input_file: pathlib.Path) -> float:
|
||||||
cmd = [
|
cmd = [
|
||||||
"ffprobe",
|
"ffprobe",
|
||||||
"-v", "error",
|
"-v", "error",
|
||||||
@@ -73,8 +73,12 @@ def get_video_duration(input_file):
|
|||||||
"-of", "default=noprint_wrappers=1:nokey=1",
|
"-of", "default=noprint_wrappers=1:nokey=1",
|
||||||
str(input_file)
|
str(input_file)
|
||||||
]
|
]
|
||||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
try:
|
||||||
return float(result.stdout)
|
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||||
|
return float(result.stdout)
|
||||||
|
except (subprocess.CalledProcessError, ValueError):
|
||||||
|
logging.error(f"Error getting duration for {input_file}")
|
||||||
|
return 1.0
|
||||||
|
|
||||||
def run_progress(process, duration, file_index=0):
|
def run_progress(process, duration, file_index=0):
|
||||||
current_time = None
|
current_time = None
|
||||||
@@ -112,7 +116,7 @@ def run_progress(process, duration, file_index=0):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def run(filepath, cmd, temp_path, file_index):
|
def run(filepath: pathlib.Path, cmd: list[str], temp_path: pathlib.Path, file_index: int) -> None:
|
||||||
duration = get_video_duration(filepath)
|
duration = get_video_duration(filepath)
|
||||||
xdg_state_home = os.environ.get('XDG_STATE_HOME', str(pathlib.Path.home() / '.local' / 'state'))
|
xdg_state_home = os.environ.get('XDG_STATE_HOME', str(pathlib.Path.home() / '.local' / 'state'))
|
||||||
ffmpeg_log_path = pathlib.Path(xdg_state_home) / 'vcmp' / 'ffmpeg.log'
|
ffmpeg_log_path = pathlib.Path(xdg_state_home) / 'vcmp' / 'ffmpeg.log'
|
||||||
@@ -129,13 +133,23 @@ def run(filepath, cmd, temp_path, file_index):
|
|||||||
sys.stdout.write("\n")
|
sys.stdout.write("\n")
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
# Wait for the process to complete and get the return code
|
||||||
|
process.wait()
|
||||||
|
|
||||||
if process.returncode != 0:
|
if process.returncode != 0:
|
||||||
logging.error(f"FFmpeg exited with code {process.returncode}")
|
logging.error(f"FFmpeg exited with code {process.returncode}")
|
||||||
if temp_path.exists():
|
if temp_path.exists():
|
||||||
temp_path.unlink()
|
temp_path.unlink()
|
||||||
sys.exit(1)
|
raise subprocess.CalledProcessError(process.returncode, cmd)
|
||||||
|
|
||||||
def process_single_file(filepath, target_path, temp_dir, remove_source, nice, file_index):
|
def process_single_file(
|
||||||
|
filepath: pathlib.Path,
|
||||||
|
target_path: pathlib.Path,
|
||||||
|
temp_dir: pathlib.Path,
|
||||||
|
remove_source: bool,
|
||||||
|
nice: int,
|
||||||
|
file_index: int
|
||||||
|
) -> bool: # Return success/failure status
|
||||||
temp_path = temp_dir / filepath.with_suffix(".mp4").name
|
temp_path = temp_dir / filepath.with_suffix(".mp4").name
|
||||||
clean_directory(temp_dir) # Should be empty if mktemp was used, but clear it regardless.
|
clean_directory(temp_dir) # Should be empty if mktemp was used, but clear it regardless.
|
||||||
logging.info(f"Processing {filepath} to {target_path}")
|
logging.info(f"Processing {filepath} to {target_path}")
|
||||||
@@ -149,14 +163,14 @@ def process_single_file(filepath, target_path, temp_dir, remove_source, nice, fi
|
|||||||
logging.warning("Process interrupted by user")
|
logging.warning("Process interrupted by user")
|
||||||
if temp_path.exists():
|
if temp_path.exists():
|
||||||
temp_path.unlink()
|
temp_path.unlink()
|
||||||
sys.exit(1)
|
raise # Re-raise KeyboardInterrupt to exit the program
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
logging.error(f"FFmpeg error processing {filepath}: {e}")
|
logging.error(f"FFmpeg error processing {filepath}: {e}")
|
||||||
if temp_path.exists():
|
if temp_path.exists():
|
||||||
temp_path.unlink()
|
temp_path.unlink()
|
||||||
sys.exit(1)
|
return False
|
||||||
else:
|
else:
|
||||||
# File succesfully converted to av1, move to destination
|
# File successfully converted to av1, move to destination
|
||||||
# Use shutil move because of inter-fs issues
|
# Use shutil move because of inter-fs issues
|
||||||
target_path.parent.mkdir(parents=True, exist_ok=True)
|
target_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
shutil.move(str(temp_path), str(target_path))
|
shutil.move(str(temp_path), str(target_path))
|
||||||
@@ -164,13 +178,23 @@ def process_single_file(filepath, target_path, temp_dir, remove_source, nice, fi
|
|||||||
if remove_source:
|
if remove_source:
|
||||||
logging.info(f"Removing source file {filepath}")
|
logging.info(f"Removing source file {filepath}")
|
||||||
filepath.unlink()
|
filepath.unlink()
|
||||||
|
return True
|
||||||
|
|
||||||
def process_directory(*, source_dir, destination_dir, temp_dir=None, remove_source=False, nice=20, count_only=False):
|
def process_directory(
|
||||||
|
*,
|
||||||
|
source_dir: pathlib.Path,
|
||||||
|
destination_dir: pathlib.Path,
|
||||||
|
temp_dir: pathlib.Path | None = None,
|
||||||
|
remove_source: bool = False,
|
||||||
|
nice: int = 20,
|
||||||
|
count_only: bool = False
|
||||||
|
) -> int:
|
||||||
"""Process all video files in source directory recursively."""
|
"""Process all video files in source directory recursively."""
|
||||||
if not count_only:
|
if not count_only:
|
||||||
if not temp_dir:
|
if not temp_dir:
|
||||||
raise ValueError("Temporary directory is required for processing files")
|
raise ValueError("Temporary directory is required for processing files")
|
||||||
count = 0
|
count = 0
|
||||||
|
successful = 0
|
||||||
for dirpath, _dirnames, filenames in os.walk(source_dir):
|
for dirpath, _dirnames, filenames in os.walk(source_dir):
|
||||||
for filename in filenames:
|
for filename in filenames:
|
||||||
filepath = pathlib.Path(dirpath) / filename
|
filepath = pathlib.Path(dirpath) / filename
|
||||||
@@ -197,9 +221,11 @@ def process_directory(*, source_dir, destination_dir, temp_dir=None, remove_sour
|
|||||||
if count_only:
|
if count_only:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
process_single_file(filepath, target_path, temp_dir, remove_source, nice, count)
|
if process_single_file(filepath, target_path, temp_dir, remove_source, nice, count):
|
||||||
|
successful += 1
|
||||||
|
|
||||||
logging.info(f"Found {count} files to process")
|
if not count_only:
|
||||||
|
logging.info(f"Processed {count} files, {successful} successful")
|
||||||
return count
|
return count
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user