def detect_file_on_disk(
k_value: str,
k_eval: str,
project_dir: str,
first_frame: int,
allow_relative: bool
) -> tuple[str | None, int, int]:
"""Detects a file or image sequence on disk and returns its path along
with the first and last frame numbers.
Args:
k_value (str): The original file path pattern, potentially
containing frame padding token "%04d".
k_eval (str): The evaluated file path potentially with a specific
frame number.
project_dir (str): The root directory of the project.
first_frame (int): The first frame number to consider when detecting
the file sequence.
allow_relative (bool): Whether to return the file path as relative to
the project directory.
Returns:
tuple[str, int, int] | None: A tuple containing the file path,
first frame number, and last frame number if the file or sequence
is detected; otherwise, None.
"""
combined_relative_path = None
filepath = None
firstframe = first_frame
lastframe = first_frame
if not os.path.exists(k_eval):
raise FileNotFoundError(
"Cannot create Read node as the "
f"file does not exist: `{k_eval}`"
)
if k_eval is not None and project_dir is not None:
combined_relative_path = os.path.abspath(
os.path.join(project_dir, k_eval)
)
directory = os.path.dirname(k_eval)
if directory and not os.path.isdir(directory):
return None, 0, 0
# Handle single file case (no frame padding)
if k_eval == k_value:
# If the evaluated path is the same as the original pattern,
# this means it does not contain any frame token
if os.path.exists(k_eval):
filepath = k_eval
elif project_dir is not None:
# Try with project directory
combined_path = os.path.abspath(os.path.join(project_dir, k_eval))
if os.path.exists(combined_path):
filepath = combined_path
if filepath and allow_relative and project_dir is not None:
filepath = os.path.relpath(filepath, project_dir)
return filepath, firstframe, lastframe
collections, _ = clique.assemble(
[combined_relative_path],
assume_padded_when_ambiguous=True,
minimum_items=1,
patterns=[clique.PATTERNS['frames']]
)
collection = collections[0] if collections else None
if collection:
# Get all files in the directory to find all frames
files_in_dir = [
os.path.join(directory, f)
for f in os.listdir(directory)
if os.path.isfile(os.path.join(directory, f))
]
if files_in_dir:
# Assemble all files in directory to find the complete sequence
coll = clique.assemble(
files_in_dir,
assume_padded_when_ambiguous=True,
patterns=[clique.PATTERNS['frames']]
)[0][0]
if coll.padding == collection.padding:
# Found matching sequence
firstframe = min(coll.indexes)
lastframe = max(coll.indexes)
filepath = f"{coll.head}{'#' * coll.padding}{coll.tail}"
# Convert to relative path if requested
if filepath and allow_relative and project_dir:
filepath = os.path.relpath(filepath, project_dir)
filepath = filepath.replace('\\', '/')
return filepath, firstframe, lastframe