FaceAnything / src /faceanything /io_utils.py
Umut Kocasari
Add FaceAnything Gradio demo app
4db294e
Raw
History Blame Contribute Delete
4 kB
"""Input handling: a directory of images or a video file -> list of frame paths."""
from __future__ import annotations
import glob
import os
import tempfile
IMAGE_EXTS = (".png", ".jpg", ".jpeg", ".bmp", ".webp")
VIDEO_EXTS = (".mp4", ".mov", ".avi", ".mkv", ".webm", ".m4v")
def _natural_key(path: str):
import re
name = os.path.basename(path)
return [int(c) if c.isdigit() else c.lower() for c in re.split(r"(\d+)", name)]
def load_frame_paths(input_path: str, max_frames: int | None = None,
stride: int = 1, work_dir: str | None = None):
"""Return a sorted list of image paths from a folder or extracted from a video.
Args:
input_path: a directory of images or a single video file.
max_frames: keep at most this many frames (after stride).
stride: take every ``stride``-th frame.
work_dir: where to extract video frames (a temp dir if ``None``).
Returns:
(frame_paths, fps) where ``fps`` is the source video fps or ``None``.
"""
if os.path.isdir(input_path):
paths = []
for ext in IMAGE_EXTS:
paths += glob.glob(os.path.join(input_path, f"*{ext}"))
paths += glob.glob(os.path.join(input_path, f"*{ext.upper()}"))
paths = sorted(set(paths), key=_natural_key)
if not paths:
raise FileNotFoundError(f"No images found in directory: {input_path}")
paths = paths[::stride]
if max_frames:
paths = paths[:max_frames]
return paths, None
ext = os.path.splitext(input_path)[1].lower()
if ext in VIDEO_EXTS:
return _extract_video_frames(input_path, max_frames, stride, work_dir)
if ext in IMAGE_EXTS: # a single image
return [input_path], None
raise ValueError(f"Unsupported input '{input_path}'. Provide a single image, "
f"an image folder, or a video file ({', '.join(VIDEO_EXTS)}).")
def _extract_video_frames(video_path, max_frames, stride, work_dir):
import imageio.v2 as imageio
from PIL import Image
if work_dir is None:
work_dir = tempfile.mkdtemp(prefix="faceanything_frames_")
os.makedirs(work_dir, exist_ok=True)
reader = imageio.get_reader(video_path)
try:
fps = float(reader.get_meta_data().get("fps", 0)) or None
except Exception:
fps = None
paths = []
count = 0
for i, frame in enumerate(reader):
if i % stride != 0:
continue
out = os.path.join(work_dir, f"frame_{count:06d}.png")
Image.fromarray(frame).save(out)
paths.append(out)
count += 1
if max_frames and count >= max_frames:
break
reader.close()
if not paths:
raise RuntimeError(f"No frames extracted from video: {video_path}")
return paths, fps
def find_mask_paths(frame_paths, mask_dir: str | None):
"""Best-effort match of a foreground mask for each frame.
Tries an explicit ``mask_dir`` (matching by basename), then a sibling
``masks/`` directory next to the frames. Returns a list aligned with
``frame_paths`` (entries may be ``None``).
"""
candidates = []
if mask_dir and os.path.isdir(mask_dir):
candidates.append(mask_dir)
if frame_paths:
sibling = os.path.join(os.path.dirname(os.path.dirname(frame_paths[0])), "masks")
if os.path.isdir(sibling):
candidates.append(sibling)
masks = []
for fp in frame_paths:
stem = os.path.splitext(os.path.basename(fp))[0]
found = None
for d in candidates:
for ext in IMAGE_EXTS:
for cand in (os.path.join(d, stem + ext),
os.path.join(d, os.path.basename(fp))):
if os.path.exists(cand):
found = cand
break
if found:
break
if found:
break
masks.append(found)
return masks