adds file locking
This commit is contained in:
@@ -13,6 +13,31 @@ video_extensions: list[str] = ["mp4", "mkv", "avi", "wmv", "mov", "m4v", "ts", "
|
|||||||
|
|
||||||
total_files: int = 0
|
total_files: int = 0
|
||||||
|
|
||||||
|
def acquire_file_lock(filepath: pathlib.Path) -> bool:
|
||||||
|
lock_file = filepath.with_suffix(filepath.suffix + '.lock')
|
||||||
|
try:
|
||||||
|
# Atomic file creation
|
||||||
|
lock_fd = os.open(str(lock_file), os.O_CREAT | os.O_EXCL | os.O_WRONLY)
|
||||||
|
with os.fdopen(lock_fd, 'w') as f:
|
||||||
|
f.write(f"{os.getpid()}\n{time.time()}\n")
|
||||||
|
return True
|
||||||
|
except FileExistsError:
|
||||||
|
try:
|
||||||
|
lock_age = time.time() - lock_file.stat().st_mtime
|
||||||
|
if lock_age > 86400: # 24 hours
|
||||||
|
lock_file.unlink()
|
||||||
|
return acquire_file_lock(filepath)
|
||||||
|
except FileNotFoundError:
|
||||||
|
return acquire_file_lock(filepath)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def release_file_lock(filepath: pathlib.Path) -> None:
|
||||||
|
lock_file = filepath.with_suffix(filepath.suffix + '.lock')
|
||||||
|
try:
|
||||||
|
lock_file.unlink()
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
def setup_logging(verbose: bool) -> None:
|
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
|
||||||
@@ -150,6 +175,10 @@ def process_single_file(
|
|||||||
nice: int,
|
nice: int,
|
||||||
file_index: int
|
file_index: int
|
||||||
) -> bool: # Return success/failure status
|
) -> bool: # Return success/failure status
|
||||||
|
if not acquire_file_lock(filepath):
|
||||||
|
logging.info(f"Skipping {filepath}: already being processed")
|
||||||
|
return False
|
||||||
|
|
||||||
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}")
|
||||||
@@ -163,11 +192,13 @@ def process_single_file(
|
|||||||
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()
|
||||||
|
release_file_lock(filepath)
|
||||||
raise # Re-raise KeyboardInterrupt to exit the program
|
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()
|
||||||
|
release_file_lock(filepath)
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
# File successfully converted to av1, move to destination
|
# File successfully converted to av1, move to destination
|
||||||
@@ -178,6 +209,7 @@ def process_single_file(
|
|||||||
if remove_source:
|
if remove_source:
|
||||||
logging.info(f"Removing source file {filepath}")
|
logging.info(f"Removing source file {filepath}")
|
||||||
filepath.unlink()
|
filepath.unlink()
|
||||||
|
release_file_lock(filepath)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def process_directory(
|
def process_directory(
|
||||||
|
|||||||
Reference in New Issue
Block a user