Source code for sleap.io.pathutils
"""
Utilities for working with file paths.
"""
import os
from typing import Callable, Dict, List, Optional, Text, Tuple
from sleap import util
[docs]def list_file_missing(filenames):
"""Given a list of filenames, returns list of whether file exists."""
return [not os.path.exists(filename) for filename in filenames]
[docs]def filenames_prefix_change(
filenames,
old_prefix,
new_prefix,
missing: bool = None,
confirm_callback: Optional[Callable] = None,
):
"""
Finds missing files by changing the initial part of paths.
Args:
filenames: The list of filenames, needn't all be missing.
old_prefix: Initial part of path to replace.
new_prefix: Initial part with which to replace it.
missing: List of which files are known to be missing; if not given,
then we'll check each file.
confirm_callback: If given, then we'll call this before applying
change to confirm that user wants to apply the change.
Returns:
None; `filenames` (and `missing`, if given) have new data.
"""
if not filenames or not old_prefix or not new_prefix:
return
# Ask for confirmation if there's a confirmation callback given
need_to_ask = True if callable(confirm_callback) else False
# Try changing every filename unless we're given list of which are missing
check = missing if missing else [True] * len(filenames)
# Just to be on the safe side, make sure this list covers all filenames
if len(check) < len(filenames):
check.extend([True] * (len(filenames) - len(check)))
for i, filename in enumerate(filenames):
if check[i]:
if filename.startswith(old_prefix):
try_filename = filename.replace(old_prefix, new_prefix)
try_filename = fix_path_separator(try_filename)
if os.path.exists(try_filename):
# Check if user would like to apply change to all paths
# with the same initial segment.
if need_to_ask and not confirm_callback():
return
# We're still here, so we can go ahead and replace
need_to_ask = False
filenames[i] = try_filename
check[i] = False
# Save prefix change in config file so that it can be used
# automatically in the future
save_path_prefix_replacement(old_prefix, new_prefix)
def fix_path_separator(path: str):
return path.replace("\\", "/")
[docs]def find_changed_subpath(old_path: str, new_path: str) -> Tuple[str, str]:
"""Finds the smallest initial section of path that was changed.
Args:
old_path: Old path
new_path: New path
Returns:
(initial part of old path), (corresponding replacement in new path)
"""
seps = ("/", "\\")
# Find overlap at end of paths
old_common = ""
new_char_idx = -1
for old_char_idx in range(len(old_path) - 1, 0, -1):
old_char = old_path[old_char_idx]
new_char = new_path[new_char_idx]
if old_char == new_char or old_char in seps and new_char in seps:
old_common = old_char + old_common
new_char_idx -= 1
else:
break
# Get the initial part of the old path which was replaced, and the
# initial part of the new path which replaced it.
old_initial = old_path[: old_char_idx + 1]
new_inital = new_path[: new_char_idx + 1] if new_char_idx < -1 else new_path
return (old_initial, new_inital)
def fix_paths_with_saved_prefix(
filenames,
missing: Optional[List[bool]] = None,
path_prefix_conversions: Optional[Dict[Text, Text]] = None,
):
if path_prefix_conversions is None:
path_prefix_conversions = util.get_config_yaml("path_prefixes.yaml")
if path_prefix_conversions is None:
return
for i, filename in enumerate(filenames):
if missing is not None:
if not missing[i]:
continue
elif os.path.exists(filename):
continue
for old_prefix, new_prefix in path_prefix_conversions.items():
if filename.startswith(old_prefix):
try_filename = filename.replace(old_prefix, new_prefix)
try_filename = fix_path_separator(try_filename)
if os.path.exists(try_filename):
filenames[i] = try_filename
if missing is not None:
missing[i] = False
continue
def save_path_prefix_replacement(old_prefix: str, new_prefix: str):
data = util.get_config_yaml("path_prefixes.yaml") or dict()
data[old_prefix] = new_prefix
util.save_config_yaml("path_prefixes.yaml", data)